Add click notification listener to open app in the chat room

This commit is contained in:
Benoit Martins 2024-10-02 14:15:09 +02:00
parent dc4c3833f7
commit 86cd7f452e
12 changed files with 175 additions and 72 deletions

View file

@ -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;

View file

@ -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")

View file

@ -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" : {

View file

@ -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!)

View file

@ -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(),

View file

@ -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()

View file

@ -178,7 +178,7 @@ struct ConversationFragment: View {
if isShowConversationFragment {
isShowConversationFragment = false
}
conversationViewModel.displayedConversation = nil
conversationViewModel.removeConversationDelegate()
}
}
}

View file

@ -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..<conversationsListViewModel.conversationsList.count, id: \.self) { index in
@ -135,10 +142,25 @@ struct ConversationsListFragment: View {
.listRowInsets(EdgeInsets(top: 6, leading: 20, bottom: 6, trailing: 20))
.listRowSeparator(.hidden)
.background(.white)
.onReceive(pub) { _ in
if enteredForeground && conversationViewModel.displayedConversation != nil
&& (navigationManager.peerAddr == nil || navigationManager.peerAddr == conversationViewModel.displayedConversation!.remoteSipUri) {
if conversationViewModel.displayedConversation != nil {
conversationViewModel.resetDisplayedChatRoom(conversationsList: conversationsListViewModel.conversationsList)
}
}
enteredForeground = false
if navigationManager.peerAddr != nil && conversationsListViewModel.conversationsList[index].remoteSipUri.contains(navigationManager.peerAddr!) {
conversationViewModel.getChatRoomWithStringAddress(conversationsList: conversationsListViewModel.conversationsList, stringAddr: navigationManager.peerAddr!)
navigationManager.peerAddr = nil
}
}
.onTapGesture {
if index < conversationsListViewModel.conversationsList.count {
if conversationViewModel.displayedConversation != nil {
conversationViewModel.displayedConversation = nil
if conversationViewModel.displayedConversation != nil {
conversationViewModel.removeConversationDelegate()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
conversationViewModel.selectedMessage = nil
conversationViewModel.resetMessage()
@ -189,6 +211,9 @@ struct ConversationsListFragment: View {
}
.navigationTitle("")
.navigationBarHidden(true)
.onChange(of: scenePhase) { newPhase in
enteredForeground = newPhase == .active
}
}
}

View file

@ -239,7 +239,7 @@ struct StartConversationFragment: View {
if startConversationViewModel.displayedConversation != nil {
if self.conversationViewModel.displayedConversation != nil {
self.conversationViewModel.displayedConversation = nil
self.conversationViewModel.removeConversationDelegate()
self.conversationViewModel.resetMessage()
self.conversationViewModel.changeDisplayedChatRoom(conversationModel: startConversationViewModel.displayedConversation!)

View file

@ -193,7 +193,7 @@ class ConversationModel: ObservableObject {
: ContactAvatarModel(
friend: nil,
name: subjectTmp,
address: addressTmp,
address: self.chatRoom.peerAddress?.asStringUriOnly() ?? addressTmp,
withPresence: false
)

View file

@ -199,10 +199,13 @@ class ConversationViewModel: ObservableObject {
func removeConversationDelegate() {
if let crDelegate = self.chatRoomDelegate {
self.displayedConversation?.chatRoom.removeDelegate(delegate: crDelegate)
if self.displayedConversation != nil {
self.displayedConversation!.chatRoom.removeDelegate(delegate: crDelegate)
}
}
self.chatRoomDelegate = nil
self.chatMessageDelegateHolders.removeAll()
self.displayedConversation = nil
}
func getHistorySize() {
@ -1377,40 +1380,36 @@ class ConversationViewModel: ObservableObject {
}
func resetDisplayedChatRoom(conversationsList: [ConversationModel]) {
removeConversationDelegate()
if self.displayedConversation != nil {
conversationsList.forEach { conversation in
if conversation.id == self.displayedConversation!.id {
self.displayedConversation = conversation
self.computeComposingLabel()
if self.displayedConversation != nil {
if !self.conversationMessagesSection.isEmpty && !self.conversationMessagesSection[0].rows.isEmpty {
if self.displayedConversation != nil {
conversationsList.forEach { conversation in
if conversation.id == self.displayedConversation!.id {
self.displayedConversation = conversation
self.computeComposingLabel()
let eventLogFirst = self.displayedConversation!.chatRoom.findEventLog(messageId: self.conversationMessagesSection[0].rows.first!.eventModel.eventLog.chatMessage!.messageId)
let eventLogLast = self.displayedConversation!.chatRoom.getHistoryRangeEvents(begin: 0, end: 1).first
var eventLogList = self.displayedConversation!.chatRoom.getHistoryRangeBetween(
firstEvent: eventLogFirst,
lastEvent: eventLogLast,
filters: UInt(ChatRoom.HistoryFilter([.ChatMessage, .InfoNoDevice]).rawValue)
)
if eventLogLast != nil {
eventLogList.append(eventLogLast!)
if !eventLogList.isEmpty && self.conversationMessagesSection[0].rows.first?.message.id != eventLogLast!.chatMessage?.messageId {
self.getNewMessages(eventLogs: eventLogList)
if self.displayedConversation != nil {
CoreContext.shared.doOnCoreQueue { _ in
let eventLogFirst = self.displayedConversation!.chatRoom.findEventLog(messageId: self.conversationMessagesSection[0].rows.first!.eventModel.eventLog.chatMessage!.messageId)
let eventLogLast = self.displayedConversation!.chatRoom.getHistoryRangeEvents(begin: 0, end: 1).first
var eventLogList = self.displayedConversation!.chatRoom.getHistoryRangeBetween(
firstEvent: eventLogFirst,
lastEvent: eventLogLast,
filters: UInt(ChatRoom.HistoryFilter([.ChatMessage, .InfoNoDevice]).rawValue)
)
if eventLogLast != nil {
eventLogList.append(eventLogLast!)
if !eventLogList.isEmpty && self.conversationMessagesSection[0].rows.first?.message.id != eventLogLast!.chatMessage?.messageId {
self.getNewMessages(eventLogs: eventLogList)
}
}
self.addConversationDelegate()
}
}
self.chatRoomDelegate = ChatRoomDelegateStub(onChatMessagesReceived: { (_: ChatRoom, eventLogs: [EventLog]) in
self.getNewMessages(eventLogs: eventLogs)
}, onChatMessageSending: { (_: ChatRoom, eventLog: EventLog) in
self.getNewMessages(eventLogs: [eventLog])
})
self.displayedConversation?.chatRoom.addDelegate(delegate: self.chatRoomDelegate!)
}
}
}
}
@ -1787,6 +1786,37 @@ class ConversationViewModel: ObservableObject {
}
}
}
func getChatRoomWithStringAddress(conversationsList: [ConversationModel], stringAddr: String) {
CoreContext.shared.doOnCoreQueue { _ in
do {
let stringAddrCleaned = stringAddr.components(separatedBy: ";gr=")
let address = try Factory.Instance.createAddress(addr: stringAddrCleaned[0])
if let dispChatRoom = conversationsList.first(where: {$0.chatRoom.peerAddress != nil && $0.chatRoom.peerAddress!.equal(address2: address)}) {
if self.displayedConversation != nil {
if dispChatRoom.id != self.displayedConversation!.id {
DispatchQueue.main.async {
self.removeConversationDelegate()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
self.selectedMessage = nil
self.resetMessage()
self.changeDisplayedChatRoom(conversationModel: dispChatRoom)
self.getMessages()
}
}
}
} else {
DispatchQueue.main.async {
self.selectedMessage = nil
self.changeDisplayedChatRoom(conversationModel: dispChatRoom)
}
}
}
} catch {
}
}
}
}
// swiftlint:enable line_length
// swiftlint:enable type_body_length

View file

@ -57,6 +57,7 @@ class ConversationsListViewModel: ObservableObject {
DispatchQueue.main.async {
self.conversationsList = self.conversationsListTmp
NotificationCenter.default.post(name: NSNotification.Name("ChatRoomsComputed"), object: nil)
}
self.updateUnreadMessagesCount()