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(
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:

View File

@ -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))
}

View File

@ -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<Content>(
isPresented: Binding<Bool>,
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<Popover>: 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 {

View File

@ -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]
}
}

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 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

View File

@ -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<Content>(
isPresented: Binding<Bool>,
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<Content, T>(
item: Binding<T?>,
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<Modal>: ViewModifier where Modal: View {
@EnvironmentObject
@ -208,25 +244,34 @@ struct ThemeBooleanModalModifier<Modal>: 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<Modal, T>: ViewModifier where Modal: View, T: Identifiable {
@ -237,25 +282,34 @@ struct ThemeItemModalModifier<Modal, T>: 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 {