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 {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup(id: SceneDelegate.mainSceneID) {
|
||||||
appCoordinator.toPresentable()
|
appCoordinator.toPresentable()
|
||||||
.statusBarHidden(shouldHideStatusBar)
|
.statusBarHidden(shouldHideStatusBar)
|
||||||
.overlay(alignment: .top) {
|
.overlay(alignment: .top) {
|
||||||
@@ -52,7 +52,7 @@ struct Application: App {
|
|||||||
}
|
}
|
||||||
.task {
|
.task {
|
||||||
appCoordinator.start()
|
appCoordinator.start()
|
||||||
appCoordinator.windowManager.configure(withOpenWinddowAction: openWindow,
|
appCoordinator.windowManager.configure(withOpenWindowAction: openWindow,
|
||||||
dismissWindowAction: dismissWindow)
|
dismissWindowAction: dismissWindow)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,8 +14,18 @@ import SwiftUI
|
|||||||
class SceneDelegate: NSObject, UIWindowSceneDelegate {
|
class SceneDelegate: NSObject, UIWindowSceneDelegate {
|
||||||
weak static var windowManager: SecureWindowManagerProtocol!
|
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) {
|
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
|
||||||
guard let windowScene = scene as? UIWindowScene else { return }
|
guard let windowScene = scene as? UIWindowScene else { return }
|
||||||
Self.windowManager.configure(withScene: windowScene, session: session)
|
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) {
|
func configure(withScene scene: UIWindowScene, session: UISceneSession) {
|
||||||
// This gets called for all opened windows, we're only interested in the
|
// This gets called for all opened windows, we're only interested in the main window.
|
||||||
// first call, for the main window (works with state restoration too).
|
guard let userInfo = session.userInfo, userInfo[SceneDelegate.sceneIDKey] as? String == SceneDelegate.mainSceneID else {
|
||||||
guard mainWindow == nil 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
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
mainScene = scene
|
mainScene = scene
|
||||||
mainSession = session
|
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 = scene.keyWindow
|
||||||
mainWindow.tintColor = .compound.textActionPrimary
|
mainWindow.tintColor = .compound.textActionPrimary
|
||||||
|
|
||||||
@@ -76,12 +89,20 @@ class WindowManager: SecureWindowManagerProtocol {
|
|||||||
delegate?.windowManagerDidConfigureWindows(self)
|
delegate?.windowManagerDidConfigureWindows(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
func configure(withOpenWinddowAction openWindowAction: OpenWindowAction,
|
func configure(withOpenWindowAction openWindowAction: OpenWindowAction,
|
||||||
dismissWindowAction: DismissWindowAction) {
|
dismissWindowAction: DismissWindowAction) {
|
||||||
self.openWindowAction = openWindowAction
|
self.openWindowAction = openWindowAction
|
||||||
self.dismissWindowAction = dismissWindowAction
|
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) {
|
func handleRoute(_ appRoute: AppRoute, windowType: SecondaryWindowType) {
|
||||||
MXLog.info("Handling app route: \(appRoute) for window type: \(windowType)")
|
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.
|
/// Configures the window manager to operate on the supplied scene.
|
||||||
func configure(withScene scene: UIWindowScene, session: UISceneSession)
|
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)
|
func handleRoute(_ appRoute: AppRoute, windowType: SecondaryWindowType)
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ enum SettingsFlowCoordinatorAction {
|
|||||||
|
|
||||||
class SettingsFlowCoordinator: FlowCoordinatorProtocol {
|
class SettingsFlowCoordinator: FlowCoordinatorProtocol {
|
||||||
private let appLockService: AppLockServiceProtocol
|
private let appLockService: AppLockServiceProtocol
|
||||||
|
private let isInSecondaryWindow: Bool
|
||||||
private let navigationStackCoordinator: NavigationStackCoordinator
|
private let navigationStackCoordinator: NavigationStackCoordinator
|
||||||
private let flowParameters: CommonFlowParameters
|
private let flowParameters: CommonFlowParameters
|
||||||
|
|
||||||
@@ -39,9 +40,11 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
}
|
}
|
||||||
|
|
||||||
init(appLockService: AppLockServiceProtocol,
|
init(appLockService: AppLockServiceProtocol,
|
||||||
|
isInSecondaryWindow: Bool,
|
||||||
navigationStackCoordinator: NavigationStackCoordinator,
|
navigationStackCoordinator: NavigationStackCoordinator,
|
||||||
flowParameters: CommonFlowParameters) {
|
flowParameters: CommonFlowParameters) {
|
||||||
self.appLockService = appLockService
|
self.appLockService = appLockService
|
||||||
|
self.isInSecondaryWindow = isInSecondaryWindow
|
||||||
self.navigationStackCoordinator = navigationStackCoordinator
|
self.navigationStackCoordinator = navigationStackCoordinator
|
||||||
self.flowParameters = flowParameters
|
self.flowParameters = flowParameters
|
||||||
}
|
}
|
||||||
@@ -72,7 +75,8 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
private func presentSettingsScreen(animated: Bool) {
|
private func presentSettingsScreen(animated: Bool) {
|
||||||
let settingsScreenCoordinator = SettingsScreenCoordinator(parameters: .init(userSession: flowParameters.userSession,
|
let settingsScreenCoordinator = SettingsScreenCoordinator(parameters: .init(userSession: flowParameters.userSession,
|
||||||
appSettings: flowParameters.appSettings,
|
appSettings: flowParameters.appSettings,
|
||||||
isBugReportServiceEnabled: flowParameters.bugReportService.isEnabled))
|
isBugReportServiceEnabled: flowParameters.bugReportService.isEnabled,
|
||||||
|
isInSecondaryWindow: isInSecondaryWindow))
|
||||||
|
|
||||||
settingsScreenCoordinator.actions
|
settingsScreenCoordinator.actions
|
||||||
.sink { [weak self] action in
|
.sink { [weak self] action in
|
||||||
|
|||||||
@@ -124,7 +124,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
case .accountProvisioningLink:
|
case .accountProvisioningLink:
|
||||||
break // We always ignore this flow when logged in.
|
break // We always ignore this flow when logged in.
|
||||||
case .settings, .chatBackupSettings:
|
case .settings, .chatBackupSettings:
|
||||||
if ProcessInfo.processInfo.isiOSAppOnMac {
|
if ProcessInfo.processInfo.isiOSAppOnMac, flowParameters.windowManager.secondaryWindowsEnabled {
|
||||||
startSettingsFlow(detached: true)
|
startSettingsFlow(detached: true)
|
||||||
} else {
|
} else {
|
||||||
if stateMachine.state != .settingsScreen {
|
if stateMachine.state != .settingsScreen {
|
||||||
@@ -310,6 +310,7 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol {
|
|||||||
private func startSettingsFlow(detached: Bool) {
|
private func startSettingsFlow(detached: Bool) {
|
||||||
let navigationStackCoordinator = NavigationStackCoordinator()
|
let navigationStackCoordinator = NavigationStackCoordinator()
|
||||||
let coordinator = SettingsFlowCoordinator(appLockService: appLockService,
|
let coordinator = SettingsFlowCoordinator(appLockService: appLockService,
|
||||||
|
isInSecondaryWindow: detached,
|
||||||
navigationStackCoordinator: navigationStackCoordinator,
|
navigationStackCoordinator: navigationStackCoordinator,
|
||||||
flowParameters: flowParameters)
|
flowParameters: flowParameters)
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ struct SettingsScreenCoordinatorParameters {
|
|||||||
let userSession: UserSessionProtocol
|
let userSession: UserSessionProtocol
|
||||||
let appSettings: AppSettings
|
let appSettings: AppSettings
|
||||||
let isBugReportServiceEnabled: Bool
|
let isBugReportServiceEnabled: Bool
|
||||||
|
let isInSecondaryWindow: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SettingsScreenCoordinatorAction {
|
enum SettingsScreenCoordinatorAction {
|
||||||
@@ -49,7 +50,8 @@ final class SettingsScreenCoordinator: CoordinatorProtocol {
|
|||||||
init(parameters: SettingsScreenCoordinatorParameters) {
|
init(parameters: SettingsScreenCoordinatorParameters) {
|
||||||
viewModel = SettingsScreenViewModel(userSession: parameters.userSession,
|
viewModel = SettingsScreenViewModel(userSession: parameters.userSession,
|
||||||
appSettings: parameters.appSettings,
|
appSettings: parameters.appSettings,
|
||||||
isBugReportServiceEnabled: parameters.isBugReportServiceEnabled)
|
isBugReportServiceEnabled: parameters.isBugReportServiceEnabled,
|
||||||
|
isInSecondaryWindow: parameters.isInSecondaryWindow)
|
||||||
|
|
||||||
viewModel.actions
|
viewModel.actions
|
||||||
.sink { [weak self] action in
|
.sink { [weak self] action in
|
||||||
|
|||||||
@@ -6,8 +6,7 @@
|
|||||||
// Please see LICENSE files in the repository root for full details.
|
// Please see LICENSE files in the repository root for full details.
|
||||||
//
|
//
|
||||||
|
|
||||||
import Foundation
|
import SwiftUI
|
||||||
import UIKit
|
|
||||||
|
|
||||||
enum SettingsScreenViewModelAction: Equatable {
|
enum SettingsScreenViewModelAction: Equatable {
|
||||||
case close
|
case close
|
||||||
@@ -51,6 +50,8 @@ struct SettingsScreenViewState: BindableState {
|
|||||||
|
|
||||||
let isBugReportServiceEnabled: Bool
|
let isBugReportServiceEnabled: Bool
|
||||||
|
|
||||||
|
let navigationBarVisibility: Visibility
|
||||||
|
|
||||||
var bindings = SettingsScreenViewStateBindings()
|
var bindings = SettingsScreenViewStateBindings()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
|
|||||||
actionsSubject.eraseToAnyPublisher()
|
actionsSubject.eraseToAnyPublisher()
|
||||||
}
|
}
|
||||||
|
|
||||||
init(userSession: UserSessionProtocol, appSettings: AppSettings, isBugReportServiceEnabled: Bool) {
|
init(userSession: UserSessionProtocol, appSettings: AppSettings, isBugReportServiceEnabled: Bool, isInSecondaryWindow: Bool) {
|
||||||
self.appSettings = appSettings
|
self.appSettings = appSettings
|
||||||
|
|
||||||
super.init(initialViewState: .init(deviceID: userSession.clientProxy.deviceID,
|
super.init(initialViewState: .init(deviceID: userSession.clientProxy.deviceID,
|
||||||
@@ -29,7 +29,8 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
|
|||||||
showAccountDeactivation: userSession.clientProxy.canDeactivateAccount,
|
showAccountDeactivation: userSession.clientProxy.canDeactivateAccount,
|
||||||
showDeveloperOptions: appSettings.developerOptionsEnabled,
|
showDeveloperOptions: appSettings.developerOptionsEnabled,
|
||||||
showAnalyticsSettings: appSettings.canPromptForAnalytics,
|
showAnalyticsSettings: appSettings.canPromptForAnalytics,
|
||||||
isBugReportServiceEnabled: isBugReportServiceEnabled),
|
isBugReportServiceEnabled: isBugReportServiceEnabled,
|
||||||
|
navigationBarVisibility: isInSecondaryWindow ? .hidden : .automatic),
|
||||||
mediaProvider: userSession.mediaProvider)
|
mediaProvider: userSession.mediaProvider)
|
||||||
|
|
||||||
appSettings.$developerOptionsEnabled
|
appSettings.$developerOptionsEnabled
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ struct SettingsScreen: View {
|
|||||||
.compoundList()
|
.compoundList()
|
||||||
.navigationTitle(L10n.commonSettings)
|
.navigationTitle(L10n.commonSettings)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.toolbarVisibility(ProcessInfo.processInfo.isiOSAppOnMac ? .hidden : .automatic, for: .navigationBar)
|
.toolbarVisibility(context.viewState.navigationBarVisibility, for: .navigationBar)
|
||||||
.toolbar { toolbar }
|
.toolbar { toolbar }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,6 +278,7 @@ struct SettingsScreen_Previews: PreviewProvider, TestablePreview {
|
|||||||
deviceID: "AAAAAAAAAAA"))))
|
deviceID: "AAAAAAAAAAA"))))
|
||||||
return SettingsScreenViewModel(userSession: userSession,
|
return SettingsScreenViewModel(userSession: userSession,
|
||||||
appSettings: ServiceLocator.shared.settings,
|
appSettings: ServiceLocator.shared.settings,
|
||||||
isBugReportServiceEnabled: isBugReportServiceEnabled)
|
isBugReportServiceEnabled: isBugReportServiceEnabled,
|
||||||
|
isInSecondaryWindow: false)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ struct SettingsScreenViewModelTests {
|
|||||||
let userSession = UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userID: ""))))
|
let userSession = UserSessionMock(.init(clientProxy: ClientProxyMock(.init(userID: ""))))
|
||||||
viewModel = SettingsScreenViewModel(userSession: userSession,
|
viewModel = SettingsScreenViewModel(userSession: userSession,
|
||||||
appSettings: ServiceLocator.shared.settings,
|
appSettings: ServiceLocator.shared.settings,
|
||||||
isBugReportServiceEnabled: true)
|
isBugReportServiceEnabled: true,
|
||||||
|
isInSecondaryWindow: false)
|
||||||
context = viewModel.context
|
context = viewModel.context
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user