mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 12:07:55 +00:00
Add more flexible background support to FullscreenDialog (#2675)
Hide the background when the main content extends underneath the bottom content.
This commit is contained in:
@@ -38,6 +38,7 @@ internal enum Asset {
|
||||
internal static let composerAttachment = ImageAsset(name: "images/composer-attachment")
|
||||
internal static let stopRecording = ImageAsset(name: "images/stop-recording")
|
||||
internal static let settingsIconWithBadge = ImageAsset(name: "images/settings-icon-with-badge")
|
||||
internal static let joinRoomBackground = ImageAsset(name: "images/join-room-background")
|
||||
internal static let launchBackground = ImageAsset(name: "images/launch-background")
|
||||
internal static let locationMarkerShape = ImageAsset(name: "images/location-marker-shape")
|
||||
internal static let mediaPause = ImageAsset(name: "images/media-pause")
|
||||
|
||||
@@ -25,6 +25,7 @@ import SwiftUI
|
||||
/// environment value, so make sure to set this to match the screen's background.
|
||||
struct FullscreenDialog<Content: View, BottomContent: View>: View {
|
||||
@Environment(\.dynamicTypeSize) private var dynamicTypeSize
|
||||
@Environment(\.backgroundStyle) private var backgroundStyle
|
||||
|
||||
/// Padding applied to the top of the content automatically. Use `UIConstants` for preset values.
|
||||
var topPadding: CGFloat = UIConstants.titleTopPaddingToNavigationBar
|
||||
@@ -33,18 +34,41 @@ struct FullscreenDialog<Content: View, BottomContent: View>: View {
|
||||
/// The spacing between the content and the buttons.
|
||||
var spacing: CGFloat = 16
|
||||
|
||||
var showBackgroundGradient = false
|
||||
/// The type of background that should be shown behind the content. This
|
||||
/// will be hidden if the main content extends behind the bottom content.
|
||||
var background: FullscreenDialogBackground?
|
||||
|
||||
/// The main content shown at the top of the layout.
|
||||
@ViewBuilder var content: () -> Content
|
||||
/// The content shown at the bottom of the layout.
|
||||
@ViewBuilder var bottomContent: () -> BottomContent
|
||||
|
||||
var body: some View {
|
||||
if dynamicTypeSize < .accessibility1 {
|
||||
standardLayout
|
||||
/// Whether or not the screen should show its background.
|
||||
@State private var showsBackground = true
|
||||
/// The background style given to the bottom content.
|
||||
private var bottomContentBackgroundStyle: AnyShapeStyle? {
|
||||
if background != nil, showsBackground {
|
||||
AnyShapeStyle(Color.clear)
|
||||
} else {
|
||||
accessibilityLayout
|
||||
backgroundStyle
|
||||
}
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
if let background, showsBackground {
|
||||
Color.clear
|
||||
.background(alignment: .bottom) {
|
||||
background.image
|
||||
}
|
||||
.ignoresSafeArea()
|
||||
}
|
||||
|
||||
if dynamicTypeSize < .accessibility1 {
|
||||
standardLayout
|
||||
} else {
|
||||
accessibilityLayout
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,28 +88,20 @@ struct FullscreenDialog<Content: View, BottomContent: View>: View {
|
||||
}
|
||||
.scrollBounceBehavior(.basedOnSize)
|
||||
.safeAreaInset(edge: .bottom) {
|
||||
if showBackgroundGradient {
|
||||
adjustedBottomContent(geometry: geometry)
|
||||
.background(Asset.Images.backgroundBottom.swiftUIImage.resizable().ignoresSafeArea())
|
||||
} else {
|
||||
adjustedBottomContent(geometry: geometry)
|
||||
.background()
|
||||
VStack(spacing: 0) {
|
||||
bottomContent()
|
||||
.readableFrame()
|
||||
.padding(.horizontal, horizontalPadding)
|
||||
.padding(.top, spacing)
|
||||
.padding(.bottom, UIConstants.actionButtonBottomPadding)
|
||||
|
||||
Spacer()
|
||||
.frame(height: UIConstants.spacerHeight(in: geometry))
|
||||
}
|
||||
.background(bottomContentBackgroundStyle ?? AnyShapeStyle(Color.clear))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func adjustedBottomContent(geometry: GeometryProxy) -> some View {
|
||||
VStack(spacing: 0) {
|
||||
bottomContent()
|
||||
.readableFrame()
|
||||
.padding(.horizontal, horizontalPadding)
|
||||
.padding(.top, spacing)
|
||||
.padding(.bottom, UIConstants.actionButtonBottomPadding)
|
||||
|
||||
Spacer()
|
||||
.frame(height: UIConstants.spacerHeight(in: geometry))
|
||||
}
|
||||
.introspect(.scrollView, on: .supportedVersions, customize: updateBackgroundVisibility)
|
||||
}
|
||||
|
||||
/// A continuously scrolling layout used for accessibility font sizes.
|
||||
@@ -116,6 +132,48 @@ struct FullscreenDialog<Content: View, BottomContent: View>: View {
|
||||
.scrollBounceBehavior(.basedOnSize)
|
||||
}
|
||||
}
|
||||
|
||||
func updateBackgroundVisibility(scrollView: UIScrollView) {
|
||||
guard dynamicTypeSize < .accessibility1 else {
|
||||
showsBackground = true
|
||||
return
|
||||
}
|
||||
let insetHeight = scrollView.adjustedContentInset.top + scrollView.adjustedContentInset.bottom
|
||||
let availableHeight = scrollView.frame.height - insetHeight
|
||||
showsBackground = scrollView.contentSize.height < availableHeight
|
||||
}
|
||||
}
|
||||
|
||||
/// The different types of background supported by the `FullscreenDialog` view.
|
||||
enum FullscreenDialogBackground {
|
||||
/// The bottom gradient from the FTUE flow.
|
||||
case gradient
|
||||
/// The subtle bloom from the room discovery and permalink flows.
|
||||
case bloom
|
||||
|
||||
private var asset: ImageAsset {
|
||||
switch self {
|
||||
case .gradient:
|
||||
Asset.Images.backgroundBottom
|
||||
case .bloom:
|
||||
Asset.Images.joinRoomBackground
|
||||
}
|
||||
}
|
||||
|
||||
private var capInsets: EdgeInsets {
|
||||
switch self {
|
||||
case .gradient:
|
||||
EdgeInsets(top: 0, leading: 0, bottom: 250, trailing: 0)
|
||||
case .bloom:
|
||||
EdgeInsets() // This one covers the entire screen.
|
||||
}
|
||||
}
|
||||
|
||||
/// The image that represents the background.
|
||||
var image: Image {
|
||||
Image(asset: asset)
|
||||
.resizable(capInsets: capInsets)
|
||||
}
|
||||
}
|
||||
|
||||
struct FullscreenDialog_Previews: PreviewProvider, TestablePreview {
|
||||
@@ -126,7 +184,26 @@ struct FullscreenDialog_Previews: PreviewProvider, TestablePreview {
|
||||
buttons
|
||||
}
|
||||
.background()
|
||||
.environment(\.backgroundStyle, AnyShapeStyle(Color.compound.bgCanvasDefault))
|
||||
.backgroundStyle(.compound.bgCanvasDefault)
|
||||
.previewDisplayName("Plain")
|
||||
|
||||
FullscreenDialog(topPadding: UIConstants.iconTopPaddingToNavigationBar, background: .gradient) {
|
||||
content
|
||||
} bottomContent: {
|
||||
buttons
|
||||
}
|
||||
.background()
|
||||
.backgroundStyle(.compound.bgCanvasDefault)
|
||||
.previewDisplayName("Gradient")
|
||||
|
||||
FullscreenDialog(topPadding: UIConstants.iconTopPaddingToNavigationBar, background: .bloom) {
|
||||
content
|
||||
} bottomContent: {
|
||||
buttons
|
||||
}
|
||||
.background()
|
||||
.backgroundStyle(.compound.bgCanvasDefault)
|
||||
.previewDisplayName("Bloom")
|
||||
}
|
||||
|
||||
private static var content: some View {
|
||||
|
||||
@@ -55,7 +55,7 @@ struct AppLockScreen: View {
|
||||
.font(.compound.bodyMDSemibold)
|
||||
}
|
||||
.background()
|
||||
.environment(\.backgroundStyle, AnyShapeStyle(Color.compound.bgCanvasDefault))
|
||||
.backgroundStyle(.compound.bgCanvasDefault)
|
||||
.disabled(context.viewState.forcedLogoutIndicator != nil)
|
||||
.overlay {
|
||||
context.viewState.forcedLogoutIndicator.map(UserIndicatorModalView.init)
|
||||
|
||||
+1
-1
@@ -26,7 +26,7 @@ struct ServerConfirmationScreen: View {
|
||||
buttons
|
||||
}
|
||||
.background()
|
||||
.environment(\.backgroundStyle, AnyShapeStyle(Color.compound.bgCanvasDefault))
|
||||
.backgroundStyle(.compound.bgCanvasDefault)
|
||||
.introspect(.window, on: .supportedVersions) { window in
|
||||
context.send(viewAction: .updateWindow(window))
|
||||
}
|
||||
|
||||
+2
-2
@@ -21,13 +21,13 @@ struct AnalyticsPromptScreen: View {
|
||||
@ObservedObject var context: AnalyticsPromptScreenViewModel.Context
|
||||
|
||||
var body: some View {
|
||||
FullscreenDialog(topPadding: UIConstants.startScreenBreakerScreenTopPadding, showBackgroundGradient: true) {
|
||||
FullscreenDialog(topPadding: UIConstants.startScreenBreakerScreenTopPadding, background: .gradient) {
|
||||
mainContent
|
||||
} bottomContent: {
|
||||
buttons
|
||||
}
|
||||
.background()
|
||||
.environment(\.backgroundStyle, AnyShapeStyle(Color.compound.bgCanvasDefault))
|
||||
.backgroundStyle(.compound.bgCanvasDefault)
|
||||
.navigationBarHidden(true)
|
||||
.navigationBarBackButtonHidden(true)
|
||||
.interactiveDismissDisabled()
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ struct IdentityConfirmationScreen: View {
|
||||
actionButtons
|
||||
}
|
||||
.background()
|
||||
.environment(\.backgroundStyle, AnyShapeStyle(Color.compound.bgCanvasDefault))
|
||||
.backgroundStyle(.compound.bgCanvasDefault)
|
||||
.navigationBarHidden(true)
|
||||
.navigationBarBackButtonHidden(true)
|
||||
.interactiveDismissDisabled()
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ struct IdentityConfirmedScreen: View {
|
||||
actionButtons
|
||||
}
|
||||
.background()
|
||||
.environment(\.backgroundStyle, AnyShapeStyle(Color.compound.bgCanvasDefault))
|
||||
.backgroundStyle(.compound.bgCanvasDefault)
|
||||
.navigationBarHidden(true)
|
||||
.navigationBarBackButtonHidden(true)
|
||||
.interactiveDismissDisabled()
|
||||
|
||||
+2
-2
@@ -21,13 +21,13 @@ struct NotificationPermissionsScreen: View {
|
||||
@ObservedObject var context: NotificationPermissionsScreenViewModel.Context
|
||||
|
||||
var body: some View {
|
||||
FullscreenDialog(topPadding: UIConstants.startScreenBreakerScreenTopPadding, showBackgroundGradient: true) {
|
||||
FullscreenDialog(topPadding: UIConstants.startScreenBreakerScreenTopPadding, background: .gradient) {
|
||||
mainContent
|
||||
} bottomContent: {
|
||||
buttons
|
||||
}
|
||||
.background()
|
||||
.environment(\.backgroundStyle, AnyShapeStyle(Color.compound.bgCanvasDefault))
|
||||
.backgroundStyle(.compound.bgCanvasDefault)
|
||||
.navigationBarHidden(true)
|
||||
.navigationBarBackButtonHidden(true)
|
||||
.interactiveDismissDisabled()
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ struct SessionVerificationScreen: View {
|
||||
actionButtons
|
||||
}
|
||||
.background()
|
||||
.environment(\.backgroundStyle, AnyShapeStyle(Color.compound.bgCanvasDefault))
|
||||
.backgroundStyle(.compound.bgCanvasDefault)
|
||||
.interactiveDismissDisabled()
|
||||
}
|
||||
|
||||
|
||||
@@ -41,7 +41,7 @@ struct PlaceholderScreen: View {
|
||||
}
|
||||
}
|
||||
.background()
|
||||
.environment(\.backgroundStyle, AnyShapeStyle(Color.compound.bgCanvasDefault))
|
||||
.backgroundStyle(.compound.bgCanvasDefault)
|
||||
.ignoresSafeArea(edges: .top) // Remain vertically centred even if there's a navigation bar.
|
||||
.ignoresSafeArea(.keyboard) // Specifically for the lock screen, but make sense everywhere.
|
||||
}
|
||||
|
||||
@@ -27,7 +27,7 @@ struct QRCodeLoginScreen: View {
|
||||
.toolbar { toolbar }
|
||||
.toolbar(.visible, for: .navigationBar)
|
||||
.background()
|
||||
.environment(\.backgroundStyle, AnyShapeStyle(Color.compound.bgSubtleSecondary))
|
||||
.backgroundStyle(.compound.bgSubtleSecondary)
|
||||
.interactiveDismissDisabled()
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -30,7 +30,7 @@ struct ResetRecoveryKeyScreen: View {
|
||||
.toolbar { toolbar }
|
||||
.toolbar(.visible, for: .navigationBar)
|
||||
.background()
|
||||
.environment(\.backgroundStyle, AnyShapeStyle(Color.compound.bgSubtleSecondary))
|
||||
.backgroundStyle(.compound.bgSubtleSecondary)
|
||||
.interactiveDismissDisabled()
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ struct SecureBackupKeyBackupScreen: View {
|
||||
.buttonStyle(.compound(.primary))
|
||||
}
|
||||
.background()
|
||||
.environment(\.backgroundStyle, AnyShapeStyle(Color.compound.bgCanvasDefault))
|
||||
.backgroundStyle(.compound.bgCanvasDefault)
|
||||
.interactiveDismissDisabled()
|
||||
.toolbar { toolbar }
|
||||
.alert(item: $context.alertInfo)
|
||||
|
||||
+1
-1
@@ -33,7 +33,7 @@ struct SecureBackupLogoutConfirmationScreen: View {
|
||||
}
|
||||
.toolbar { toolbar }
|
||||
.background()
|
||||
.environment(\.backgroundStyle, AnyShapeStyle(Color.compound.bgCanvasDefault))
|
||||
.backgroundStyle(.compound.bgCanvasDefault)
|
||||
.alert(item: $context.alertInfo)
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -39,7 +39,7 @@ struct SecureBackupRecoveryKeyScreen: View {
|
||||
.toolbar { toolbar }
|
||||
.toolbar(.visible, for: .navigationBar)
|
||||
.background()
|
||||
.environment(\.backgroundStyle, AnyShapeStyle(Color.compound.bgCanvasDefault))
|
||||
.backgroundStyle(.compound.bgCanvasDefault)
|
||||
.interactiveDismissDisabled()
|
||||
.alert(item: $context.alertInfo)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user