From a6fc2a4035203e9e00f13638330054c6e03f1c47 Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Mon, 17 Nov 2025 17:50:05 +0100 Subject: [PATCH 01/15] update calling UI in the replaceRootViewController --- wire-ios/Wire-iOS/Sources/AppRootRouter.swift | 22 ++++++++++++--- .../CallController/CallController.swift | 27 +++++++++++++++++++ 2 files changed, 46 insertions(+), 3 deletions(-) diff --git a/wire-ios/Wire-iOS/Sources/AppRootRouter.swift b/wire-ios/Wire-iOS/Sources/AppRootRouter.swift index 3f7f902b4ab..37f2046d5e3 100644 --- a/wire-ios/Wire-iOS/Sources/AppRootRouter.swift +++ b/wire-ios/Wire-iOS/Sources/AppRootRouter.swift @@ -431,7 +431,20 @@ extension AppRootRouter: AppStateCalculatorDelegate { self.authenticatedRouter = authenticatedRouter - replaceRootViewController(by: authenticatedRouter.zClientViewController, completion: completion) +// replaceRootViewController(by: authenticatedRouter.zClientViewController, completion: completion) +// replaceRootViewController(by: authenticatedRouter.zClientViewController) { [weak self] in +// // UI is now ready - safe to present modals +// self?.authenticatedRouter?.updateActiveCallPresentationState() +// completion() +// } + replaceRootViewController(by: authenticatedRouter.zClientViewController) { [weak self] in + // UI is now ready - safe to present modals + self?.authenticatedRouter?.updateActiveCallPresentationState() + + // UI is ready now - safe to wire up URL router + self?.urlActionRouter.authenticatedRouter = authenticatedRouter + completion() + } } private func showAppLock(userSession: UserSession, completion: @escaping () -> Void) { @@ -510,8 +523,11 @@ extension AppRootRouter { sessionManager.processPendingURLActionDoesNotRequireAuthentication() case .authenticated: // This is needed to display an ongoing call when coming from the background. - authenticatedRouter?.updateActiveCallPresentationState() - urlActionRouter.authenticatedRouter = authenticatedRouter +// authenticatedRouter?.updateActiveCallPresentationState() +// DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in +// self?.authenticatedRouter?.updateActiveCallPresentationState() +// } + //urlActionRouter.authenticatedRouter = authenticatedRouter ZClientViewController.shared?.legalHoldDisclosureController?.discloseCurrentState(cause: .appOpen) sessionManager.processPendingURLActionRequiresAuthentication() sessionManager.processPendingURLActionDoesNotRequireAuthentication() diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/CallController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/CallController.swift index c92f0ff2ed0..b02a35a8b12 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/CallController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/CallController.swift @@ -159,6 +159,33 @@ extension CallController: WireCallCenterCallStateObserver { self.updateActiveCallPresentationState() } } + if shouldUpdatePresentationForCallState(callState, previousCallState: previousCallState) { + updateActiveCallPresentationState() + } + } + + private func shouldUpdatePresentationForCallState( + _ callState: CallState, + previousCallState: CallState? + ) -> Bool { + switch callState { + case .established: + // Always update when call becomes established (media flowing) + // This handles the case where call was connecting during account switch + return true + case .incoming, .answered: + // Update for incoming/answered calls to show UI + return true + case .outgoing: + // Update for outgoing calls + return true + case .terminating, .mediaStopped, .none: + // These are handled by dismissCall() in updateActiveCallPresentationState + return true + case .establishedDataChannel, .unknown: + // Don't update for these intermediate states + return false + } } private func presentUnsupportedVersionAlertIfNecessary(callState: CallState) { From 0d06ee4c2b3401dc8dca205533bd224eb26d4864 Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Tue, 18 Nov 2025 23:59:14 +0100 Subject: [PATCH 02/15] temp --- wire-ios/Wire-iOS/Sources/AppRootRouter.swift | 37 ++++---- .../Sources/AuthenticatedRouter.swift | 13 +++ .../CallController/ActiveCallRouter.swift | 90 +++++++++++++++++-- .../CallController/CallController.swift | 50 +++++------ .../ZClientViewController.swift | 21 ++++- .../Overlay/CallTopOverlayController.swift | 23 +++-- 6 files changed, 178 insertions(+), 56 deletions(-) diff --git a/wire-ios/Wire-iOS/Sources/AppRootRouter.swift b/wire-ios/Wire-iOS/Sources/AppRootRouter.swift index 37f2046d5e3..e22ed6729c7 100644 --- a/wire-ios/Wire-iOS/Sources/AppRootRouter.swift +++ b/wire-ios/Wire-iOS/Sources/AppRootRouter.swift @@ -126,7 +126,13 @@ final class AppRootRouter { duration: 0.2, options: .transitionCrossDissolve, animations: {}, - completion: { _ in completion() } + completion: { _ in + // Defer completion to next run loop to ensure view hierarchy is fully ready + // This allows the view controller to complete viewDidAppear before we present modals + DispatchQueue.main.async { + completion() + } + } ) } @@ -431,16 +437,10 @@ extension AppRootRouter: AppStateCalculatorDelegate { self.authenticatedRouter = authenticatedRouter -// replaceRootViewController(by: authenticatedRouter.zClientViewController, completion: completion) -// replaceRootViewController(by: authenticatedRouter.zClientViewController) { [weak self] in -// // UI is now ready - safe to present modals -// self?.authenticatedRouter?.updateActiveCallPresentationState() -// completion() -// } - replaceRootViewController(by: authenticatedRouter.zClientViewController) { [weak self] in - // UI is now ready - safe to present modals - self?.authenticatedRouter?.updateActiveCallPresentationState() + // Signal that we should check for active call when view appears + authenticatedRouter.shouldCheckForActiveCallOnAppear = true + replaceRootViewController(by: authenticatedRouter.zClientViewController) { [weak self] in // UI is ready now - safe to wire up URL router self?.urlActionRouter.authenticatedRouter = authenticatedRouter completion() @@ -522,12 +522,17 @@ extension AppRootRouter { presentAlertForDeletedAccountIfNeeded(error) sessionManager.processPendingURLActionDoesNotRequireAuthentication() case .authenticated: - // This is needed to display an ongoing call when coming from the background. -// authenticatedRouter?.updateActiveCallPresentationState() -// DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { [weak self] in -// self?.authenticatedRouter?.updateActiveCallPresentationState() -// } - //urlActionRouter.authenticatedRouter = authenticatedRouter +// // Start observing calls now that UI is ready and state transition is complete +// // This prevents observers from firing before root view controller is set up +// authenticatedRouter?.startObservingCalls() + + // Mark UI ready FIRST (before any presentation attempts) + authenticatedRouter?.markUIReadyForCallPresentation() + + // Note: Call presentation now happens in ZClientViewController.viewDidAppear() + // when shouldCheckForActiveCallOnAppear flag is set (set in showAuthenticated) + // This ensures the view hierarchy is fully ready before presenting the call UI + ZClientViewController.shared?.legalHoldDisclosureController?.discloseCurrentState(cause: .appOpen) sessionManager.processPendingURLActionRequiresAuthentication() sessionManager.processPendingURLActionDoesNotRequireAuthentication() diff --git a/wire-ios/Wire-iOS/Sources/AuthenticatedRouter.swift b/wire-ios/Wire-iOS/Sources/AuthenticatedRouter.swift index 722c66f9d54..a80a1b933ff 100644 --- a/wire-ios/Wire-iOS/Sources/AuthenticatedRouter.swift +++ b/wire-ios/Wire-iOS/Sources/AuthenticatedRouter.swift @@ -31,9 +31,12 @@ enum NavigationDestination { } protocol AuthenticatedRouterProtocol: AnyObject { +// func startObservingCalls() func updateActiveCallPresentationState() func minimizeCallOverlay(animated: Bool, completion: Completion?) func navigate(to destination: NavigationDestination) + func markUIReadyForCallPresentation() + var shouldCheckForActiveCallOnAppear: Bool { get set } } final class AuthenticatedRouter { @@ -60,6 +63,8 @@ final class AuthenticatedRouter { return zClientViewController } + var shouldCheckForActiveCallOnAppear = false + // MARK: - Init init( @@ -166,6 +171,14 @@ final class AuthenticatedRouter { extension AuthenticatedRouter: AuthenticatedRouterProtocol { +// func startObservingCalls() { +// activeCallRouter.startObserving() +// } + + func markUIReadyForCallPresentation() { + activeCallRouter.markUIReadyForCallPresentation() + } + func updateActiveCallPresentationState() { activeCallRouter.updateActiveCallPresentationState() } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/ActiveCallRouter.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/ActiveCallRouter.swift index 10cff2c7769..f23c69a53db 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/ActiveCallRouter.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/ActiveCallRouter.swift @@ -87,6 +87,8 @@ final class ActiveCallRouter private var isCallQualityShown = false private var isCallTopOverlayShown = false + private var isUIReadyForCallPresentation = false + private var lastUpdateCallPresentationTime: Date? private(set) var scheduledPostCallAction: PostCallAction? private(set) weak var presentedDegradedAlert: UIAlertController? @@ -100,7 +102,7 @@ final class ActiveCallRouter self.topOverlayPresenter = topOverlayPresenter self.callController = CallController(userSession: userSession) - callController.callConversationProvider = ZMUserSession.shared() + callController.callConversationProvider = userSession as? CallConversationProvider self.callQualityController = CallQualityController( mainWindow: mainWindow, @@ -114,9 +116,58 @@ final class ActiveCallRouter // MARK: - Public Implementation +// func startObserving() { +// callController.startObserving(userSession: userSession) +// } + func updateActiveCallPresentationState() { + guard isUIReadyForCallPresentation else { + // UI not ready - ignore this update + return + } + + // CRITICAL: Check if flags claim UI is shown but it's actually not visible + // Do this BEFORE debouncing or calling controller, so we catch stale state + if isActiveCallShown { + let actuallyPresented = mainWindow.rootViewController?.presentedViewController != nil + if !actuallyPresented { + print("⚠️ CRITICAL: Resetting isActiveCallShown - UI not actually visible") + isActiveCallShown = false + isPresentingActiveCall = false + isCallTopOverlayShown = false + // Don't return - continue to present + } + } + + // Debounce rapid consecutive calls (allow maximum one call per 0.15 seconds) + // Check isPresentingActiveCall too since isActiveCallShown is set async + let now = Date() + if let lastTime = lastUpdateCallPresentationTime, + now.timeIntervalSince(lastTime) < 0.15, + (isActiveCallShown || isPresentingActiveCall || isCallTopOverlayShown) { + print("⏱️ Debouncing - called too quickly (shown:\(isActiveCallShown), presenting:\(isPresentingActiveCall))") + return + } + lastUpdateCallPresentationTime = now + + // Reset stale state before presenting + if !isActiveCallShown { + isPresentingActiveCall = false + } + callController.updateActiveCallPresentationState() } + + func markUIReadyForCallPresentation() { + print("🟢 markUIReadyForCallPresentation called") + if !isActiveCallShown { + isPresentingActiveCall = false + } + isCallTopOverlayShown = false + isUIReadyForCallPresentation = true + + } + } // MARK: - ActiveCallRouterProtocol @@ -126,13 +177,16 @@ extension ActiveCallRouter: ActiveCallRouterProtocol { // MARK: - ActiveCall func presentActiveCall(for voiceChannel: VoiceChannel, animated: Bool) { + print("🔵 presentActiveCall called - isUIReady: \(isUIReadyForCallPresentation), isPresentingActiveCall: \(isPresentingActiveCall), isActiveCallShown: \(isActiveCallShown)") guard !isPresentingActiveCall, !isActiveCallShown else { + print("❌ presentActiveCall guard failed") return } + print("✅ presentActiveCall proceeding") // NOTE: We resign first reponder for the input bar since it will attempt to restore // first responder when the call overlay is interactively dismissed but canceled. UIResponder.currentFirst?.resignFirstResponder() @@ -149,18 +203,25 @@ extension ActiveCallRouter: ActiveCallRouterProtocol { enableDismissOnPan: !CallingConfiguration.config.paginationEnabled ) - if mainWindow.rootViewController?.presentedViewController != nil { + let existingPresentedVC = mainWindow.rootViewController?.presentedViewController + print("📊 Presenting call - existingPresentedVC: \(existingPresentedVC != nil ? String(describing: type(of: existingPresentedVC!)) : "nil")") + + if existingPresentedVC != nil { + print("📊 Path: dismissPresentedAndPresentActiveCall") dismissPresentedAndPresentActiveCall(modalViewController: modalVC, animated: animated) } else { + print("📊 Path: presentActiveCall directly") presentActiveCall(modalViewController: modalVC, animated: animated) } } func dismissActiveCall(animated: Bool = true, completion: Completion? = nil) { + print("🔴 dismissActiveCall called - isActiveCallShown: \(isActiveCallShown)") guard isActiveCallShown else { completion?() return } + print("🔴 dismissActiveCall proceeding to dismiss") mainWindow.rootViewController?.dismiss(animated: animated) { [weak self] in self?.isActiveCallShown = false if let action = self?.scheduledPostCallAction { @@ -175,6 +236,7 @@ extension ActiveCallRouter: ActiveCallRouterProtocol { } func minimizeCall(animated: Bool = true, completion: Completion? = nil) { + print("🟠 minimizeCall called - isActiveCallShown: \(isActiveCallShown)") guard isActiveCallShown else { completion?() return @@ -185,8 +247,16 @@ extension ActiveCallRouter: ActiveCallRouterProtocol { // MARK: - CallTopOverlay func showCallTopOverlay(for conversation: ZMConversation) { - guard !isCallTopOverlayShown else { return } - let callTopOverlayController = CallTopOverlayController(conversation: conversation) + print("🟡 showCallTopOverlay called - isUIReady: \(isUIReadyForCallPresentation), isCallTopOverlayShown: \(isCallTopOverlayShown)") + guard + isUIReadyForCallPresentation, + !isCallTopOverlayShown + else { + print("❌ showCallTopOverlay guard failed") + return + } + print("✅ showCallTopOverlay proceeding") + let callTopOverlayController = CallTopOverlayController(conversation: conversation, userSession: userSession) callTopOverlayController.delegate = self topOverlayPresenter.presentTopOverlay(callTopOverlayController, animated: true) isCallTopOverlayShown = true @@ -287,15 +357,25 @@ extension ActiveCallRouter: ActiveCallRouterProtocol { modalViewController: ModalPresentationViewController, animated: Bool ) { + print("📌 dismissPresentedAndPresentActiveCall: dismissing existing") mainWindow.rootViewController?.presentedViewController?.dismiss(animated: true) { [weak self] in + print("📌 Dismiss completed, now presenting call") self?.presentActiveCall(modalViewController: modalViewController, animated: animated) } } private func presentActiveCall(modalViewController: ModalPresentationViewController, animated: Bool) { + print("📌 presentActiveCall: starting presentation, setting isPresentingActiveCall = true") + print("📌 mainWindow: \(mainWindow), rootVC: \(String(describing: mainWindow.rootViewController))") + print("📌 mainWindow.isKeyWindow: \(mainWindow.isKeyWindow), mainWindow.isHidden: \(mainWindow.isHidden)") isPresentingActiveCall = true mainWindow.rootViewController?.present(modalViewController, animated: animated) { [weak self] in - self?.isActiveCallShown = true + guard let self = self else { return } + print("📌 Present COMPLETED, setting isActiveCallShown = true") + print("📌 Modal view.window: \(String(describing: modalViewController.view.window))") + print("📌 Modal isBeingPresented: \(modalViewController.isBeingPresented)") + print("📌 RootVC.presentedViewController: \(String(describing: self.mainWindow.rootViewController?.presentedViewController))") + self.isActiveCallShown = true } } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/CallController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/CallController.swift index b02a35a8b12..bf8ebc93f5b 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/CallController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/CallController.swift @@ -40,9 +40,24 @@ final class CallController: NSObject { // MARK: - Init +// init(userSession: UserSession) { +// super.init() +// // Start with clean state - no minimized calls +// minimizedCall = nil +// // Don't register observers yet - wait until UI is ready +// // This prevents observers from firing before root view controller is set up +// // Observers will be registered when startObserving() is called +// } +// +// func startObserving(userSession: UserSession) { +// guard observerTokens.isEmpty else { return } // Already observing +// addObservers(userSession: userSession) +// } + init(userSession: UserSession) { super.init() - addObservers(userSession: userSession) + minimizedCall = nil + addObservers(userSession: userSession) // ← Register immediately } deinit { @@ -52,10 +67,13 @@ final class CallController: NSObject { // MARK: - Public Implementation func updateActiveCallPresentationState() { + print("🔍 updateActiveCallPresentationState - has priorityCallConversation: \(priorityCallConversation != nil)") guard let priorityCallConversation else { + print("⚠️ No priorityCallConversation - dismissing call") dismissCall() return } + print("✅ Has priorityCallConversation - continuing") showCallTopOverlay(for: priorityCallConversation) presentOrMinimizeActiveCall(for: priorityCallConversation) } @@ -68,9 +86,12 @@ final class CallController: NSObject { } private func presentOrMinimizeActiveCall(for conversation: ZMConversation) { + print("🟣 presentOrMinimizeActiveCall - conversation: \(conversation.remoteIdentifier?.uuidString ?? "nil"), minimizedCall: \(minimizedCall?.remoteIdentifier?.uuidString ?? "nil")") if conversation == minimizedCall { + print("⚠️ Choosing to minimize (conversation == minimizedCall)") minimizeCall() } else { + print("📱 Choosing to present") presentCall(in: conversation) } } @@ -159,33 +180,6 @@ extension CallController: WireCallCenterCallStateObserver { self.updateActiveCallPresentationState() } } - if shouldUpdatePresentationForCallState(callState, previousCallState: previousCallState) { - updateActiveCallPresentationState() - } - } - - private func shouldUpdatePresentationForCallState( - _ callState: CallState, - previousCallState: CallState? - ) -> Bool { - switch callState { - case .established: - // Always update when call becomes established (media flowing) - // This handles the case where call was connecting during account switch - return true - case .incoming, .answered: - // Update for incoming/answered calls to show UI - return true - case .outgoing: - // Update for outgoing calls - return true - case .terminating, .mediaStopped, .none: - // These are handled by dismissCall() in updateActiveCallPresentationState - return true - case .establishedDataChannel, .unknown: - // Don't update for these intermediate states - return false - } } private func presentUnsupportedVersionAlertIfNecessary(callState: CallState) { diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/MainController/ZClientViewController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/MainController/ZClientViewController.swift index 4571c135e8f..b3152ff23ad 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/MainController/ZClientViewController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/MainController/ZClientViewController.swift @@ -348,6 +348,13 @@ final class ZClientViewController: UIViewController { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) + // Check for active call that needs to be presented after account switch + if router?.shouldCheckForActiveCallOnAppear == true { + router?.shouldCheckForActiveCallOnAppear = false + router?.markUIReadyForCallPresentation() + router?.updateActiveCallPresentationState() + } + migrateAnalytics() firstTimeRequestToEnableAnalytics() view.backgroundColor = ColorTheme.Backgrounds.surface @@ -539,7 +546,19 @@ final class ZClientViewController: UIViewController { // MARK: - Animated conversation switch func dismissAllModalControllers() async { - if userSession.ringingCallConversation != nil { + // Check for ANY active call (ringing or ongoing), not just ringing + // During account switching, ringingCallConversation may be stale + var hasActiveCall = userSession.ringingCallConversation != nil + + // Also check for non-idle calls if we can access callCenter + if let zmUserSession = userSession as? ZMUserSession, + let callCenter = zmUserSession.callCenter { + let nonIdleCalls = callCenter.nonIdleCallConversations(in: zmUserSession) + hasActiveCall = hasActiveCall || !nonIdleCalls.isEmpty + } + + if hasActiveCall { + // Don't minimize if there's an active call await mainCoordinator.dismissPresentedViewController() } else { await withCheckedContinuation { continuation in diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Overlay/CallTopOverlayController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Overlay/CallTopOverlayController.swift index b29d6ce479e..c2ac5eacfbd 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Overlay/CallTopOverlayController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Overlay/CallTopOverlayController.swift @@ -79,18 +79,29 @@ final class CallTopOverlayController: UIViewController { // MARK: - Init - init(conversation: ZMConversation) { + init(conversation: ZMConversation, userSession: UserSession) { self.conversation = conversation callDurationFormatter.allowedUnits = [.minute, .second] callDurationFormatter.zeroFormattingBehavior = DateComponentsFormatter.ZeroFormattingBehavior(rawValue: 0) super.init(nibName: nil, bundle: nil) - - if let userSession = ZMUserSession.shared() { - observerTokens.append(WireCallCenterV3.addCallStateObserver( +// +// if let userSession = ZMUserSession.shared() { +// observerTokens.append(WireCallCenterV3.addCallStateObserver( +// observer: self, +// contextProvider: userSession.contextProvider +// )) +// observerTokens.append(WireCallCenterV3.addMuteStateObserver(observer: self, userSession: userSession)) +// } + observerTokens.append(WireCallCenterV3.addCallStateObserver( + observer: self, + contextProvider: userSession.contextProvider + )) + + if let zmUserSession = userSession as? ZMUserSession { + observerTokens.append(WireCallCenterV3.addMuteStateObserver( observer: self, - contextProvider: userSession.contextProvider + userSession: zmUserSession )) - observerTokens.append(WireCallCenterV3.addMuteStateObserver(observer: self, userSession: userSession)) } } From 663abb8c6294e92c7de9c804f74424282216828b Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Mon, 24 Nov 2025 12:30:13 +0100 Subject: [PATCH 03/15] roll back changes --- wire-ios/Wire-iOS/Sources/AppRootRouter.swift | 30 ++----- .../Sources/AuthenticatedRouter.swift | 13 --- .../CallController/ActiveCallRouter.swift | 81 +------------------ .../ZClientViewController.swift | 21 +---- .../Overlay/CallTopOverlayController.swift | 23 ++---- 5 files changed, 15 insertions(+), 153 deletions(-) diff --git a/wire-ios/Wire-iOS/Sources/AppRootRouter.swift b/wire-ios/Wire-iOS/Sources/AppRootRouter.swift index e22ed6729c7..fa1ec950b6b 100644 --- a/wire-ios/Wire-iOS/Sources/AppRootRouter.swift +++ b/wire-ios/Wire-iOS/Sources/AppRootRouter.swift @@ -126,13 +126,7 @@ final class AppRootRouter { duration: 0.2, options: .transitionCrossDissolve, animations: {}, - completion: { _ in - // Defer completion to next run loop to ensure view hierarchy is fully ready - // This allows the view controller to complete viewDidAppear before we present modals - DispatchQueue.main.async { - completion() - } - } + completion: { _ in completion() } ) } @@ -437,14 +431,7 @@ extension AppRootRouter: AppStateCalculatorDelegate { self.authenticatedRouter = authenticatedRouter - // Signal that we should check for active call when view appears - authenticatedRouter.shouldCheckForActiveCallOnAppear = true - - replaceRootViewController(by: authenticatedRouter.zClientViewController) { [weak self] in - // UI is ready now - safe to wire up URL router - self?.urlActionRouter.authenticatedRouter = authenticatedRouter - completion() - } + replaceRootViewController(by: authenticatedRouter.zClientViewController, completion: completion) } private func showAppLock(userSession: UserSession, completion: @escaping () -> Void) { @@ -522,16 +509,9 @@ extension AppRootRouter { presentAlertForDeletedAccountIfNeeded(error) sessionManager.processPendingURLActionDoesNotRequireAuthentication() case .authenticated: -// // Start observing calls now that UI is ready and state transition is complete -// // This prevents observers from firing before root view controller is set up -// authenticatedRouter?.startObservingCalls() - - // Mark UI ready FIRST (before any presentation attempts) - authenticatedRouter?.markUIReadyForCallPresentation() - - // Note: Call presentation now happens in ZClientViewController.viewDidAppear() - // when shouldCheckForActiveCallOnAppear flag is set (set in showAuthenticated) - // This ensures the view hierarchy is fully ready before presenting the call UI + // This is needed to display an ongoing call when coming from the background. + authenticatedRouter?.updateActiveCallPresentationState() + urlActionRouter.authenticatedRouter = authenticatedRouter ZClientViewController.shared?.legalHoldDisclosureController?.discloseCurrentState(cause: .appOpen) sessionManager.processPendingURLActionRequiresAuthentication() diff --git a/wire-ios/Wire-iOS/Sources/AuthenticatedRouter.swift b/wire-ios/Wire-iOS/Sources/AuthenticatedRouter.swift index a80a1b933ff..722c66f9d54 100644 --- a/wire-ios/Wire-iOS/Sources/AuthenticatedRouter.swift +++ b/wire-ios/Wire-iOS/Sources/AuthenticatedRouter.swift @@ -31,12 +31,9 @@ enum NavigationDestination { } protocol AuthenticatedRouterProtocol: AnyObject { -// func startObservingCalls() func updateActiveCallPresentationState() func minimizeCallOverlay(animated: Bool, completion: Completion?) func navigate(to destination: NavigationDestination) - func markUIReadyForCallPresentation() - var shouldCheckForActiveCallOnAppear: Bool { get set } } final class AuthenticatedRouter { @@ -63,8 +60,6 @@ final class AuthenticatedRouter { return zClientViewController } - var shouldCheckForActiveCallOnAppear = false - // MARK: - Init init( @@ -171,14 +166,6 @@ final class AuthenticatedRouter { extension AuthenticatedRouter: AuthenticatedRouterProtocol { -// func startObservingCalls() { -// activeCallRouter.startObserving() -// } - - func markUIReadyForCallPresentation() { - activeCallRouter.markUIReadyForCallPresentation() - } - func updateActiveCallPresentationState() { activeCallRouter.updateActiveCallPresentationState() } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/ActiveCallRouter.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/ActiveCallRouter.swift index f23c69a53db..e2bd5d8e885 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/ActiveCallRouter.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/ActiveCallRouter.swift @@ -87,8 +87,6 @@ final class ActiveCallRouter private var isCallQualityShown = false private var isCallTopOverlayShown = false - private var isUIReadyForCallPresentation = false - private var lastUpdateCallPresentationTime: Date? private(set) var scheduledPostCallAction: PostCallAction? private(set) weak var presentedDegradedAlert: UIAlertController? @@ -102,7 +100,7 @@ final class ActiveCallRouter self.topOverlayPresenter = topOverlayPresenter self.callController = CallController(userSession: userSession) - callController.callConversationProvider = userSession as? CallConversationProvider + callController.callConversationProvider = ZMUserSession.shared() self.callQualityController = CallQualityController( mainWindow: mainWindow, @@ -116,58 +114,10 @@ final class ActiveCallRouter // MARK: - Public Implementation -// func startObserving() { -// callController.startObserving(userSession: userSession) -// } - func updateActiveCallPresentationState() { - guard isUIReadyForCallPresentation else { - // UI not ready - ignore this update - return - } - - // CRITICAL: Check if flags claim UI is shown but it's actually not visible - // Do this BEFORE debouncing or calling controller, so we catch stale state - if isActiveCallShown { - let actuallyPresented = mainWindow.rootViewController?.presentedViewController != nil - if !actuallyPresented { - print("⚠️ CRITICAL: Resetting isActiveCallShown - UI not actually visible") - isActiveCallShown = false - isPresentingActiveCall = false - isCallTopOverlayShown = false - // Don't return - continue to present - } - } - - // Debounce rapid consecutive calls (allow maximum one call per 0.15 seconds) - // Check isPresentingActiveCall too since isActiveCallShown is set async - let now = Date() - if let lastTime = lastUpdateCallPresentationTime, - now.timeIntervalSince(lastTime) < 0.15, - (isActiveCallShown || isPresentingActiveCall || isCallTopOverlayShown) { - print("⏱️ Debouncing - called too quickly (shown:\(isActiveCallShown), presenting:\(isPresentingActiveCall))") - return - } - lastUpdateCallPresentationTime = now - - // Reset stale state before presenting - if !isActiveCallShown { - isPresentingActiveCall = false - } - callController.updateActiveCallPresentationState() } - func markUIReadyForCallPresentation() { - print("🟢 markUIReadyForCallPresentation called") - if !isActiveCallShown { - isPresentingActiveCall = false - } - isCallTopOverlayShown = false - isUIReadyForCallPresentation = true - - } - } // MARK: - ActiveCallRouterProtocol @@ -177,16 +127,13 @@ extension ActiveCallRouter: ActiveCallRouterProtocol { // MARK: - ActiveCall func presentActiveCall(for voiceChannel: VoiceChannel, animated: Bool) { - print("🔵 presentActiveCall called - isUIReady: \(isUIReadyForCallPresentation), isPresentingActiveCall: \(isPresentingActiveCall), isActiveCallShown: \(isActiveCallShown)") guard !isPresentingActiveCall, !isActiveCallShown else { - print("❌ presentActiveCall guard failed") return } - print("✅ presentActiveCall proceeding") // NOTE: We resign first reponder for the input bar since it will attempt to restore // first responder when the call overlay is interactively dismissed but canceled. UIResponder.currentFirst?.resignFirstResponder() @@ -204,24 +151,19 @@ extension ActiveCallRouter: ActiveCallRouterProtocol { ) let existingPresentedVC = mainWindow.rootViewController?.presentedViewController - print("📊 Presenting call - existingPresentedVC: \(existingPresentedVC != nil ? String(describing: type(of: existingPresentedVC!)) : "nil")") if existingPresentedVC != nil { - print("📊 Path: dismissPresentedAndPresentActiveCall") dismissPresentedAndPresentActiveCall(modalViewController: modalVC, animated: animated) } else { - print("📊 Path: presentActiveCall directly") presentActiveCall(modalViewController: modalVC, animated: animated) } } func dismissActiveCall(animated: Bool = true, completion: Completion? = nil) { - print("🔴 dismissActiveCall called - isActiveCallShown: \(isActiveCallShown)") guard isActiveCallShown else { completion?() return } - print("🔴 dismissActiveCall proceeding to dismiss") mainWindow.rootViewController?.dismiss(animated: animated) { [weak self] in self?.isActiveCallShown = false if let action = self?.scheduledPostCallAction { @@ -236,7 +178,6 @@ extension ActiveCallRouter: ActiveCallRouterProtocol { } func minimizeCall(animated: Bool = true, completion: Completion? = nil) { - print("🟠 minimizeCall called - isActiveCallShown: \(isActiveCallShown)") guard isActiveCallShown else { completion?() return @@ -247,15 +188,8 @@ extension ActiveCallRouter: ActiveCallRouterProtocol { // MARK: - CallTopOverlay func showCallTopOverlay(for conversation: ZMConversation) { - print("🟡 showCallTopOverlay called - isUIReady: \(isUIReadyForCallPresentation), isCallTopOverlayShown: \(isCallTopOverlayShown)") - guard - isUIReadyForCallPresentation, - !isCallTopOverlayShown - else { - print("❌ showCallTopOverlay guard failed") - return - } - print("✅ showCallTopOverlay proceeding") + guard !isCallTopOverlayShown else { return } + let callTopOverlayController = CallTopOverlayController(conversation: conversation, userSession: userSession) callTopOverlayController.delegate = self topOverlayPresenter.presentTopOverlay(callTopOverlayController, animated: true) @@ -357,24 +291,15 @@ extension ActiveCallRouter: ActiveCallRouterProtocol { modalViewController: ModalPresentationViewController, animated: Bool ) { - print("📌 dismissPresentedAndPresentActiveCall: dismissing existing") mainWindow.rootViewController?.presentedViewController?.dismiss(animated: true) { [weak self] in - print("📌 Dismiss completed, now presenting call") self?.presentActiveCall(modalViewController: modalViewController, animated: animated) } } private func presentActiveCall(modalViewController: ModalPresentationViewController, animated: Bool) { - print("📌 presentActiveCall: starting presentation, setting isPresentingActiveCall = true") - print("📌 mainWindow: \(mainWindow), rootVC: \(String(describing: mainWindow.rootViewController))") - print("📌 mainWindow.isKeyWindow: \(mainWindow.isKeyWindow), mainWindow.isHidden: \(mainWindow.isHidden)") isPresentingActiveCall = true mainWindow.rootViewController?.present(modalViewController, animated: animated) { [weak self] in guard let self = self else { return } - print("📌 Present COMPLETED, setting isActiveCallShown = true") - print("📌 Modal view.window: \(String(describing: modalViewController.view.window))") - print("📌 Modal isBeingPresented: \(modalViewController.isBeingPresented)") - print("📌 RootVC.presentedViewController: \(String(describing: self.mainWindow.rootViewController?.presentedViewController))") self.isActiveCallShown = true } } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/MainController/ZClientViewController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/MainController/ZClientViewController.swift index b3152ff23ad..4571c135e8f 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/MainController/ZClientViewController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/MainController/ZClientViewController.swift @@ -348,13 +348,6 @@ final class ZClientViewController: UIViewController { override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) - // Check for active call that needs to be presented after account switch - if router?.shouldCheckForActiveCallOnAppear == true { - router?.shouldCheckForActiveCallOnAppear = false - router?.markUIReadyForCallPresentation() - router?.updateActiveCallPresentationState() - } - migrateAnalytics() firstTimeRequestToEnableAnalytics() view.backgroundColor = ColorTheme.Backgrounds.surface @@ -546,19 +539,7 @@ final class ZClientViewController: UIViewController { // MARK: - Animated conversation switch func dismissAllModalControllers() async { - // Check for ANY active call (ringing or ongoing), not just ringing - // During account switching, ringingCallConversation may be stale - var hasActiveCall = userSession.ringingCallConversation != nil - - // Also check for non-idle calls if we can access callCenter - if let zmUserSession = userSession as? ZMUserSession, - let callCenter = zmUserSession.callCenter { - let nonIdleCalls = callCenter.nonIdleCallConversations(in: zmUserSession) - hasActiveCall = hasActiveCall || !nonIdleCalls.isEmpty - } - - if hasActiveCall { - // Don't minimize if there's an active call + if userSession.ringingCallConversation != nil { await mainCoordinator.dismissPresentedViewController() } else { await withCheckedContinuation { continuation in diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Overlay/CallTopOverlayController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Overlay/CallTopOverlayController.swift index c2ac5eacfbd..b29d6ce479e 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Overlay/CallTopOverlayController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Overlay/CallTopOverlayController.swift @@ -79,29 +79,18 @@ final class CallTopOverlayController: UIViewController { // MARK: - Init - init(conversation: ZMConversation, userSession: UserSession) { + init(conversation: ZMConversation) { self.conversation = conversation callDurationFormatter.allowedUnits = [.minute, .second] callDurationFormatter.zeroFormattingBehavior = DateComponentsFormatter.ZeroFormattingBehavior(rawValue: 0) super.init(nibName: nil, bundle: nil) -// -// if let userSession = ZMUserSession.shared() { -// observerTokens.append(WireCallCenterV3.addCallStateObserver( -// observer: self, -// contextProvider: userSession.contextProvider -// )) -// observerTokens.append(WireCallCenterV3.addMuteStateObserver(observer: self, userSession: userSession)) -// } - observerTokens.append(WireCallCenterV3.addCallStateObserver( - observer: self, - contextProvider: userSession.contextProvider - )) - - if let zmUserSession = userSession as? ZMUserSession { - observerTokens.append(WireCallCenterV3.addMuteStateObserver( + + if let userSession = ZMUserSession.shared() { + observerTokens.append(WireCallCenterV3.addCallStateObserver( observer: self, - userSession: zmUserSession + contextProvider: userSession.contextProvider )) + observerTokens.append(WireCallCenterV3.addMuteStateObserver(observer: self, userSession: userSession)) } } From 42268df1f204a5a58fd31aac829a4f83d1616823 Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Mon, 24 Nov 2025 13:39:34 +0100 Subject: [PATCH 04/15] roll back changes --- wire-ios/Wire-iOS/Sources/AppRootRouter.swift | 1 - .../CallController/ActiveCallRouter.swift | 6 ++--- .../CallController/CallController.swift | 23 +------------------ 3 files changed, 3 insertions(+), 27 deletions(-) diff --git a/wire-ios/Wire-iOS/Sources/AppRootRouter.swift b/wire-ios/Wire-iOS/Sources/AppRootRouter.swift index fa1ec950b6b..3f7f902b4ab 100644 --- a/wire-ios/Wire-iOS/Sources/AppRootRouter.swift +++ b/wire-ios/Wire-iOS/Sources/AppRootRouter.swift @@ -512,7 +512,6 @@ extension AppRootRouter { // This is needed to display an ongoing call when coming from the background. authenticatedRouter?.updateActiveCallPresentationState() urlActionRouter.authenticatedRouter = authenticatedRouter - ZClientViewController.shared?.legalHoldDisclosureController?.discloseCurrentState(cause: .appOpen) sessionManager.processPendingURLActionRequiresAuthentication() sessionManager.processPendingURLActionDoesNotRequireAuthentication() diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/ActiveCallRouter.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/ActiveCallRouter.swift index e2bd5d8e885..712660ec09b 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/ActiveCallRouter.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/ActiveCallRouter.swift @@ -117,7 +117,6 @@ final class ActiveCallRouter func updateActiveCallPresentationState() { callController.updateActiveCallPresentationState() } - } // MARK: - ActiveCallRouterProtocol @@ -190,7 +189,7 @@ extension ActiveCallRouter: ActiveCallRouterProtocol { func showCallTopOverlay(for conversation: ZMConversation) { guard !isCallTopOverlayShown else { return } - let callTopOverlayController = CallTopOverlayController(conversation: conversation, userSession: userSession) + let callTopOverlayController = CallTopOverlayController(conversation: conversation) callTopOverlayController.delegate = self topOverlayPresenter.presentTopOverlay(callTopOverlayController, animated: true) isCallTopOverlayShown = true @@ -299,8 +298,7 @@ extension ActiveCallRouter: ActiveCallRouterProtocol { private func presentActiveCall(modalViewController: ModalPresentationViewController, animated: Bool) { isPresentingActiveCall = true mainWindow.rootViewController?.present(modalViewController, animated: animated) { [weak self] in - guard let self = self else { return } - self.isActiveCallShown = true + self?.isActiveCallShown = true } } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/CallController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/CallController.swift index bf8ebc93f5b..c92f0ff2ed0 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/CallController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/CallController.swift @@ -40,24 +40,9 @@ final class CallController: NSObject { // MARK: - Init -// init(userSession: UserSession) { -// super.init() -// // Start with clean state - no minimized calls -// minimizedCall = nil -// // Don't register observers yet - wait until UI is ready -// // This prevents observers from firing before root view controller is set up -// // Observers will be registered when startObserving() is called -// } -// -// func startObserving(userSession: UserSession) { -// guard observerTokens.isEmpty else { return } // Already observing -// addObservers(userSession: userSession) -// } - init(userSession: UserSession) { super.init() - minimizedCall = nil - addObservers(userSession: userSession) // ← Register immediately + addObservers(userSession: userSession) } deinit { @@ -67,13 +52,10 @@ final class CallController: NSObject { // MARK: - Public Implementation func updateActiveCallPresentationState() { - print("🔍 updateActiveCallPresentationState - has priorityCallConversation: \(priorityCallConversation != nil)") guard let priorityCallConversation else { - print("⚠️ No priorityCallConversation - dismissing call") dismissCall() return } - print("✅ Has priorityCallConversation - continuing") showCallTopOverlay(for: priorityCallConversation) presentOrMinimizeActiveCall(for: priorityCallConversation) } @@ -86,12 +68,9 @@ final class CallController: NSObject { } private func presentOrMinimizeActiveCall(for conversation: ZMConversation) { - print("🟣 presentOrMinimizeActiveCall - conversation: \(conversation.remoteIdentifier?.uuidString ?? "nil"), minimizedCall: \(minimizedCall?.remoteIdentifier?.uuidString ?? "nil")") if conversation == minimizedCall { - print("⚠️ Choosing to minimize (conversation == minimizedCall)") minimizeCall() } else { - print("📱 Choosing to present") presentCall(in: conversation) } } From 6994cfd671508829e713861cdc2625b579251fc0 Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Mon, 24 Nov 2025 14:00:08 +0100 Subject: [PATCH 05/15] pass _zClientViewController --- wire-ios/Wire-iOS/Sources/AuthenticatedRouter.swift | 2 +- .../Calling/CallController/ActiveCallRouter.swift | 11 +++++------ 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/wire-ios/Wire-iOS/Sources/AuthenticatedRouter.swift b/wire-ios/Wire-iOS/Sources/AuthenticatedRouter.swift index 722c66f9d54..d2a66bf459d 100644 --- a/wire-ios/Wire-iOS/Sources/AuthenticatedRouter.swift +++ b/wire-ios/Wire-iOS/Sources/AuthenticatedRouter.swift @@ -167,7 +167,7 @@ final class AuthenticatedRouter { extension AuthenticatedRouter: AuthenticatedRouterProtocol { func updateActiveCallPresentationState() { - activeCallRouter.updateActiveCallPresentationState() + activeCallRouter.updateActiveCallPresentationState(from: _zClientViewController) } func minimizeCallOverlay(animated: Bool, completion: Completion?) { diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/ActiveCallRouter.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/ActiveCallRouter.swift index 712660ec09b..722f64505ed 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/ActiveCallRouter.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/ActiveCallRouter.swift @@ -81,6 +81,7 @@ final class ActiveCallRouter private let userSession: UserSession private let topOverlayPresenter: TopOverlayPresenter private let mainWindow: UIWindow + private var presentingViewController: UIViewController? private let callController: CallController private let callQualityController: CallQualityController private var transitioningDelegate: CallQualityAnimator @@ -114,7 +115,8 @@ final class ActiveCallRouter // MARK: - Public Implementation - func updateActiveCallPresentationState() { + func updateActiveCallPresentationState(from presentingViewController: UIViewController?) { + self.presentingViewController = presentingViewController callController.updateActiveCallPresentationState() } } @@ -149,9 +151,7 @@ extension ActiveCallRouter: ActiveCallRouterProtocol { enableDismissOnPan: !CallingConfiguration.config.paginationEnabled ) - let existingPresentedVC = mainWindow.rootViewController?.presentedViewController - - if existingPresentedVC != nil { + if mainWindow.rootViewController?.presentedViewController != nil { dismissPresentedAndPresentActiveCall(modalViewController: modalVC, animated: animated) } else { presentActiveCall(modalViewController: modalVC, animated: animated) @@ -188,7 +188,6 @@ extension ActiveCallRouter: ActiveCallRouterProtocol { func showCallTopOverlay(for conversation: ZMConversation) { guard !isCallTopOverlayShown else { return } - let callTopOverlayController = CallTopOverlayController(conversation: conversation) callTopOverlayController.delegate = self topOverlayPresenter.presentTopOverlay(callTopOverlayController, animated: true) @@ -297,7 +296,7 @@ extension ActiveCallRouter: ActiveCallRouterProtocol { private func presentActiveCall(modalViewController: ModalPresentationViewController, animated: Bool) { isPresentingActiveCall = true - mainWindow.rootViewController?.present(modalViewController, animated: animated) { [weak self] in + presentingViewController?.present(modalViewController, animated: animated) { [weak self] in self?.isActiveCallShown = true } } From 9743cb56e1671660c398b6e78d299ce9baf54d6d Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Mon, 24 Nov 2025 14:56:37 +0100 Subject: [PATCH 06/15] setup presentingViewController --- wire-ios/Wire-iOS/Sources/AuthenticatedRouter.swift | 3 ++- .../Calling/CallController/ActiveCallRouter.swift | 12 ++++++++---- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/wire-ios/Wire-iOS/Sources/AuthenticatedRouter.swift b/wire-ios/Wire-iOS/Sources/AuthenticatedRouter.swift index d2a66bf459d..eae1ea7a875 100644 --- a/wire-ios/Wire-iOS/Sources/AuthenticatedRouter.swift +++ b/wire-ios/Wire-iOS/Sources/AuthenticatedRouter.swift @@ -57,6 +57,7 @@ final class AuthenticatedRouter { @MainActor var zClientViewController: ZClientViewController { let zClientViewController = _zClientViewController ?? zClientControllerBuilder(router: self) _zClientViewController = zClientViewController + activeCallRouter.setPresentingViewController(from: zClientViewController) return zClientViewController } @@ -167,7 +168,7 @@ final class AuthenticatedRouter { extension AuthenticatedRouter: AuthenticatedRouterProtocol { func updateActiveCallPresentationState() { - activeCallRouter.updateActiveCallPresentationState(from: _zClientViewController) + activeCallRouter.updateActiveCallPresentationState() } func minimizeCallOverlay(animated: Bool, completion: Completion?) { diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/ActiveCallRouter.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/ActiveCallRouter.swift index 722f64505ed..1cc5a678155 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/ActiveCallRouter.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/ActiveCallRouter.swift @@ -115,10 +115,14 @@ final class ActiveCallRouter // MARK: - Public Implementation - func updateActiveCallPresentationState(from presentingViewController: UIViewController?) { - self.presentingViewController = presentingViewController + func updateActiveCallPresentationState() { callController.updateActiveCallPresentationState() } + + func setPresentingViewController(_ presentingViewController: UIViewController?) { + self.presentingViewController = presentingViewController + } + } // MARK: - ActiveCallRouterProtocol @@ -151,7 +155,7 @@ extension ActiveCallRouter: ActiveCallRouterProtocol { enableDismissOnPan: !CallingConfiguration.config.paginationEnabled ) - if mainWindow.rootViewController?.presentedViewController != nil { + if presentingViewController?.presentedViewController != nil { dismissPresentedAndPresentActiveCall(modalViewController: modalVC, animated: animated) } else { presentActiveCall(modalViewController: modalVC, animated: animated) @@ -289,7 +293,7 @@ extension ActiveCallRouter: ActiveCallRouterProtocol { modalViewController: ModalPresentationViewController, animated: Bool ) { - mainWindow.rootViewController?.presentedViewController?.dismiss(animated: true) { [weak self] in + presentingViewController?.presentedViewController?.dismiss(animated: true) { [weak self] in self?.presentActiveCall(modalViewController: modalViewController, animated: animated) } } From 69cdc0f651aafda7dfe7ad03534964ad920084be Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Mon, 24 Nov 2025 15:17:39 +0100 Subject: [PATCH 07/15] clean up --- wire-ios/Wire-iOS/Sources/AuthenticatedRouter.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wire-ios/Wire-iOS/Sources/AuthenticatedRouter.swift b/wire-ios/Wire-iOS/Sources/AuthenticatedRouter.swift index eae1ea7a875..3590dadc594 100644 --- a/wire-ios/Wire-iOS/Sources/AuthenticatedRouter.swift +++ b/wire-ios/Wire-iOS/Sources/AuthenticatedRouter.swift @@ -57,7 +57,7 @@ final class AuthenticatedRouter { @MainActor var zClientViewController: ZClientViewController { let zClientViewController = _zClientViewController ?? zClientControllerBuilder(router: self) _zClientViewController = zClientViewController - activeCallRouter.setPresentingViewController(from: zClientViewController) + activeCallRouter.setPresentingViewController(zClientViewController) return zClientViewController } From 7ae343de7b36234981f4b8d31375e55898c2d4cf Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Fri, 28 Nov 2025 16:54:26 +0100 Subject: [PATCH 08/15] post and observ notification --- .../SessionManager/PresentationDelegate.swift | 4 ++++ .../SessionManager/SessionManager+Push.swift | 17 +++++++++++++++++ wire-ios/Wire-iOS/Sources/AppRootRouter.swift | 1 - .../Wire-iOS/Sources/AuthenticatedRouter.swift | 2 +- wire-ios/Wire-iOS/Sources/URLActionRouter.swift | 4 ++++ .../CallController/ActiveCallRouter.swift | 15 ++++++++------- ...nversationViewController+ViewLifeCycle.swift | 6 ++++++ 7 files changed, 40 insertions(+), 9 deletions(-) diff --git a/wire-ios-sync-engine/Source/SessionManager/PresentationDelegate.swift b/wire-ios-sync-engine/Source/SessionManager/PresentationDelegate.swift index 6a5b044a89e..07b09ddf827 100644 --- a/wire-ios-sync-engine/Source/SessionManager/PresentationDelegate.swift +++ b/wire-ios-sync-engine/Source/SessionManager/PresentationDelegate.swift @@ -70,4 +70,8 @@ public protocol PresentationDelegate: AnyObject { // Called when showing the password prompt before joining a group conversation func showPasswordPrompt(for conversationName: String, completion: @escaping (String?) -> Void) + + /// Updates the calling UI state if there's an active call + func updateActiveCallPresentationStateIfNeeded() + } diff --git a/wire-ios-sync-engine/Source/SessionManager/SessionManager+Push.swift b/wire-ios-sync-engine/Source/SessionManager/SessionManager+Push.swift index ef5174be20b..c8fd3625898 100644 --- a/wire-ios-sync-engine/Source/SessionManager/SessionManager+Push.swift +++ b/wire-ios-sync-engine/Source/SessionManager/SessionManager+Push.swift @@ -112,6 +112,18 @@ extension SessionManager: UNUserNotificationCenterDelegate { var foundSession = false backgroundUserSessions.forEach { accountId, backgroundSession in if session == backgroundSession, let account = self.accountManager.account(with: accountId) { + var observer: NSObjectProtocol? + observer = NotificationCenter.default.addObserver( + forName: .conversationDidBecomeVisible, + object: nil, + queue: .main + ) { [weak self] _ in + // Remove observer immediately (one-time only) + if let observer = observer { + NotificationCenter.default.removeObserver(observer) + } + self?.presentationDelegate?.updateActiveCallPresentationStateIfNeeded() + } self.select(account, completion: { _ in completion() @@ -153,3 +165,8 @@ public extension SessionManager { presentationDelegate?.showUserProfile(user: user) } } + + +public extension Notification.Name { + static let conversationDidBecomeVisible = Notification.Name("ConversationDidBecomeVisible") + } diff --git a/wire-ios/Wire-iOS/Sources/AppRootRouter.swift b/wire-ios/Wire-iOS/Sources/AppRootRouter.swift index 3f7f902b4ab..68b95ad29e2 100644 --- a/wire-ios/Wire-iOS/Sources/AppRootRouter.swift +++ b/wire-ios/Wire-iOS/Sources/AppRootRouter.swift @@ -510,7 +510,6 @@ extension AppRootRouter { sessionManager.processPendingURLActionDoesNotRequireAuthentication() case .authenticated: // This is needed to display an ongoing call when coming from the background. - authenticatedRouter?.updateActiveCallPresentationState() urlActionRouter.authenticatedRouter = authenticatedRouter ZClientViewController.shared?.legalHoldDisclosureController?.discloseCurrentState(cause: .appOpen) sessionManager.processPendingURLActionRequiresAuthentication() diff --git a/wire-ios/Wire-iOS/Sources/AuthenticatedRouter.swift b/wire-ios/Wire-iOS/Sources/AuthenticatedRouter.swift index 3590dadc594..8ab3f1560d9 100644 --- a/wire-ios/Wire-iOS/Sources/AuthenticatedRouter.swift +++ b/wire-ios/Wire-iOS/Sources/AuthenticatedRouter.swift @@ -57,7 +57,7 @@ final class AuthenticatedRouter { @MainActor var zClientViewController: ZClientViewController { let zClientViewController = _zClientViewController ?? zClientControllerBuilder(router: self) _zClientViewController = zClientViewController - activeCallRouter.setPresentingViewController(zClientViewController) +// activeCallRouter.setPresentingViewController(zClientViewController) return zClientViewController } diff --git a/wire-ios/Wire-iOS/Sources/URLActionRouter.swift b/wire-ios/Wire-iOS/Sources/URLActionRouter.swift index b3e4564f2c6..967ed3e498b 100644 --- a/wire-ios/Wire-iOS/Sources/URLActionRouter.swift +++ b/wire-ios/Wire-iOS/Sources/URLActionRouter.swift @@ -143,6 +143,10 @@ class URLActionRouter: URLActionRouterProtocol { extension URLActionRouter: PresentationDelegate { + func updateActiveCallPresentationStateIfNeeded() { + authenticatedRouter?.updateActiveCallPresentationState() + } + func showPasswordPrompt(for conversationName: String, completion: @escaping (String?) -> Void) { typealias ConversationAlert = L10n.Localizable.Join.Group.Conversation.Alert diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/ActiveCallRouter.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/ActiveCallRouter.swift index 1cc5a678155..9f35fef59d6 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/ActiveCallRouter.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/ActiveCallRouter.swift @@ -81,7 +81,7 @@ final class ActiveCallRouter private let userSession: UserSession private let topOverlayPresenter: TopOverlayPresenter private let mainWindow: UIWindow - private var presentingViewController: UIViewController? + //private var presentingViewController: UIViewController? private let callController: CallController private let callQualityController: CallQualityController private var transitioningDelegate: CallQualityAnimator @@ -119,9 +119,9 @@ final class ActiveCallRouter callController.updateActiveCallPresentationState() } - func setPresentingViewController(_ presentingViewController: UIViewController?) { - self.presentingViewController = presentingViewController - } +// func setPresentingViewController(_ presentingViewController: UIViewController?) { +// self.presentingViewController = presentingViewController +// } } @@ -155,7 +155,8 @@ extension ActiveCallRouter: ActiveCallRouterProtocol { enableDismissOnPan: !CallingConfiguration.config.paginationEnabled ) - if presentingViewController?.presentedViewController != nil { + if mainWindow.rootViewController?.presentedViewController != nil { +// if presentingViewController?.presentedViewController != nil { dismissPresentedAndPresentActiveCall(modalViewController: modalVC, animated: animated) } else { presentActiveCall(modalViewController: modalVC, animated: animated) @@ -293,14 +294,14 @@ extension ActiveCallRouter: ActiveCallRouterProtocol { modalViewController: ModalPresentationViewController, animated: Bool ) { - presentingViewController?.presentedViewController?.dismiss(animated: true) { [weak self] in + mainWindow.rootViewController?.presentedViewController?.dismiss(animated: true) { [weak self] in self?.presentActiveCall(modalViewController: modalViewController, animated: animated) } } private func presentActiveCall(modalViewController: ModalPresentationViewController, animated: Bool) { isPresentingActiveCall = true - presentingViewController?.present(modalViewController, animated: animated) { [weak self] in + mainWindow.rootViewController?.present(modalViewController, animated: animated) { [weak self] in self?.isActiveCallShown = true } } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/ConversationViewController+ViewLifeCycle.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/ConversationViewController+ViewLifeCycle.swift index 38d583d1d9f..2493758d14c 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/ConversationViewController+ViewLifeCycle.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/ConversationViewController+ViewLifeCycle.swift @@ -41,5 +41,11 @@ extension ConversationViewController { isAppearing = false syncCellsState() + // Post notification that conversation is now visible + NotificationCenter.default.post( + name: .conversationDidBecomeVisible, + object: conversation + ) + } } From 7d4b6e9ad0fd7776a18668d3c56dbd41e28ec4d8 Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Fri, 28 Nov 2025 17:05:24 +0100 Subject: [PATCH 09/15] remove last potential fix --- .../Source/SessionManager/SessionManager+Push.swift | 7 +++---- wire-ios/Wire-iOS/Sources/AuthenticatedRouter.swift | 1 - .../Calling/CallController/ActiveCallRouter.swift | 6 ------ .../ConversationViewController+ViewLifeCycle.swift | 2 +- 4 files changed, 4 insertions(+), 12 deletions(-) diff --git a/wire-ios-sync-engine/Source/SessionManager/SessionManager+Push.swift b/wire-ios-sync-engine/Source/SessionManager/SessionManager+Push.swift index c8fd3625898..a9791447ad7 100644 --- a/wire-ios-sync-engine/Source/SessionManager/SessionManager+Push.swift +++ b/wire-ios-sync-engine/Source/SessionManager/SessionManager+Push.swift @@ -119,7 +119,7 @@ extension SessionManager: UNUserNotificationCenterDelegate { queue: .main ) { [weak self] _ in // Remove observer immediately (one-time only) - if let observer = observer { + if let observer { NotificationCenter.default.removeObserver(observer) } self?.presentationDelegate?.updateActiveCallPresentationStateIfNeeded() @@ -166,7 +166,6 @@ public extension SessionManager { } } - public extension Notification.Name { - static let conversationDidBecomeVisible = Notification.Name("ConversationDidBecomeVisible") - } + static let conversationDidBecomeVisible = Notification.Name("ConversationDidBecomeVisible") +} diff --git a/wire-ios/Wire-iOS/Sources/AuthenticatedRouter.swift b/wire-ios/Wire-iOS/Sources/AuthenticatedRouter.swift index 8ab3f1560d9..722c66f9d54 100644 --- a/wire-ios/Wire-iOS/Sources/AuthenticatedRouter.swift +++ b/wire-ios/Wire-iOS/Sources/AuthenticatedRouter.swift @@ -57,7 +57,6 @@ final class AuthenticatedRouter { @MainActor var zClientViewController: ZClientViewController { let zClientViewController = _zClientViewController ?? zClientControllerBuilder(router: self) _zClientViewController = zClientViewController -// activeCallRouter.setPresentingViewController(zClientViewController) return zClientViewController } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/ActiveCallRouter.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/ActiveCallRouter.swift index 9f35fef59d6..6ee5b004531 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/ActiveCallRouter.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/ActiveCallRouter.swift @@ -81,7 +81,6 @@ final class ActiveCallRouter private let userSession: UserSession private let topOverlayPresenter: TopOverlayPresenter private let mainWindow: UIWindow - //private var presentingViewController: UIViewController? private let callController: CallController private let callQualityController: CallQualityController private var transitioningDelegate: CallQualityAnimator @@ -119,10 +118,6 @@ final class ActiveCallRouter callController.updateActiveCallPresentationState() } -// func setPresentingViewController(_ presentingViewController: UIViewController?) { -// self.presentingViewController = presentingViewController -// } - } // MARK: - ActiveCallRouterProtocol @@ -156,7 +151,6 @@ extension ActiveCallRouter: ActiveCallRouterProtocol { ) if mainWindow.rootViewController?.presentedViewController != nil { -// if presentingViewController?.presentedViewController != nil { dismissPresentedAndPresentActiveCall(modalViewController: modalVC, animated: animated) } else { presentActiveCall(modalViewController: modalVC, animated: animated) diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/ConversationViewController+ViewLifeCycle.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/ConversationViewController+ViewLifeCycle.swift index 2493758d14c..35321607e42 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/ConversationViewController+ViewLifeCycle.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/ConversationViewController+ViewLifeCycle.swift @@ -41,7 +41,7 @@ extension ConversationViewController { isAppearing = false syncCellsState() - // Post notification that conversation is now visible + NotificationCenter.default.post( name: .conversationDidBecomeVisible, object: conversation From bc31477fa9625f1779c832213183f3cd5e8846f8 Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Fri, 28 Nov 2025 17:37:44 +0100 Subject: [PATCH 10/15] clean up --- .../UserInterface/Calling/CallController/ActiveCallRouter.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/ActiveCallRouter.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/ActiveCallRouter.swift index 6ee5b004531..10cff2c7769 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/ActiveCallRouter.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Calling/CallController/ActiveCallRouter.swift @@ -117,7 +117,6 @@ final class ActiveCallRouter func updateActiveCallPresentationState() { callController.updateActiveCallPresentationState() } - } // MARK: - ActiveCallRouterProtocol From 49d2a429c9320c73495e6b4d066704b25e933f0f Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Mon, 1 Dec 2025 18:02:46 +0100 Subject: [PATCH 11/15] clean up --- .../Source/SessionManager/SessionManager+Push.swift | 11 ++++++----- .../Source/SessionManager/SessionManager.swift | 1 + .../Tests/Source/MockPresentationDelegate.swift | 2 ++ .../ConversationViewController+ViewLifeCycle.swift | 6 +++++- 4 files changed, 14 insertions(+), 6 deletions(-) diff --git a/wire-ios-sync-engine/Source/SessionManager/SessionManager+Push.swift b/wire-ios-sync-engine/Source/SessionManager/SessionManager+Push.swift index a9791447ad7..6d6f8419df6 100644 --- a/wire-ios-sync-engine/Source/SessionManager/SessionManager+Push.swift +++ b/wire-ios-sync-engine/Source/SessionManager/SessionManager+Push.swift @@ -112,17 +112,18 @@ extension SessionManager: UNUserNotificationCenterDelegate { var foundSession = false backgroundUserSessions.forEach { accountId, backgroundSession in if session == backgroundSession, let account = self.accountManager.account(with: accountId) { - var observer: NSObjectProtocol? - observer = NotificationCenter.default.addObserver( + conversationVisibleObserver = NotificationCenter.default.addObserver( forName: .conversationDidBecomeVisible, object: nil, queue: .main ) { [weak self] _ in - // Remove observer immediately (one-time only) - if let observer { + guard let self else { return } + + if let observer = self.conversationVisibleObserver { NotificationCenter.default.removeObserver(observer) + self.conversationVisibleObserver = nil } - self?.presentationDelegate?.updateActiveCallPresentationStateIfNeeded() + self.presentationDelegate?.updateActiveCallPresentationStateIfNeeded() } self.select(account, completion: { _ in diff --git a/wire-ios-sync-engine/Source/SessionManager/SessionManager.swift b/wire-ios-sync-engine/Source/SessionManager/SessionManager.swift index 7949da3efce..1073e83cf5d 100644 --- a/wire-ios-sync-engine/Source/SessionManager/SessionManager.swift +++ b/wire-ios-sync-engine/Source/SessionManager/SessionManager.swift @@ -348,6 +348,7 @@ public final class SessionManager: NSObject, SessionManagerType { let jailbreakDetector: JailbreakDetectorProtocol? fileprivate var accountTokens: [UUID: [Any]] = [:] fileprivate var memoryWarningObserver: NSObjectProtocol? + var conversationVisibleObserver: NSObjectProtocol? fileprivate var isSelectingAccount: Bool = false var proxyCredentials: WireTransport.ProxyCredentials? diff --git a/wire-ios-sync-engine/Tests/Source/MockPresentationDelegate.swift b/wire-ios-sync-engine/Tests/Source/MockPresentationDelegate.swift index 13ddc265f2a..028465ccae6 100644 --- a/wire-ios-sync-engine/Tests/Source/MockPresentationDelegate.swift +++ b/wire-ios-sync-engine/Tests/Source/MockPresentationDelegate.swift @@ -74,4 +74,6 @@ class MockPresentationDelegate: PresentationDelegate { completion(mockPassword) } + func updateActiveCallPresentationStateIfNeeded() {} + } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/ConversationViewController+ViewLifeCycle.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/ConversationViewController+ViewLifeCycle.swift index 35321607e42..3f9e8893fbb 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/ConversationViewController+ViewLifeCycle.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/ConversationViewController+ViewLifeCycle.swift @@ -41,11 +41,15 @@ extension ConversationViewController { isAppearing = false syncCellsState() + notifyConversationDidBecomeVisible() + } + + private func notifyConversationDidBecomeVisible() { NotificationCenter.default.post( name: .conversationDidBecomeVisible, object: conversation ) - } + } From 93621f4cda0088d080eb73318056fb4ec58ba795 Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Tue, 2 Dec 2025 09:50:27 +0100 Subject: [PATCH 12/15] fix SwiftFormat issue --- .../Source/SessionManager/SessionManager+Push.swift | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wire-ios-sync-engine/Source/SessionManager/SessionManager+Push.swift b/wire-ios-sync-engine/Source/SessionManager/SessionManager+Push.swift index 6d6f8419df6..1712d385d53 100644 --- a/wire-ios-sync-engine/Source/SessionManager/SessionManager+Push.swift +++ b/wire-ios-sync-engine/Source/SessionManager/SessionManager+Push.swift @@ -119,11 +119,11 @@ extension SessionManager: UNUserNotificationCenterDelegate { ) { [weak self] _ in guard let self else { return } - if let observer = self.conversationVisibleObserver { + if let observer = conversationVisibleObserver { NotificationCenter.default.removeObserver(observer) - self.conversationVisibleObserver = nil + conversationVisibleObserver = nil } - self.presentationDelegate?.updateActiveCallPresentationStateIfNeeded() + presentationDelegate?.updateActiveCallPresentationStateIfNeeded() } self.select(account, completion: { _ in From a1cb85c895d34b11b0b4748d30358fea8c6a13e3 Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Wed, 10 Dec 2025 14:06:41 +0100 Subject: [PATCH 13/15] move the code around --- .../SessionManager/SessionManager+Push.swift | 36 +++++++++++-------- ...ersationViewController+ViewLifeCycle.swift | 2 +- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/wire-ios-sync-engine/Source/SessionManager/SessionManager+Push.swift b/wire-ios-sync-engine/Source/SessionManager/SessionManager+Push.swift index 1712d385d53..00b27ff568a 100644 --- a/wire-ios-sync-engine/Source/SessionManager/SessionManager+Push.swift +++ b/wire-ios-sync-engine/Source/SessionManager/SessionManager+Push.swift @@ -112,20 +112,6 @@ extension SessionManager: UNUserNotificationCenterDelegate { var foundSession = false backgroundUserSessions.forEach { accountId, backgroundSession in if session == backgroundSession, let account = self.accountManager.account(with: accountId) { - conversationVisibleObserver = NotificationCenter.default.addObserver( - forName: .conversationDidBecomeVisible, - object: nil, - queue: .main - ) { [weak self] _ in - guard let self else { return } - - if let observer = conversationVisibleObserver { - NotificationCenter.default.removeObserver(observer) - conversationVisibleObserver = nil - } - presentationDelegate?.updateActiveCallPresentationStateIfNeeded() - } - self.select(account, completion: { _ in completion() }) @@ -138,6 +124,23 @@ extension SessionManager: UNUserNotificationCenterDelegate { fatalError("User session \(session) is not present in backgroundSessions") } } + + /// Registers an observer to update active call presentation state when the conversation becomes visible. + fileprivate func observeConversationDidBecomeVisible() { + conversationVisibleObserver = NotificationCenter.default.addObserver( + forName: .conversationDidBecomeVisible, + object: nil, + queue: .main + ) { [weak self] _ in + guard let self else { return } + + if let observer = conversationVisibleObserver { + NotificationCenter.default.removeObserver(observer) + conversationVisibleObserver = nil + } + presentationDelegate?.updateActiveCallPresentationStateIfNeeded() + } + } } public extension SessionManager { @@ -151,6 +154,11 @@ public extension SessionManager { return } + // If switching accounts, observe when conversation becomes visible to update call UI + if session != activeUserSession { + observeConversationDidBecomeVisible() + } + activateAccount(for: session) { self.presentationDelegate?.showConversation(conversation, at: message) } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/ConversationViewController+ViewLifeCycle.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/ConversationViewController+ViewLifeCycle.swift index 3f9e8893fbb..750fa0f000f 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/ConversationViewController+ViewLifeCycle.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Conversation/ConversationViewController+ViewLifeCycle.swift @@ -48,7 +48,7 @@ extension ConversationViewController { private func notifyConversationDidBecomeVisible() { NotificationCenter.default.post( name: .conversationDidBecomeVisible, - object: conversation + object: nil ) } From b6b61d9a7c16ba2aa5a315b5f25259ff7e7e9d4f Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Wed, 10 Dec 2025 14:08:35 +0100 Subject: [PATCH 14/15] add an emty line --- .../Source/SessionManager/SessionManager+Push.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/wire-ios-sync-engine/Source/SessionManager/SessionManager+Push.swift b/wire-ios-sync-engine/Source/SessionManager/SessionManager+Push.swift index 00b27ff568a..dba5c1cfbaa 100644 --- a/wire-ios-sync-engine/Source/SessionManager/SessionManager+Push.swift +++ b/wire-ios-sync-engine/Source/SessionManager/SessionManager+Push.swift @@ -112,6 +112,7 @@ extension SessionManager: UNUserNotificationCenterDelegate { var foundSession = false backgroundUserSessions.forEach { accountId, backgroundSession in if session == backgroundSession, let account = self.accountManager.account(with: accountId) { + self.select(account, completion: { _ in completion() }) From fd9f9586062666bdb17815a2edeee04d76dc158e Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Fri, 12 Dec 2025 16:59:01 +0100 Subject: [PATCH 15/15] remove comment --- wire-ios/Wire-iOS/Sources/AppRootRouter.swift | 1 - 1 file changed, 1 deletion(-) diff --git a/wire-ios/Wire-iOS/Sources/AppRootRouter.swift b/wire-ios/Wire-iOS/Sources/AppRootRouter.swift index 68b95ad29e2..2c594d3686f 100644 --- a/wire-ios/Wire-iOS/Sources/AppRootRouter.swift +++ b/wire-ios/Wire-iOS/Sources/AppRootRouter.swift @@ -509,7 +509,6 @@ extension AppRootRouter { presentAlertForDeletedAccountIfNeeded(error) sessionManager.processPendingURLActionDoesNotRequireAuthentication() case .authenticated: - // This is needed to display an ongoing call when coming from the background. urlActionRouter.authenticatedRouter = authenticatedRouter ZClientViewController.shared?.legalHoldDisclosureController?.discloseCurrentState(cause: .appOpen) sessionManager.processPendingURLActionRequiresAuthentication()