Fixes for multi-window support. (#5528)
* Correctly handle the re-opening of the main window. Add an additional safe-guard to ensure only one main window exists. Make sure all secondary windows use the correct tint colour. * Fix a bug where the settings screen isn't shown on macOS when the AppLock feature is enabled.
This commit is contained in:
@@ -32,7 +32,7 @@ struct Application: App {
|
||||
}
|
||||
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
WindowGroup(id: SceneDelegate.mainSceneID) {
|
||||
appCoordinator.toPresentable()
|
||||
.statusBarHidden(shouldHideStatusBar)
|
||||
.overlay(alignment: .top) {
|
||||
@@ -52,7 +52,7 @@ struct Application: App {
|
||||
}
|
||||
.task {
|
||||
appCoordinator.start()
|
||||
appCoordinator.windowManager.configure(withOpenWinddowAction: openWindow,
|
||||
appCoordinator.windowManager.configure(withOpenWindowAction: openWindow,
|
||||
dismissWindowAction: dismissWindow)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,8 +14,18 @@ import SwiftUI
|
||||
class SceneDelegate: NSObject, UIWindowSceneDelegate {
|
||||
weak static var windowManager: SecureWindowManagerProtocol!
|
||||
|
||||
/// The app's main window scene identifier.
|
||||
static let mainSceneID = "Main"
|
||||
/// The user info key used by SwiftUI for a `WindowGroup`s `id` parameter.
|
||||
static let sceneIDKey = "com.apple.SwiftUI.sceneID"
|
||||
|
||||
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
|
||||
guard let windowScene = scene as? UIWindowScene else { return }
|
||||
Self.windowManager.configure(withScene: windowScene, session: session)
|
||||
}
|
||||
|
||||
func sceneDidDisconnect(_ scene: UIScene) {
|
||||
guard let windowScene = scene as? UIWindowScene else { return }
|
||||
Self.windowManager.handleSceneDisconnection(windowScene)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,15 +48,28 @@ class WindowManager: SecureWindowManagerProtocol {
|
||||
}
|
||||
|
||||
func configure(withScene scene: UIWindowScene, session: UISceneSession) {
|
||||
// This gets called for all opened windows, we're only interested in the
|
||||
// first call, for the main window (works with state restoration too).
|
||||
guard mainWindow == nil else {
|
||||
// This gets called for all opened windows, we're only interested in the main window.
|
||||
guard let userInfo = session.userInfo, userInfo[SceneDelegate.sceneIDKey] as? String == SceneDelegate.mainSceneID else {
|
||||
scene.windows.forEach { $0.tintColor = .compound.textActionPrimary } // SecondaryWindow tinting.
|
||||
return
|
||||
}
|
||||
|
||||
// Don't allow more than 1 main window to be presented.
|
||||
if mainScene != nil {
|
||||
// The window will be presented momentarily, so lets leave it blank.
|
||||
scene.keyWindow?.rootViewController = UIHostingController(rootView: Color.clear)
|
||||
UIApplication.shared.requestSceneSessionDestruction(session, options: nil)
|
||||
return
|
||||
}
|
||||
|
||||
mainScene = scene
|
||||
mainSession = session
|
||||
|
||||
// Restore the previous window size on macOS as this isn't automatic.
|
||||
if let previousSize = mainWindow?.frame.size {
|
||||
scene.resizeWindowOnMac(to: previousSize)
|
||||
}
|
||||
|
||||
mainWindow = scene.keyWindow
|
||||
mainWindow.tintColor = .compound.textActionPrimary
|
||||
|
||||
@@ -76,12 +89,20 @@ class WindowManager: SecureWindowManagerProtocol {
|
||||
delegate?.windowManagerDidConfigureWindows(self)
|
||||
}
|
||||
|
||||
func configure(withOpenWinddowAction openWindowAction: OpenWindowAction,
|
||||
func configure(withOpenWindowAction openWindowAction: OpenWindowAction,
|
||||
dismissWindowAction: DismissWindowAction) {
|
||||
self.openWindowAction = openWindowAction
|
||||
self.dismissWindowAction = dismissWindowAction
|
||||
}
|
||||
|
||||
func handleSceneDisconnection(_ scene: UIWindowScene) {
|
||||
if scene == mainScene {
|
||||
mainScene = nil
|
||||
mainSession = nil
|
||||
// Leave the mainWindow so we can reapply it's size on macOS.
|
||||
}
|
||||
}
|
||||
|
||||
func handleRoute(_ appRoute: AppRoute, windowType: SecondaryWindowType) {
|
||||
MXLog.info("Handling app route: \(appRoute) for window type: \(windowType)")
|
||||
|
||||
@@ -258,3 +279,19 @@ private struct InstantlyDismissingWindow: View {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private extension UIWindowScene {
|
||||
func resizeWindowOnMac(to size: CGSize) {
|
||||
// Hackity hack 🔨
|
||||
guard ProcessInfo.processInfo.isiOSAppOnMac, let sizeRestrictions else { return }
|
||||
|
||||
self.sizeRestrictions?.minimumSize = size
|
||||
self.sizeRestrictions?.maximumSize = size
|
||||
|
||||
Task {
|
||||
try await Task.sleep(for: .milliseconds(100))
|
||||
self.sizeRestrictions?.minimumSize = sizeRestrictions.minimumSize
|
||||
self.sizeRestrictions?.maximumSize = sizeRestrictions.maximumSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,7 +25,9 @@ protocol SecureWindowManagerProtocol: WindowManagerProtocol {
|
||||
/// Configures the window manager to operate on the supplied scene.
|
||||
func configure(withScene scene: UIWindowScene, session: UISceneSession)
|
||||
|
||||
func configure(withOpenWinddowAction openWindowAction: OpenWindowAction, dismissWindowAction: DismissWindowAction)
|
||||
func configure(withOpenWindowAction openWindowAction: OpenWindowAction, dismissWindowAction: DismissWindowAction)
|
||||
|
||||
func handleSceneDisconnection(_ scene: UIWindowScene)
|
||||
|
||||
func handleRoute(_ appRoute: AppRoute, windowType: SecondaryWindowType)
|
||||
|
||||
|
||||
@@ -19,6 +19,7 @@ enum SettingsFlowCoordinatorAction {
|
||||
|
||||
class SettingsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
private let appLockService: AppLockServiceProtocol
|
||||
private let isInSecondaryWindow: Bool
|
||||
private let navigationStackCoordinator: NavigationStackCoordinator
|
||||
private let flowParameters: CommonFlowParameters
|
||||
|
||||
@@ -39,9 +40,11 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
}
|
||||
|
||||
init(appLockService: AppLockServiceProtocol,
|
||||
isInSecondaryWindow: Bool,
|
||||
navigationStackCoordinator: NavigationStackCoordinator,
|
||||
flowParameters: CommonFlowParameters) {
|
||||
self.appLockService = appLockService
|
||||
self.isInSecondaryWindow = isInSecondaryWindow
|
||||
self.navigationStackCoordinator = navigationStackCoordinator
|
||||
self.flowParameters = flowParameters
|
||||
}
|
||||
@@ -72,7 +75,8 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
|
||||
private func presentSettingsScreen(animated: Bool) {
|
||||
let settingsScreenCoordinator = SettingsScreenCoordinator(parameters: .init(userSession: flowParameters.userSession,
|
||||
appSettings: flowParameters.appSettings,
|
||||
isBugReportServiceEnabled: flowParameters.bugReportService.isEnabled))
|
||||
isBugReportServiceEnabled: flowParameters.bugReportService.isEnabled,
|
||||
isInSecondaryWindow: isInSecondaryWindow))
|
||||
|
||||
settingsScreenCoordinator.actions
|
||||
.sink { [weak self] action in
|
||||
|
||||
@@ -124,7 +124,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
case .accountProvisioningLink:
|
||||
break // We always ignore this flow when logged in.
|
||||
case .settings, .chatBackupSettings:
|
||||
if ProcessInfo.processInfo.isiOSAppOnMac {
|
||||
if ProcessInfo.processInfo.isiOSAppOnMac, flowParameters.windowManager.secondaryWindowsEnabled {
|
||||
startSettingsFlow(detached: true)
|
||||
} else {
|
||||
if stateMachine.state != .settingsScreen {
|
||||
@@ -310,6 +310,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
||||
private func startSettingsFlow(detached: Bool) {
|
||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||
let coordinator = SettingsFlowCoordinator(appLockService: appLockService,
|
||||
isInSecondaryWindow: detached,
|
||||
navigationStackCoordinator: navigationStackCoordinator,
|
||||
flowParameters: flowParameters)
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ struct SettingsScreenCoordinatorParameters {
|
||||
let userSession: UserSessionProtocol
|
||||
let appSettings: AppSettings
|
||||
let isBugReportServiceEnabled: Bool
|
||||
let isInSecondaryWindow: Bool
|
||||
}
|
||||
|
||||
enum SettingsScreenCoordinatorAction {
|
||||
@@ -49,7 +50,8 @@ final class SettingsScreenCoordinator: CoordinatorProtocol {
|
||||
init(parameters: SettingsScreenCoordinatorParameters) {
|
||||
viewModel = SettingsScreenViewModel(userSession: parameters.userSession,
|
||||
appSettings: parameters.appSettings,
|
||||
isBugReportServiceEnabled: parameters.isBugReportServiceEnabled)
|
||||
isBugReportServiceEnabled: parameters.isBugReportServiceEnabled,
|
||||
isInSecondaryWindow: parameters.isInSecondaryWindow)
|
||||
|
||||
viewModel.actions
|
||||
.sink { [weak self] action in
|
||||
|
||||
@@ -6,8 +6,7 @@
|
||||
// Please see LICENSE files in the repository root for full details.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import SwiftUI
|
||||
|
||||
enum SettingsScreenViewModelAction: Equatable {
|
||||
case close
|
||||
@@ -51,6 +50,8 @@ struct SettingsScreenViewState: BindableState {
|
||||
|
||||
let isBugReportServiceEnabled: Bool
|
||||
|
||||
let navigationBarVisibility: Visibility
|
||||
|
||||
var bindings = SettingsScreenViewStateBindings()
|
||||
}
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init(userSession: UserSessionProtocol, appSettings: AppSettings, isBugReportServiceEnabled: Bool) {
|
||||
init(userSession: UserSessionProtocol, appSettings: AppSettings, isBugReportServiceEnabled: Bool, isInSecondaryWindow: Bool) {
|
||||
self.appSettings = appSettings
|
||||
|
||||
super.init(initialViewState: .init(deviceID: userSession.clientProxy.deviceID,
|
||||
@@ -29,7 +29,8 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
|
||||
showAccountDeactivation: userSession.clientProxy.canDeactivateAccount,
|
||||
showDeveloperOptions: appSettings.developerOptionsEnabled,
|
||||
showAnalyticsSettings: appSettings.canPromptForAnalytics,
|
||||
isBugReportServiceEnabled: isBugReportServiceEnabled),
|
||||
isBugReportServiceEnabled: isBugReportServiceEnabled,
|
||||
navigationBarVisibility: isInSecondaryWindow ? .hidden : .automatic),
|
||||
mediaProvider: userSession.mediaProvider)
|
||||
|
||||
appSettings.$developerOptionsEnabled
|
||||
|
||||
@@ -40,7 +40,7 @@ struct SettingsScreen: View {
|
||||
.compoundList()
|
||||
.navigationTitle(L10n.commonSettings)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbarVisibility(ProcessInfo.processInfo.isiOSAppOnMac ? .hidden : .automatic, for: .navigationBar)
|
||||
.toolbarVisibility(context.viewState.navigationBarVisibility, for: .navigationBar)
|
||||
.toolbar { toolbar }
|
||||
}
|
||||
|
||||
@@ -278,6 +278,7 @@ struct SettingsScreen_Previews: PreviewProvider, TestablePreview {
|
||||
deviceID: "AAAAAAAAAAA"))))
|
||||
return SettingsScreenViewModel(userSession: userSession,
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
isBugReportServiceEnabled: isBugReportServiceEnabled)
|
||||
isBugReportServiceEnabled: isBugReportServiceEnabled,
|
||||
isInSecondaryWindow: false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,8 @@ struct SettingsScreenViewModelTests {
|
||||
let userSession = UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userID: ""))))
|
||||
viewModel = SettingsScreenViewModel(userSession: userSession,
|
||||
appSettings: ServiceLocator.shared.settings,
|
||||
isBugReportServiceEnabled: true)
|
||||
isBugReportServiceEnabled: true,
|
||||
isInSecondaryWindow: false)
|
||||
context = viewModel.context
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user