Refactor modal size to be a modifer parameter (#877)

Way more flexible.
This commit is contained in:
Davide 2024-11-15 23:32:54 +01:00 committed by GitHub
parent b08243949c
commit 9ca103e949
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 119 additions and 30 deletions

View File

@ -74,7 +74,9 @@ public struct AppCoordinator: View, AppCoordinatorConforming {
} }
.themeModal( .themeModal(
item: $modalRoute, item: $modalRoute,
isRoot: true, size: modalRoute?.size ?? .large,
isFixedWidth: modalRoute?.isFixedWidth ?? false,
isFixedHeight: modalRoute?.isFixedHeight ?? false,
isInteractive: modalRoute?.isInteractive ?? true, isInteractive: modalRoute?.isInteractive ?? true,
content: modalDestination 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 { var isInteractive: Bool {
switch self { switch self {
case .editProfile: case .editProfile:

View File

@ -56,7 +56,10 @@ private extension VPNProviderServerView {
} label: { } label: {
ThemeImage(.filters) ThemeImage(.filters)
} }
.themePopover(isPresented: $isPresented) { .themePopover(
isPresented: $isPresented,
size: .custom(width: 400, height: 400)
) {
filtersContent filtersContent
.modifier(FiltersViewModifier(isPresented: $isPresented)) .modifier(FiltersViewModifier(isPresented: $isPresented))
} }

View File

@ -32,7 +32,6 @@ extension Theme {
public convenience init() { public convenience init() {
self.init(dummy: ()) self.init(dummy: ())
animationCategories = [.diagnostics, .modules, .profiles, .providers] animationCategories = [.diagnostics, .modules, .profiles, .providers]
popoverSize = .init(width: 400.0, height: 400.0)
} }
} }
@ -41,10 +40,12 @@ extension Theme {
extension View { extension View {
public func themePopover<Content>( public func themePopover<Content>(
isPresented: Binding<Bool>, isPresented: Binding<Bool>,
size: ThemeModalSize = .medium,
content: @escaping () -> Content content: @escaping () -> Content
) -> some View where Content: View { ) -> some View where Content: View {
modifier(ThemeBooleanPopoverModifier( modifier(ThemeBooleanPopoverModifier(
isPresented: isPresented, isPresented: isPresented,
size: size,
popover: content popover: content
)) ))
} }
@ -70,15 +71,18 @@ struct ThemeBooleanPopoverModifier<Popover>: ViewModifier, SizeClassProviding wh
@Binding @Binding
var isPresented: Bool var isPresented: Bool
let size: ThemeModalSize
@ViewBuilder @ViewBuilder
let popover: Popover let popover: Popover
func body(content: Content) -> some View { func body(content: Content) -> some View {
let modalSize = theme.modalSize(size)
if isBigDevice { if isBigDevice {
content content
.popover(isPresented: $isPresented) { .popover(isPresented: $isPresented) {
popover popover
.frame(minWidth: theme.popoverSize?.width, minHeight: theme.popoverSize?.height) .frame(minWidth: modalSize.width, minHeight: modalSize.height)
.themeLockScreen() .themeLockScreen()
} }
} else { } else {

View File

@ -30,8 +30,6 @@ import SwiftUI
extension Theme { extension Theme {
public convenience init() { public convenience init() {
self.init(dummy: Void()) self.init(dummy: Void())
rootModalSize = CGSize(width: 750, height: 500)
secondaryModalSize = CGSize(width: 500.0, height: 200.0)
animationCategories = [.diagnostics, .profiles, .providers] animationCategories = [.diagnostics, .profiles, .providers]
} }
} }

View File

@ -32,12 +32,6 @@ public final class Theme: ObservableObject {
public internal(set) var animationCategories: Set<ThemeAnimationCategory> = Set(ThemeAnimationCategory.allCases) public internal(set) var animationCategories: Set<ThemeAnimationCategory> = 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 relevantWeight: Font.Weight = .semibold
public internal(set) var titleColor: Color = .primary 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 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 systemImageName: (ImageName) -> String = Theme.ImageName.defaultSystemName
public internal(set) var menuImageName: (MenuImageName) -> String = Theme.MenuImageName.defaultImageName public internal(set) var menuImageName: (MenuImageName) -> String = Theme.MenuImageName.defaultImageName

View File

@ -32,16 +32,30 @@ import SwiftUI
// MARK: Shortcuts // MARK: Shortcuts
public enum ThemeModalSize {
case small
case medium
case large
case custom(width: CGFloat, height: CGFloat)
}
extension View { extension View {
public func themeModal<Content>( public func themeModal<Content>(
isPresented: Binding<Bool>, isPresented: Binding<Bool>,
isRoot: Bool = false, size: ThemeModalSize = .medium,
isFixedWidth: Bool = false,
isFixedHeight: Bool = false,
isInteractive: Bool = true, isInteractive: Bool = true,
content: @escaping () -> Content content: @escaping () -> Content
) -> some View where Content: View { ) -> some View where Content: View {
modifier(ThemeBooleanModalModifier( modifier(ThemeBooleanModalModifier(
isPresented: isPresented, isPresented: isPresented,
isRoot: isRoot, size: size,
isFixedWidth: isFixedWidth,
isFixedHeight: isFixedHeight,
isInteractive: isInteractive, isInteractive: isInteractive,
modal: content modal: content
)) ))
@ -49,13 +63,17 @@ extension View {
public func themeModal<Content, T>( public func themeModal<Content, T>(
item: Binding<T?>, item: Binding<T?>,
isRoot: Bool = false, size: ThemeModalSize = .medium,
isFixedWidth: Bool = false,
isFixedHeight: Bool = false,
isInteractive: Bool = true, isInteractive: Bool = true,
content: @escaping (T) -> Content content: @escaping (T) -> Content
) -> some View where Content: View, T: Identifiable { ) -> some View where Content: View, T: Identifiable {
modifier(ThemeItemModalModifier( modifier(ThemeItemModalModifier(
item: item, item: item,
isRoot: isRoot, size: size,
isFixedWidth: isFixedWidth,
isFixedHeight: isFixedHeight,
isInteractive: isInteractive, isInteractive: isInteractive,
modal: content modal: content
)) ))
@ -200,6 +218,24 @@ extension View {
// MARK: - Presentation modifiers // 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<Modal>: ViewModifier where Modal: View { struct ThemeBooleanModalModifier<Modal>: ViewModifier where Modal: View {
@EnvironmentObject @EnvironmentObject
@ -208,25 +244,34 @@ struct ThemeBooleanModalModifier<Modal>: ViewModifier where Modal: View {
@Binding @Binding
var isPresented: Bool var isPresented: Bool
let isRoot: Bool let size: ThemeModalSize
let isFixedWidth: Bool
let isFixedHeight: Bool
let isInteractive: Bool let isInteractive: Bool
let modal: () -> Modal let modal: () -> Modal
func body(content: Content) -> some View { func body(content: Content) -> some View {
content let modalSize = theme.modalSize(size)
_ = modalSize
return content
.sheet(isPresented: $isPresented) { .sheet(isPresented: $isPresented) {
modal() 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) .interactiveDismissDisabled(!isInteractive)
.themeLockScreen() .themeLockScreen()
} }
} }
private var modalSize: CGSize? {
isRoot ? theme.rootModalSize : theme.secondaryModalSize
}
} }
struct ThemeItemModalModifier<Modal, T>: ViewModifier where Modal: View, T: Identifiable { struct ThemeItemModalModifier<Modal, T>: ViewModifier where Modal: View, T: Identifiable {
@ -237,25 +282,34 @@ struct ThemeItemModalModifier<Modal, T>: ViewModifier where Modal: View, T: Iden
@Binding @Binding
var item: T? var item: T?
let isRoot: Bool let size: ThemeModalSize
let isFixedWidth: Bool
let isFixedHeight: Bool
let isInteractive: Bool let isInteractive: Bool
let modal: (T) -> Modal let modal: (T) -> Modal
func body(content: Content) -> some View { func body(content: Content) -> some View {
content let modalSize = theme.modalSize(size)
_ = modalSize
return content
.sheet(item: $item) { .sheet(item: $item) {
modal($0) 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) .interactiveDismissDisabled(!isInteractive)
.themeLockScreen() .themeLockScreen()
} }
} }
private var modalSize: CGSize? {
isRoot ? theme.rootModalSize : theme.secondaryModalSize
}
} }
struct ThemeConfirmationModifier: ViewModifier { struct ThemeConfirmationModifier: ViewModifier {