Refactor modal size to be a modifer parameter (#877)
Way more flexible.
This commit is contained in:
parent
b08243949c
commit
9ca103e949
|
@ -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:
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in New Issue