diff --git a/Linphone/Assets.xcassets/calendar.imageset/calendar.svg b/Linphone/Assets.xcassets/calendar.imageset/calendar.svg index 5caacdbef..c066f4a4b 100644 --- a/Linphone/Assets.xcassets/calendar.imageset/calendar.svg +++ b/Linphone/Assets.xcassets/calendar.imageset/calendar.svg @@ -1 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/Linphone/Assets.xcassets/trash.imageset/Contents.json b/Linphone/Assets.xcassets/trash.imageset/Contents.json new file mode 100644 index 000000000..4c3681644 --- /dev/null +++ b/Linphone/Assets.xcassets/trash.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "trash.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/Linphone/Assets.xcassets/trash.imageset/trash.svg b/Linphone/Assets.xcassets/trash.imageset/trash.svg new file mode 100644 index 000000000..b2d5dfe18 --- /dev/null +++ b/Linphone/Assets.xcassets/trash.imageset/trash.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Linphone/Contacts/ContactsManager.swift b/Linphone/Contacts/ContactsManager.swift index 194e7a556..1338effcc 100644 --- a/Linphone/Contacts/ContactsManager.swift +++ b/Linphone/Contacts/ContactsManager.swift @@ -318,7 +318,7 @@ final class ContactsManager: ObservableObject { // Clear existing addresses and add new ones friend.addresses.forEach { friend.removeAddress(address: $0) } for sipAddress in contact.sipAddresses where !sipAddress.isEmpty { - if let address = core.interpretUrl(url: sipAddress, applyInternationalPrefix: true), + if let address = core.interpretUrl(url: sipAddress, applyInternationalPrefix: LinphoneUtils.applyInternationalPrefix(core: core)), !friend.addresses.contains(where: { $0.asString() == address.asString() }) { friend.addAddress(address: address) } diff --git a/Linphone/Core/CoreContext.swift b/Linphone/Core/CoreContext.swift index 023e4680b..f027a909c 100644 --- a/Linphone/Core/CoreContext.swift +++ b/Linphone/Core/CoreContext.swift @@ -148,11 +148,21 @@ class CoreContext: ObservableObject { self.mCore.callkitEnabled = true self.mCore.pushNotificationEnabled = true - let appName = Bundle.main.infoDictionary?["CFBundleName"] as? String - let version = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String + let appGitVersion = APP_GIT_COMMIT + let appGitBranch = APP_GIT_BRANCH + let appGitTag = APP_GIT_TAG + let sdkGitVersion = linphonesw.sdkVersion + var sdkGitBranch = linphonesw.sdkBranch - let userAgent = "LinphoneiOS/\(version ?? "6.0.0") (\(UIDevice.current.localizedModel.replacingOccurrences(of: "'", with: ""))) LinphoneSDK" + if sdkGitBranch.hasPrefix("remotes/origin/") { + sdkGitBranch = String(sdkGitBranch.dropFirst("remotes/origin/".count)) + } + + Log.info("Git Info — App: \(appGitTag + "-" + appGitVersion) [\(appGitBranch)] | SDK: \(sdkGitVersion) [\(sdkGitBranch)]") + + let userAgent = "LinphoneiOS/\(appGitTag) (\(UIDevice.current.localizedModel.replacingOccurrences(of: "'", with: ""))) LinphoneSDK" self.mCore.setUserAgent(name: userAgent, version: self.coreVersion) + self.mCore.videoCaptureEnabled = true self.mCore.videoDisplayEnabled = true self.mCore.videoPreviewEnabled = false diff --git a/Linphone/GeneratedGit.swift b/Linphone/GeneratedGit.swift new file mode 100644 index 000000000..e6941c350 --- /dev/null +++ b/Linphone/GeneratedGit.swift @@ -0,0 +1,5 @@ +import Foundation + +public let APP_GIT_BRANCH = "master" +public let APP_GIT_COMMIT = "5492a3e3a" +public let APP_GIT_TAG = "6.1.0-alpha" diff --git a/Linphone/Localizable/cs.lproj/Localizable.strings b/Linphone/Localizable/cs.lproj/Localizable.strings index 27e239ef7..7f6df198e 100644 --- a/Linphone/Localizable/cs.lproj/Localizable.strings +++ b/Linphone/Localizable/cs.lproj/Localizable.strings @@ -2,7 +2,7 @@ "assistant_sip_account_transport_protocol" = "Transport"; "contact_call_action" = "Volat"; "conversation_action_call" = "Volat"; -"dialog_ok" = "OK"; +"dialog_confirm" = "Confirm"; "drawer_menu_manage_account" = "Spravovat profil"; "help_error_checking_version_toast_message" = "Během kontroly aktualizací nastala chyba"; "settings_contacts_carddav_name_title" = "Zobrazené jméno"; @@ -474,9 +474,9 @@ "message_copied_to_clipboard_toast" = "Zpráva zkopírována do schránky"; "message_delivery_info_read_title" = "Přečteno"; "message_delivery_info_received_title" = "Přijato"; -"message_meeting_invitation_cancelled_notification" = "📅 Schůzka byla zrušena"; -"message_meeting_invitation_notification" = "📅 Jste pozváni na schůzku"; -"message_meeting_invitation_updated_notification" = "📅 Schůzka byla aktualizována"; +"message_meeting_invitation_cancelled_notification" = "Schůzka byla zrušena"; +"message_meeting_invitation_notification" = "Jste pozváni na schůzku"; +"message_meeting_invitation_updated_notification" = "Schůzka byla aktualizována"; "message_reactions_info_all_title" = "Reakce"; "network_reachable_again" = "Síť je znovu dostupná"; "menu_block_number" = "Blokovat číslo"; diff --git a/Linphone/Localizable/de.lproj/Localizable.strings b/Linphone/Localizable/de.lproj/Localizable.strings index 97d4d7e1c..158351512 100644 --- a/Linphone/Localizable/de.lproj/Localizable.strings +++ b/Linphone/Localizable/de.lproj/Localizable.strings @@ -192,7 +192,7 @@ "dialog_deny" = "Ablehnen"; "dialog_install" = "Installieren"; "dialog_no" = "Nein"; -"dialog_ok" = "OK"; +"dialog_confirm" = "Confirm"; "dialog_yes" = "Ja"; "drawer_menu_account_connection_status_cleared" = "Deaktiviert"; "drawer_menu_account_connection_status_connected" = "Verbunden"; @@ -467,9 +467,9 @@ "message_delivery_info_read_title" = "Gelesen"; "message_delivery_info_received_title" = "Empfangen"; "message_delivery_info_sent_title" = "Gesendet"; -"message_meeting_invitation_cancelled_notification" = "📅 Die Besprechung wurde abgesagt"; -"message_meeting_invitation_notification" = "📅 Sie sind zu einer Besprechung eingeladen"; -"message_meeting_invitation_updated_notification" = "📅 Besprechung wurde aktualisiert"; +"message_meeting_invitation_cancelled_notification" = "Die Besprechung wurde abgesagt"; +"message_meeting_invitation_notification" = "Sie sind zu einer Besprechung eingeladen"; +"message_meeting_invitation_updated_notification" = "Besprechung wurde aktualisiert"; "message_reactions_info_all_title" = "Reaktionen"; "network_reachable_again" = "Netzwerk ist nun wieder erreichbar"; "None" = "Kein"; diff --git a/Linphone/Localizable/en.lproj/Localizable.strings b/Linphone/Localizable/en.lproj/Localizable.strings index 78479300d..cf1b49e09 100644 --- a/Linphone/Localizable/en.lproj/Localizable.strings +++ b/Linphone/Localizable/en.lproj/Localizable.strings @@ -47,6 +47,8 @@ "account_settings_im_encryption_mandatory_title" = "IM encryption mandatory"; "account_settings_lime_server_url_title" = "E2E encryption keys server URL"; "account_settings_mwi_uri_title" = "MWI server URI (Message Waiting Indicator)"; +"account_settings_apply_international_prefix_title" = "Format phone numbers using international prefix"; +"account_settings_replace_plus_by_00_title" = "Replace + by 00 when formatting phone numbers"; "account_settings_nat_policy_title" = "NAT policy settings"; "account_settings_outbound_proxy_title" = "Outbound proxy"; "account_settings_push_notification_not_available_title" = "Push notifications aren't available!"; @@ -188,6 +190,7 @@ "contacts_list_favourites_title" = "Favourites"; "contacts_list_filter_popup_see_all" = "See all"; "contacts_list_filter_popup_see_linphone_only" = "See %@ contacts"; +"contacts_list_filter_popup_see_sip_only" = "See SIP contacts"; "conversation_action_call" = "Call"; "conversation_action_configure_ephemeral_messages" = "Configure ephemeral messages"; "conversation_action_delete" = "Delete conversation"; @@ -201,6 +204,13 @@ "conversation_dialog_edit_subject" = "Edit conversation subject"; "conversation_dialog_set_subject" = "Set conversation subject"; "conversation_dialog_subject_hint" = "Conversation subject"; +"conversation_editing_message_title" = "Message being edited"; +"conversation_message_edited_label" = "Edited"; +"conversation_dialog_delete_chat_message_title" = "Delete this message?"; +"conversation_dialog_delete_locally_label" = "For me"; +"conversation_dialog_delete_for_everyone_label" = "For everyone"; +"conversation_message_content_deleted_label" = "This message has been deleted"; +"conversation_message_content_deleted_by_us_label" = "You have deleted this message"; "conversation_end_to_end_encrypted_bottom_sheet_link" = "https://linphone.org/en/features/#security"; "conversation_end_to_end_encrypted_event_title" = "End-to-end encrypted conversation"; "conversation_end_to_end_encrypted_event_subtitle" = "Messages in this conversation are e2e encrypted. Only your correspondent can decrypt them."; @@ -267,7 +277,7 @@ "dialog_deny" = "Deny"; "dialog_install" = "Install"; "dialog_no" = "No"; -"dialog_ok" = "OK"; +"dialog_confirm" = "Confirm"; "dialog_yes" = "Yes"; "drawer_menu_account_connection_status_cleared" = "Disabled"; "drawer_menu_account_connection_status_connected" = "Connected"; @@ -391,6 +401,7 @@ "menu_delete_selected_item" = "Delete"; "menu_forward_chat_message" = "Forward"; "menu_invite" = "Invite"; +"menu_edit_chat_message" = "Edit"; "menu_reply_to_chat_message" = "Reply"; "menu_resend_chat_message" = "Re-send"; "menu_see_existing_contact" = "See contact"; @@ -401,9 +412,9 @@ "message_delivery_info_received_title" = "Received"; "message_delivery_info_sent_title" = "Sent"; "message_forwarded_label" = "Forwarded"; -"message_meeting_invitation_cancelled_notification" = "📅 Meeting has been cancelled"; -"message_meeting_invitation_notification" = "📅 You are invited to a meeting"; -"message_meeting_invitation_updated_notification" = "📅 Meeting has been updated"; +"message_meeting_invitation_cancelled_notification" = "Meeting has been cancelled"; +"message_meeting_invitation_notification" = "You are invited to a meeting"; +"message_meeting_invitation_updated_notification" = "Meeting has been updated"; "message_reaction_click_to_remove_label" = "Click to remove"; "message_reactions_info_all_title" = "Reactions"; "network_not_reachable" = "You aren't connected to internet"; diff --git a/Linphone/Localizable/fr.lproj/Localizable.strings b/Linphone/Localizable/fr.lproj/Localizable.strings index 58961a458..d9b064b5d 100644 --- a/Linphone/Localizable/fr.lproj/Localizable.strings +++ b/Linphone/Localizable/fr.lproj/Localizable.strings @@ -47,6 +47,8 @@ "account_settings_im_encryption_mandatory_title" = "Chiffrement obligatoire des conversations"; "account_settings_lime_server_url_title" = "URL du serveur d'échange de clés de chiffrement"; "account_settings_mwi_uri_title" = "URI du serveur MWI (Message Waiting Indicator)"; +"account_settings_apply_international_prefix_title" = "Formater les numéros en utilisant l'indicatif international"; +"account_settings_replace_plus_by_00_title" = "Remplacer + par 00 lors du formatage des numéros de téléphone"; "account_settings_nat_policy_title" = "Paramètres de politique NAT"; "account_settings_outbound_proxy_title" = "Serveur mandataire sortant"; "account_settings_push_notification_not_available_title" = "Notifications push non disponibles"; @@ -188,6 +190,7 @@ "contacts_list_favourites_title" = "Favoris"; "contacts_list_filter_popup_see_all" = "Tous les contacts"; "contacts_list_filter_popup_see_linphone_only" = "Contacts %@"; +"contacts_list_filter_popup_see_sip_only" = "Contacts SIP"; "conversation_action_call" = "Appeler"; "conversation_action_configure_ephemeral_messages" = "Configurer les messages éphémères"; "conversation_action_delete" = "Supprimer la conversation"; @@ -201,6 +204,13 @@ "conversation_dialog_edit_subject" = "Renommer la conversation"; "conversation_dialog_set_subject" = "Nommer la conversation"; "conversation_dialog_subject_hint" = "Nom de la conversation"; +"conversation_editing_message_title" = "Modification du message"; +"conversation_message_edited_label" = "Modifié"; +"conversation_dialog_delete_chat_message_title" = "Supprimer le message ?"; +"conversation_dialog_delete_locally_label" = "Pour moi"; +"conversation_dialog_delete_for_everyone_label" = "Pour tout le monde"; +"conversation_message_content_deleted_label" = "Le message a été supprimé"; +"conversation_message_content_deleted_by_us_label" = "Vous avez supprimé le message"; "conversation_end_to_end_encrypted_bottom_sheet_link" = "https://linphone.org/en/features/#security"; "conversation_end_to_end_encrypted_event_title" = "Conversation chiffrée de bout en bout"; "conversation_end_to_end_encrypted_event_subtitle" = "Les messages de cette conversation sont chiffrés de bout en bout. Seul votre correspondant peut les déchiffrer."; @@ -267,7 +277,7 @@ "dialog_deny" = "Refuser"; "dialog_install" = "Installer"; "dialog_no" = "Non"; -"dialog_ok" = "OK"; +"dialog_confirm" = "Confirmer"; "dialog_yes" = "Oui"; "drawer_menu_account_connection_status_cleared" = "Désactivé"; "drawer_menu_account_connection_status_connected" = "Connecté"; @@ -391,6 +401,7 @@ "menu_delete_selected_item" = "Supprimer"; "menu_forward_chat_message" = "Transférer"; "menu_invite" = "Inviter"; +"menu_edit_chat_message" = "Modifier"; "menu_reply_to_chat_message" = "Répondre"; "menu_resend_chat_message" = "Ré-envoyer"; "menu_see_existing_contact" = "Voir le contact"; @@ -401,9 +412,9 @@ "message_delivery_info_received_title" = "Reçu"; "message_delivery_info_sent_title" = "Envoyé"; "message_forwarded_label" = "Transféré"; -"message_meeting_invitation_cancelled_notification" = "📅 Réunion annulée"; -"message_meeting_invitation_notification" = "📅 Invitation à une réunion"; -"message_meeting_invitation_updated_notification" = "📅 Réunion mise à jour"; +"message_meeting_invitation_cancelled_notification" = "Réunion annulée"; +"message_meeting_invitation_notification" = "Invitation à une réunion"; +"message_meeting_invitation_updated_notification" = "Réunion mise à jour"; "message_reaction_click_to_remove_label" = "Cliquez pour supprimer"; "message_reactions_info_all_title" = "Réactions"; "network_not_reachable" = "Vous n’êtes pas connecté à internet"; diff --git a/Linphone/Localizable/ru.lproj/Localizable.strings b/Linphone/Localizable/ru.lproj/Localizable.strings index 0f6b7d6f9..bd4edb2a2 100644 --- a/Linphone/Localizable/ru.lproj/Localizable.strings +++ b/Linphone/Localizable/ru.lproj/Localizable.strings @@ -187,7 +187,7 @@ "dialog_deny" = "Отказать"; "dialog_install" = "Установить"; "dialog_no" = "Нет"; -"dialog_ok" = "ОК"; +"dialog_confirm" = "ОК"; "dialog_yes" = "Да"; "drawer_menu_account_connection_status_cleared" = "Отключить"; "drawer_menu_account_connection_status_connected" = "Подключен"; diff --git a/Linphone/Localizable/sk.lproj/Localizable.strings b/Linphone/Localizable/sk.lproj/Localizable.strings index 7a04eba8f..582bba3a4 100644 --- a/Linphone/Localizable/sk.lproj/Localizable.strings +++ b/Linphone/Localizable/sk.lproj/Localizable.strings @@ -171,7 +171,7 @@ "dialog_deny" = "Odmietnuť"; "dialog_install" = "Inštalovať"; "dialog_no" = "Nie"; -"dialog_ok" = "OK"; +"dialog_confirm" = "Confirm"; "dialog_yes" = "Áno"; "meeting_waiting_room_cancel" = "Zrušiť"; "menu_delete_selected_item" = "Vymazať"; diff --git a/Linphone/Localizable/uk.lproj/Localizable.strings b/Linphone/Localizable/uk.lproj/Localizable.strings index b965bbb6f..0bfb10b18 100644 --- a/Linphone/Localizable/uk.lproj/Localizable.strings +++ b/Linphone/Localizable/uk.lproj/Localizable.strings @@ -377,7 +377,7 @@ "conversation_ephemeral_messages_duration_one_week" = "1 тиждень"; "conversation_ephemeral_messages_duration_three_days" = "3 доби"; "conversation_ephemeral_messages_duration_one_day" = "1 доба"; -"dialog_ok" = "ОК"; +"dialog_confirm" = "ОК"; "welcome_page_title" = "Ласкаво просимо"; ": %@" = ": %@"; "[https://sip.linphone.org](https://sip.linphone.org)" = "[https://sip.linphone.org](https://sip.linphone.org)"; @@ -456,9 +456,9 @@ "message_delivery_info_read_title" = "Читати"; "message_delivery_info_received_title" = "Отримано"; "message_delivery_info_sent_title" = "Відправлено"; -"message_meeting_invitation_cancelled_notification" = "📅 Нараду скасовано"; -"message_meeting_invitation_notification" = "📅 Вас запрошено на нараду"; -"message_meeting_invitation_updated_notification" = "📅 Нараду оновлено"; +"message_meeting_invitation_cancelled_notification" = "Нараду скасовано"; +"message_meeting_invitation_notification" = "Вас запрошено на нараду"; +"message_meeting_invitation_updated_notification" = "Нараду оновлено"; "message_reactions_info_all_title" = "Реакції"; "network_reachable_again" = "Мережа знову доступна"; "None" = "Жоден"; diff --git a/Linphone/Localizable/zh-Hans.lproj/Localizable.strings b/Linphone/Localizable/zh-Hans.lproj/Localizable.strings index 853d1469c..b3b295824 100644 --- a/Linphone/Localizable/zh-Hans.lproj/Localizable.strings +++ b/Linphone/Localizable/zh-Hans.lproj/Localizable.strings @@ -181,7 +181,7 @@ "dialog_deny" = "拒绝"; "dialog_install" = "安装"; "dialog_no" = "否"; -"dialog_ok" = "OK"; +"dialog_confirm" = "Confirm"; "dialog_yes" = "是"; "drawer_menu_account_connection_status_cleared" = "禁用"; "drawer_menu_account_connection_status_connected" = "已连接"; diff --git a/Linphone/UI/Assistant/Fragments/LoginFragment.swift b/Linphone/UI/Assistant/Fragments/LoginFragment.swift index 3fde7171e..655dd638d 100644 --- a/Linphone/UI/Assistant/Fragments/LoginFragment.swift +++ b/Linphone/UI/Assistant/Fragments/LoginFragment.swift @@ -70,25 +70,33 @@ struct LoginFragment: View { let contentPopup3 = Text(.init(splitMsg[1])) let contentPopup4 = Text(.init(privacyPolicy)).underline() let contentPopup5 = Text(.init(splitMsg[2])) - PopupView(isShowPopup: $isShowPopup, - title: Text("assistant_dialog_general_terms_and_privacy_policy_title"), - content: contentPopup1 + contentPopup2 + contentPopup3 + contentPopup4 + contentPopup5, - titleFirstButton: Text("dialog_deny"), - actionFirstButton: {self.isShowPopup.toggle()}, - titleSecondButton: Text("dialog_accept"), - actionSecondButton: {acceptGeneralTerms()}) + PopupView( + isShowPopup: $isShowPopup, + title: Text("assistant_dialog_general_terms_and_privacy_policy_title"), + content: contentPopup1 + contentPopup2 + contentPopup3 + contentPopup4 + contentPopup5, + titleFirstButton: nil, + actionFirstButton: {}, + titleSecondButton: Text("dialog_accept"), + actionSecondButton: { acceptGeneralTerms() }, + titleThirdButton: Text("dialog_deny"), + actionThirdButton: { self.isShowPopup.toggle() } + ) .background(.black.opacity(0.65)) .onTapGesture { self.isShowPopup.toggle() } } else { // backup just in case - PopupView(isShowPopup: $isShowPopup, - title: Text("assistant_dialog_general_terms_and_privacy_policy_title"), - content: Text(.init(String(format: String(localized: "assistant_dialog_general_terms_and_privacy_policy_message"), generalTerms, privacyPolicy))), - titleFirstButton: Text("dialog_deny"), - actionFirstButton: {self.isShowPopup.toggle()}, - titleSecondButton: Text("dialog_accept"), - actionSecondButton: {acceptGeneralTerms()}) + PopupView( + isShowPopup: $isShowPopup, + title: Text("assistant_dialog_general_terms_and_privacy_policy_title"), + content: Text(.init(String(format: String(localized: "assistant_dialog_general_terms_and_privacy_policy_message"), generalTerms, privacyPolicy))), + titleFirstButton: nil, + actionFirstButton: {}, + titleSecondButton: Text("dialog_accept"), + actionSecondButton: { acceptGeneralTerms() }, + titleThirdButton: Text("dialog_deny"), + actionThirdButton: { self.isShowPopup.toggle() } + ) .background(.black.opacity(0.65)) .onTapGesture { self.isShowPopup.toggle() diff --git a/Linphone/UI/Assistant/Fragments/ProfileModeFragment.swift b/Linphone/UI/Assistant/Fragments/ProfileModeFragment.swift index b5aad06a6..bfde7c666 100644 --- a/Linphone/UI/Assistant/Fragments/ProfileModeFragment.swift +++ b/Linphone/UI/Assistant/Fragments/ProfileModeFragment.swift @@ -145,20 +145,21 @@ struct ProfileModeFragment: View { } if self.isShowPopup { - PopupView(isShowPopup: $isShowPopup, - title: Text(isShowPopupForDefault ? "Default mode" : "Interoperable mode"), - content: Text( - isShowPopupForDefault - ? "Texte explicatif du default mode : lorem ipsum dolor sit amet, consectetur adipiscing elit." - + "Etiam velit sapien, egestas sit amet dictum eget, condimentum a ligula." - : "Texte explicatif du interoperable mode : lorem ipsum dolor sit amet, consectetur adipiscing elit." - + " Etiam velit sapien, egestas sit amet dictum eget, condimentum a ligula."), - titleFirstButton: nil, - actionFirstButton: {}, - titleSecondButton: Text("dialog_close"), - actionSecondButton: { - self.isShowPopup.toggle() - } + PopupView( + isShowPopup: $isShowPopup, + title: Text(isShowPopupForDefault ? "Default mode" : "Interoperable mode"), + content: Text( + isShowPopupForDefault + ? "Texte explicatif du default mode : lorem ipsum dolor sit amet, consectetur adipiscing elit." + + "Etiam velit sapien, egestas sit amet dictum eget, condimentum a ligula." + : "Texte explicatif du interoperable mode : lorem ipsum dolor sit amet, consectetur adipiscing elit." + + " Etiam velit sapien, egestas sit amet dictum eget, condimentum a ligula."), + titleFirstButton: nil, + actionFirstButton: {}, + titleSecondButton: nil, + actionSecondButton: {}, + titleThirdButton: Text("dialog_close"), + actionThirdButton: { self.isShowPopup.toggle() } ) .background(.black.opacity(0.65)) .onTapGesture { diff --git a/Linphone/UI/Assistant/Fragments/RegisterFragment.swift b/Linphone/UI/Assistant/Fragments/RegisterFragment.swift index 69f42192e..2d5179eb7 100644 --- a/Linphone/UI/Assistant/Fragments/RegisterFragment.swift +++ b/Linphone/UI/Assistant/Fragments/RegisterFragment.swift @@ -57,22 +57,21 @@ struct RegisterFragment: View { let titlePopup = Text("assistant_dialog_confirm_phone_number_title") let contentPopup = Text(String(format: String(localized: "assistant_dialog_confirm_phone_number_message"), registerViewModel.phoneNumber)) - PopupView( isShowPopup: $isShowPopup, title: titlePopup, content: contentPopup, - titleFirstButton: Text("dialog_cancel"), - actionFirstButton: { - self.isShowPopup = false - }, + titleFirstButton: nil, + actionFirstButton: {}, titleSecondButton: Text("dialog_continue"), actionSecondButton: { self.isShowPopup = false registerViewModel.createInProgress = true registerViewModel.startAccountCreation() registerViewModel.phoneNumberConfirmedByUser() - } + }, + titleThirdButton: Text("dialog_cancel"), + actionThirdButton: { self.isShowPopup = false }, ) .background(.black.opacity(0.65)) .onTapGesture { diff --git a/Linphone/UI/Call/Fragments/CallsListFragment.swift b/Linphone/UI/Call/Fragments/CallsListFragment.swift index 2c670cb87..d6211a6a4 100644 --- a/Linphone/UI/Call/Fragments/CallsListFragment.swift +++ b/Linphone/UI/Call/Fragments/CallsListFragment.swift @@ -103,17 +103,21 @@ struct CallsListFragment: View { .background(.white) if self.isShowPopup { - PopupView(isShowPopup: $isShowPopup, - title: Text("calls_list_dialog_merge_into_conference_title"), - content: nil, - titleFirstButton: Text("dialog_cancel"), - actionFirstButton: {self.isShowPopup.toggle()}, - titleSecondButton: Text("calls_list_dialog_merge_into_conference_label"), - actionSecondButton: { - callViewModel.mergeCallsIntoConference() - self.isShowPopup.toggle() - isShowCallsListFragment.toggle() - }) + PopupView( + isShowPopup: $isShowPopup, + title: Text("calls_list_dialog_merge_into_conference_title"), + content: nil, + titleFirstButton: nil, + actionFirstButton: {}, + titleSecondButton: Text("calls_list_dialog_merge_into_conference_label"), + actionSecondButton: { + callViewModel.mergeCallsIntoConference() + self.isShowPopup.toggle() + isShowCallsListFragment.toggle() + }, + titleThirdButton: Text("dialog_cancel"), + actionThirdButton: { self.isShowPopup.toggle() }, + ) .background(.black.opacity(0.65)) .onTapGesture { self.isShowPopup.toggle() diff --git a/Linphone/UI/Call/Fragments/ParticipantsListFragment.swift b/Linphone/UI/Call/Fragments/ParticipantsListFragment.swift index 756670ee5..5478a6533 100644 --- a/Linphone/UI/Call/Fragments/ParticipantsListFragment.swift +++ b/Linphone/UI/Call/Fragments/ParticipantsListFragment.swift @@ -108,17 +108,21 @@ struct ParticipantsListFragment: View { if self.isShowPopup { let contentPopup = Text(String(format: String(localized: "meeting_call_remove_participant_confirmation_message"), callViewModel.participantList[indexToRemove].name)) - PopupView(isShowPopup: $isShowPopup, - title: Text("meeting_call_remove_participant_confirmation_title"), - content: contentPopup, - titleFirstButton: Text("dialog_no"), - actionFirstButton: {self.isShowPopup.toggle()}, - titleSecondButton: Text("dialog_yes"), - actionSecondButton: { - callViewModel.removeParticipant(index: indexToRemove) - self.isShowPopup.toggle() - indexToRemove = -1 - }) + PopupView( + isShowPopup: $isShowPopup, + title: Text("meeting_call_remove_participant_confirmation_title"), + content: contentPopup, + titleFirstButton: nil, + actionFirstButton: {}, + titleSecondButton: Text("dialog_yes"), + actionSecondButton: { + callViewModel.removeParticipant(index: indexToRemove) + self.isShowPopup.toggle() + indexToRemove = -1 + }, + titleThirdButton: Text("dialog_no"), + actionThirdButton: { self.isShowPopup.toggle() } + ) .background(.black.opacity(0.65)) .onTapGesture { self.isShowPopup.toggle() diff --git a/Linphone/UI/Main/Contacts/Fragments/ContactInnerActionsFragment.swift b/Linphone/UI/Main/Contacts/Fragments/ContactInnerActionsFragment.swift index cdd0f4b98..fe1c0c62d 100644 --- a/Linphone/UI/Main/Contacts/Fragments/ContactInnerActionsFragment.swift +++ b/Linphone/UI/Main/Contacts/Fragments/ContactInnerActionsFragment.swift @@ -156,7 +156,7 @@ struct ContactInnerActionsFragment: View { .background(.white) .onTapGesture { CoreContext.shared.doOnCoreQueue { core in - let address = core.interpretUrl(url: contactAvatarModel.phoneNumbersWithLabel[index].phoneNumber, applyInternationalPrefix: true) + let address = core.interpretUrl(url: contactAvatarModel.phoneNumbersWithLabel[index].phoneNumber, applyInternationalPrefix: LinphoneUtils.applyInternationalPrefix(core: core)) if address != nil { TelecomManager.shared.doCallOrJoinConf(address: address!) } diff --git a/Linphone/UI/Main/Contacts/Fragments/ContactInnerFragment.swift b/Linphone/UI/Main/Contacts/Fragments/ContactInnerFragment.swift index c920aba7b..bece283f3 100644 --- a/Linphone/UI/Main/Contacts/Fragments/ContactInnerFragment.swift +++ b/Linphone/UI/Main/Contacts/Fragments/ContactInnerFragment.swift @@ -153,7 +153,7 @@ struct ContactInnerFragment: View { Log.error("[ContactInnerFragment] unable to create address for a new outgoing call : \(contactAvatarModel.address) \(error) ") } } else if contactAvatarModel.addresses.count < 1 && contactAvatarModel.phoneNumbersWithLabel.count == 1 { - if let firstPhoneNumbersWithLabel = contactAvatarModel.phoneNumbersWithLabel.first, let address = core.interpretUrl(url: firstPhoneNumbersWithLabel.phoneNumber, applyInternationalPrefix: true) { + if let firstPhoneNumbersWithLabel = contactAvatarModel.phoneNumbersWithLabel.first, let address = core.interpretUrl(url: firstPhoneNumbersWithLabel.phoneNumber, applyInternationalPrefix: LinphoneUtils.applyInternationalPrefix(core: core)) { telecomManager.doCallOrJoinConf(address: address, isVideo: false) } } else { @@ -194,7 +194,7 @@ struct ContactInnerFragment: View { Log.error("[ContactInnerFragment] unable to create address for a new outgoing call : \(contactAvatarModel.address) \(error) ") } } else if contactAvatarModel.addresses.count < 1 && contactAvatarModel.phoneNumbersWithLabel.count == 1 { - if let firstPhoneNumbersWithLabel = contactAvatarModel.phoneNumbersWithLabel.first, let address = core.interpretUrl(url: firstPhoneNumbersWithLabel.phoneNumber, applyInternationalPrefix: true) { + if let firstPhoneNumbersWithLabel = contactAvatarModel.phoneNumbersWithLabel.first, let address = core.interpretUrl(url: firstPhoneNumbersWithLabel.phoneNumber, applyInternationalPrefix: LinphoneUtils.applyInternationalPrefix(core: core)) { contactsListViewModel.createOneToOneChatRoomWith(remote: address) } } else { @@ -235,7 +235,7 @@ struct ContactInnerFragment: View { Log.error("[ContactInnerFragment] unable to create address for a new outgoing call : \(contactAvatarModel.address) \(error) ") } } else if contactAvatarModel.addresses.count < 1 && contactAvatarModel.phoneNumbersWithLabel.count == 1 { - if let firstPhoneNumbersWithLabel = contactAvatarModel.phoneNumbersWithLabel.first, let address = core.interpretUrl(url: firstPhoneNumbersWithLabel.phoneNumber, applyInternationalPrefix: true) { + if let firstPhoneNumbersWithLabel = contactAvatarModel.phoneNumbersWithLabel.first, let address = core.interpretUrl(url: firstPhoneNumbersWithLabel.phoneNumber, applyInternationalPrefix: LinphoneUtils.applyInternationalPrefix(core: core)) { telecomManager.doCallOrJoinConf(address: address, isVideo: true) } } else { diff --git a/Linphone/UI/Main/Contacts/Fragments/ContactsInnerFragment.swift b/Linphone/UI/Main/Contacts/Fragments/ContactsInnerFragment.swift index 8d6c362d9..dc52c0c62 100644 --- a/Linphone/UI/Main/Contacts/Fragments/ContactsInnerFragment.swift +++ b/Linphone/UI/Main/Contacts/Fragments/ContactsInnerFragment.swift @@ -24,6 +24,7 @@ struct ContactsInnerFragment: View { @ObservedObject var sharedMainViewModel = SharedMainViewModel.shared @ObservedObject var contactsManager = ContactsManager.shared + @ObservedObject var magicSearch = MagicSearchSingleton.shared @EnvironmentObject var contactsListViewModel: ContactsListViewModel @@ -33,76 +34,84 @@ struct ContactsInnerFragment: View { @Binding var text: String var body: some View { - VStack(alignment: .leading) { - if contactsManager.avatarListModel.contains(where: { $0.starred }) { - HStack(alignment: .center) { - Text("contacts_list_favourites_title") - .default_text_style_800(styleSize: 16) - - Spacer() - - Image(isFavoriteOpen ? "caret-up" : "caret-down") - .renderingMode(.template) - .resizable() - .foregroundStyle(Color.grayMain2c600) - .frame(width: 25, height: 25, alignment: .leading) - .padding(.all, 10) - } - .padding(.top, 10) - .padding(.horizontal, 16) - .background(.white) - .onTapGesture { - withAnimation { - isFavoriteOpen.toggle() + ZStack { + VStack(alignment: .leading) { + if contactsManager.avatarListModel.contains(where: { $0.starred }) { + HStack(alignment: .center) { + Text("contacts_list_favourites_title") + .default_text_style_800(styleSize: 16) + + Spacer() + + Image(isFavoriteOpen ? "caret-up" : "caret-down") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.grayMain2c600) + .frame(width: 25, height: 25, alignment: .leading) + .padding(.all, 10) } - } - - if isFavoriteOpen { - FavoriteContactsListFragment(showingSheet: $showingSheet) - .zIndex(-1) - .transition(.move(edge: .top)) - } - - HStack(alignment: .center) { - Text("contacts_list_all_contacts_title") - .default_text_style_800(styleSize: 16) - - Spacer() - } - .padding(.top, 10) - .padding(.horizontal, 16) - } - - VStack { - List { - ContactsListFragment(showingSheet: $showingSheet, startCallFunc: {_ in })} - .safeAreaInset(edge: .top, content: { - Spacer() - .frame(height: 12) - }) - .listStyle(.plain) - .if(sharedMainViewModel.cardDavFriendsListsCount > 0) { view in - view.refreshable { - contactsManager.refreshCardDavContacts() - } - } - .overlay( - VStack { - if contactsManager.avatarListModel.isEmpty { - Spacer() - Image("illus-belledonne") - .resizable() - .scaledToFit() - .clipped() - .padding(.all) - Text(!text.isEmpty ? "list_filter_no_result_found" : "contacts_list_empty") - .default_text_style_800(styleSize: 16) - Spacer() - Spacer() + .padding(.top, 10) + .padding(.horizontal, 16) + .background(.white) + .onTapGesture { + withAnimation { + isFavoriteOpen.toggle() } } - .padding(.all) - ) + + if isFavoriteOpen { + FavoriteContactsListFragment(showingSheet: $showingSheet) + .zIndex(-1) + .transition(.move(edge: .top)) + } + + HStack(alignment: .center) { + Text("contacts_list_all_contacts_title") + .default_text_style_800(styleSize: 16) + + Spacer() + } + .padding(.top, 10) + .padding(.horizontal, 16) + } + + VStack { + List { + ContactsListFragment(showingSheet: $showingSheet, startCallFunc: {_ in })} + .safeAreaInset(edge: .top, content: { + Spacer() + .frame(height: 12) + }) + .listStyle(.plain) + .if(sharedMainViewModel.cardDavFriendsListsCount > 0) { view in + view.refreshable { + contactsManager.refreshCardDavContacts() + } + } + .overlay( + VStack { + if contactsManager.avatarListModel.isEmpty { + Spacer() + Image("illus-belledonne") + .resizable() + .scaledToFit() + .clipped() + .padding(.all) + Text(!text.isEmpty ? "list_filter_no_result_found" : "contacts_list_empty") + .default_text_style_800(styleSize: 16) + Spacer() + Spacer() + } + } + .padding(.all) + ) + } + } + + if magicSearch.isLoading { + ProgressView() + .controlSize(.large) + .progressViewStyle(CircularProgressViewStyle(tint: .orangeMain500)) } } .navigationBarHidden(true) diff --git a/Linphone/UI/Main/ContentView.swift b/Linphone/UI/Main/ContentView.swift index 271716a7e..f72ffe2dd 100644 --- a/Linphone/UI/Main/ContentView.swift +++ b/Linphone/UI/Main/ContentView.swift @@ -60,6 +60,7 @@ struct ContentView: View { @State var isShowDismissPopup = false @State var isShowSendCancelMeetingNotificationPopup = false @State var isShowStartCallGroupPopup = false + @State var isShowDeleteMessagePopup = false @State var isShowSipAddressesPopup = false @State var isShowSipAddressesPopupType = 0 // 0 to call, 1 to message, 2 to video call @State var isShowConversationFragment = false @@ -545,7 +546,7 @@ struct ContentView: View { magicSearch.searchForContacts() } label: { HStack { - Text(String(format: String(localized: "contacts_list_filter_popup_see_linphone_only"), Bundle.main.displayName)) + Text(magicSearch.domainDefaultAccount == "*" ? String(localized: "contacts_list_filter_popup_see_sip_only") : String(format: String(localized: "contacts_list_filter_popup_see_linphone_only"), Bundle.main.displayName)) Spacer() if !magicSearch.allContact { Image("green-check") @@ -988,6 +989,7 @@ struct ContentView: View { ConversationFragment( isShowConversationFragment: $isShowConversationFragment, isShowStartCallGroupPopup: $isShowStartCallGroupPopup, + isShowDeleteMessagePopup: $isShowDeleteMessagePopup, isShowEditContactFragment: $isShowEditContactFragment, isShowEditContactFragmentAddress: $isShowEditContactFragmentAddress, isShowScheduleMeetingFragment: $isShowScheduleMeetingFragment, @@ -1113,14 +1115,16 @@ struct ContentView: View { ) ), content: Text("contact_dialog_delete_message"), - titleFirstButton: Text("dialog_cancel"), - actionFirstButton: { - self.isShowDeleteContactPopup.toggle()}, - titleSecondButton: Text("dialog_ok"), + titleFirstButton: nil, + actionFirstButton: {}, + titleSecondButton: Text("dialog_confirm"), actionSecondButton: { contactsListVM.deleteSelectedContact() self.isShowDeleteContactPopup.toggle() - }) + }, + titleThirdButton: Text("dialog_cancel"), + actionThirdButton: { self.isShowDeleteContactPopup.toggle() } + ) .background(.black.opacity(0.65)) .zIndex(3) .onTapGesture { @@ -1132,27 +1136,31 @@ struct ContentView: View { } if isShowDeleteAllHistoryPopup { - PopupView(isShowPopup: $isShowDeleteContactPopup, - title: Text("history_dialog_delete_all_call_logs_title"), - content: Text("history_dialog_delete_all_call_logs_message"), - titleFirstButton: Text("dialog_cancel"), - actionFirstButton: { - self.isShowDeleteAllHistoryPopup.toggle() - if let historyListVM = historyListViewModel { - historyListVM.callLogsAddressToDelete = "" + PopupView( + isShowPopup: $isShowDeleteContactPopup, + title: Text("history_dialog_delete_all_call_logs_title"), + content: Text("history_dialog_delete_all_call_logs_message"), + titleFirstButton: nil, + actionFirstButton: {}, + titleSecondButton: Text("dialog_confirm"), + actionSecondButton: { + if let historyListVM = historyListViewModel { + historyListVM.removeCallLogs() + } + self.isShowDeleteAllHistoryPopup.toggle() + sharedMainViewModel.displayedCall = nil + + ToastViewModel.shared.toastMessage = "Success_remove_call_logs" + ToastViewModel.shared.displayToast.toggle() + }, + titleThirdButton: Text("dialog_cancel"), + actionThirdButton: { + self.isShowDeleteAllHistoryPopup.toggle() + if let historyListVM = historyListViewModel { + historyListVM.callLogsAddressToDelete = "" + } } - }, - titleSecondButton: Text("dialog_ok"), - actionSecondButton: { - if let historyListVM = historyListViewModel { - historyListVM.removeCallLogs() - } - self.isShowDeleteAllHistoryPopup.toggle() - sharedMainViewModel.displayedCall = nil - - ToastViewModel.shared.toastMessage = "Success_remove_call_logs" - ToastViewModel.shared.displayToast.toggle() - }) + ) .background(.black.opacity(0.65)) .zIndex(3) .onTapGesture { @@ -1161,20 +1169,24 @@ struct ContentView: View { } if isShowDismissPopup { - PopupView(isShowPopup: $isShowDismissPopup, - title: Text("contact_editor_dialog_abort_confirmation_title"), - content: Text("contact_editor_dialog_abort_confirmation_message"), - titleFirstButton: Text("dialog_cancel"), - actionFirstButton: {self.isShowDismissPopup.toggle()}, - titleSecondButton: Text("dialog_ok"), - actionSecondButton: { - self.isShowDismissPopup.toggle() - if isShowEditContactFragment { - isShowEditContactFragment = false - } else { - isShowEditContactFragmentInContactDetails = false - } - }) + PopupView( + isShowPopup: $isShowDismissPopup, + title: Text("contact_editor_dialog_abort_confirmation_title"), + content: Text("contact_editor_dialog_abort_confirmation_message"), + titleFirstButton: nil, + actionFirstButton: {}, + titleSecondButton: Text("dialog_confirm"), + actionSecondButton: { + self.isShowDismissPopup.toggle() + if isShowEditContactFragment { + isShowEditContactFragment = false + } else { + isShowEditContactFragmentInContactDetails = false + } + }, + titleThirdButton: Text("dialog_cancel"), + actionThirdButton: { self.isShowDismissPopup.toggle() } + ) .background(.black.opacity(0.65)) .zIndex(3) .onTapGesture { @@ -1304,21 +1316,25 @@ struct ContentView: View { } if let meetingsListVM = meetingsListViewModel, isShowSendCancelMeetingNotificationPopup { - PopupView(isShowPopup: $isShowSendCancelMeetingNotificationPopup, - title: Text("meeting_schedule_cancel_dialog_title"), - content: !sharedMainViewModel.disableChatFeature ? Text("meeting_schedule_cancel_dialog_message") : Text(""), - titleFirstButton: Text("dialog_cancel"), - actionFirstButton: { - sharedMainViewModel.displayedMeeting = nil - meetingsListVM.deleteSelectedMeeting() - self.isShowSendCancelMeetingNotificationPopup.toggle( - ) }, - titleSecondButton: Text("dialog_ok"), - actionSecondButton: { - sharedMainViewModel.displayedMeeting = nil - meetingsListVM.cancelMeetingWithNotifications() - self.isShowSendCancelMeetingNotificationPopup.toggle() - }) + PopupView( + isShowPopup: $isShowSendCancelMeetingNotificationPopup, + title: Text("meeting_schedule_cancel_dialog_title"), + content: !sharedMainViewModel.disableChatFeature ? Text("meeting_schedule_cancel_dialog_message") : Text(""), + titleFirstButton: nil, + actionFirstButton: {}, + titleSecondButton: Text("dialog_confirm"), + actionSecondButton: { + sharedMainViewModel.displayedMeeting = nil + meetingsListVM.cancelMeetingWithNotifications() + self.isShowSendCancelMeetingNotificationPopup.toggle() + }, + titleThirdButton: Text("dialog_cancel"), + actionThirdButton: { + sharedMainViewModel.displayedMeeting = nil + meetingsListVM.deleteSelectedMeeting() + self.isShowSendCancelMeetingNotificationPopup.toggle() + } + ) .background(.black.opacity(0.65)) .zIndex(3) .onTapGesture { @@ -1331,17 +1347,17 @@ struct ContentView: View { isShowPopup: $isShowStartCallGroupPopup, title: Text("conversation_info_confirm_start_group_call_dialog_title"), content: Text("conversation_info_confirm_start_group_call_dialog_message"), - titleFirstButton: Text("dialog_cancel"), - actionFirstButton: { - self.isShowStartCallGroupPopup.toggle() - }, - titleSecondButton: Text("dialog_ok"), + titleFirstButton: nil, + actionFirstButton: {}, + titleSecondButton: Text("dialog_confirm"), actionSecondButton: { if sharedMainViewModel.displayedConversation != nil { sharedMainViewModel.displayedConversation!.createGroupCall() } self.isShowStartCallGroupPopup.toggle() - } + }, + titleThirdButton: Text("dialog_cancel"), + actionThirdButton: { self.isShowStartCallGroupPopup.toggle() } ) .background(.black.opacity(0.65)) .zIndex(3) @@ -1350,6 +1366,31 @@ struct ContentView: View { } } + if isShowDeleteMessagePopup { + PopupView( + isShowPopup: $isShowDeleteMessagePopup, + title: Text("conversation_dialog_delete_chat_message_title"), + content: nil, + titleFirstButton: Text("conversation_dialog_delete_for_everyone_label"), + actionFirstButton: { + NotificationCenter.default.post(name: NSNotification.Name("DeleteMessageForEveryone"), object: nil) + self.isShowDeleteMessagePopup.toggle() + }, + titleSecondButton: Text("conversation_dialog_delete_locally_label"), + actionSecondButton: { + NotificationCenter.default.post(name: NSNotification.Name("DeleteMessageForMe"), object: nil) + self.isShowDeleteMessagePopup.toggle() + }, + titleThirdButton: Text("dialog_cancel"), + actionThirdButton: { self.isShowDeleteMessagePopup.toggle() } + ) + .background(.black.opacity(0.65)) + .zIndex(3) + .onTapGesture { + self.isShowDeleteMessagePopup.toggle() + } + } + if isShowConversationInfoPopup { PopupViewWithTextField( isShowConversationInfoPopup: $isShowConversationInfoPopup, diff --git a/Linphone/UI/Main/Conversations/Fragments/ChatBubbleView.swift b/Linphone/UI/Main/Conversations/Fragments/ChatBubbleView.swift index 368817e12..5c489df4a 100644 --- a/Linphone/UI/Main/Conversations/Fragments/ChatBubbleView.swift +++ b/Linphone/UI/Main/Conversations/Fragments/ChatBubbleView.swift @@ -52,7 +52,7 @@ struct ChatBubbleView: View { HStack { if eventLogMessage.eventModel.eventLogType == .ConferenceChatMessage { VStack { - if !eventLogMessage.message.text.isEmpty || !eventLogMessage.message.attachments.isEmpty || eventLogMessage.message.isIcalendar { + if !eventLogMessage.message.text.isEmpty || !eventLogMessage.message.attachments.isEmpty || eventLogMessage.message.isIcalendar || eventLogMessage.message.isRetracted { HStack(alignment: .top, content: { if eventLogMessage.message.isOutgoing { Spacer() @@ -137,6 +137,12 @@ struct ChatBubbleView: View { .foregroundStyle(Color.grayMain2c700) .default_text_style(styleSize: 14) .lineLimit(/*@START_MENU_TOKEN@*/2/*@END_MENU_TOKEN@*/) + } else if eventLogMessage.message.replyMessage!.isRetracted { + Text(eventLogMessage.message.replyMessage!.isOutgoing ? "conversation_message_content_deleted_by_us_label" : "conversation_message_content_deleted_label") + .italic() + .foregroundStyle(Color.grayMain2c500) + .font(.system(size: 14)) + .lineLimit(1) } } .padding(.all, 15) @@ -174,6 +180,12 @@ struct ChatBubbleView: View { if !eventLogMessage.message.text.isEmpty { DynamicLinkText(text: eventLogMessage.message.text) + } else if eventLogMessage.message.isRetracted { + Text(eventLogMessage.message.isOutgoing ? "conversation_message_content_deleted_by_us_label" : "conversation_message_content_deleted_label") + .italic() + .foregroundStyle(Color.grayMain2c500) + .font(.system(size: 14)) + .lineLimit(1) } if eventLogMessage.message.isIcalendar && eventLogMessage.message.messageConferenceInfo != nil { @@ -325,6 +337,14 @@ struct ChatBubbleView: View { .padding(.top, 1) } + if eventLogMessage.message.isEdited && eventLogMessage.message.isOutgoing { + Text("conversation_message_edited_label") + .foregroundStyle(Color.grayMain2c500) + .default_text_style_300(styleSize: 12) + .padding(.top, 1) + .padding(.trailing, -4) + } + Text(conversationViewModel.getMessageTime(startDate: eventLogMessage.message.dateReceived)) .foregroundStyle(Color.grayMain2c500) .default_text_style_300(styleSize: 12) @@ -349,6 +369,14 @@ struct ChatBubbleView: View { } } + if eventLogMessage.message.isEdited && !eventLogMessage.message.isOutgoing { + Text("conversation_message_edited_label") + .foregroundStyle(Color.grayMain2c500) + .default_text_style_300(styleSize: 12) + .padding(.top, 1) + .padding(.trailing, -4) + } + if eventLogMessage.message.isEphemeral && !eventLogMessage.message.isOutgoing { Image("clock-countdown") .renderingMode(.template) diff --git a/Linphone/UI/Main/Conversations/Fragments/ConversationForwardMessageFragment.swift b/Linphone/UI/Main/Conversations/Fragments/ConversationForwardMessageFragment.swift index 5a41b038d..403f0a14b 100644 --- a/Linphone/UI/Main/Conversations/Fragments/ConversationForwardMessageFragment.swift +++ b/Linphone/UI/Main/Conversations/Fragments/ConversationForwardMessageFragment.swift @@ -141,50 +141,58 @@ struct ConversationForwardMessageFragment: View { .padding(.vertical) .padding(.horizontal) - ScrollView { - if !conversationForwardMessageViewModel.conversationsList.isEmpty { - HStack(alignment: .center) { - Text("bottom_navigation_conversations_label") - .default_text_style_800(styleSize: 16) + ZStack { + ScrollView { + if !conversationForwardMessageViewModel.conversationsList.isEmpty { + HStack(alignment: .center) { + Text("bottom_navigation_conversations_label") + .default_text_style_800(styleSize: 16) + + Spacer() + } + .padding(.vertical, 10) + .padding(.horizontal, 16) - Spacer() + conversationsList } - .padding(.vertical, 10) + + if !ContactsManager.shared.lastSearch.isEmpty { + HStack(alignment: .center) { + Text("contacts_list_all_contacts_title") + .default_text_style_800(styleSize: 16) + + Spacer() + } + .padding(.vertical, 10) + .padding(.horizontal, 16) + } + + ContactsListFragment(showingSheet: .constant(false), startCallFunc: { addr in + withAnimation { + conversationForwardMessageViewModel.createOneToOneChatRoomWith(remote: addr) + } + + }) .padding(.horizontal, 16) - conversationsList + if !contactsManager.lastSearchSuggestions.isEmpty { + HStack(alignment: .center) { + Text("generic_address_picker_suggestions_list_title") + .default_text_style_800(styleSize: 16) + + Spacer() + } + .padding(.vertical, 10) + .padding(.horizontal, 16) + + suggestionsList + } } - if !ContactsManager.shared.lastSearch.isEmpty { - HStack(alignment: .center) { - Text("contacts_list_all_contacts_title") - .default_text_style_800(styleSize: 16) - - Spacer() - } - .padding(.vertical, 10) - .padding(.horizontal, 16) - } - - ContactsListFragment(showingSheet: .constant(false), startCallFunc: { addr in - withAnimation { - conversationForwardMessageViewModel.createOneToOneChatRoomWith(remote: addr) - } - - }) - .padding(.horizontal, 16) - - if !contactsManager.lastSearchSuggestions.isEmpty { - HStack(alignment: .center) { - Text("generic_address_picker_suggestions_list_title") - .default_text_style_800(styleSize: 16) - - Spacer() - } - .padding(.vertical, 10) - .padding(.horizontal, 16) - - suggestionsList + if magicSearch.isLoading { + ProgressView() + .controlSize(.large) + .progressViewStyle(CircularProgressViewStyle(tint: .orangeMain500)) } } } diff --git a/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift b/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift index 25b67299a..8faf53e6f 100644 --- a/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift +++ b/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift @@ -64,6 +64,7 @@ struct ConversationFragment: View { @Binding var isShowConversationFragment: Bool @Binding var isShowStartCallGroupPopup: Bool + @Binding var isShowDeleteMessagePopup: Bool @State private var selectedCategoryIndex = 0 @@ -233,6 +234,8 @@ struct ConversationFragment: View { if SharedMainViewModel.shared.displayedConversation != nil && (navigationManager.peerAddr == nil || navigationManager.peerAddr!.contains(SharedMainViewModel.shared.displayedConversation!.remoteSipUri)) { conversationViewModel.resetDisplayedChatRoom() } + } else { + conversationViewModel.compose(stop: true, cachedConversation: cachedConversation) } } } @@ -460,6 +463,7 @@ struct ConversationFragment: View { } } .onDisappear { + conversationViewModel.compose(stop: true, cachedConversation: cachedConversation) conversationViewModel.resetMessage() } } else { @@ -555,6 +559,7 @@ struct ConversationFragment: View { conversationViewModel.getMessages() } .onDisappear { + conversationViewModel.compose(stop: true, cachedConversation: cachedConversation) conversationViewModel.resetMessage() } } @@ -620,6 +625,43 @@ struct ConversationFragment: View { } } .transition(.move(edge: .bottom)) + } else if conversationViewModel.messageToEdit != nil { + ZStack(alignment: .top) { + HStack { + VStack { + Text("conversation_editing_message_title") + .default_text_style_300(styleSize: 15) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.bottom, 1) + .lineLimit(1) + + Text("\(conversationViewModel.messageToEdit!.message.text)") + .default_text_style_300(styleSize: 15) + .frame(maxWidth: .infinity, alignment: .leading) + .lineLimit(1) + } + } + .frame(maxWidth: .infinity) + .padding(.all, 20) + .background(Color.gray100) + + HStack { + Spacer() + + Button(action: { + messageText = "" + withAnimation { + conversationViewModel.messageToEdit = nil + } + }, label: { + Image("x") + .resizable() + .frame(width: 30, height: 30, alignment: .leading) + .padding(.all, 10) + }) + } + } + .transition(.move(edge: .bottom)) } if !conversationViewModel.mediasToSend.isEmpty || mediasIsLoading { @@ -847,9 +889,7 @@ struct ConversationFragment: View { .focused($isMessageTextFocused) .padding(.vertical, 5) .onChange(of: messageText) { text in - if !text.isEmpty { - conversationViewModel.compose() - } + conversationViewModel.compose(stop: text.isEmpty) } } else { ZStack(alignment: .leading) { @@ -860,9 +900,7 @@ struct ConversationFragment: View { .default_text_style(styleSize: 15) .focused($isMessageTextFocused) .onChange(of: messageText) { text in - if !text.isEmpty { - conversationViewModel.compose() - } + conversationViewModel.compose(stop: text.isEmpty) } if messageText.isEmpty { @@ -879,43 +917,66 @@ struct ConversationFragment: View { } } - if messageText.isEmpty && conversationViewModel.mediasToSend.isEmpty { - Button { - voiceRecordingInProgress = true - } label: { - Image("microphone") - .renderingMode(.template) - .resizable() - .foregroundStyle(Color.grayMain2c500) - .frame(width: 28, height: 28, alignment: .leading) - .padding(.all, 6) - .padding(.top, 4) + if conversationViewModel.messageToEdit == nil { + if messageText.isEmpty && conversationViewModel.mediasToSend.isEmpty { + Button { + voiceRecordingInProgress = true + } label: { + Image("microphone") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.grayMain2c500) + .frame(width: 28, height: 28, alignment: .leading) + .padding(.all, 6) + .padding(.top, 4) + } + } else { + Button { + if conversationViewModel.displayedConversationHistorySize > 1 { + NotificationCenter.default.post(name: .onScrollToBottom, object: nil) + } + + let messageTextTmp = self.messageText + messageText = " " + DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) { + messageText = "" + isMessageTextFocused = true + + conversationViewModel.sendMessage(messageText: messageTextTmp) + } + } label: { + Image("paper-plane-tilt") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.orangeMain500) + .frame(width: 28, height: 28, alignment: .leading) + .padding(.all, 6) + .padding(.top, 4) + .rotationEffect(.degrees(45)) + } + .padding(.trailing, 4) } } else { Button { - if conversationViewModel.displayedConversationHistorySize > 1 { - NotificationCenter.default.post(name: .onScrollToBottom, object: nil) - } - let messageTextTmp = self.messageText - messageText = " " - DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) { - messageText = "" - isMessageTextFocused = true - - conversationViewModel.sendMessage(messageText: messageTextTmp) - } + messageText = " " + DispatchQueue.main.asyncAfter(deadline: .now() + 0.01) { + messageText = "" + isMessageTextFocused = true + + conversationViewModel.sendMessage(messageText: messageTextTmp) + } } label: { - Image("paper-plane-tilt") + Image("pencil-simple") .renderingMode(.template) .resizable() - .foregroundStyle(Color.orangeMain500) + .foregroundStyle(messageText.isEmpty ? Color.gray300 : Color.orangeMain500) .frame(width: 28, height: 28, alignment: .leading) .padding(.all, 6) .padding(.top, 4) - .rotationEffect(.degrees(45)) } .padding(.trailing, 4) + .disabled(messageText.isEmpty) } } .padding(.leading, 15) @@ -1096,28 +1157,67 @@ struct ConversationFragment: View { Divider() } - - Button { - let indexMessage = conversationViewModel.conversationMessagesSection[0].rows.firstIndex(where: {$0.message.id == conversationViewModel.selectedMessage!.message.id}) - conversationViewModel.selectedMessage = nil - conversationViewModel.replyToMessage(index: indexMessage ?? 0, isMessageTextFocused: Binding( - get: { isMessageTextFocused }, - set: { isMessageTextFocused = $0 } - )) - } label: { - HStack { - Text("menu_reply_to_chat_message") - .default_text_style(styleSize: 15) - Spacer() - Image("reply") - .resizable() - .frame(width: 20, height: 20, alignment: .leading) - } - .padding(.vertical, 5) - .padding(.horizontal, 20) - } - Divider() + if conversationViewModel.selectedMessage!.message.isOutgoing + && !(SharedMainViewModel.shared.displayedConversation?.isReadOnly ?? cachedConversation!.isReadOnly) + && conversationViewModel.selectedMessage!.message.isEditable { + Button { + if let chatMessage = conversationViewModel.selectedMessage { + if voiceRecordingInProgress { + voiceRecordingInProgress = false + } + + messageText = chatMessage.message.text + conversationViewModel.selectedMessage = nil + conversationViewModel.editMessage( + chatMessage: chatMessage, + isMessageTextFocused: Binding( + get: { isMessageTextFocused }, + set: { isMessageTextFocused = $0 } + ) + ) + } + } label: { + HStack { + Text("menu_edit_chat_message") + .default_text_style(styleSize: 15) + Spacer() + Image("pencil-simple") + .renderingMode(.template) + .resizable() + .foregroundStyle(Color.grayMain2c600) + .frame(width: 20, height: 20, alignment: .leading) + } + .padding(.vertical, 5) + .padding(.horizontal, 20) + } + + Divider() + } + + if !conversationViewModel.selectedMessage!.message.isRetracted { + Button { + let indexMessage = conversationViewModel.conversationMessagesSection[0].rows.firstIndex(where: {$0.message.id == conversationViewModel.selectedMessage!.message.id}) + conversationViewModel.selectedMessage = nil + conversationViewModel.replyToMessage(index: indexMessage ?? 0, isMessageTextFocused: Binding( + get: { isMessageTextFocused }, + set: { isMessageTextFocused = $0 } + )) + } label: { + HStack { + Text("menu_reply_to_chat_message") + .default_text_style(styleSize: 15) + Spacer() + Image("reply") + .resizable() + .frame(width: 20, height: 20, alignment: .leading) + } + .padding(.vertical, 5) + .padding(.horizontal, 20) + } + + Divider() + } if !conversationViewModel.selectedMessage!.message.text.isEmpty { Button { @@ -1146,27 +1246,35 @@ struct ConversationFragment: View { Divider() } - Button { - withAnimation { - isShowConversationForwardMessageFragment = true + if !conversationViewModel.selectedMessage!.message.isRetracted { + Button { + withAnimation { + isShowConversationForwardMessageFragment = true + } + } label: { + HStack { + Text("menu_forward_chat_message") + .default_text_style(styleSize: 15) + Spacer() + Image("forward") + .resizable() + .frame(width: 20, height: 20, alignment: .leading) + } + .padding(.vertical, 5) + .padding(.horizontal, 20) } - } label: { - HStack { - Text("menu_forward_chat_message") - .default_text_style(styleSize: 15) - Spacer() - Image("forward") - .resizable() - .frame(width: 20, height: 20, alignment: .leading) - } - .padding(.vertical, 5) - .padding(.horizontal, 20) + + Divider() } - Divider() - Button { - conversationViewModel.deleteMessage() + if conversationViewModel.selectedMessage!.message.isOutgoing + && !(SharedMainViewModel.shared.displayedConversation?.isReadOnly ?? cachedConversation!.isReadOnly) + && conversationViewModel.selectedMessage!.message.isRetractable && !conversationViewModel.selectedMessage!.message.isRetracted { + isShowDeleteMessagePopup = true + } else { + conversationViewModel.deleteMessage() + } } label: { HStack { Text("menu_delete_selected_item") @@ -1211,11 +1319,18 @@ struct ConversationFragment: View { } .onAppear { touchFeedback() + if isMessageTextFocused { + isMessageTextFocused = false + } } .onDisappear { if conversationViewModel.selectedMessage != nil { conversationViewModel.selectedMessage = nil } + }.onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("DeleteMessageForMe"))) { _ in + conversationViewModel.deleteMessage() + }.onReceive(NotificationCenter.default.publisher(for: NSNotification.Name("DeleteMessageForEveryone"))) { _ in + conversationViewModel.deleteMessageForEveryone() } } diff --git a/Linphone/UI/Main/Conversations/Fragments/ConversationsListFragment.swift b/Linphone/UI/Main/Conversations/Fragments/ConversationsListFragment.swift index e9c40e871..d39a0835d 100644 --- a/Linphone/UI/Main/Conversations/Fragments/ConversationsListFragment.swift +++ b/Linphone/UI/Main/Conversations/Fragments/ConversationsListFragment.swift @@ -105,14 +105,46 @@ struct ConversationRow: View { .frame(maxWidth: .infinity, alignment: .leading) .lineLimit(1) - Text(conversation.lastMessageText) - .foregroundStyle(Color.grayMain2c400) - .if(conversation.unreadMessagesCount > 0) { view in - view.default_text_style_700(styleSize: 14) + HStack(spacing: 0) { + Text(conversation.lastMessagePrefixText) + .foregroundStyle(Color.grayMain2c400) + .if(conversation.unreadMessagesCount > 0) { view in + view.default_text_style_700(styleSize: 14) + } + .default_text_style(styleSize: 14) + .lineLimit(1) + .layoutPriority(1) + + if !conversation.lastMessageIcon.isEmpty { + Image(conversation.lastMessageIcon) + .resizable() + .frame(width: 16, height: 16) + .layoutPriority(0) + .padding(.trailing, 2) } - .default_text_style(styleSize: 14) - .frame(maxWidth: .infinity, alignment: .leading) - .lineLimit(1) + + if conversation.lastMessageInItalic { + Text(conversation.lastMessageText) + .italic() + .if(conversation.unreadMessagesCount > 0) { view in + view.bold() + } + .foregroundStyle(Color.grayMain2c400) + .font(.system(size: 14)) + .lineLimit(1) + .layoutPriority(-1) + } else { + Text(conversation.lastMessageText) + .foregroundStyle(Color.grayMain2c400) + .if(conversation.unreadMessagesCount > 0) { view in + view.default_text_style_700(styleSize: 14) + } + .default_text_style(styleSize: 14) + .lineLimit(1) + .layoutPriority(-1) + } + } + .frame(maxWidth: .infinity, alignment: .leading) Spacer() } diff --git a/Linphone/UI/Main/Conversations/Fragments/StartConversationFragment.swift b/Linphone/UI/Main/Conversations/Fragments/StartConversationFragment.swift index 55d5dc64b..2a81a536f 100644 --- a/Linphone/UI/Main/Conversations/Fragments/StartConversationFragment.swift +++ b/Linphone/UI/Main/Conversations/Fragments/StartConversationFragment.swift @@ -170,34 +170,42 @@ struct StartConversationFragment: View { ) } - ScrollView { - if !ContactsManager.shared.lastSearch.isEmpty { - HStack(alignment: .center) { - Text("contacts_list_all_contacts_title") - .default_text_style_800(styleSize: 16) - - Spacer() + ZStack { + ScrollView { + if !ContactsManager.shared.lastSearch.isEmpty { + HStack(alignment: .center) { + Text("contacts_list_all_contacts_title") + .default_text_style_800(styleSize: 16) + + Spacer() + } + .padding(.vertical, 10) + .padding(.horizontal, 16) } - .padding(.vertical, 10) - .padding(.horizontal, 16) - } - - ContactsListFragment(showingSheet: .constant(false), startCallFunc: { addr in + + ContactsListFragment(showingSheet: .constant(false), startCallFunc: { addr in startConversationViewModel.createOneToOneChatRoomWith(remote: addr) - }) - .padding(.horizontal, 16) - - if !contactsManager.lastSearchSuggestions.isEmpty { - HStack(alignment: .center) { - Text("generic_address_picker_suggestions_list_title") - .default_text_style_800(styleSize: 16) - - Spacer() - } - .padding(.vertical, 10) + }) .padding(.horizontal, 16) - suggestionsList + if !contactsManager.lastSearchSuggestions.isEmpty { + HStack(alignment: .center) { + Text("generic_address_picker_suggestions_list_title") + .default_text_style_800(styleSize: 16) + + Spacer() + } + .padding(.vertical, 10) + .padding(.horizontal, 16) + + suggestionsList + } + } + + if magicSearch.isLoading { + ProgressView() + .controlSize(.large) + .progressViewStyle(CircularProgressViewStyle(tint: .orangeMain500)) } } } diff --git a/Linphone/UI/Main/Conversations/Fragments/StartGroupConversationFragment.swift b/Linphone/UI/Main/Conversations/Fragments/StartGroupConversationFragment.swift index 1edbaf478..6963e60c3 100644 --- a/Linphone/UI/Main/Conversations/Fragments/StartGroupConversationFragment.swift +++ b/Linphone/UI/Main/Conversations/Fragments/StartGroupConversationFragment.swift @@ -111,7 +111,7 @@ struct StartGroupConversationFragment: View { Button(action: { startConversationViewModel.createGroupChatRoom() }, label: { - Text("dialog_ok") + Text("dialog_confirm") .default_text_style_white_600(styleSize: 20) .frame(height: 35) .frame(maxWidth: .infinity) diff --git a/Linphone/UI/Main/Conversations/Fragments/UIList.swift b/Linphone/UI/Main/Conversations/Fragments/UIList.swift index 59622cec2..2b116e379 100644 --- a/Linphone/UI/Main/Conversations/Fragments/UIList.swift +++ b/Linphone/UI/Main/Conversations/Fragments/UIList.swift @@ -551,6 +551,12 @@ struct UIList: UIViewRepresentable { } func tableView(_ tableView: UITableView, leadingSwipeActionsConfigurationForRowAt indexPath: IndexPath) -> UISwipeActionsConfiguration? { + let eventLogMessage = parent.conversationViewModel.conversationMessagesSection[0].rows[indexPath.row] + + guard !eventLogMessage.message.isRetracted && eventLogMessage.eventModel.eventLogType == .ConferenceChatMessage else { + return nil + } + let archiveAction = UIContextualAction(style: .normal, title: "") { _, _, completionHandler in self.parent.conversationViewModel.replyToMessage(index: indexPath.row, isMessageTextFocused: Binding( get: { self.parent.isMessageTextFocused }, diff --git a/Linphone/UI/Main/Conversations/Model/ConversationModel.swift b/Linphone/UI/Main/Conversations/Model/ConversationModel.swift index 399fbede4..584e1c7d9 100644 --- a/Linphone/UI/Main/Conversations/Model/ConversationModel.swift +++ b/Linphone/UI/Main/Conversations/Model/ConversationModel.swift @@ -46,9 +46,12 @@ class ConversationModel: ObservableObject, Identifiable { @Published var isMuted: Bool @Published var isEphemeral: Bool @Published var encryptionEnabled: Bool + @Published var lastMessagePrefixText: String @Published var lastMessageText: String + @Published var lastMessageIcon: String @Published var lastMessageIsOutgoing: Bool @Published var lastMessageState: Int + @Published var lastMessageInItalic: Bool @Published var unreadMessagesCount: Int @Published var avatarModel: ContactAvatarModel @@ -138,11 +141,17 @@ class ConversationModel: ObservableObject, Identifiable { self.lastMessage = nil + self.lastMessagePrefixText = "" + self.lastMessageText = "" + self.lastMessageIcon = "" + self.lastMessageIsOutgoing = false self.lastMessageState = 0 + + self.lastMessageInItalic = false self.unreadMessagesCount = chatRoom.unreadMessagesCount @@ -294,8 +303,10 @@ class ConversationModel: ObservableObject, Identifiable { fromAddressFriend = nil } - var lastMessageTextTmp = (fromAddressFriend ?? "") - + (lastMessage!.contents.first(where: {$0.isText == true})?.utf8Text ?? (lastMessage!.contents.first(where: {$0.isFile == true || $0.isFileTransfer == true})?.name ?? "")) + let lastMessagePrefixTextTmp = (fromAddressFriend ?? "") + var lastMessageTextTmp = (lastMessage!.contents.first(where: {$0.isText == true})?.utf8Text ?? (lastMessage!.contents.first(where: {$0.isFile == true || $0.isFileTransfer == true})?.name ?? "")) + var lastMessageIconTmp = "" + var lastMessageInItalicTmp = false if lastMessage!.contents.first != nil && lastMessage!.contents.first!.isIcalendar == true { if let conferenceInfo = try? Factory.Instance.createConferenceInfoFromIcalendarContent(content: lastMessage!.contents.first!) { @@ -308,10 +319,30 @@ class ConversationModel: ObservableObject, Identifiable { } else if conferenceInfo.state == .Cancelled { lastMessageTextTmp = String(localized: "message_meeting_invitation_cancelled_notification") } + + lastMessageIconTmp = "calendar" + + lastMessageInItalicTmp = true } } } + if (lastMessage!.contents.first(where: {$0.isFile == true || $0.isFileTransfer == true})?.name != nil) { + lastMessageIconTmp = "file" + } else if lastMessage!.isReply { + lastMessageIconTmp = "reply" + } else if lastMessage!.isForward { + lastMessageIconTmp = "forward" + } + + if lastMessage!.isRetracted { + lastMessageTextTmp += lastMessage!.isOutgoing ? String(localized: "conversation_message_content_deleted_by_us_label") : String(localized: "conversation_message_content_deleted_label") + + lastMessageIconTmp = "trash" + + lastMessageInItalicTmp = true + } + let lastMessageIsOutgoingTmp = lastMessage?.isOutgoing ?? false let lastUpdateTimeTmp = lastMessage?.time ?? chatRoom.lastUpdateTime @@ -319,13 +350,19 @@ class ConversationModel: ObservableObject, Identifiable { let lastMessageStateTmp = lastMessage?.state.rawValue ?? 0 DispatchQueue.main.async { + self.lastMessagePrefixText = lastMessagePrefixTextTmp + self.lastMessageText = lastMessageTextTmp + + self.lastMessageIcon = lastMessageIconTmp self.lastMessageIsOutgoing = lastMessageIsOutgoingTmp self.lastUpdateTime = lastUpdateTimeTmp self.lastMessageState = lastMessageStateTmp + + self.lastMessageInItalic = lastMessageInItalicTmp } getUnreadMessagesCount() diff --git a/Linphone/UI/Main/Conversations/Model/Message.swift b/Linphone/UI/Main/Conversations/Model/Message.swift index 48a36e7a1..c0096e42e 100644 --- a/Linphone/UI/Main/Conversations/Model/Message.swift +++ b/Linphone/UI/Main/Conversations/Model/Message.swift @@ -68,6 +68,10 @@ public struct Message: Identifiable, Hashable { public var status: Status? public var createdAt: Date public var isOutgoing: Bool + public var isEditable: Bool + public var isRetractable: Bool + public var isEdited: Bool + public var isRetracted: Bool public var dateReceived: time_t public var address: String @@ -94,6 +98,10 @@ public struct Message: Identifiable, Hashable { status: Status? = nil, createdAt: Date = Date(), isOutgoing: Bool, + isEditable: Bool, + isRetractable: Bool, + isEdited: Bool, + isRetracted: Bool, dateReceived: time_t, address: String, isFirstMessage: Bool = false, @@ -116,6 +124,10 @@ public struct Message: Identifiable, Hashable { self.status = status self.createdAt = createdAt self.isOutgoing = isOutgoing + self.isEditable = isEditable + self.isRetractable = isRetractable + self.isEdited = isEdited + self.isRetracted = isRetracted self.dateReceived = dateReceived self.isFirstMessage = isFirstMessage self.address = address @@ -163,6 +175,10 @@ public struct Message: Identifiable, Hashable { status: status, createdAt: draft.createdAt, isOutgoing: draft.isOutgoing, + isEditable: draft.isEditable, + isRetractable: draft.isRetractable, + isEdited: draft.isEdited, + isRetracted: draft.isRetracted, dateReceived: draft.dateReceived, address: draft.address, isFirstMessage: draft.isFirstMessage, @@ -184,7 +200,7 @@ extension Message { extension Message: Equatable { public static func == (lhs: Message, rhs: Message) -> Bool { - lhs.id == rhs.id && lhs.status == rhs.status && lhs.isFirstMessage == rhs.isFirstMessage && lhs.ownReaction == rhs.ownReaction && lhs.reactions == rhs.reactions && lhs.ephemeralExpireTime == rhs.ephemeralExpireTime && lhs.attachments == rhs.attachments + lhs.id == rhs.id && lhs.status == rhs.status && lhs.isEdited == rhs.isEdited && lhs.isRetracted == rhs.isRetracted && lhs.isFirstMessage == rhs.isFirstMessage && lhs.text == rhs.text && lhs.attachments == rhs.attachments && lhs.replyMessage?.text == rhs.replyMessage?.text && lhs.replyMessage?.isRetracted == rhs.replyMessage?.isRetracted && lhs.ownReaction == rhs.ownReaction && lhs.reactions == rhs.reactions && lhs.ephemeralExpireTime == rhs.ephemeralExpireTime } } @@ -211,6 +227,10 @@ public struct ReplyMessage: Codable, Identifiable, Hashable { public var isFirstMessage: Bool public var text: String public var isOutgoing: Bool + public var isEditable: Bool + public var isRetractable: Bool + public var isEdited: Bool + public var isRetracted: Bool public var dateReceived: time_t public var attachmentsNames: String public var attachments: [Attachment] @@ -221,6 +241,10 @@ public struct ReplyMessage: Codable, Identifiable, Hashable { isFirstMessage: Bool = false, text: String = "", isOutgoing: Bool, + isEditable: Bool, + isRetractable: Bool, + isEdited: Bool, + isRetracted: Bool, dateReceived: time_t, attachmentsNames: String = "", attachments: [Attachment] = [], @@ -231,6 +255,10 @@ public struct ReplyMessage: Codable, Identifiable, Hashable { self.isFirstMessage = isFirstMessage self.text = text self.isOutgoing = isOutgoing + self.isEditable = isEditable + self.isRetractable = isRetractable + self.isEdited = isEdited + self.isRetracted = isRetracted self.dateReceived = dateReceived self.attachmentsNames = attachmentsNames self.attachments = attachments @@ -238,20 +266,24 @@ public struct ReplyMessage: Codable, Identifiable, Hashable { } func toMessage() -> Message { - Message(id: id, isOutgoing: isOutgoing, dateReceived: dateReceived, address: address, isFirstMessage: isFirstMessage, text: text, attachments: attachments, recording: recording) + Message(id: id, isOutgoing: isOutgoing, isEditable: isEditable, isRetractable: isRetractable, isEdited: isEdited, isRetracted: isRetracted, dateReceived: dateReceived, address: address, isFirstMessage: isFirstMessage, text: text, attachments: attachments, recording: recording) } } public extension Message { func toReplyMessage() -> ReplyMessage { - ReplyMessage(id: id, address: address, isFirstMessage: isFirstMessage, text: text, isOutgoing: isOutgoing, dateReceived: dateReceived, attachments: attachments, recording: recording) + ReplyMessage(id: id, address: address, isFirstMessage: isFirstMessage, text: text, isOutgoing: isOutgoing, isEditable: isEditable, isRetractable: isRetractable, isEdited: isEdited, isRetracted: isRetracted, dateReceived: dateReceived, attachments: attachments, recording: recording) } } public struct DraftMessage { public var id: String? public let isOutgoing: Bool + public let isEditable: Bool + public let isRetractable: Bool + public let isEdited: Bool + public let isRetracted: Bool public var dateReceived: time_t public let address: String public let isFirstMessage: Bool @@ -265,6 +297,10 @@ public struct DraftMessage { public init(id: String? = nil, isOutgoing: Bool, + isEditable: Bool, + isRetractable: Bool, + isEdited: Bool, + isRetracted: Bool, dateReceived: time_t, address: String, isFirstMessage: Bool, @@ -278,6 +314,10 @@ public struct DraftMessage { ) { self.id = id self.isOutgoing = isOutgoing + self.isEditable = isEditable + self.isRetractable = isRetractable + self.isEdited = isEdited + self.isRetracted = isRetracted self.dateReceived = dateReceived self.address = address self.isFirstMessage = isFirstMessage diff --git a/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift b/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift index 50eab7080..d944cadc2 100644 --- a/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift +++ b/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift @@ -93,6 +93,7 @@ class ConversationViewModel: ObservableObject { @Published var selectedMessageToPlayVoiceRecording: EventLogMessage? @Published var selectedMessage: EventLogMessage? @Published var messageToReply: EventLogMessage? + @Published var messageToEdit: EventLogMessage? @Published var sheetCategories: [SheetCategory] = [] @@ -171,7 +172,152 @@ class ConversationViewModel: ObservableObject { self.getEventMessage(eventLog: eventLog) }, onEphemeralMessageDeleted: {(_: ChatRoom, eventLog: EventLog) in self.removeMessage(eventLog) + }, onMessageContentEdited: {(chatRoom: ChatRoom, message: ChatMessage) in + let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId == message.messageId}) + + if let displayedConversation = self.sharedMainViewModel.displayedConversation { + displayedConversation.getContentTextMessage(chatRoom: displayedConversation.chatRoom) + } + + var attachmentNameList: String = "" + var attachmentList: [Attachment] = [] + var contentText = "" + + if !message.contents.isEmpty { + message.contents.forEach { content in + if content.isText && content.name == nil { + contentText = content.utf8Text ?? "" + } else if content.name != nil && !content.name!.isEmpty { + if content.filePath == nil || content.filePath!.isEmpty { + let path = URL(string: self.getNewFilePath(name: content.name ?? "")) + + if path != nil { + let attachment = + Attachment( + id: UUID().uuidString, + name: content.name!, + url: path!, + type: .fileTransfer, + size: content.fileSize, + transferProgressIndication: content.filePath != nil && !content.filePath!.isEmpty ? 100 : -1 + ) + attachmentNameList += ", \(content.name!)" + attachmentList.append(attachment) + } + } else { + if content.type != "video" { + let filePathSep = content.filePath!.components(separatedBy: "/Library/Images/") + let path = URL(string: self.getNewFilePath(name: filePathSep[1])) + + var typeTmp: AttachmentType = .other + + switch content.type { + case "image": + typeTmp = (content.name?.lowercased().hasSuffix("gif"))! ? .gif : .image + case "audio": + typeTmp = content.isVoiceRecording ? .voiceRecording : .audio + case "application": + typeTmp = content.subtype.lowercased() == "pdf" ? .pdf : .other + case "text": + typeTmp = .text + default: + typeTmp = .other + } + + if path != nil { + let attachment = + Attachment( + id: UUID().uuidString, + name: content.name!, + url: path!, + type: typeTmp, + duration: typeTmp == .voiceRecording ? content.fileDuration : 0, + size: content.fileSize, + transferProgressIndication: content.filePath != nil && !content.filePath!.isEmpty ? 100 : -1 + ) + attachmentNameList += ", \(content.name!)" + attachmentList.append(attachment) + if typeTmp != .voiceRecording { + DispatchQueue.main.async { + if !attachment.full.pathExtension.isEmpty { + self.attachments.append(attachment) + } + } + } + } + } else if content.type == "video" { + let filePathSep = content.filePath!.components(separatedBy: "/Library/Images/") + let path = URL(string: self.getNewFilePath(name: filePathSep[1])) + let pathThumbnail = URL(string: self.generateThumbnail(name: filePathSep[1])) + + if path != nil && pathThumbnail != nil { + let attachment = + Attachment( + id: UUID().uuidString, + name: content.name!, + thumbnail: pathThumbnail!, + full: path!, + type: .video, + size: content.fileSize, + transferProgressIndication: content.filePath != nil && !content.filePath!.isEmpty ? 100 : -1 + ) + attachmentNameList += ", \(content.name!)" + attachmentList.append(attachment) + DispatchQueue.main.async { + if !attachment.full.pathExtension.isEmpty { + self.attachments.append(attachment) + } + } + } + } + } + } + } + } + + if !attachmentNameList.isEmpty { + attachmentNameList = String(attachmentNameList.dropFirst(2)) + } + + let indexReplyMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.message.replyMessage?.id == message.messageId}) + + DispatchQueue.main.async { + if indexMessage != nil { + self.conversationMessagesSection[0].rows[indexMessage!].message.text = contentText + self.conversationMessagesSection[0].rows[indexMessage!].message.isEdited = true + self.conversationMessagesSection[0].rows[indexMessage!].message.attachments = attachmentList + self.conversationMessagesSection[0].rows[indexMessage!].message.attachmentsNames = attachmentNameList + } + + if indexReplyMessage != nil { + self.conversationMessagesSection[0].rows[indexReplyMessage!].message.replyMessage?.text = contentText + } + } + }, onMessageRetracted: {(chatRoom: ChatRoom, message: ChatMessage) in + let indexMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.eventModel.eventLogId == message.messageId}) + let indexReplyMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.message.replyMessage?.id == message.messageId}) + + if let displayedConversation = self.sharedMainViewModel.displayedConversation { + displayedConversation.getContentTextMessage(chatRoom: displayedConversation.chatRoom) + } + + DispatchQueue.main.async { + if indexMessage != nil { + self.conversationMessagesSection[0].rows[indexMessage!].message.text = "" + self.conversationMessagesSection[0].rows[indexMessage!].message.isRetracted = true + self.conversationMessagesSection[0].rows[indexMessage!].message.attachments = [] + self.conversationMessagesSection[0].rows[indexMessage!].message.attachmentsNames = "" + } + + if indexReplyMessage != nil { + self.conversationMessagesSection[0].rows[indexReplyMessage!].message.replyMessage?.text = "" + self.conversationMessagesSection[0].rows[indexReplyMessage!].message.replyMessage?.isRetracted = true + self.conversationMessagesSection[0].rows[indexReplyMessage!].message.replyMessage?.attachments = [] + self.conversationMessagesSection[0].rows[indexReplyMessage!].message.replyMessage?.attachmentsNames = "" + } + } }) + self.chatRoomDelegateHolder = ChatRoomDelegateHolder(chatroom: chatRoom, delegate: chatRoomDelegate) } @@ -544,6 +690,10 @@ class ConversationViewModel: ObservableObject { id: UUID().uuidString, status: nil, isOutgoing: false, + isEditable: false, + isRetractable: false, + isEdited: false, + isRetracted: false, dateReceived: 0, address: "", isFirstMessage: false, @@ -704,6 +854,8 @@ class ConversationViewModel: ObservableObject { let contentReplyText = chatMessage.replyMessage?.utf8Text ?? "" + let isReplyRetracted = chatMessage.replyMessage?.isRetracted ?? false + var attachmentNameReplyList: String = "" chatMessage.replyMessage?.contents.forEach { content in @@ -721,7 +873,11 @@ class ConversationViewModel: ObservableObject { address: addressReplyCleaned?.asStringUriOnly() ?? "", isFirstMessage: false, text: contentReplyText, - isOutgoing: false, + isOutgoing: chatMessage.replyMessage!.isOutgoing, + isEditable: false, + isRetractable: false, + isEdited: false, + isRetracted: isReplyRetracted, dateReceived: 0, attachmentsNames: attachmentNameReplyList, attachments: [] @@ -735,6 +891,10 @@ class ConversationViewModel: ObservableObject { id: !chatMessage.messageId.isEmpty ? chatMessage.messageId : UUID().uuidString, status: statusTmp, isOutgoing: chatMessage.isOutgoing, + isEditable: chatMessage.isOutgoing ? chatMessage.isEditable : false, + isRetractable: chatMessage.isOutgoing ? chatMessage.isRetractable : false, + isEdited: chatMessage.isEdited, + isRetracted: chatMessage.isRetracted, dateReceived: chatMessage.time, address: addressCleaned?.asStringUriOnly() ?? "", isFirstMessage: isFirstMessageTmp, @@ -788,6 +948,10 @@ class ConversationViewModel: ObservableObject { id: UUID().uuidString, status: nil, isOutgoing: false, + isEditable: false, + isRetractable: false, + isEdited: false, + isRetracted: false, dateReceived: 0, address: "", isFirstMessage: false, @@ -947,6 +1111,8 @@ class ConversationViewModel: ObservableObject { let contentReplyText = chatMessage.replyMessage?.utf8Text ?? "" + let isReplyRetracted = chatMessage.replyMessage?.isRetracted ?? false + var attachmentNameReplyList: String = "" chatMessage.replyMessage?.contents.forEach { content in @@ -964,7 +1130,11 @@ class ConversationViewModel: ObservableObject { address: addressReplyCleaned?.asStringUriOnly() ?? "", isFirstMessage: false, text: contentReplyText, - isOutgoing: false, + isOutgoing: chatMessage.replyMessage!.isOutgoing, + isEditable: false, + isRetractable: false, + isEdited: false, + isRetracted: isReplyRetracted, dateReceived: 0, attachmentsNames: attachmentNameReplyList, attachments: [] @@ -978,6 +1148,10 @@ class ConversationViewModel: ObservableObject { id: !chatMessage.messageId.isEmpty ? chatMessage.messageId : UUID().uuidString, status: statusTmp, isOutgoing: chatMessage.isOutgoing, + isEditable: chatMessage.isOutgoing ? chatMessage.isEditable : false, + isRetractable: chatMessage.isOutgoing ? chatMessage.isRetractable : false, + isEdited: chatMessage.isEdited, + isRetracted: chatMessage.isRetracted, dateReceived: chatMessage.time, address: addressCleaned?.asStringUriOnly() ?? "", isFirstMessage: isFirstMessageTmp, @@ -1048,6 +1222,10 @@ class ConversationViewModel: ObservableObject { id: UUID().uuidString, status: nil, isOutgoing: false, + isEditable: false, + isRetractable: false, + isEdited: false, + isRetracted: false, dateReceived: 0, address: "", isFirstMessage: false, @@ -1221,6 +1399,8 @@ class ConversationViewModel: ObservableObject { let contentReplyText = chatMessage.replyMessage?.utf8Text ?? "" + let isReplyRetracted = chatMessage.replyMessage?.isRetracted ?? false + var attachmentNameReplyList: String = "" chatMessage.replyMessage?.contents.forEach { content in @@ -1238,7 +1418,11 @@ class ConversationViewModel: ObservableObject { address: addressReplyCleaned != nil ? addressReplyCleaned!.asStringUriOnly() : "", isFirstMessage: false, text: contentReplyText, - isOutgoing: false, + isOutgoing: chatMessage.replyMessage!.isOutgoing, + isEditable: false, + isRetractable: false, + isEdited: false, + isRetracted: isReplyRetracted, dateReceived: 0, attachmentsNames: attachmentNameReplyList, attachments: [] @@ -1253,6 +1437,10 @@ class ConversationViewModel: ObservableObject { appData: chatMessage.appdata ?? "", status: statusTmp, isOutgoing: chatMessage.isOutgoing, + isEditable: chatMessage.isOutgoing ? chatMessage.isEditable : false, + isRetractable: chatMessage.isOutgoing ? chatMessage.isRetractable : false, + isEdited: chatMessage.isEdited, + isRetracted: chatMessage.isRetracted, dateReceived: chatMessage.time, address: addressCleaned != nil ? addressCleaned!.asStringUriOnly() : "", isFirstMessage: isFirstMessageTmp, @@ -1453,6 +1641,8 @@ class ConversationViewModel: ObservableObject { let contentReplyText = chatMessage.replyMessage?.utf8Text ?? "" + let isReplyRetracted = chatMessage.replyMessage?.isRetracted ?? false + var attachmentNameReplyList: String = "" chatMessage.replyMessage?.contents.forEach { content in @@ -1470,7 +1660,11 @@ class ConversationViewModel: ObservableObject { address: addressReplyCleaned != nil ? addressReplyCleaned!.asStringUriOnly() : "", isFirstMessage: false, text: contentReplyText, - isOutgoing: false, + isOutgoing: chatMessage.replyMessage!.isOutgoing, + isEditable: false, + isRetractable: false, + isEdited: false, + isRetracted: isReplyRetracted, dateReceived: 0, attachmentsNames: attachmentNameReplyList, attachments: [] @@ -1485,6 +1679,10 @@ class ConversationViewModel: ObservableObject { appData: chatMessage.appdata ?? "", status: statusTmp, isOutgoing: chatMessage.isOutgoing, + isEditable: chatMessage.isOutgoing ? chatMessage.isEditable : false, + isRetractable: chatMessage.isOutgoing ? chatMessage.isRetractable : false, + isEdited: chatMessage.isEdited, + isRetracted: chatMessage.isRetracted, dateReceived: chatMessage.time, address: addressCleaned != nil ? addressCleaned!.asStringUriOnly() : "", isFirstMessage: isFirstMessageTmp, @@ -1526,6 +1724,10 @@ class ConversationViewModel: ObservableObject { id: UUID().uuidString, status: nil, isOutgoing: false, + isEditable: false, + isRetractable: false, + isEdited: false, + isRetracted: false, dateReceived: 0, address: "", isFirstMessage: false, @@ -1553,6 +1755,9 @@ class ConversationViewModel: ObservableObject { } func replyToMessage(index: Int, isMessageTextFocused: Binding) { + if self.messageToEdit != nil { + self.messageToEdit = nil + } coreContext.doOnCoreQueue { _ in let messageToReplyTmp = self.conversationMessagesSection[0].rows[index] DispatchQueue.main.async { @@ -1564,6 +1769,21 @@ class ConversationViewModel: ObservableObject { } } + func editMessage(chatMessage: EventLogMessage, isMessageTextFocused: Binding) { + if self.messageToReply != nil { + self.messageToReply = nil + } + coreContext.doOnCoreQueue { _ in + let messageToEditTmp = chatMessage + DispatchQueue.main.async { + withAnimation(.linear(duration: 0.15)) { + self.messageToEdit = messageToEditTmp + } + isMessageTextFocused.wrappedValue = true + } + } + } + func resendMessage(chatMessage: EventLogMessage) { coreContext.doOnCoreQueue { _ in if let message = chatMessage.eventModel.eventLog.chatMessage { @@ -1619,6 +1839,10 @@ class ConversationViewModel: ObservableObject { id: UUID().uuidString, status: nil, isOutgoing: false, + isEditable: false, + isRetractable: false, + isEdited: false, + isRetracted: false, dateReceived: 0, address: "", isFirstMessage: false, @@ -1778,6 +2002,8 @@ class ConversationViewModel: ObservableObject { let contentReplyText = chatMessage.replyMessage?.utf8Text ?? "" + let isReplyRetracted = chatMessage.replyMessage?.isRetracted ?? false + var attachmentNameReplyList: String = "" chatMessage.replyMessage?.contents.forEach { content in @@ -1795,7 +2021,11 @@ class ConversationViewModel: ObservableObject { address: addressReplyCleaned?.asStringUriOnly() ?? "", isFirstMessage: false, text: contentReplyText, - isOutgoing: false, + isOutgoing: chatMessage.replyMessage!.isOutgoing, + isEditable: false, + isRetractable: false, + isEdited: false, + isRetracted: isReplyRetracted, dateReceived: 0, attachmentsNames: attachmentNameReplyList, attachments: [] @@ -1809,6 +2039,10 @@ class ConversationViewModel: ObservableObject { id: !chatMessage.messageId.isEmpty ? chatMessage.messageId : UUID().uuidString, status: statusTmp, isOutgoing: chatMessage.isOutgoing, + isEditable: chatMessage.isOutgoing ? chatMessage.isEditable : false, + isRetractable: chatMessage.isOutgoing ? chatMessage.isRetractable : false, + isEdited: chatMessage.isEdited, + isRetracted: chatMessage.isRetracted, dateReceived: chatMessage.time, address: addressCleaned?.asStringUriOnly() ?? "", isFirstMessage: isFirstMessageTmp, @@ -1873,6 +2107,8 @@ class ConversationViewModel: ObservableObject { if chatMessageToReply != nil { message = try self.sharedMainViewModel.displayedConversation!.chatRoom.createReplyMessage(message: chatMessageToReply!) } + } else if let chatMessage = self.messageToEdit?.eventModel.eventLog.chatMessage { + message = try self.sharedMainViewModel.displayedConversation!.chatRoom.createReplacesMessage(message: chatMessage) } else { message = try self.sharedMainViewModel.displayedConversation!.chatRoom.createEmptyMessage() } @@ -1948,12 +2184,14 @@ class ConversationViewModel: ObservableObject { if message != nil && !message!.contents.isEmpty { Log.info("[ConversationViewModel] Sending message") message!.send() + self.sharedMainViewModel.displayedConversation!.chatRoom.stopComposing() } Log.info("[ConversationViewModel] Message sent, re-setting defaults") DispatchQueue.main.async { self.messageToReply = nil + self.messageToEdit = nil withAnimation { self.mediasToSend.removeAll() } @@ -2370,10 +2608,20 @@ class ConversationViewModel: ObservableObject { } } - func compose() { + func compose(stop: Bool, cachedConversation: ConversationModel? = nil) { coreContext.doOnCoreQueue { _ in - if self.sharedMainViewModel.displayedConversation != nil { - self.sharedMainViewModel.displayedConversation!.chatRoom.compose() + if let displayedConversation = self.sharedMainViewModel.displayedConversation { + if stop { + displayedConversation.chatRoom.stopComposing() + } else { + displayedConversation.chatRoom.composeTextMessage() + } + } else if let displayedConversation = cachedConversation { + if stop { + displayedConversation.chatRoom.stopComposing() + } else { + displayedConversation.chatRoom.composeTextMessage() + } } } } @@ -2672,17 +2920,39 @@ class ConversationViewModel: ObservableObject { if let displayedConversation = self.sharedMainViewModel.displayedConversation, let selectedMessage = self.selectedMessage, let chatMessage = selectedMessage.eventModel.eventLog.chatMessage { + + let indexReplyMessage = self.conversationMessagesSection[0].rows.firstIndex(where: {$0.message.replyMessage?.id == chatMessage.messageId}) displayedConversation.chatRoom.deleteMessage(message: chatMessage) + + displayedConversation.getContentTextMessage(chatRoom: displayedConversation.chatRoom) + DispatchQueue.main.async { if let sectionIndex = self.conversationMessagesSection.firstIndex(where: { $0.chatRoomID == displayedConversation.id }), let rowIndex = self.conversationMessagesSection[sectionIndex].rows.firstIndex(of: selectedMessage) { self.conversationMessagesSection[sectionIndex].rows.remove(at: rowIndex) + + if indexReplyMessage != nil { + self.conversationMessagesSection[0].rows[indexReplyMessage!].message.replyMessage = nil + } } self.selectedMessage = nil } } } } + + func deleteMessageForEveryone(){ + coreContext.doOnCoreQueue { _ in + if let displayedConversation = self.sharedMainViewModel.displayedConversation, + let selectedMessage = self.selectedMessage, + let chatMessage = selectedMessage.eventModel.eventLog.chatMessage { + displayedConversation.chatRoom.retractMessage(message: chatMessage) + DispatchQueue.main.async { + self.selectedMessage = nil + } + } + } + } } // swiftlint:enable line_length // swiftlint:enable type_body_length diff --git a/Linphone/UI/Main/Conversations/ViewModel/ConversationsListViewModel.swift b/Linphone/UI/Main/Conversations/ViewModel/ConversationsListViewModel.swift index f6c6b5bad..41e30f53e 100644 --- a/Linphone/UI/Main/Conversations/ViewModel/ConversationsListViewModel.swift +++ b/Linphone/UI/Main/Conversations/ViewModel/ConversationsListViewModel.swift @@ -89,16 +89,16 @@ class ConversationsListViewModel: ObservableObject { fromAddressFriend = nil } - let lastMessageTextTmp = (fromAddressFriend ?? "") + (lastMessage.contents.first(where: { $0.isText })?.utf8Text ?? (lastMessage.contents.first(where: { $0.isFile || $0.isFileTransfer })?.name ?? "")) + let lastMessagePrefixTextTmp = (fromAddressFriend ?? "") if let index = self.conversationsList.firstIndex(where: { $0.chatRoom === conversationModel.chatRoom }) { DispatchQueue.main.async { - conversationModel.lastMessageText = lastMessageTextTmp - self.conversationsList[index].lastMessageText = lastMessageTextTmp + conversationModel.lastMessagePrefixText = lastMessagePrefixTextTmp + self.conversationsList[index].lastMessagePrefixText = lastMessagePrefixTextTmp } } else { DispatchQueue.main.async { - conversationModel.lastMessageText = lastMessageTextTmp + conversationModel.lastMessagePrefixText = lastMessagePrefixTextTmp } } } @@ -148,16 +148,16 @@ class ConversationsListViewModel: ObservableObject { fromAddressFriend = nil } - let lastMessageTextTmp = (fromAddressFriend ?? "") + (lastMessage.contents.first(where: { $0.isText })?.utf8Text ?? (lastMessage.contents.first(where: { $0.isFile || $0.isFileTransfer })?.name ?? "")) + let lastMessagePrefixTextTmp = (fromAddressFriend ?? "") if let index = self.conversationsList.firstIndex(where: { $0.chatRoom === conversationModel.chatRoom }) { DispatchQueue.main.async { - conversationModel.lastMessageText = lastMessageTextTmp - self.conversationsList[index].lastMessageText = lastMessageTextTmp + conversationModel.lastMessagePrefixText = lastMessagePrefixTextTmp + self.conversationsList[index].lastMessagePrefixText = lastMessagePrefixTextTmp } } else { DispatchQueue.main.async { - conversationModel.lastMessageText = lastMessageTextTmp + conversationModel.lastMessagePrefixText = lastMessagePrefixTextTmp } } } @@ -183,69 +183,76 @@ class ConversationsListViewModel: ObservableObject { func addConversationDelegate() { coreContext.doOnCoreQueue { core in - self.coreConversationDelegate = CoreDelegateStub(onMessagesReceived: { (core: Core, chatRoom: ChatRoom, _: [ChatMessage]) in - if let defaultAddress = core.defaultAccount?.contactAddress, - let localAddress = chatRoom.localAddress, - defaultAddress.weakEqual(address2: localAddress) { - let idTmp = LinphoneUtils.getChatRoomId(room: chatRoom) - let model = self.conversationsList.first(where: { $0.id == idTmp }) ?? ConversationModel(chatRoom: chatRoom) - model.getContentTextMessage(chatRoom: chatRoom) - let index = self.conversationsList.firstIndex(where: { $0.id == idTmp }) - DispatchQueue.main.async { - if index != nil { - self.conversationsList.remove(at: index!) - } - self.conversationsList.insert(model, at: 0) - } - SharedMainViewModel.shared.updateUnreadMessagesCount() - } - }, onMessageSent: { (_: Core, chatRoom: ChatRoom, _: ChatMessage) in - if let defaultAddress = core.defaultAccount?.contactAddress, - let localAddress = chatRoom.localAddress, - defaultAddress.weakEqual(address2: localAddress) { - let idTmp = LinphoneUtils.getChatRoomId(room: chatRoom) - let model = self.conversationsList.first(where: { $0.id == idTmp }) ?? ConversationModel(chatRoom: chatRoom) - model.getContentTextMessage(chatRoom: chatRoom) - let index = self.conversationsList.firstIndex(where: { $0.id == idTmp }) - if index != nil { - self.conversationsList[index!].chatMessageRemoveDelegate() - } - DispatchQueue.main.async { - if index != nil { - self.conversationsList.remove(at: index!) - } - self.conversationsList.insert(model, at: 0) - } - SharedMainViewModel.shared.updateUnreadMessagesCount() - } - }, onChatRoomRead: { (_: Core, chatRoom: ChatRoom) in - let idTmp = LinphoneUtils.getChatRoomId(room: chatRoom) - let model = self.conversationsList.first(where: { $0.id == idTmp }) ?? ConversationModel(chatRoom: chatRoom) - model.getContentTextMessage(chatRoom: chatRoom) - if let index = self.conversationsList.firstIndex(where: { $0.id == idTmp }) { - DispatchQueue.main.async { - self.conversationsList.remove(at: index) - self.conversationsList.insert(model, at: index) + self.coreConversationDelegate = CoreDelegateStub( + onMessagesReceived: { (core: Core, chatRoom: ChatRoom, _: [ChatMessage]) in + if let defaultAddress = core.defaultAccount?.contactAddress, + let localAddress = chatRoom.localAddress, + defaultAddress.weakEqual(address2: localAddress) { + let idTmp = LinphoneUtils.getChatRoomId(room: chatRoom) + let model = self.conversationsList.first(where: { $0.id == idTmp }) ?? ConversationModel(chatRoom: chatRoom) + model.getContentTextMessage(chatRoom: chatRoom) + let index = self.conversationsList.firstIndex(where: { $0.id == idTmp }) + DispatchQueue.main.async { + if index != nil { + self.conversationsList.remove(at: index!) + } + self.conversationsList.insert(model, at: 0) + } + SharedMainViewModel.shared.updateUnreadMessagesCount() } + }, onMessageSent: { (_: Core, chatRoom: ChatRoom, _: ChatMessage) in + if let defaultAddress = core.defaultAccount?.contactAddress, + let localAddress = chatRoom.localAddress, + defaultAddress.weakEqual(address2: localAddress) { + let idTmp = LinphoneUtils.getChatRoomId(room: chatRoom) + let model = self.conversationsList.first(where: { $0.id == idTmp }) ?? ConversationModel(chatRoom: chatRoom) + model.getContentTextMessage(chatRoom: chatRoom) + let index = self.conversationsList.firstIndex(where: { $0.id == idTmp }) + if index != nil { + self.conversationsList[index!].chatMessageRemoveDelegate() + } + DispatchQueue.main.async { + if index != nil { + self.conversationsList.remove(at: index!) + } + self.conversationsList.insert(model, at: 0) + } + SharedMainViewModel.shared.updateUnreadMessagesCount() + } + }, onChatRoomRead: { (_: Core, chatRoom: ChatRoom) in + let idTmp = LinphoneUtils.getChatRoomId(room: chatRoom) + let model = self.conversationsList.first(where: { $0.id == idTmp }) ?? ConversationModel(chatRoom: chatRoom) + model.getContentTextMessage(chatRoom: chatRoom) + if let index = self.conversationsList.firstIndex(where: { $0.id == idTmp }) { + DispatchQueue.main.async { + self.conversationsList.remove(at: index) + self.conversationsList.insert(model, at: index) + } + } + SharedMainViewModel.shared.updateUnreadMessagesCount() + }, onChatRoomStateChanged: { (core: Core, chatroom: ChatRoom, state: ChatRoom.State) in + // Log.info("[ConversationsListViewModel] Conversation [${LinphoneUtils.getChatRoomId(chatRoom)}] state changed [$state]") + if let defaultAddress = core.defaultAccount?.contactAddress, + let localAddress = chatroom.localAddress, + defaultAddress.weakEqual(address2: localAddress) { + if core.globalState == .On { + switch state { + case .Created: + self.addChatRoom(chatRoom: chatroom) + case .Deleted: + self.removeChatRoom(chatRoom: chatroom) + default: + break + } + } + } + }, onMessageRetracted: { (core: Core, chatRoom: ChatRoom, message: ChatMessage) in + let idTmp = LinphoneUtils.getChatRoomId(room: chatRoom) + let model = self.conversationsList.first(where: { $0.id == idTmp }) ?? ConversationModel(chatRoom: chatRoom) + model.getContentTextMessage(chatRoom: chatRoom) + SharedMainViewModel.shared.updateUnreadMessagesCount() } - SharedMainViewModel.shared.updateUnreadMessagesCount() - }, onChatRoomStateChanged: { (core: Core, chatroom: ChatRoom, state: ChatRoom.State) in - // Log.info("[ConversationsListViewModel] Conversation [${LinphoneUtils.getChatRoomId(chatRoom)}] state changed [$state]") - if let defaultAddress = core.defaultAccount?.contactAddress, - let localAddress = chatroom.localAddress, - defaultAddress.weakEqual(address2: localAddress) { - if core.globalState == .On { - switch state { - case .Created: - self.addChatRoom(chatRoom: chatroom) - case .Deleted: - self.removeChatRoom(chatRoom: chatroom) - default: - break - } - } - } - }) + ) core.addDelegate(delegate: self.coreConversationDelegate!) } } diff --git a/Linphone/UI/Main/Fragments/PopupUpdatePassword.swift b/Linphone/UI/Main/Fragments/PopupUpdatePassword.swift index d7ca7d013..d61e153d6 100644 --- a/Linphone/UI/Main/Fragments/PopupUpdatePassword.swift +++ b/Linphone/UI/Main/Fragments/PopupUpdatePassword.swift @@ -105,7 +105,7 @@ struct PopupUpdatePassword: View { updateAuthInfo() isShowUpdatePasswordPopup = false }, label: { - Text("dialog_ok") + Text("dialog_confirm") .default_text_style_white_600(styleSize: 20) .frame(height: 35) .frame(maxWidth: .infinity) diff --git a/Linphone/UI/Main/Fragments/PopupView.swift b/Linphone/UI/Main/Fragments/PopupView.swift index a1bd4ac5a..784ed58ef 100644 --- a/Linphone/UI/Main/Fragments/PopupView.swift +++ b/Linphone/UI/Main/Fragments/PopupView.swift @@ -34,6 +34,9 @@ struct PopupView: View { var titleSecondButton: Text? var actionSecondButton: () -> Void + var titleThirdButton: Text? + var actionThirdButton: () -> Void + var body: some View { GeometryReader { geometry in VStack(alignment: .leading) { @@ -49,40 +52,57 @@ struct PopupView: View { .padding(.bottom, 20) } - if titleFirstButton != nil { - Button(action: { - actionFirstButton() - }, label: { - titleFirstButton - .default_text_style_orange_600(styleSize: 20) - .frame(height: 35) - .frame(maxWidth: .infinity) - }) - .padding(.horizontal, 20) - .padding(.vertical, 10) - .cornerRadius(60) - .overlay( - RoundedRectangle(cornerRadius: 60) - .inset(by: 0.5) - .stroke(Color.orangeMain500, lineWidth: 1) - ) - .padding(.bottom, 10) - } - - if titleSecondButton != nil { - Button(action: { - actionSecondButton() - }, label: { - titleSecondButton - .default_text_style_white_600(styleSize: 20) - .frame(height: 35) - .frame(maxWidth: .infinity) - }) - .padding(.horizontal, 20) - .padding(.vertical, 10) - .background(Color.orangeMain500) - .cornerRadius(60) + HStack { + if titleFirstButton != nil { + Button(action: { + actionFirstButton() + }, label: { + titleFirstButton + .default_text_style_white_600(styleSize: 14) + .frame(height: 30) + }) + .padding(.horizontal, 20) + .padding(.vertical, 10) + .background(Color.orangeMain500) + .cornerRadius(60) + .padding(.horizontal, 2) + } + + if titleSecondButton != nil { + Button(action: { + actionSecondButton() + }, label: { + titleSecondButton + .default_text_style_white_600(styleSize: 14) + .frame(height: 30) + }) + .padding(.horizontal, 20) + .padding(.vertical, 10) + .background(Color.orangeMain500) + .cornerRadius(60) + .padding(.horizontal, 2) + } + + if titleThirdButton != nil { + Button(action: { + actionThirdButton() + }, label: { + titleThirdButton + .default_text_style_orange_600(styleSize: 14) + .frame(height: 30) + }) + .padding(.horizontal, 20) + .padding(.vertical, 10) + .cornerRadius(60) + .overlay( + RoundedRectangle(cornerRadius: 60) + .inset(by: 0.5) + .stroke(Color.orangeMain500, lineWidth: 1) + ) + .padding(.horizontal, 2) + } } + .frame(maxWidth: .infinity, alignment: .trailing) } .padding(.horizontal, 20) .padding(.vertical, 20) @@ -101,9 +121,11 @@ struct PopupView: View { PopupView(isShowPopup: .constant(true), title: Text("Title"), content: Text("Content"), - titleFirstButton: Text("Deny all"), + titleFirstButton: Text("Accept all"), actionFirstButton: {}, - titleSecondButton: Text("Accept all"), - actionSecondButton: {}) + titleSecondButton: Text("dialog_confirm"), + actionSecondButton: {}, + titleThirdButton: Text("dialog_cancel"), + actionThirdButton: {}) .background(.black.opacity(0.65)) } diff --git a/Linphone/UI/Main/Fragments/PopupViewWithTextField.swift b/Linphone/UI/Main/Fragments/PopupViewWithTextField.swift index 58d22c217..7f8f58bfc 100644 --- a/Linphone/UI/Main/Fragments/PopupViewWithTextField.swift +++ b/Linphone/UI/Main/Fragments/PopupViewWithTextField.swift @@ -72,7 +72,7 @@ struct PopupViewWithTextField: View { setNewChatRoomSubject() isShowConversationInfoPopup = false }, label: { - Text("dialog_ok") + Text("dialog_confirm") .default_text_style_white_600(styleSize: 20) .frame(height: 35) .frame(maxWidth: .infinity) diff --git a/Linphone/UI/Main/Help/Fragments/HelpFragment.swift b/Linphone/UI/Main/Help/Fragments/HelpFragment.swift index 004bda777..549e64d49 100644 --- a/Linphone/UI/Main/Help/Fragments/HelpFragment.swift +++ b/Linphone/UI/Main/Help/Fragments/HelpFragment.swift @@ -177,7 +177,7 @@ struct HelpFragment: View { .frame(maxWidth: .infinity, alignment: .leading) .multilineTextAlignment(.leading) - Text(helpViewModel.version) + Text(helpViewModel.appVersion) .default_text_style(styleSize: 14) .frame(maxWidth: .infinity, alignment: .leading) .multilineTextAlignment(.leading) @@ -302,17 +302,17 @@ struct HelpFragment: View { isShowPopup: $helpViewModel.checkUpdateAvailable, title: Text("help_dialog_update_available_title"), content: Text(String(format: String(localized: "help_dialog_update_available_message"), helpViewModel.versionAvailable)), - titleFirstButton: Text("dialog_cancel"), - actionFirstButton: { - helpViewModel.checkUpdateAvailable = false - }, + titleFirstButton: nil, + actionFirstButton: {}, titleSecondButton: Text("dialog_install"), actionSecondButton: { helpViewModel.checkUpdateAvailable = false if let url = URL(string: helpViewModel.urlVersionAvailable) { UIApplication.shared.open(url) } - } + }, + titleThirdButton: Text("dialog_cancel"), + actionThirdButton: { helpViewModel.checkUpdateAvailable = false } ) .background(.black.opacity(0.65)) .zIndex(3) diff --git a/Linphone/UI/Main/Help/ViewModel/HelpViewModel.swift b/Linphone/UI/Main/Help/ViewModel/HelpViewModel.swift index 75d4b0456..f8d71ad84 100644 --- a/Linphone/UI/Main/Help/ViewModel/HelpViewModel.swift +++ b/Linphone/UI/Main/Help/ViewModel/HelpViewModel.swift @@ -38,13 +38,20 @@ class HelpViewModel: ObservableObject { private var coreDelegate: CoreDelegate? init() { - let appName = Bundle.main.infoDictionary?["CFBundleName"] as? String - let versionTmp = Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String - let build = Bundle.main.infoDictionary?["CFBundleVersion"] as? String + let appGitVersion = APP_GIT_COMMIT + let appGitBranch = APP_GIT_BRANCH + let appGitTag = APP_GIT_TAG + let sdkGitVersion = linphonesw.sdkVersion + var sdkGitBranch = linphonesw.sdkBranch - self.version = (versionTmp ?? "6.0.0") + if sdkGitBranch.hasPrefix("remotes/origin/") { + sdkGitBranch = String(sdkGitBranch.dropFirst("remotes/origin/".count)) + } - self.sdkVersion = Core.getVersion + self.appVersion = appGitTag + self.version = appGitTag + "-" + appGitVersion + "\n(\(appGitBranch))" + + self.sdkVersion = sdkGitVersion + "\n(\(sdkGitBranch))" if let path = Bundle.main.path(forResource: "GoogleService-Info", ofType: "plist"), let plist = NSDictionary(contentsOfFile: path) as? [String: Any], diff --git a/Linphone/UI/Main/History/Fragments/HistoryContactFragment.swift b/Linphone/UI/Main/History/Fragments/HistoryContactFragment.swift index 8ecbc13ad..132d4442d 100644 --- a/Linphone/UI/Main/History/Fragments/HistoryContactFragment.swift +++ b/Linphone/UI/Main/History/Fragments/HistoryContactFragment.swift @@ -196,14 +196,8 @@ struct HistoryContactFragment: View { .padding(.top, 5) } - if let avatarModel = historyModel.avatarModel { - Text(avatarModel.lastPresenceInfo) - .foregroundStyle(avatarModel.lastPresenceInfo == "Online" ? Color.greenSuccess500 : Color.orangeWarning600) - .multilineTextAlignment(.center) - .default_text_style_300(styleSize: 12) - .frame(maxWidth: .infinity) - .frame(height: 20) - .padding(.top, 5) + if let avatar = historyModel.avatarModel { + AvatarPresenceView(avatarModel: avatar) } else { Text("") .multilineTextAlignment(.center) @@ -423,6 +417,21 @@ struct HistoryContactFragment: View { } } +struct AvatarPresenceView: View { + @ObservedObject var avatarModel: ContactAvatarModel + + var body: some View { + Text(avatarModel.lastPresenceInfo) + .foregroundStyle(avatarModel.lastPresenceInfo == "Online" ? Color.greenSuccess500 : Color.orangeWarning600) + .multilineTextAlignment(.center) + .default_text_style_300(styleSize: 12) + .frame(maxWidth: .infinity) + .frame(height: 20) + .padding(.top, 5) + } +} + + #Preview { HistoryContactFragment( isShowDeleteAllHistoryPopup: .constant(false), diff --git a/Linphone/UI/Main/History/Fragments/StartCallFragment.swift b/Linphone/UI/Main/History/Fragments/StartCallFragment.swift index 735f3928a..6e526c32c 100644 --- a/Linphone/UI/Main/History/Fragments/StartCallFragment.swift +++ b/Linphone/UI/Main/History/Fragments/StartCallFragment.swift @@ -235,75 +235,83 @@ struct StartCallFragment: View { ) } - ScrollView { - if !ContactsManager.shared.lastSearch.isEmpty { - HStack(alignment: .center) { - Text("contacts_list_all_contacts_title") - .default_text_style_800(styleSize: 16) - - Spacer() + ZStack { + ScrollView { + if !ContactsManager.shared.lastSearch.isEmpty { + HStack(alignment: .center) { + Text("contacts_list_all_contacts_title") + .default_text_style_800(styleSize: 16) + + Spacer() + } + .padding(.vertical, 10) + .padding(.horizontal, 16) } - .padding(.vertical, 10) - .padding(.horizontal, 16) - } - - ContactsListFragment(showingSheet: .constant(false) - , startCallFunc: { addr in - if callViewModel.isTransferInsteadCall { - showingDialer = false - - startCallViewModel.searchField = "" - magicSearch.currentFilter = "" - - magicSearch.searchForContacts() - - if callViewModel.isTransferInsteadCall == true { - callViewModel.isTransferInsteadCall = false + + ContactsListFragment(showingSheet: .constant(false) + , startCallFunc: { addr in + if callViewModel.isTransferInsteadCall { + showingDialer = false + + startCallViewModel.searchField = "" + magicSearch.currentFilter = "" + + magicSearch.searchForContacts() + + if callViewModel.isTransferInsteadCall == true { + callViewModel.isTransferInsteadCall = false + } + + resetCallView() + + delayColorDismiss() + + withAnimation { + isShowStartCallFragment.toggle() + callViewModel.blindTransferCallTo(toAddress: addr) + } + } else { + showingDialer = false + + startCallViewModel.searchField = "" + magicSearch.currentFilter = "" + + magicSearch.searchForContacts() + + if callViewModel.isTransferInsteadCall == true { + callViewModel.isTransferInsteadCall = false + } + + resetCallView() + + delayColorDismiss() + + withAnimation { + isShowStartCallFragment.toggle() + telecomManager.doCallOrJoinConf(address: addr) + } } - - resetCallView() - - delayColorDismiss() - - withAnimation { - isShowStartCallFragment.toggle() - callViewModel.blindTransferCallTo(toAddress: addr) - } - } else { - showingDialer = false - - startCallViewModel.searchField = "" - magicSearch.currentFilter = "" - - magicSearch.searchForContacts() - - if callViewModel.isTransferInsteadCall == true { - callViewModel.isTransferInsteadCall = false - } - - resetCallView() - - delayColorDismiss() - - withAnimation { - isShowStartCallFragment.toggle() - telecomManager.doCallOrJoinConf(address: addr) - } - } - }) - .padding(.horizontal, 16) - - if !contactsManager.lastSearchSuggestions.isEmpty { - HStack(alignment: .center) { - Text("generic_address_picker_suggestions_list_title") - .default_text_style_800(styleSize: 16) - - Spacer() - } - .padding(.vertical, 10) + }) .padding(.horizontal, 16) - suggestionsList + if !contactsManager.lastSearchSuggestions.isEmpty { + HStack(alignment: .center) { + Text("generic_address_picker_suggestions_list_title") + .default_text_style_800(styleSize: 16) + + Spacer() + } + .padding(.vertical, 10) + .padding(.horizontal, 16) + + suggestionsList + } + } + + if magicSearch.isLoading { + ProgressView() + .controlSize(.large) + .progressViewStyle(CircularProgressViewStyle(tint: .orangeMain500)) } } } diff --git a/Linphone/UI/Main/History/ViewModel/HistoryListViewModel.swift b/Linphone/UI/Main/History/ViewModel/HistoryListViewModel.swift index 90aff417b..ceb6df8ea 100644 --- a/Linphone/UI/Main/History/ViewModel/HistoryListViewModel.swift +++ b/Linphone/UI/Main/History/ViewModel/HistoryListViewModel.swift @@ -45,7 +45,12 @@ class HistoryListViewModel: ObservableObject { func computeCallLogsList() { coreContext.doOnCoreQueue { core in let account = core.defaultAccount - let logs = account?.callLogs != nil ? account!.callLogs : core.callLogs + + // Fetch all call logs if only one account to workaround no history issue + // TODO FIXME: remove workaround later + let logs = (core.accountList.count > 1) + ? (account?.callLogs ?? core.callLogs) + : core.callLogs var callLogsBis: [HistoryModel] = [] var callLogsTmpBis: [HistoryModel] = [] diff --git a/Linphone/UI/Main/History/ViewModel/StartCallViewModel.swift b/Linphone/UI/Main/History/ViewModel/StartCallViewModel.swift index 1b3580a95..3b69db4cf 100644 --- a/Linphone/UI/Main/History/ViewModel/StartCallViewModel.swift +++ b/Linphone/UI/Main/History/ViewModel/StartCallViewModel.swift @@ -157,7 +157,7 @@ class StartCallViewModel: ObservableObject { func interpretAndStartCall() { CoreContext.shared.doOnCoreQueue { core in - let address = core.interpretUrl(url: self.searchField, applyInternationalPrefix: true) + let address = core.interpretUrl(url: self.searchField, applyInternationalPrefix: LinphoneUtils.applyInternationalPrefix(core: core)) if address != nil { TelecomManager.shared.doCallOrJoinConf(address: address!) } diff --git a/Linphone/UI/Main/Meetings/Fragments/AddParticipantsFragment.swift b/Linphone/UI/Main/Meetings/Fragments/AddParticipantsFragment.swift index 92cdfb634..7d2df6c33 100644 --- a/Linphone/UI/Main/Meetings/Fragments/AddParticipantsFragment.swift +++ b/Linphone/UI/Main/Meetings/Fragments/AddParticipantsFragment.swift @@ -167,76 +167,84 @@ struct AddParticipantsFragment: View { .padding(.bottom) .padding(.horizontal) - ScrollView { - ForEach(0.. \"$SRCROOT/Linphone/GeneratedGit.swift\"\nimport Foundation\n\npublic let APP_GIT_BRANCH = \"$branch\"\npublic let APP_GIT_COMMIT = \"$commit\"\npublic let APP_GIT_TAG = \"$tag\"\nEOF\n"; + }; /* End PBXShellScriptBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -1398,6 +1419,7 @@ C628172E2C1C3A3600DBA646 /* AccountExtension.swift in Sources */, 66C491FF2B24D4AC00CEA16D /* FileUtils.swift in Sources */, C62817322C1C400A00DBA646 /* StringExtension.swift in Sources */, + D7D1F5452EDDBBA70034EEB0 /* GeneratedGit.swift in Sources */, D74C9D012ACB098C0021626A /* PermissionManager.swift in Sources */, D7B99E992B29B39000BE7BF2 /* CallViewModel.swift in Sources */, D7702EF22AC7205000557C00 /* WelcomeView.swift in Sources */, @@ -1984,7 +2006,7 @@ kind = branch; }; }; - D7690B3B2EAF8878009CB3B7 /* XCRemoteSwiftPackageReference "linphone-sdk-swift-ios" */ = { + D7D1F5482EDDD8D30034EEB0 /* XCRemoteSwiftPackageReference "linphone-sdk-swift-ios" */ = { isa = XCRemoteSwiftPackageReference; repositoryURL = "https://gitlab.linphone.org/BC/public/linphone-sdk-swift-ios.git"; requirement = { diff --git a/LinphoneApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/LinphoneApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index 91670816a..197fc9498 100644 --- a/LinphoneApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/LinphoneApp.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -124,7 +124,7 @@ "location" : "https://gitlab.linphone.org/BC/public/linphone-sdk-swift-ios.git", "state" : { "branch" : "alpha", - "revision" : "b92c41b87c69771ccd276a957ab02c20178dffeb" + "revision" : "43ee1a062ef73808e27afe3c5341a27c1b82aae7" } }, {