From 86cd7f452e6697d7aa5080282fea35a324275d96 Mon Sep 17 00:00:00 2001 From: Benoit Martins Date: Wed, 2 Oct 2024 14:15:09 +0200 Subject: [PATCH] Add click notification listener to open app in the chat room --- Linphone.xcodeproj/project.pbxproj | 9 +- Linphone/LinphoneApp.swift | 59 ++++++++++-- Linphone/Localizable.xcstrings | 12 --- Linphone/UI/Call/CallView.swift | 6 +- Linphone/UI/Main/ContentView.swift | 31 +++++-- .../ConversationForwardMessageFragment.swift | 2 +- .../Fragments/ConversationFragment.swift | 2 +- .../Fragments/ConversationsListFragment.swift | 29 +++++- .../Fragments/StartConversationFragment.swift | 2 +- .../Model/ConversationModel.swift | 2 +- .../ViewModel/ConversationViewModel.swift | 92 ++++++++++++------- .../ConversationsListViewModel.swift | 1 + 12 files changed, 175 insertions(+), 72 deletions(-) diff --git a/Linphone.xcodeproj/project.pbxproj b/Linphone.xcodeproj/project.pbxproj index c365f1847..47d57c7b6 100644 --- a/Linphone.xcodeproj/project.pbxproj +++ b/Linphone.xcodeproj/project.pbxproj @@ -7,7 +7,7 @@ objects = { /* Begin PBXBuildFile section */ - 4ED1F0A881A9ACB5977A8987 /* BuildFile in Frameworks */ = {isa = PBXBuildFile; }; + 4ED1F0A881A9ACB5977A8987 /* (null) in Frameworks */ = {isa = PBXBuildFile; }; 660AAF7F2B839272004C0FA6 /* msgNotificationService.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 660AAF7B2B839271004C0FA6 /* msgNotificationService.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 660D8A712B517D260092694D /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 660D8A702B517D260092694D /* GoogleService-Info.plist */; }; 6613A0AE2BAEB7DF008923A4 /* MeetingFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6613A0AD2BAEB7DF008923A4 /* MeetingFragment.swift */; }; @@ -17,7 +17,6 @@ 66246C6A2C622AE900973E97 /* TimeZoneExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 66246C692C622AE900973E97 /* TimeZoneExtension.swift */; }; 662B69D92B25DE18007118BF /* TelecomManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 662B69D82B25DE18007118BF /* TelecomManager.swift */; }; 662B69DB2B25DE25007118BF /* ProviderDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 662B69DA2B25DE25007118BF /* ProviderDelegate.swift */; }; - 663E07E42CAAFD3B0010029D /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = D70C93DD2AC2D0F60063CA3B /* Localizable.xcstrings */; }; 6646A7A32BB2E224006B842A /* ScheduleMeetingFragment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6646A7A22BB2E224006B842A /* ScheduleMeetingFragment.swift */; }; 667E5D7F2B8E430C00EBCFC4 /* linphonerc-factory in Resources */ = {isa = PBXBuildFile; fileRef = D732A90B2B0376F500DB42BA /* linphonerc-factory */; }; 667E5D812B8E444E00EBCFC4 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 667E5D802B8E444D00EBCFC4 /* GoogleService-Info.plist */; }; @@ -373,7 +372,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 4ED1F0A881A9ACB5977A8987 /* BuildFile in Frameworks */, + 4ED1F0A881A9ACB5977A8987 /* (null) in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -1445,7 +1444,7 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CODE_SIGN_ENTITLEMENTS = Linphone/Linphone.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 45; + CURRENT_PROJECT_VERSION = 48; DEAD_CODE_STRIPPING = YES; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_ASSET_PATHS = "\"Linphone/Preview Content\""; @@ -1502,7 +1501,7 @@ ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = NO; CODE_SIGN_ENTITLEMENTS = Linphone/Linphone.entitlements; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 45; + CURRENT_PROJECT_VERSION = 48; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_ASSET_PATHS = "\"Linphone/Preview Content\""; DEVELOPMENT_TEAM = Z2V957B3D6; diff --git a/Linphone/LinphoneApp.swift b/Linphone/LinphoneApp.swift index 636ce5427..9ecfffa09 100644 --- a/Linphone/LinphoneApp.swift +++ b/Linphone/LinphoneApp.swift @@ -19,10 +19,17 @@ import SwiftUI import linphonesw +import UserNotifications let accountTokenNotification = Notification.Name("AccountCreationTokenReceived") -class AppDelegate: NSObject, UIApplicationDelegate { +class AppDelegate: NSObject, UIApplicationDelegate, UNUserNotificationCenterDelegate { + + var launchNotificationCallId: String? + var launchNotificationPeerAddr: String? + var launchNotificationLocalAddr: String? + + var navigationManager: NavigationManager? func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) { let tokenStr = deviceToken.map { String(format: "%02.2hhx", $0) }.joined() @@ -44,8 +51,35 @@ class AppDelegate: NSObject, UIApplicationDelegate { if let creationToken = creationToken { NotificationCenter.default.post(name: accountTokenNotification, object: nil, userInfo: ["token": creationToken]) } + completionHandler(UIBackgroundFetchResult.newData) } + + func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { + // Set up notifications + UNUserNotificationCenter.current().delegate = self + + return true + } + + // Called when the user interacts with the notification + func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) { + let userInfo = response.notification.request.content.userInfo + + if let callId = userInfo["CallId"] as? String, let peerAddr = userInfo["peer_addr"] as? String, let localAddr = userInfo["local_addr"] as? String { + if self.navigationManager != nil { + self.navigationManager!.selectedCallId = callId + self.navigationManager!.peerAddr = peerAddr + self.navigationManager!.localAddr = localAddr + } else { + launchNotificationCallId = callId + launchNotificationPeerAddr = peerAddr + launchNotificationLocalAddr = localAddr + } + } + + completionHandler() + } func applicationWillTerminate(_ application: UIApplication) { Log.info("IOS applicationWillTerminate") @@ -59,7 +93,6 @@ class AppDelegate: NSObject, UIApplicationDelegate { } } } - } @main @@ -67,6 +100,8 @@ struct LinphoneApp: App { @Environment(\.scenePhase) var scenePhase @UIApplicationDelegateAdaptor(AppDelegate.self) var delegate + @StateObject var navigationManager = NavigationManager() + @ObservedObject private var coreContext = CoreContext.shared @ObservedObject private var sharedMainViewModel = SharedMainViewModel.shared @@ -129,7 +164,19 @@ struct LinphoneApp: App { meetingsListViewModel: meetingsListViewModel!, meetingViewModel: meetingViewModel!, conversationForwardMessageViewModel: conversationForwardMessageViewModel! - ).onOpenURL { url in + ) + .environmentObject(navigationManager) + .onAppear { + // Link the navigation manager to the AppDelegate + delegate.navigationManager = navigationManager + + // Check if the app was launched with a notification payload + if let callId = delegate.launchNotificationCallId, let peerAddr = delegate.launchNotificationPeerAddr, let localAddr = delegate.launchNotificationLocalAddr { + // Notify the app to navigate to the chat room + navigationManager.openChatRoom(callId: callId, peerAddr: peerAddr, localAddr: localAddr) + } + } + .onOpenURL { url in URIHandler.handleURL(url: url) } } else { @@ -161,12 +208,6 @@ struct LinphoneApp: App { if newPhase == .active { Log.info("Entering foreground") coreContext.onEnterForeground() - - DispatchQueue.main.asyncAfter(deadline: .now() + 1) { - if conversationViewModel != nil && conversationViewModel!.displayedConversation != nil && conversationsListViewModel != nil { - conversationViewModel!.resetDisplayedChatRoom(conversationsList: conversationsListViewModel!.conversationsList) - } - } } else if newPhase == .inactive { } else if newPhase == .background { Log.info("Entering background") diff --git a/Linphone/Localizable.xcstrings b/Linphone/Localizable.xcstrings index 8f316799a..205ff1ae7 100644 --- a/Linphone/Localizable.xcstrings +++ b/Linphone/Localizable.xcstrings @@ -222,9 +222,6 @@ }, "All contacts" : { - }, - "All day" : { - }, "All modifications will be canceled." : { @@ -899,9 +896,6 @@ } } } - }, - "Content" : { - }, "Continue" : { "localizations" : { @@ -1339,9 +1333,6 @@ }, "Copy SIP address" : { - }, - "Could not reach network" : { - }, "Could not send ICS invitations to meeting to any participant" : { @@ -2361,9 +2352,6 @@ }, "Time Zone: %@" : { - }, - "Title" : { - }, "TLS" : { diff --git a/Linphone/UI/Call/CallView.swift b/Linphone/UI/Call/CallView.swift index 7d12df34a..395919d31 100644 --- a/Linphone/UI/Call/CallView.swift +++ b/Linphone/UI/Call/CallView.swift @@ -205,7 +205,7 @@ struct CallView: View { .zIndex(4) .transition(.move(edge: .bottom)) .onDisappear { - conversationViewModel.displayedConversation = nil + conversationViewModel.removeConversationDelegate() isShowConversationFragment = false } } @@ -2199,7 +2199,7 @@ struct CallView: View { .onDisappear { if callViewModel.isOneOneCall && callViewModel.displayedConversation != nil { if conversationViewModel.displayedConversation != nil { - conversationViewModel.displayedConversation = nil + conversationViewModel.removeConversationDelegate() conversationViewModel.resetMessage() conversationViewModel.changeDisplayedChatRoom(conversationModel: callViewModel.displayedConversation!) @@ -2582,7 +2582,7 @@ struct CallView: View { .onDisappear { if callViewModel.isOneOneCall && callViewModel.displayedConversation != nil { if conversationViewModel.displayedConversation != nil { - conversationViewModel.displayedConversation = nil + conversationViewModel.removeConversationDelegate() conversationViewModel.resetMessage() conversationViewModel.changeDisplayedChatRoom(conversationModel: callViewModel.displayedConversation!) diff --git a/Linphone/UI/Main/ContentView.swift b/Linphone/UI/Main/ContentView.swift index 72f6cd6e9..81347cdea 100644 --- a/Linphone/UI/Main/ContentView.swift +++ b/Linphone/UI/Main/ContentView.swift @@ -27,6 +27,8 @@ struct ContentView: View { @Environment(\.scenePhase) var scenePhase private var idiom: UIUserInterfaceIdiom { UIDevice.current.userInterfaceIdiom } + @EnvironmentObject var navigationManager: NavigationManager + @ObservedObject private var coreContext = CoreContext.shared @ObservedObject private var sharedMainViewModel = SharedMainViewModel.shared @ObservedObject private var telecomManager = TelecomManager.shared @@ -127,7 +129,7 @@ struct ContentView: View { Button(action: { self.index = 0 historyViewModel.displayedCall = nil - conversationViewModel.displayedConversation = nil + conversationViewModel.removeConversationDelegate() meetingViewModel.displayedMeeting = nil }, label: { VStack { @@ -172,7 +174,7 @@ struct ContentView: View { Button(action: { self.index = 1 contactViewModel.indexDisplayedFriend = nil - conversationViewModel.displayedConversation = nil + conversationViewModel.removeConversationDelegate() meetingViewModel.displayedMeeting = nil if historyListViewModel.missedCallsCount > 0 { historyListViewModel.resetMissedCallsCount() @@ -248,7 +250,7 @@ struct ContentView: View { self.index = 3 contactViewModel.indexDisplayedFriend = nil historyViewModel.displayedCall = nil - conversationViewModel.displayedConversation = nil + conversationViewModel.removeConversationDelegate() }, label: { VStack { Image("video-conference") @@ -656,7 +658,7 @@ struct ContentView: View { Button(action: { self.index = 0 historyViewModel.displayedCall = nil - conversationViewModel.displayedConversation = nil + conversationViewModel.removeConversationDelegate() meetingViewModel.displayedMeeting = nil }, label: { VStack { @@ -703,7 +705,7 @@ struct ContentView: View { Button(action: { self.index = 1 contactViewModel.indexDisplayedFriend = nil - conversationViewModel.displayedConversation = nil + conversationViewModel.removeConversationDelegate() meetingViewModel.displayedMeeting = nil if historyListViewModel.missedCallsCount > 0 { historyListViewModel.resetMissedCallsCount() @@ -782,7 +784,7 @@ struct ContentView: View { self.index = 3 contactViewModel.indexDisplayedFriend = nil historyViewModel.displayedCall = nil - conversationViewModel.displayedConversation = nil + conversationViewModel.removeConversationDelegate() }, label: { VStack { Image("video-conference") @@ -1199,6 +1201,11 @@ struct ContentView: View { .onAppear { MagicSearchSingleton.shared.searchForContacts(sourceFlags: MagicSearch.Source.Friends.rawValue | MagicSearch.Source.LdapServers.rawValue) } + .onChange(of: navigationManager.selectedCallId) { newCallId in + if newCallId != nil { + self.index = 2 + } + } .onReceive(pub) { _ in conversationsListViewModel.computeChatRoomsList(filter: "") historyListViewModel.refreshHistoryAvatarModel() @@ -1231,6 +1238,18 @@ struct ContentView: View { } } +class NavigationManager: ObservableObject { + @Published var selectedCallId: String? = nil + @Published var peerAddr: String? = nil + @Published var localAddr: String? = nil + + func openChatRoom(callId: String, peerAddr: String, localAddr: String) { + self.selectedCallId = callId + self.peerAddr = peerAddr + self.localAddr = localAddr + } +} + #Preview { ContentView( contactViewModel: ContactViewModel(), diff --git a/Linphone/UI/Main/Conversations/Fragments/ConversationForwardMessageFragment.swift b/Linphone/UI/Main/Conversations/Fragments/ConversationForwardMessageFragment.swift index 88ecd8cf2..d1830ea5f 100644 --- a/Linphone/UI/Main/Conversations/Fragments/ConversationForwardMessageFragment.swift +++ b/Linphone/UI/Main/Conversations/Fragments/ConversationForwardMessageFragment.swift @@ -211,7 +211,7 @@ struct ConversationForwardMessageFragment: View { if conversationForwardMessageViewModel.displayedConversation != nil { if conversationViewModel.displayedConversation != nil { - conversationViewModel.displayedConversation = nil + conversationViewModel.removeConversationDelegate() DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { conversationViewModel.selectedMessage = nil conversationViewModel.resetMessage() diff --git a/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift b/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift index e029d1ef1..45fc441e4 100644 --- a/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift +++ b/Linphone/UI/Main/Conversations/Fragments/ConversationFragment.swift @@ -178,7 +178,7 @@ struct ConversationFragment: View { if isShowConversationFragment { isShowConversationFragment = false } - conversationViewModel.displayedConversation = nil + conversationViewModel.removeConversationDelegate() } } } diff --git a/Linphone/UI/Main/Conversations/Fragments/ConversationsListFragment.swift b/Linphone/UI/Main/Conversations/Fragments/ConversationsListFragment.swift index bbc9dc750..af2ab8f36 100644 --- a/Linphone/UI/Main/Conversations/Fragments/ConversationsListFragment.swift +++ b/Linphone/UI/Main/Conversations/Fragments/ConversationsListFragment.swift @@ -22,6 +22,10 @@ import linphonesw struct ConversationsListFragment: View { + @EnvironmentObject var navigationManager: NavigationManager + @Environment(\.scenePhase) var scenePhase + @State private var enteredForeground: Bool = false + @ObservedObject var conversationViewModel: ConversationViewModel @ObservedObject var conversationsListViewModel: ConversationsListViewModel @@ -29,6 +33,9 @@ struct ConversationsListFragment: View { @Binding var text: String var body: some View { + let pub = NotificationCenter.default + .publisher(for: NSNotification.Name("ChatRoomsComputed")) + VStack { List { ForEach(0..