From 6b93a7ef5e417c61c2dc7a818db58fcba572d861 Mon Sep 17 00:00:00 2001 From: Benoit Martins Date: Tue, 20 Jan 2026 13:32:21 +0100 Subject: [PATCH] Refactor toast system --- Linphone/Core/CoreContext.swift | 20 +++---- Linphone/GeneratedGit.swift | 2 +- Linphone/TelecomManager/TelecomManager.swift | 56 +++++++++---------- .../Viewmodel/AccountLoginViewModel.swift | 3 +- .../UI/Assistant/Viewmodel/QRScanner.swift | 15 +++-- .../Viewmodel/RegisterViewModel.swift | 9 +-- Linphone/UI/Call/CallView.swift | 3 +- .../UI/Call/ViewModel/CallViewModel.swift | 23 ++++---- .../Fragments/ContactListBottomSheet.swift | 3 +- .../ViewModel/ContactsListViewModel.swift | 12 ++-- Linphone/UI/Main/ContentView.swift | 3 +- .../Fragments/ConversationFragment.swift | 3 +- .../Model/ConversationModel.swift | 3 +- .../ConversationForwardMessageViewModel.swift | 12 ++-- .../ViewModel/ConversationViewModel.swift | 10 ++-- .../StartConversationViewModel.swift | 15 ++--- Linphone/UI/Main/Fragments/HelpView.swift | 3 +- Linphone/UI/Main/Fragments/ToastView.swift | 42 +++++--------- .../Main/Help/Fragments/DebugFragment.swift | 6 +- .../Main/Help/ViewModel/HelpViewModel.swift | 9 +-- .../Fragments/HistoryContactFragment.swift | 3 +- .../Fragments/HistoryListBottomSheet.swift | 3 +- .../ViewModel/HistoryListViewModel.swift | 12 ++-- .../ViewModel/StartCallViewModel.swift | 3 +- .../Meetings/Fragments/MeetingFragment.swift | 3 +- .../Meetings/ViewModel/MeetingViewModel.swift | 24 +++----- .../ViewModel/MeetingsListViewModel.swift | 6 +- .../Fragments/AccountProfileFragment.swift | 3 +- .../Settings/ViewModel/CardDavViewModel.swift | 9 +-- .../UI/Main/Viewmodel/ToastViewModel.swift | 34 +++++++++-- Linphone/Utils/URIHandler.swift | 3 +- 31 files changed, 156 insertions(+), 199 deletions(-) diff --git a/Linphone/Core/CoreContext.swift b/Linphone/Core/CoreContext.swift index 2f0222d12..65a60a307 100644 --- a/Linphone/Core/CoreContext.swift +++ b/Linphone/Core/CoreContext.swift @@ -106,12 +106,11 @@ class CoreContext: ObservableObject { DispatchQueue.main.async { if isConnected { Log.info("Network is now satisfied") - ToastViewModel.shared.toastMessage = "Success_toast_network_connected" + ToastViewModel.shared.show("Success_toast_network_connected") } else { Log.error("Network is now \(path.status)") - ToastViewModel.shared.toastMessage = "Unavailable_network" + ToastViewModel.shared.show("Unavailable_network") } - ToastViewModel.shared.displayToast = true } self.networkStatusIsConnected = isConnected } @@ -317,14 +316,11 @@ class CoreContext: ObservableObject { Log.info("[CoreContext] Transferred call \(transferred.remoteAddress!.asStringUriOnly()) state changed \(callState)") DispatchQueue.main.async { if callState == Call.State.Connected { - ToastViewModel.shared.toastMessage = "Success_toast_call_transfer_successful" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Success_toast_call_transfer_successful") } else if callState == Call.State.OutgoingProgress { - ToastViewModel.shared.toastMessage = "Success_toast_call_transfer_in_progress" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Success_toast_call_transfer_in_progress") } else if callState == Call.State.End || callState == Call.State.Error { - ToastViewModel.shared.toastMessage = "Failed_toast_call_transfer_failed" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Failed_toast_call_transfer_failed") } } }, onConfiguringStatus: { (_: Core, status: ConfiguringState, message: String) in @@ -345,8 +341,7 @@ class CoreContext: ObservableObject { if info.starts(with: "https") { DispatchQueue.main.async { UIPasteboard.general.setValue(info, forPasteboardType: UTType.plainText.identifier) - ToastViewModel.shared.toastMessage = "Success_send_logs" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Success_send_logs") } } }, onAccountRegistrationStateChanged: { (core: Core, account: Account, state: RegistrationState, message: String) in @@ -396,8 +391,7 @@ class CoreContext: ObservableObject { self.loggedIn = false if self.networkStatusIsConnected { // If network is disconnected, a toast message with key "Unavailable_network" should already be displayed - ToastViewModel.shared.toastMessage = "Registration_failed" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Registration_failed") } } diff --git a/Linphone/GeneratedGit.swift b/Linphone/GeneratedGit.swift index 65f061152..b67b705a1 100644 --- a/Linphone/GeneratedGit.swift +++ b/Linphone/GeneratedGit.swift @@ -2,6 +2,6 @@ import Foundation public enum AppGitInfo { public static let branch = "feature/search_chat_message" - public static let commit = "50b9c69b6" + public static let commit = "ac5a23bff" public static let tag = "6.1.0-alpha" } diff --git a/Linphone/TelecomManager/TelecomManager.swift b/Linphone/TelecomManager/TelecomManager.swift index 61139420f..b06bad07c 100644 --- a/Linphone/TelecomManager/TelecomManager.swift +++ b/Linphone/TelecomManager/TelecomManager.swift @@ -493,38 +493,36 @@ class TelecomManager: ObservableObject { let isRecordingByRemoteTmp = call.remoteParams?.isRecording ?? false - if isRecordingByRemoteTmp && ToastViewModel.shared.toastMessage.isEmpty { - - var displayName = "" - let friend = ContactsManager.shared.getFriendWithAddress(address: call.remoteAddress!) - if friend != nil && friend!.address != nil && friend!.address!.displayName != nil { - displayName = friend!.address!.displayName! - } else { - if call.remoteAddress!.displayName != nil { - displayName = call.remoteAddress!.displayName! - } else if call.remoteAddress!.username != nil { - displayName = call.remoteAddress!.username! - } else { - displayName = String(call.remoteAddress!.asStringUriOnly().dropFirst(4)) - } - } - - DispatchQueue.main.async { - self.isRecordingByRemote = isRecordingByRemoteTmp - ToastViewModel.shared.toastMessage = "\(displayName) is recording" - ToastViewModel.shared.displayToast = true - } - - Log.info("[Call] Call is recording by \(call.remoteAddress!.asStringUriOnly())") + let displayName: String + let friend = ContactsManager.shared.getFriendWithAddress(address: call.remoteAddress) + + if let name = friend?.address?.displayName { + displayName = name + } else if let name = call.remoteAddress?.displayName { + displayName = name + } else if let username = call.remoteAddress?.username { + displayName = username + } else if let uri = call.remoteAddress?.asStringUriOnly() { + displayName = String(uri.dropFirst(4)) + } else { + displayName = "Unknown" } - if !isRecordingByRemoteTmp && ToastViewModel.shared.toastMessage.contains("is recording") { - DispatchQueue.main.async { - self.isRecordingByRemote = isRecordingByRemoteTmp - ToastViewModel.shared.toastMessage = "" - ToastViewModel.shared.displayToast = false + DispatchQueue.main.async { + self.isRecordingByRemote = isRecordingByRemoteTmp + + if isRecordingByRemoteTmp { + ToastViewModel.shared.show("\(displayName) is recording") + } else if let toast = ToastViewModel.shared.toast, + toast.message.contains("is recording") { + ToastViewModel.shared.hide() } - Log.info("[Call] Recording is stopped by \(call.remoteAddress!.asStringUriOnly())") + } + + if isRecordingByRemoteTmp { + Log.info("[Call] Call is recording by \(call.remoteAddress?.asStringUriOnly() ?? "")") + } else { + Log.info("[Call] Recording is stopped by \(call.remoteAddress?.asStringUriOnly() ?? "")") } if cstate == Call.State.PausedByRemote { diff --git a/Linphone/UI/Assistant/Viewmodel/AccountLoginViewModel.swift b/Linphone/UI/Assistant/Viewmodel/AccountLoginViewModel.swift index f559918a8..bfdd46e47 100644 --- a/Linphone/UI/Assistant/Viewmodel/AccountLoginViewModel.swift +++ b/Linphone/UI/Assistant/Viewmodel/AccountLoginViewModel.swift @@ -41,8 +41,7 @@ class AccountLoginViewModel: ObservableObject { guard self.coreContext.networkStatusIsConnected else { DispatchQueue.main.async { self.coreContext.loggingInProgress = false - ToastViewModel.shared.toastMessage = "Unavailable_network" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Unavailable_network") } return } diff --git a/Linphone/UI/Assistant/Viewmodel/QRScanner.swift b/Linphone/UI/Assistant/Viewmodel/QRScanner.swift index a6db12b28..3d6512860 100644 --- a/Linphone/UI/Assistant/Viewmodel/QRScanner.swift +++ b/Linphone/UI/Assistant/Viewmodel/QRScanner.swift @@ -75,15 +75,18 @@ class Coordinator: NSObject, AVCaptureMetadataOutputObjectsDelegate { core.stop() try? core.start() } - ToastViewModel.shared.toastMessage = "Success_qr_code_validated" - ToastViewModel.shared.displayToast = true + DispatchQueue.main.async { + ToastViewModel.shared.show("Success_qr_code_validated") + } } else { - ToastViewModel.shared.toastMessage = "Invalide URI" - ToastViewModel.shared.displayToast.toggle() + DispatchQueue.main.async { + ToastViewModel.shared.show("Invalide URI") + } } } else { - ToastViewModel.shared.toastMessage = "Invalide URI" - ToastViewModel.shared.displayToast.toggle() + DispatchQueue.main.async { + ToastViewModel.shared.show("Invalide URI") + } } } } diff --git a/Linphone/UI/Assistant/Viewmodel/RegisterViewModel.swift b/Linphone/UI/Assistant/Viewmodel/RegisterViewModel.swift index 81154d54f..a796f0718 100644 --- a/Linphone/UI/Assistant/Viewmodel/RegisterViewModel.swift +++ b/Linphone/UI/Assistant/Viewmodel/RegisterViewModel.swift @@ -168,8 +168,7 @@ class RegisterViewModel: ObservableObject { if !errorMessage.isEmpty { DispatchQueue.main.async { - ToastViewModel.shared.toastMessage = "Error: \(errorMessage)" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Error: \(errorMessage)") } } @@ -341,8 +340,7 @@ class RegisterViewModel: ObservableObject { DispatchQueue.main.async { self.createInProgress = false - ToastViewModel.shared.toastMessage = "Failed_push_notification_not_received_error" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Failed_push_notification_not_received_error") } } @@ -430,8 +428,7 @@ class RegisterViewModel: ObservableObject { Log.error("\(RegisterViewModel.TAG) Account manager services hasn't been initialized!") DispatchQueue.main.async { - ToastViewModel.shared.toastMessage = "Failed_account_register_unexpected_error" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Failed_account_register_unexpected_error") } } } diff --git a/Linphone/UI/Call/CallView.swift b/Linphone/UI/Call/CallView.swift index fbe85853c..bd16c1b15 100644 --- a/Linphone/UI/Call/CallView.swift +++ b/Linphone/UI/Call/CallView.swift @@ -672,8 +672,7 @@ struct CallView: View { ) DispatchQueue.main.async { - ToastViewModel.shared.toastMessage = "Success_address_copied_into_clipboard" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Success_address_copied_into_clipboard") } }, label: { HStack { diff --git a/Linphone/UI/Call/ViewModel/CallViewModel.swift b/Linphone/UI/Call/ViewModel/CallViewModel.swift index 9c5c97dd5..4a9cb87b7 100644 --- a/Linphone/UI/Call/ViewModel/CallViewModel.swift +++ b/Linphone/UI/Call/ViewModel/CallViewModel.swift @@ -974,8 +974,7 @@ class CallViewModel: ObservableObject { self.isNotEncrypted = false if isDeviceTrusted && withToast { - ToastViewModel.shared.toastMessage = "Info_call_securised" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Info_call_securised") } } @@ -1058,8 +1057,9 @@ class CallViewModel: ObservableObject { try callToTransferTo!.transferToAnother(dest: self.currentCall!) Log.info("[CallViewModel] Attended transfer is successful") } catch _ { - ToastViewModel.shared.toastMessage = "Failed_toast_call_transfer_failed" - ToastViewModel.shared.displayToast = true + DispatchQueue.main.async { + ToastViewModel.shared.show("Failed_toast_call_transfer_failed") + } Log.error("[CallViewModel] Failed to make attended transfer!") } @@ -1078,9 +1078,9 @@ class CallViewModel: ObservableObject { try self.currentCall!.transferTo(referTo: toAddress) Log.info("[CallViewModel] Blind call transfer is successful") } catch _ { - ToastViewModel.shared.toastMessage = "Failed_toast_call_transfer_failed" - ToastViewModel.shared.displayToast = true - + DispatchQueue.main.async { + ToastViewModel.shared.show("Failed_toast_call_transfer_failed") + } Log.error("[CallViewModel] Failed to make blind call transfer!") } } @@ -1275,8 +1275,7 @@ class CallViewModel: ObservableObject { ) DispatchQueue.main.async { self.operationInProgress = false - ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Failed_to_create_conversation_error") } } } @@ -1366,8 +1365,7 @@ class CallViewModel: ObservableObject { self.chatRoomDelegate = nil DispatchQueue.main.async { self.operationInProgress = false - ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Failed_to_create_conversation_error") } } }, onConferenceJoined: { (chatRoom: ChatRoom, _: EventLog) in @@ -1401,8 +1399,7 @@ class CallViewModel: ObservableObject { self.chatRoomDelegate = nil DispatchQueue.main.async { self.operationInProgress = false - ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Failed_to_create_conversation_error") } } }) diff --git a/Linphone/UI/Main/Contacts/Fragments/ContactListBottomSheet.swift b/Linphone/UI/Main/Contacts/Fragments/ContactListBottomSheet.swift index f1e3794e6..af37f2917 100644 --- a/Linphone/UI/Main/Contacts/Fragments/ContactListBottomSheet.swift +++ b/Linphone/UI/Main/Contacts/Fragments/ContactListBottomSheet.swift @@ -66,8 +66,7 @@ struct ContactListBottomSheet: View { dismiss() } - ToastViewModel.shared.toastMessage = "Success_address_copied_into_clipboard" - ToastViewModel.shared.displayToast.toggle() + ToastViewModel.shared.show("Success_address_copied_into_clipboard") } label: { HStack { diff --git a/Linphone/UI/Main/Contacts/ViewModel/ContactsListViewModel.swift b/Linphone/UI/Main/Contacts/ViewModel/ContactsListViewModel.swift index 56414b9f2..d94550342 100644 --- a/Linphone/UI/Main/Contacts/ViewModel/ContactsListViewModel.swift +++ b/Linphone/UI/Main/Contacts/ViewModel/ContactsListViewModel.swift @@ -91,8 +91,7 @@ class ContactsListViewModel: ObservableObject { DispatchQueue.main.async { SharedMainViewModel.shared.operationInProgress = false - ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Failed_to_create_conversation_error") } return } @@ -137,8 +136,7 @@ class ContactsListViewModel: ObservableObject { DispatchQueue.main.async { SharedMainViewModel.shared.operationInProgress = false - ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Failed_to_create_conversation_error") } } } else { @@ -168,8 +166,7 @@ class ContactsListViewModel: ObservableObject { } DispatchQueue.main.async { SharedMainViewModel.shared.operationInProgress = false - ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Failed_to_create_conversation_error") } } }, onConferenceJoined: { (chatRoom: ChatRoom, _: EventLog) in @@ -207,8 +204,7 @@ class ContactsListViewModel: ObservableObject { } DispatchQueue.main.async { SharedMainViewModel.shared.operationInProgress = false - ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Failed_to_create_conversation_error") } } }) diff --git a/Linphone/UI/Main/ContentView.swift b/Linphone/UI/Main/ContentView.swift index f837974f8..ddd8d984b 100644 --- a/Linphone/UI/Main/ContentView.swift +++ b/Linphone/UI/Main/ContentView.swift @@ -1198,8 +1198,7 @@ struct ContentView: View { self.isShowDeleteAllHistoryPopup.toggle() sharedMainViewModel.displayedCall = nil - ToastViewModel.shared.toastMessage = "Success_remove_call_logs" - ToastViewModel.shared.displayToast.toggle() + ToastViewModel.shared.show("Success_remove_call_logs") }, titleThirdButton: Text("dialog_cancel"), actionThirdButton: { diff --git a/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift b/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift index 3b50525a4..caddb0aec 100644 --- a/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift +++ b/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift @@ -1393,8 +1393,7 @@ struct ConversationFragment: View { forPasteboardType: UTType.plainText.identifier ) - ToastViewModel.shared.toastMessage = "Success_message_copied_into_clipboard" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Success_message_copied_into_clipboard") conversationViewModel.selectedMessage = nil } label: { diff --git a/Linphone/UI/Main/Conversations/Model/ConversationModel.swift b/Linphone/UI/Main/Conversations/Model/ConversationModel.swift index 584e1c7d9..fe307a8d1 100644 --- a/Linphone/UI/Main/Conversations/Model/ConversationModel.swift +++ b/Linphone/UI/Main/Conversations/Model/ConversationModel.swift @@ -240,8 +240,7 @@ class ConversationModel: ObservableObject, Identifiable { } else if state == .CreationFailed { Log.error("\(ConversationModel.TAG) Failed to create group call!") DispatchQueue.main.async { - ToastViewModel.shared.toastMessage = "Failed_to_create_group_call_error" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Failed_to_create_group_call_error") } } }) diff --git a/Linphone/UI/Main/Conversations/ViewModel/ConversationForwardMessageViewModel.swift b/Linphone/UI/Main/Conversations/ViewModel/ConversationForwardMessageViewModel.swift index 50f2c37f3..47589e575 100644 --- a/Linphone/UI/Main/Conversations/ViewModel/ConversationForwardMessageViewModel.swift +++ b/Linphone/UI/Main/Conversations/ViewModel/ConversationForwardMessageViewModel.swift @@ -134,8 +134,7 @@ class ConversationForwardMessageViewModel: ObservableObject { DispatchQueue.main.async { self.operationInProgress = false - ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Failed_to_create_conversation_error") } return } @@ -180,8 +179,7 @@ class ConversationForwardMessageViewModel: ObservableObject { DispatchQueue.main.async { self.operationInProgress = false - ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Failed_to_create_conversation_error") } } } else { @@ -211,8 +209,7 @@ class ConversationForwardMessageViewModel: ObservableObject { self.chatRoomDelegate = nil DispatchQueue.main.async { self.operationInProgress = false - ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Failed_to_create_conversation_error") } } }, onConferenceJoined: { (chatRoom: ChatRoom, _: EventLog) in @@ -250,8 +247,7 @@ class ConversationForwardMessageViewModel: ObservableObject { self.chatRoomDelegate = nil DispatchQueue.main.async { self.operationInProgress = false - ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Failed_to_create_conversation_error") } } }) diff --git a/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift b/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift index 6c55db2aa..6c4461062 100644 --- a/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift +++ b/Linphone/UI/Main/Conversations/ViewModel/ConversationViewModel.swift @@ -3013,8 +3013,9 @@ class ConversationViewModel: ObservableObject { searchInProgress = false if latestMatch == nil { print("searchChatMessageAAA 22") - ToastViewModel.shared.toastMessage = "Failed_search_no_match_found" - ToastViewModel.shared.displayToast = true + DispatchQueue.main.async { + ToastViewModel.shared.show("Failed_search_no_match_found") + } } else { print("searchChatMessageAAA 33") // Scroll to last matching event anyway, user may have scrolled away @@ -3029,8 +3030,9 @@ class ConversationViewModel: ObservableObject { } print("searchChatMessageAAA 33Bis") } - ToastViewModel.shared.toastMessage = "Failed_search_results_limit_reached" - ToastViewModel.shared.displayToast = true + DispatchQueue.main.async { + ToastViewModel.shared.show("Failed_search_results_limit_reached") + } } } } diff --git a/Linphone/UI/Main/Conversations/ViewModel/StartConversationViewModel.swift b/Linphone/UI/Main/Conversations/ViewModel/StartConversationViewModel.swift index 165d01e73..5973ca674 100644 --- a/Linphone/UI/Main/Conversations/ViewModel/StartConversationViewModel.swift +++ b/Linphone/UI/Main/Conversations/ViewModel/StartConversationViewModel.swift @@ -149,8 +149,7 @@ class StartConversationViewModel: ObservableObject { DispatchQueue.main.async { self.operationGroupInProgress = false - ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Failed_to_create_conversation_error") } } } @@ -206,8 +205,7 @@ class StartConversationViewModel: ObservableObject { DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { self.operationOneToOneInProgress = false - ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Failed_to_create_conversation_error") } return } @@ -252,8 +250,7 @@ class StartConversationViewModel: ObservableObject { DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { self.operationOneToOneInProgress = false - ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Failed_to_create_conversation_error") } } } else { @@ -284,8 +281,7 @@ class StartConversationViewModel: ObservableObject { DispatchQueue.main.async { self.operationOneToOneInProgress = false self.operationGroupInProgress = false - ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Failed_to_create_conversation_error") } } }, onConferenceJoined: { (chatRoom: ChatRoom, _: EventLog) in @@ -314,8 +310,7 @@ class StartConversationViewModel: ObservableObject { DispatchQueue.main.async { self.operationOneToOneInProgress = false self.operationGroupInProgress = false - ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Failed_to_create_conversation_error") } } }) diff --git a/Linphone/UI/Main/Fragments/HelpView.swift b/Linphone/UI/Main/Fragments/HelpView.swift index d7932a40d..f5aa79a4d 100644 --- a/Linphone/UI/Main/Fragments/HelpView.swift +++ b/Linphone/UI/Main/Fragments/HelpView.swift @@ -32,8 +32,7 @@ class HelpView { // TODO (basic debug moved here until halp view is implemented) CoreContext.shared.doOnCoreQueue { _ in Core.resetLogCollection() DispatchQueue.main.async { - ToastViewModel.shared.toastMessage = "help_troubleshooting_debug_logs_cleaned_toast_message" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("help_troubleshooting_debug_logs_cleaned_toast_message") } } } diff --git a/Linphone/UI/Main/Fragments/ToastView.swift b/Linphone/UI/Main/Fragments/ToastView.swift index f5833c143..95fe774e3 100644 --- a/Linphone/UI/Main/Fragments/ToastView.swift +++ b/Linphone/UI/Main/Fragments/ToastView.swift @@ -25,39 +25,39 @@ struct ToastView: View { var body: some View { VStack { - if toastViewModel.displayToast { + if let toast = toastViewModel.toast { HStack { - if toastViewModel.toastMessage.contains("Failed_search") { + if toast.message.contains("Failed_search") { Image("magnifying-glass") .resizable() .renderingMode(.template) .frame(width: 25, height: 25, alignment: .leading) .foregroundStyle(Color.redDanger500) - } else if toastViewModel.toastMessage.contains("toast_call_transfer") { + } else if toast.message.contains("toast_call_transfer") { Image("phone-transfer") .resizable() .renderingMode(.template) .frame(width: 25, height: 25, alignment: .leading) - .foregroundStyle(toastViewModel.toastMessage.contains("Success") ? Color.greenSuccess500 : Color.redDanger500) - } else if toastViewModel.toastMessage.contains("is recording") { + .foregroundStyle(toast.message.contains("Success") ? Color.greenSuccess500 : Color.redDanger500) + } else if toast.message.contains("is recording") { Image("record-fill") .resizable() .renderingMode(.template) .frame(width: 25, height: 25, alignment: .leading) .foregroundStyle(Color.redDanger500) - } else if toastViewModel.toastMessage.contains("Info_") { + } else if toast.message.contains("Info_") { Image("trusted") .resizable() .frame(width: 25, height: 25, alignment: .leading) } else { - Image(toastViewModel.toastMessage.contains("Success") ? "check" : "warning-circle") + Image(toast.message.contains("Success") ? "check" : "warning-circle") .resizable() .renderingMode(.template) .frame(width: 25, height: 25, alignment: .leading) - .foregroundStyle(toastViewModel.toastMessage.contains("Success") ? Color.greenSuccess500 : Color.redDanger500) + .foregroundStyle(toast.message.contains("Success") ? Color.greenSuccess500 : Color.redDanger500) } - switch toastViewModel.toastMessage { + switch toast.message { case "Success_qr_code_validated": Text("qr_code_validated") .multilineTextAlignment(.center) @@ -122,7 +122,7 @@ struct ToastView: View { .padding(8) case let str where str.contains("is recording"): - Text(toastViewModel.toastMessage) + Text(toast.message) .multilineTextAlignment(.center) .foregroundStyle(Color.redDanger500) .default_text_style(styleSize: 15) @@ -255,7 +255,7 @@ struct ToastView: View { .padding(8) case let str where str.contains("Error: "): - Text(toastViewModel.toastMessage) + Text(toast.message) .multilineTextAlignment(.center) .foregroundStyle(Color.redDanger500) .default_text_style(styleSize: 15) @@ -371,26 +371,14 @@ struct ToastView: View { .overlay( RoundedRectangle(cornerRadius: 50) .inset(by: 0.5) - .stroke(toastViewModel.toastMessage.contains("Success") - ? Color.greenSuccess500 : (toastViewModel.toastMessage.contains("Info_") + .stroke(toast.message.contains("Success") + ? Color.greenSuccess500 : (toast.message.contains("Info_") ? Color.blueInfo500 : Color.redDanger500), lineWidth: 1) ) .onTapGesture { - if !toastViewModel.toastMessage.contains("is recording") { + if !toast.message.contains("is recording") { withAnimation { - toastViewModel.toastMessage = "" - toastViewModel.displayToast = false - } - } - } - .onAppear { - print("toastMessagetoastMessage 00 \(toastViewModel.toastMessage) \(toastViewModel.displayToast)") - if !toastViewModel.toastMessage.contains("is recording") { - DispatchQueue.main.asyncAfter(deadline: .now() + 2) { - withAnimation { - toastViewModel.toastMessage = "" - toastViewModel.displayToast = false - } + toastViewModel.hide() } } } diff --git a/Linphone/UI/Main/Help/Fragments/DebugFragment.swift b/Linphone/UI/Main/Help/Fragments/DebugFragment.swift index 15cc2141d..638f8e5ff 100644 --- a/Linphone/UI/Main/Help/Fragments/DebugFragment.swift +++ b/Linphone/UI/Main/Help/Fragments/DebugFragment.swift @@ -109,8 +109,7 @@ struct DebugFragment: View { helpViewModel.version, forPasteboardType: UTType.plainText.identifier ) - ToastViewModel.shared.toastMessage = "Success_text_copied_into_clipboard" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Success_text_copied_into_clipboard") } label: { HStack { Image("app-store-logo") @@ -145,8 +144,7 @@ struct DebugFragment: View { helpViewModel.sdkVersion, forPasteboardType: UTType.plainText.identifier ) - ToastViewModel.shared.toastMessage = "Success_text_copied_into_clipboard" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Success_text_copied_into_clipboard") } label: { HStack { Image("package") diff --git a/Linphone/UI/Main/Help/ViewModel/HelpViewModel.swift b/Linphone/UI/Main/Help/ViewModel/HelpViewModel.swift index 97ce95fbc..476e80e13 100644 --- a/Linphone/UI/Main/Help/ViewModel/HelpViewModel.swift +++ b/Linphone/UI/Main/Help/ViewModel/HelpViewModel.swift @@ -92,14 +92,12 @@ class HelpViewModel: ObservableObject { case .UpToDate: Log.info("\(self.TAG): This version is up-to-date") DispatchQueue.main.async { - ToastViewModel.shared.toastMessage = "Success_version_up_to_date" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Success_version_up_to_date") } default: Log.info("\(self.TAG): Can't check for update, an error happened [\(result)]") DispatchQueue.main.async { - ToastViewModel.shared.toastMessage = "Error" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Error") } } } @@ -142,8 +140,7 @@ class HelpViewModel: ObservableObject { Core.resetLogCollection() Log.info("\(self.TAG) Debug logs have been cleaned") DispatchQueue.main.async { - ToastViewModel.shared.toastMessage = "Success_clear_logs" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Success_clear_logs") } } } diff --git a/Linphone/UI/Main/History/Fragments/HistoryContactFragment.swift b/Linphone/UI/Main/History/Fragments/HistoryContactFragment.swift index 055c72e34..dd85deb3a 100644 --- a/Linphone/UI/Main/History/Fragments/HistoryContactFragment.swift +++ b/Linphone/UI/Main/History/Fragments/HistoryContactFragment.swift @@ -120,8 +120,7 @@ struct HistoryContactFragment: View { ) } - ToastViewModel.shared.toastMessage = "Success_address_copied_into_clipboard" - ToastViewModel.shared.displayToast.toggle() + ToastViewModel.shared.show("Success_address_copied_into_clipboard") } label: { HStack { diff --git a/Linphone/UI/Main/History/Fragments/HistoryListBottomSheet.swift b/Linphone/UI/Main/History/Fragments/HistoryListBottomSheet.swift index 5b682f655..cbb88e6c4 100644 --- a/Linphone/UI/Main/History/Fragments/HistoryListBottomSheet.swift +++ b/Linphone/UI/Main/History/Fragments/HistoryListBottomSheet.swift @@ -152,8 +152,7 @@ struct HistoryListBottomSheet: View { dismiss() } - ToastViewModel.shared.toastMessage = "Success_address_copied_into_clipboard" - ToastViewModel.shared.displayToast.toggle() + ToastViewModel.shared.show("Success_address_copied_into_clipboard") } label: { HStack { diff --git a/Linphone/UI/Main/History/ViewModel/HistoryListViewModel.swift b/Linphone/UI/Main/History/ViewModel/HistoryListViewModel.swift index ceb6df8ea..e515e48a1 100644 --- a/Linphone/UI/Main/History/ViewModel/HistoryListViewModel.swift +++ b/Linphone/UI/Main/History/ViewModel/HistoryListViewModel.swift @@ -280,8 +280,7 @@ class HistoryListViewModel: ObservableObject { DispatchQueue.main.async { SharedMainViewModel.shared.operationInProgress = false - ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Failed_to_create_conversation_error") } return } @@ -326,8 +325,7 @@ class HistoryListViewModel: ObservableObject { DispatchQueue.main.async { SharedMainViewModel.shared.operationInProgress = false - ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Failed_to_create_conversation_error") } } } else { @@ -357,8 +355,7 @@ class HistoryListViewModel: ObservableObject { } DispatchQueue.main.async { SharedMainViewModel.shared.operationInProgress = false - ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Failed_to_create_conversation_error") } } }, onConferenceJoined: { (chatRoom: ChatRoom, _: EventLog) in @@ -396,8 +393,7 @@ class HistoryListViewModel: ObservableObject { } DispatchQueue.main.async { SharedMainViewModel.shared.operationInProgress = false - ToastViewModel.shared.toastMessage = "Failed_to_create_conversation_error" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Failed_to_create_conversation_error") } } }) diff --git a/Linphone/UI/Main/History/ViewModel/StartCallViewModel.swift b/Linphone/UI/Main/History/ViewModel/StartCallViewModel.swift index 3b69db4cf..99d9c45dc 100644 --- a/Linphone/UI/Main/History/ViewModel/StartCallViewModel.swift +++ b/Linphone/UI/Main/History/ViewModel/StartCallViewModel.swift @@ -139,8 +139,7 @@ class StartCallViewModel: ObservableObject { } else if state == .CreationFailed { Log.error("\(StartCallViewModel.TAG) Failed to create group call!") DispatchQueue.main.async { - ToastViewModel.shared.toastMessage = "Failed_to_create_group_call_error" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Failed_to_create_group_call_error") self.operationInProgress = false } } diff --git a/Linphone/UI/Main/Meetings/Fragments/MeetingFragment.swift b/Linphone/UI/Main/Meetings/Fragments/MeetingFragment.swift index 5c0de1313..fb8b9d102 100644 --- a/Linphone/UI/Main/Meetings/Fragments/MeetingFragment.swift +++ b/Linphone/UI/Main/Meetings/Fragments/MeetingFragment.swift @@ -199,8 +199,7 @@ struct MeetingFragment: View { ) DispatchQueue.main.async { - ToastViewModel.shared.toastMessage = "Success_address_copied_into_clipboard" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Success_address_copied_into_clipboard") } }, label: { HStack { diff --git a/Linphone/UI/Main/Meetings/ViewModel/MeetingViewModel.swift b/Linphone/UI/Main/Meetings/ViewModel/MeetingViewModel.swift index 0a33b5b1e..7dd87d9d8 100644 --- a/Linphone/UI/Main/Meetings/ViewModel/MeetingViewModel.swift +++ b/Linphone/UI/Main/Meetings/ViewModel/MeetingViewModel.swift @@ -201,8 +201,7 @@ class MeetingViewModel: ObservableObject { DispatchQueue.main.async { self.operationInProgress = false self.errorMsg = (SharedMainViewModel.shared.displayedMeeting != nil) ? "Could not edit conference" : "Could not create conference" - ToastViewModel.shared.toastMessage = (SharedMainViewModel.shared.displayedMeeting != nil) ? "meeting_failed_to_edit_toast" : "meeting_failed_to_schedule_toast" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show((SharedMainViewModel.shared.displayedMeeting != nil) ? "meeting_failed_to_edit_toast" : "meeting_failed_to_schedule_toast") } } else if state == ConferenceScheduler.State.Ready { if let confInfo = scheduler.info, let conferenceAddress = confInfo.uri { @@ -210,8 +209,7 @@ class MeetingViewModel: ObservableObject { } DispatchQueue.main.async { - ToastViewModel.shared.toastMessage = "Success_meeting_info_created_toast" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Success_meeting_info_created_toast") } if SharedMainViewModel.shared.displayedMeeting != nil { @@ -243,8 +241,7 @@ class MeetingViewModel: ObservableObject { } else if failedInvitations.count == self.participants.count { Log.error("\(MeetingViewModel.TAG) No invitation sent!") DispatchQueue.main.async { - ToastViewModel.shared.toastMessage = "meeting_failed_to_send_invites_toast" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("meeting_failed_to_send_invites_toast") } } else { var failInvList = "" @@ -256,8 +253,7 @@ class MeetingViewModel: ObservableObject { } Log.warn("\(MeetingViewModel.TAG) \(failedInvitations.count) invitations couldn't have been sent to: \(failInvList)") DispatchQueue.main.async { - ToastViewModel.shared.toastMessage = "meeting_failed_to_send_part_of_invites_toast" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("meeting_failed_to_send_part_of_invites_toast") } } @@ -273,16 +269,14 @@ class MeetingViewModel: ObservableObject { guard !subject.isEmpty && !participants.isEmpty else { Log.error("\(MeetingViewModel.TAG) Either no subject was set or no participant was selected, can't schedule meeting.") DispatchQueue.main.async { - ToastViewModel.shared.toastMessage = "Failed_no_subject_or_participant" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Failed_no_subject_or_participant") } return } guard CoreContext.shared.networkStatusIsConnected else { DispatchQueue.main.async { - ToastViewModel.shared.toastMessage = "Unavailable_network" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Unavailable_network") } return } @@ -427,12 +421,10 @@ class MeetingViewModel: ObservableObject { do { try self.eventStore.save(event, span: .thisEvent) Log.info("\(MeetingViewModel.TAG) Meeting '\(self.subject)' added to calendar") - ToastViewModel.shared.toastMessage = "Meeting_added_to_calendar" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Meeting_added_to_calendar") } catch let error as NSError { Log.error("\(MeetingViewModel.TAG) Failed to add meeting to calendar: \(error)") - ToastViewModel.shared.toastMessage = "Error: \(error)" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Error: \(error)") } } else { Log.error("\(MeetingViewModel.TAG) Failed to add meeting to calendar: \(error?.localizedDescription ?? "")") diff --git a/Linphone/UI/Main/Meetings/ViewModel/MeetingsListViewModel.swift b/Linphone/UI/Main/Meetings/ViewModel/MeetingsListViewModel.swift index 56eb6d512..2ae43d0a0 100644 --- a/Linphone/UI/Main/Meetings/ViewModel/MeetingsListViewModel.swift +++ b/Linphone/UI/Main/Meetings/ViewModel/MeetingsListViewModel.swift @@ -143,8 +143,7 @@ class MeetingsListViewModel: ObservableObject { // Only remaining meeting is the fake TodayMeeting, remove it too self.meetingsList.removeAll() } - ToastViewModel.shared.toastMessage = "Success_toast_meeting_deleted" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Success_toast_meeting_deleted") } } } @@ -180,8 +179,7 @@ class MeetingsListViewModel: ObservableObject { } Log.warn("\(MeetingViewModel.TAG) \(failedInvitations.count) invitations couldn't have been sent to: \(failInvList)") DispatchQueue.main.async { - ToastViewModel.shared.toastMessage = "meeting_failed_to_send_part_of_invites_toast" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("meeting_failed_to_send_part_of_invites_toast") } } }) diff --git a/Linphone/UI/Main/Settings/Fragments/AccountProfileFragment.swift b/Linphone/UI/Main/Settings/Fragments/AccountProfileFragment.swift index 90d4fbd6b..21aec010d 100644 --- a/Linphone/UI/Main/Settings/Fragments/AccountProfileFragment.swift +++ b/Linphone/UI/Main/Settings/Fragments/AccountProfileFragment.swift @@ -287,8 +287,7 @@ struct AccountProfileFragment: View { forPasteboardType: UTType.plainText.identifier ) - ToastViewModel.shared.toastMessage = "Success_address_copied_into_clipboard" - ToastViewModel.shared.displayToast.toggle() + ToastViewModel.shared.show("Success_address_copied_into_clipboard") }, label: { Image("copy") .resizable() diff --git a/Linphone/UI/Main/Settings/ViewModel/CardDavViewModel.swift b/Linphone/UI/Main/Settings/ViewModel/CardDavViewModel.swift index 5853bcb6f..e8cffdc39 100644 --- a/Linphone/UI/Main/Settings/ViewModel/CardDavViewModel.swift +++ b/Linphone/UI/Main/Settings/ViewModel/CardDavViewModel.swift @@ -128,8 +128,7 @@ class CardDavViewModel: ObservableObject { NotificationCenter.default.post(name: NSNotification.Name("ContactLoaded"), object: nil) self.cardDavServerOperationSuccessful = true - ToastViewModel.shared.toastMessage = "Success_settings_contacts_carddav_deleted_toast" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Success_settings_contacts_carddav_deleted_toast") } } } @@ -227,8 +226,7 @@ class CardDavViewModel: ObservableObject { DispatchQueue.main.async { self.cardDavServerOperationInProgress = false - ToastViewModel.shared.toastMessage = "Success_settings_contacts_carddav_sync_successful_toast" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("Success_settings_contacts_carddav_sync_successful_toast") } let name = self.displayName @@ -256,8 +254,7 @@ class CardDavViewModel: ObservableObject { DispatchQueue.main.async { self.cardDavServerOperationInProgress = false - ToastViewModel.shared.toastMessage = "settings_contacts_carddav_sync_error_toast" - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show("settings_contacts_carddav_sync_error_toast") } if !self.isEdit { Log.error("\(CardDavViewModel.TAG) Synchronization failed, removing Friend list from Core") diff --git a/Linphone/UI/Main/Viewmodel/ToastViewModel.swift b/Linphone/UI/Main/Viewmodel/ToastViewModel.swift index b046c5a06..c62f41d92 100644 --- a/Linphone/UI/Main/Viewmodel/ToastViewModel.swift +++ b/Linphone/UI/Main/Viewmodel/ToastViewModel.swift @@ -19,13 +19,39 @@ import Foundation -class ToastViewModel: ObservableObject { +@MainActor +final class ToastViewModel: ObservableObject { static let shared = ToastViewModel() - var toastMessage: String = "" - @Published var displayToast = false + @Published var toast: ToastData? - private init() { + private var hideWorkItem: DispatchWorkItem? + + private init() {} + + func show(_ message: String, duration: TimeInterval = 2.0) { + hideWorkItem?.cancel() + + toast = ToastData(message: message) + + let workItem = DispatchWorkItem { [weak self] in + self?.toast = nil + } + hideWorkItem = workItem + + if !message.contains("is recording") { + DispatchQueue.main.asyncAfter(deadline: .now() + duration, execute: workItem) + } + } + + func hide() { + hideWorkItem?.cancel() + toast = nil } } + +struct ToastData: Identifiable, Equatable { + let id = UUID() + let message: String +} diff --git a/Linphone/Utils/URIHandler.swift b/Linphone/Utils/URIHandler.swift index 3bc2153d0..09d6af463 100644 --- a/Linphone/Utils/URIHandler.swift +++ b/Linphone/Utils/URIHandler.swift @@ -170,8 +170,7 @@ class URIHandler { private static func toast(_ message: String) { DispatchQueue.main.async { - ToastViewModel.shared.toastMessage = message - ToastViewModel.shared.displayToast = true + ToastViewModel.shared.show(message) } } }