From 9ca103e94988375f372bffe0b660fa47c65be021 Mon Sep 17 00:00:00 2001 From: Davide Date: Fri, 15 Nov 2024 23:32:54 +0100 Subject: [PATCH] Refactor modal size to be a modifer parameter (#877) Way more flexible. --- .../AppUIMain/Views/App/AppCoordinator.swift | 34 ++++++- .../iOS/VPNProviderServerView+iOS.swift | 5 +- .../UILibrary/Theme/Platforms/Theme+iOS.swift | 8 +- .../Theme/Platforms/Theme+macOS.swift | 2 - .../Sources/UILibrary/Theme/Theme.swift | 10 +-- .../UILibrary/Theme/UI/Theme+Modifiers.swift | 90 +++++++++++++++---- 6 files changed, 119 insertions(+), 30 deletions(-) diff --git a/Passepartout/Library/Sources/AppUIMain/Views/App/AppCoordinator.swift b/Passepartout/Library/Sources/AppUIMain/Views/App/AppCoordinator.swift index 23b1ac96..e0197896 100644 --- a/Passepartout/Library/Sources/AppUIMain/Views/App/AppCoordinator.swift +++ b/Passepartout/Library/Sources/AppUIMain/Views/App/AppCoordinator.swift @@ -74,7 +74,9 @@ public struct AppCoordinator: View, AppCoordinatorConforming { } .themeModal( item: $modalRoute, - isRoot: true, + size: modalRoute?.size ?? .large, + isFixedWidth: modalRoute?.isFixedWidth ?? false, + isFixedHeight: modalRoute?.isFixedHeight ?? false, isInteractive: modalRoute?.isInteractive ?? true, content: modalDestination ) @@ -105,6 +107,36 @@ extension AppCoordinator { } } + var size: ThemeModalSize { + switch self { + case .migrateProfiles: + return .custom(width: 700, height: 400) + + default: + return .large + } + } + + var isFixedWidth: Bool { + switch self { + case .migrateProfiles: + return true + + default: + return false + } + } + + var isFixedHeight: Bool { + switch self { + case .migrateProfiles: + return true + + default: + return false + } + } + var isInteractive: Bool { switch self { case .editProfile: diff --git a/Passepartout/Library/Sources/AppUIMain/Views/Provider/iOS/VPNProviderServerView+iOS.swift b/Passepartout/Library/Sources/AppUIMain/Views/Provider/iOS/VPNProviderServerView+iOS.swift index 0f931b8f..bd790466 100644 --- a/Passepartout/Library/Sources/AppUIMain/Views/Provider/iOS/VPNProviderServerView+iOS.swift +++ b/Passepartout/Library/Sources/AppUIMain/Views/Provider/iOS/VPNProviderServerView+iOS.swift @@ -56,7 +56,10 @@ private extension VPNProviderServerView { } label: { ThemeImage(.filters) } - .themePopover(isPresented: $isPresented) { + .themePopover( + isPresented: $isPresented, + size: .custom(width: 400, height: 400) + ) { filtersContent .modifier(FiltersViewModifier(isPresented: $isPresented)) } diff --git a/Passepartout/Library/Sources/UILibrary/Theme/Platforms/Theme+iOS.swift b/Passepartout/Library/Sources/UILibrary/Theme/Platforms/Theme+iOS.swift index a9a2dd6b..ab5c9154 100644 --- a/Passepartout/Library/Sources/UILibrary/Theme/Platforms/Theme+iOS.swift +++ b/Passepartout/Library/Sources/UILibrary/Theme/Platforms/Theme+iOS.swift @@ -32,7 +32,6 @@ extension Theme { public convenience init() { self.init(dummy: ()) animationCategories = [.diagnostics, .modules, .profiles, .providers] - popoverSize = .init(width: 400.0, height: 400.0) } } @@ -41,10 +40,12 @@ extension Theme { extension View { public func themePopover( isPresented: Binding, + size: ThemeModalSize = .medium, content: @escaping () -> Content ) -> some View where Content: View { modifier(ThemeBooleanPopoverModifier( isPresented: isPresented, + size: size, popover: content )) } @@ -70,15 +71,18 @@ struct ThemeBooleanPopoverModifier: ViewModifier, SizeClassProviding wh @Binding var isPresented: Bool + let size: ThemeModalSize + @ViewBuilder let popover: Popover func body(content: Content) -> some View { + let modalSize = theme.modalSize(size) if isBigDevice { content .popover(isPresented: $isPresented) { popover - .frame(minWidth: theme.popoverSize?.width, minHeight: theme.popoverSize?.height) + .frame(minWidth: modalSize.width, minHeight: modalSize.height) .themeLockScreen() } } else { diff --git a/Passepartout/Library/Sources/UILibrary/Theme/Platforms/Theme+macOS.swift b/Passepartout/Library/Sources/UILibrary/Theme/Platforms/Theme+macOS.swift index b3d50fa6..591b81d4 100644 --- a/Passepartout/Library/Sources/UILibrary/Theme/Platforms/Theme+macOS.swift +++ b/Passepartout/Library/Sources/UILibrary/Theme/Platforms/Theme+macOS.swift @@ -30,8 +30,6 @@ import SwiftUI extension Theme { public convenience init() { self.init(dummy: Void()) - rootModalSize = CGSize(width: 750, height: 500) - secondaryModalSize = CGSize(width: 500.0, height: 200.0) animationCategories = [.diagnostics, .profiles, .providers] } } diff --git a/Passepartout/Library/Sources/UILibrary/Theme/Theme.swift b/Passepartout/Library/Sources/UILibrary/Theme/Theme.swift index 6c2be55d..fce61960 100644 --- a/Passepartout/Library/Sources/UILibrary/Theme/Theme.swift +++ b/Passepartout/Library/Sources/UILibrary/Theme/Theme.swift @@ -32,12 +32,6 @@ public final class Theme: ObservableObject { public internal(set) var animationCategories: Set = Set(ThemeAnimationCategory.allCases) - public internal(set) var rootModalSize: CGSize? - - public internal(set) var secondaryModalSize: CGSize? - - public internal(set) var popoverSize: CGSize? - public internal(set) var relevantWeight: Font.Weight = .semibold public internal(set) var titleColor: Color = .primary @@ -70,6 +64,10 @@ public final class Theme: ObservableObject { public internal(set) var logoImage = "Logo" + public internal(set) var modalSize: (ThemeModalSize) -> CGSize = { + $0.defaultSize + } + public internal(set) var systemImageName: (ImageName) -> String = Theme.ImageName.defaultSystemName public internal(set) var menuImageName: (MenuImageName) -> String = Theme.MenuImageName.defaultImageName diff --git a/Passepartout/Library/Sources/UILibrary/Theme/UI/Theme+Modifiers.swift b/Passepartout/Library/Sources/UILibrary/Theme/UI/Theme+Modifiers.swift index ed0d5089..c37e9ef8 100644 --- a/Passepartout/Library/Sources/UILibrary/Theme/UI/Theme+Modifiers.swift +++ b/Passepartout/Library/Sources/UILibrary/Theme/UI/Theme+Modifiers.swift @@ -32,16 +32,30 @@ import SwiftUI // MARK: Shortcuts +public enum ThemeModalSize { + case small + + case medium + + case large + + case custom(width: CGFloat, height: CGFloat) +} + extension View { public func themeModal( isPresented: Binding, - isRoot: Bool = false, + size: ThemeModalSize = .medium, + isFixedWidth: Bool = false, + isFixedHeight: Bool = false, isInteractive: Bool = true, content: @escaping () -> Content ) -> some View where Content: View { modifier(ThemeBooleanModalModifier( isPresented: isPresented, - isRoot: isRoot, + size: size, + isFixedWidth: isFixedWidth, + isFixedHeight: isFixedHeight, isInteractive: isInteractive, modal: content )) @@ -49,13 +63,17 @@ extension View { public func themeModal( item: Binding, - isRoot: Bool = false, + size: ThemeModalSize = .medium, + isFixedWidth: Bool = false, + isFixedHeight: Bool = false, isInteractive: Bool = true, content: @escaping (T) -> Content ) -> some View where Content: View, T: Identifiable { modifier(ThemeItemModalModifier( item: item, - isRoot: isRoot, + size: size, + isFixedWidth: isFixedWidth, + isFixedHeight: isFixedHeight, isInteractive: isInteractive, modal: content )) @@ -200,6 +218,24 @@ extension View { // MARK: - Presentation modifiers +extension ThemeModalSize { + var defaultSize: CGSize { + switch self { + case .small: + return CGSize(width: 300, height: 300) + + case .medium: + return CGSize(width: 550, height: 350) + + case .large: + return CGSize(width: 800, height: 500) + + case .custom(let width, let height): + return CGSize(width: width, height: height) + } + } +} + struct ThemeBooleanModalModifier: ViewModifier where Modal: View { @EnvironmentObject @@ -208,25 +244,34 @@ struct ThemeBooleanModalModifier: ViewModifier where Modal: View { @Binding var isPresented: Bool - let isRoot: Bool + let size: ThemeModalSize + + let isFixedWidth: Bool + + let isFixedHeight: Bool let isInteractive: Bool let modal: () -> Modal func body(content: Content) -> some View { - content + let modalSize = theme.modalSize(size) + _ = modalSize + return content .sheet(isPresented: $isPresented) { modal() - .frame(minWidth: modalSize?.width, minHeight: modalSize?.height) +#if os(macOS) + .frame( + minWidth: modalSize.width, + maxWidth: isFixedWidth ? modalSize.width : nil, + minHeight: modalSize.height, + maxHeight: isFixedHeight ? modalSize.height : nil + ) +#endif .interactiveDismissDisabled(!isInteractive) .themeLockScreen() } } - - private var modalSize: CGSize? { - isRoot ? theme.rootModalSize : theme.secondaryModalSize - } } struct ThemeItemModalModifier: ViewModifier where Modal: View, T: Identifiable { @@ -237,25 +282,34 @@ struct ThemeItemModalModifier: ViewModifier where Modal: View, T: Iden @Binding var item: T? - let isRoot: Bool + let size: ThemeModalSize + + let isFixedWidth: Bool + + let isFixedHeight: Bool let isInteractive: Bool let modal: (T) -> Modal func body(content: Content) -> some View { - content + let modalSize = theme.modalSize(size) + _ = modalSize + return content .sheet(item: $item) { modal($0) - .frame(minWidth: modalSize?.width, minHeight: modalSize?.height) +#if os(macOS) + .frame( + minWidth: modalSize.width, + maxWidth: isFixedWidth ? modalSize.width : nil, + minHeight: modalSize.height, + maxHeight: isFixedHeight ? modalSize.height : nil + ) +#endif .interactiveDismissDisabled(!isInteractive) .themeLockScreen() } } - - private var modalSize: CGSize? { - isRoot ? theme.rootModalSize : theme.secondaryModalSize - } } struct ThemeConfirmationModifier: ViewModifier {