diff --git a/Linphone/Localizable.xcstrings b/Linphone/Localizable.xcstrings index 5490add1f..4267eeb33 100644 --- a/Linphone/Localizable.xcstrings +++ b/Linphone/Localizable.xcstrings @@ -1466,6 +1466,23 @@ } } }, + "conversation_info_add_participants_label" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Add participants" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ajouter des membres" + } + } + } + }, "conversation_info_confirm_start_group_call_dialog_message" : { "extractionState" : "manual", "localizations" : { @@ -1517,6 +1534,23 @@ } } }, + "conversation_info_menu_add_to_contacts" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Add to contacts" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ajouter aux contacts" + } + } + } + }, "conversation_info_menu_go_to_contact" : { "extractionState" : "manual", "localizations" : { @@ -1534,6 +1568,40 @@ } } }, + "conversation_info_participant_is_admin_label" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Admin" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Administrateur" + } + } + } + }, + "conversation_info_participants_list_title" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Group members" + } + }, + "fr" : { + "stringUnit" : { + "state" : "translated", + "value" : "Membres du groupe" + } + } + } + }, "conversation_invalid_participant_due_to_security_mode_toast" : { "extractionState" : "manual", "localizations" : { diff --git a/Linphone/UI/Call/CallView.swift b/Linphone/UI/Call/CallView.swift index bc90b7ace..52dac2093 100644 --- a/Linphone/UI/Call/CallView.swift +++ b/Linphone/UI/Call/CallView.swift @@ -37,6 +37,8 @@ struct CallView: View { @ObservedObject var conversationViewModel: ConversationViewModel @ObservedObject var conversationsListViewModel: ConversationsListViewModel @ObservedObject var conversationForwardMessageViewModel: ConversationForwardMessageViewModel + @ObservedObject var contactViewModel: ContactViewModel + @ObservedObject var editContactViewModel: EditContactViewModel @State private var addParticipantsViewModel: AddParticipantsViewModel? @@ -71,6 +73,9 @@ struct CallView: View { @State var buttonSize = 60.0 + @Binding var isShowEditContactFragment: Bool + @Binding var indexPage: Int + var body: some View { GeometryReader { geo in ZStack { @@ -200,8 +205,12 @@ struct CallView: View { conversationViewModel: conversationViewModel, conversationsListViewModel: conversationsListViewModel, conversationForwardMessageViewModel: conversationForwardMessageViewModel, + contactViewModel: contactViewModel, + editContactViewModel: editContactViewModel, isShowConversationFragment: $isShowConversationFragment, - isShowStartCallGroupPopup: $isShowStartCallGroupPopup + isShowStartCallGroupPopup: $isShowStartCallGroupPopup, + isShowEditContactFragment: $isShowEditContactFragment, + indexPage: $indexPage ) .frame(maxWidth: .infinity) .background(Color.gray100) @@ -2825,10 +2834,14 @@ struct PressedButtonStyle: ButtonStyle { conversationViewModel: ConversationViewModel(), conversationsListViewModel: ConversationsListViewModel(), conversationForwardMessageViewModel: ConversationForwardMessageViewModel(), + contactViewModel: ContactViewModel(), + editContactViewModel: EditContactViewModel(), fullscreenVideo: .constant(false), isShowStartCallFragment: .constant(false), isShowConversationFragment: .constant(false), - isShowStartCallGroupPopup: .constant(false) + isShowStartCallGroupPopup: .constant(false), + isShowEditContactFragment: .constant(false), + indexPage: .constant(0) ) } // swiftlint:enable type_body_length diff --git a/Linphone/UI/Main/Contacts/Model/ContactAvatarModel.swift b/Linphone/UI/Main/Contacts/Model/ContactAvatarModel.swift index 4938ae238..818083113 100644 --- a/Linphone/UI/Main/Contacts/Model/ContactAvatarModel.swift +++ b/Linphone/UI/Main/Contacts/Model/ContactAvatarModel.swift @@ -21,7 +21,7 @@ import Foundation import linphonesw import Combine -class ContactAvatarModel: ObservableObject { +class ContactAvatarModel: ObservableObject, Identifiable { let friend: Friend? diff --git a/Linphone/UI/Main/ContentView.swift b/Linphone/UI/Main/ContentView.swift index 64c4dd3b7..e460256e7 100644 --- a/Linphone/UI/Main/ContentView.swift +++ b/Linphone/UI/Main/ContentView.swift @@ -862,8 +862,12 @@ struct ContentView: View { conversationViewModel: conversationViewModel, conversationsListViewModel: conversationsListViewModel, conversationForwardMessageViewModel: conversationForwardMessageViewModel, + contactViewModel: contactViewModel, + editContactViewModel: editContactViewModel, isShowConversationFragment: $isShowConversationFragment, - isShowStartCallGroupPopup: $isShowStartCallGroupPopup + isShowStartCallGroupPopup: $isShowStartCallGroupPopup, + isShowEditContactFragment: $isShowEditContactFragment, + indexPage: $index ) .frame(maxWidth: .infinity) .background(Color.gray100) @@ -1200,10 +1204,14 @@ struct ContentView: View { conversationViewModel: conversationViewModel, conversationsListViewModel: conversationsListViewModel, conversationForwardMessageViewModel: conversationForwardMessageViewModel, + contactViewModel: contactViewModel, + editContactViewModel: editContactViewModel, fullscreenVideo: $fullscreenVideo, isShowStartCallFragment: $isShowStartCallFragment, isShowConversationFragment: $isShowConversationFragment, - isShowStartCallGroupPopup: $isShowStartCallGroupPopup + isShowStartCallGroupPopup: $isShowStartCallGroupPopup, + isShowEditContactFragment: $isShowEditContactFragment, + indexPage: $index ) .zIndex(5) .transition(.scale.combined(with: .move(edge: .top))) diff --git a/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift b/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift index 611b9a48c..cae7ab69d 100644 --- a/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift +++ b/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift @@ -33,6 +33,8 @@ struct ConversationFragment: View { @ObservedObject var conversationViewModel: ConversationViewModel @ObservedObject var conversationsListViewModel: ConversationsListViewModel @ObservedObject var conversationForwardMessageViewModel: ConversationForwardMessageViewModel + @ObservedObject var contactViewModel: ContactViewModel + @ObservedObject var editContactViewModel: EditContactViewModel @State var isMenuOpen = false @State private var isMuted: Bool = false @@ -63,6 +65,9 @@ struct ConversationFragment: View { @State private var selectedCategoryIndex = 0 + @Binding var isShowEditContactFragment: Bool + @Binding var indexPage: Int + var body: some View { NavigationView { GeometryReader { geometry in @@ -977,10 +982,14 @@ struct ConversationFragment: View { ConversationInfoFragment( conversationViewModel: conversationViewModel, conversationsListViewModel: conversationsListViewModel, + contactViewModel: contactViewModel, + editContactViewModel: editContactViewModel, isMuted: $isMuted, isShowEphemeralFragment: $isShowEphemeralFragment, isShowStartCallGroupPopup: $isShowStartCallGroupPopup, - isShowInfoConversationFragment: $isShowInfoConversationFragment + isShowInfoConversationFragment: $isShowInfoConversationFragment, + isShowEditContactFragment: $isShowEditContactFragment, + indexPage: $indexPage ) .zIndex(5) .transition(.move(edge: .trailing)) diff --git a/Linphone/UI/Main/Conversations/Fragments/ConversationInfoFragment.swift b/Linphone/UI/Main/Conversations/Fragments/ConversationInfoFragment.swift index 28b092c53..8975ade93 100644 --- a/Linphone/UI/Main/Conversations/Fragments/ConversationInfoFragment.swift +++ b/Linphone/UI/Main/Conversations/Fragments/ConversationInfoFragment.swift @@ -22,15 +22,22 @@ import SwiftUI struct ConversationInfoFragment: View { @State private var orientation = UIDevice.current.orientation + @ObservedObject var contactsManager = ContactsManager.shared @ObservedObject private var sharedMainViewModel = SharedMainViewModel.shared @ObservedObject var conversationViewModel: ConversationViewModel @ObservedObject var conversationsListViewModel: ConversationsListViewModel + @ObservedObject var contactViewModel: ContactViewModel + @ObservedObject var editContactViewModel: EditContactViewModel @Binding var isMuted: Bool @Binding var isShowEphemeralFragment: Bool @Binding var isShowStartCallGroupPopup: Bool @Binding var isShowInfoConversationFragment: Bool + @Binding var isShowEditContactFragment: Bool + @Binding var indexPage: Int + + @State private var participantListIsOpen = true var body: some View { NavigationView { @@ -87,7 +94,7 @@ struct ConversationInfoFragment: View { .frame(maxWidth: .infinity) .padding(.top, 10) - Text(conversationViewModel.displayedConversation!.avatarModel.address) + Text(conversationViewModel.participantConversationModel.first?.address ?? "") .foregroundStyle(Color.grayMain2c700) .multilineTextAlignment(.center) .default_text_style(styleSize: 14) @@ -115,12 +122,29 @@ struct ConversationInfoFragment: View { Avatar(contactAvatarModel: conversationViewModel.displayedConversation!.avatarModel, avatarSize: 100) .padding(.top, 4) - Text(conversationViewModel.displayedConversation!.avatarModel.name) - .foregroundStyle(Color.grayMain2c700) - .multilineTextAlignment(.center) - .default_text_style(styleSize: 14) - .frame(maxWidth: .infinity) - .padding(.top, 10) + HStack { + Text(conversationViewModel.displayedConversation!.avatarModel.name) + .foregroundStyle(Color.grayMain2c700) + .multilineTextAlignment(.center) + .default_text_style(styleSize: 14) + .padding(.top, 10) + + if conversationViewModel.isUserAdmin { + Button( + action: { + }, + label: { + Image("pencil-simple") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.orangeMain500) + .frame(width: 20, height: 20) + } + ) + .padding(.top, 10) + } + } + .padding(.leading, conversationViewModel.isUserAdmin ? 20 : 0) } } .frame(minHeight: 150) @@ -219,6 +243,91 @@ struct ConversationInfoFragment: View { .background(Color.gray100) } + if conversationViewModel.displayedConversation!.isGroup { + HStack(alignment: .center) { + Text("conversation_info_participants_list_title") + .default_text_style_800(styleSize: 18) + .frame(maxWidth: .infinity, alignment: .leading) + + Spacer() + + Image(participantListIsOpen ? "caret-up" : "caret-down") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.grayMain2c600) + .frame(width: 25, height: 25, alignment: .leading) + .padding(.all, 10) + } + .padding(.top, 30) + .padding(.bottom, 10) + .padding(.horizontal, 20) + .background(Color.gray100) + .onTapGesture { + withAnimation { + participantListIsOpen.toggle() + } + } + + if participantListIsOpen { + VStack(spacing: 0) { + ForEach(conversationViewModel.participantConversationModel) { participantConversationModel in + HStack { + Avatar(contactAvatarModel: participantConversationModel, avatarSize: 50) + + VStack { + Text(participantConversationModel.name) + .foregroundStyle(Color.grayMain2c700) + .default_text_style(styleSize: 14) + .frame(maxWidth: .infinity, alignment: .leading) + .lineLimit(1) + + if conversationViewModel.participantConversationModelAdmin != nil && participantConversationModel.address == conversationViewModel.participantConversationModelAdmin!.address { + Text("conversation_info_participant_is_admin_label") + .foregroundStyle(Color.grayMain2c400) + .default_text_style(styleSize: 12) + .frame(maxWidth: .infinity, alignment: .leading) + .lineLimit(1) + } + } + } + .padding(.vertical, 15) + .padding(.horizontal, 20) + } + + if conversationViewModel.isUserAdmin { + Button( + action: { + }, + label: { + HStack { + Image("plus-circle") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.orangeMain500) + .frame(width: 20, height: 20) + + Text("conversation_info_add_participants_label") + .default_text_style_orange_500(styleSize: 14) + .frame(height: 35) + } + } + ) + .padding(.horizontal, 20) + .padding(.vertical, 5) + .background(Color.orangeMain100) + .cornerRadius(60) + .padding(.top, 10) + .padding(.bottom, 20) + } + } + .background(.white) + .cornerRadius(15) + .padding(.horizontal) + .zIndex(-1) + .transition(.move(edge: .top)) + } + } + Text("contact_details_actions_title") .default_text_style_800(styleSize: 18) .frame(maxWidth: .infinity, alignment: .leading) @@ -230,20 +339,60 @@ struct ConversationInfoFragment: View { if !conversationViewModel.displayedConversation!.isGroup { Button( action: { + if conversationViewModel.displayedConversation != nil { + + let addressConv = conversationViewModel.participantConversationModel.first?.address ?? "" + + let friendIndex = contactsManager.lastSearch.firstIndex( + where: {$0.friend!.addresses.contains(where: {$0.asStringUriOnly() == addressConv})}) + if friendIndex != nil { + withAnimation { + conversationViewModel.displayedConversation = nil + indexPage = 0 + contactViewModel.indexDisplayedFriend = friendIndex + } + } else { + withAnimation { + conversationViewModel.displayedConversation = nil + indexPage = 0 + + isShowEditContactFragment.toggle() + editContactViewModel.sipAddresses.removeAll() + editContactViewModel.sipAddresses.append(String(conversationViewModel.participantConversationModel.first?.address.dropFirst(4) ?? "")) + editContactViewModel.sipAddresses.append("") + } + } + } }, label: { HStack { - Image("address-book") - .renderingMode(.template) - .resizable() - .foregroundStyle(Color.grayMain2c600) - .frame(width: 25, height: 25) - - Text("conversation_info_menu_go_to_contact") - .default_text_style(styleSize: 16) - .frame(maxWidth: .infinity, alignment: .leading) - .lineLimit(1) + let addressConv = conversationViewModel.participantConversationModel.first?.address ?? "" + let friendIndex = contactsManager.lastSearch.firstIndex( + where: {$0.friend!.addresses.contains(where: {$0.asStringUriOnly() == addressConv})}) + if friendIndex != nil { + Image("address-book") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.grayMain2c600) + .frame(width: 25, height: 25) + + Text("conversation_info_menu_go_to_contact") + .default_text_style(styleSize: 16) + .frame(maxWidth: .infinity, alignment: .leading) + .lineLimit(1) + } else { + Image("user-plus") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.grayMain2c600) + .frame(width: 25, height: 25) + + Text("conversation_info_menu_add_to_contacts") + .default_text_style(styleSize: 16) + .frame(maxWidth: .infinity, alignment: .leading) + .lineLimit(1) + } } } ) @@ -361,9 +510,13 @@ struct ConversationInfoFragment: View { ConversationInfoFragment( conversationViewModel: ConversationViewModel(), conversationsListViewModel: ConversationsListViewModel(), + contactViewModel: ContactViewModel(), + editContactViewModel: EditContactViewModel(), isMuted: .constant(false), isShowEphemeralFragment: .constant(false), isShowStartCallGroupPopup: .constant(false), - isShowInfoConversationFragment: .constant(true) + isShowInfoConversationFragment: .constant(true), + isShowEditContactFragment: .constant(false), + indexPage: .constant(0) ) } diff --git a/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift b/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift index 4b71fad8d..6759c6331 100644 --- a/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift +++ b/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift @@ -76,6 +76,8 @@ class ConversationViewModel: ObservableObject { @Published var conversationMessagesSection: [MessagesSection] = [] @Published var participantConversationModel: [ContactAvatarModel] = [] + @Published var participantConversationModelAdmin: ContactAvatarModel? + @Published var isUserAdmin: Bool = false @Published var mediasToSend: [Attachment] = [] var maxMediaCount = 12 @@ -310,12 +312,24 @@ class ConversationViewModel: ObservableObject { func getParticipantConversationModel() { coreContext.doOnCoreQueue { _ in if self.displayedConversation != nil { + DispatchQueue.main.async { + self.isUserAdmin = false + self.participantConversationModelAdmin = nil + self.participantConversationModel.removeAll() + } self.displayedConversation!.chatRoom.participants.forEach { participant in if participant.address != nil { ContactAvatarModel.getAvatarModelFromAddress(address: participant.address!) { avatarResult in let avatarModelTmp = avatarResult - DispatchQueue.main.async { - self.participantConversationModel.append(avatarModelTmp) + if participant.isAdmin { + DispatchQueue.main.async { + self.participantConversationModelAdmin = avatarModelTmp + self.participantConversationModel.append(avatarModelTmp) + } + } else { + DispatchQueue.main.async { + self.participantConversationModel.append(avatarModelTmp) + } } } } @@ -324,8 +338,16 @@ class ConversationViewModel: ObservableObject { if self.displayedConversation!.chatRoom.me != nil { ContactAvatarModel.getAvatarModelFromAddress(address: self.displayedConversation!.chatRoom.me!.address!) { avatarResult in let avatarModelTmp = avatarResult - DispatchQueue.main.async { - self.participantConversationModel.append(avatarModelTmp) + if self.displayedConversation!.chatRoom.me!.isAdmin { + DispatchQueue.main.async { + self.isUserAdmin = true + self.participantConversationModelAdmin = avatarModelTmp + self.participantConversationModel.append(avatarModelTmp) + } + } else { + DispatchQueue.main.async { + self.participantConversationModel.append(avatarModelTmp) + } } } } @@ -338,7 +360,9 @@ class ConversationViewModel: ObservableObject { ContactAvatarModel.getAvatarModelFromAddress(address: address) { avatarResult in let avatarModelTmp = avatarResult DispatchQueue.main.async { - self.participantConversationModel.append(avatarModelTmp) + if self.participantConversationModel.first(where: {$0.address == avatarModelTmp.address}) == nil { + self.participantConversationModel.append(avatarModelTmp) + } } } } diff --git a/Linphone/UI/Main/History/Fragments/HistoryContactFragment.swift b/Linphone/UI/Main/History/Fragments/HistoryContactFragment.swift index ab49963ef..2356af002 100644 --- a/Linphone/UI/Main/History/Fragments/HistoryContactFragment.swift +++ b/Linphone/UI/Main/History/Fragments/HistoryContactFragment.swift @@ -86,7 +86,6 @@ struct HistoryContactFragment: View { let friendIndex = contactsManager.lastSearch.firstIndex( where: {$0.friend!.addresses.contains(where: {$0.asStringUriOnly() == addressCall!.asStringUriOnly()})}) if friendIndex != nil { - withAnimation { historyViewModel.displayedCall = nil indexPage = 0