diff --git a/DesignKit/Sources/Buttons/ElementActionButtonStyle.swift b/DesignKit/Sources/Buttons/ElementActionButtonStyle.swift index 7ea139886a..7ae86d2a12 100644 --- a/DesignKit/Sources/Buttons/ElementActionButtonStyle.swift +++ b/DesignKit/Sources/Buttons/ElementActionButtonStyle.swift @@ -56,13 +56,14 @@ public struct ElementActionButtonStyle: ButtonStyle { .frame(maxWidth: maxWidth) .foregroundColor(fontColor) .font(.element.bodyBold) - .background(color.opacity(backgroundOpacity(when: configuration.isPressed))) - .cornerRadius(cornerRadius) + .background(Capsule() + .fill(color) + .opacity(backgroundOpacity(when: configuration.isPressed))) } private func backgroundOpacity(when isPressed: Bool) -> CGFloat { - guard isEnabled else { return 0.3 } - return isPressed ? 0.6 : 1.0 + guard isEnabled else { return colorScheme == .dark ? 0.2 : 0.1 } + return isPressed ? 0.3 : 1.0 } } diff --git a/DesignKit/Sources/Buttons/ElementGhostButtonStyle.swift b/DesignKit/Sources/Buttons/ElementGhostButtonStyle.swift index 9c684e5b3c..a3d7f1d5a9 100644 --- a/DesignKit/Sources/Buttons/ElementGhostButtonStyle.swift +++ b/DesignKit/Sources/Buttons/ElementGhostButtonStyle.swift @@ -22,7 +22,7 @@ public extension ButtonStyle where Self == ElementGhostButtonStyle { /// - Parameter size: The control size to use. Defaults to `regular`. /// - Parameter color: The color of the label and border. Defaults to the accent color. static func elementGhost(_ size: ElementControlSize = .regular, - color: Color = .element.accent) -> ElementGhostButtonStyle { + color: Color = .element.primaryContent) -> ElementGhostButtonStyle { ElementGhostButtonStyle(size: size, color: color) } } @@ -36,7 +36,7 @@ public struct ElementGhostButtonStyle: ButtonStyle { private var verticalPadding: CGFloat { size == .xLarge ? 12 : 4 } private var maxWidth: CGFloat? { size == .xLarge ? .infinity : nil } - public init(size: ElementControlSize = .regular, color: Color = .element.accent) { + public init(size: ElementControlSize = .regular, color: Color = .element.primaryContent) { self.size = size self.color = color } @@ -47,19 +47,19 @@ public struct ElementGhostButtonStyle: ButtonStyle { .padding(.vertical, verticalPadding) .frame(maxWidth: maxWidth) .foregroundColor(color) - .font(.element.body) + .font(.element.footnoteBold) .background(border) .opacity(opacity(when: configuration.isPressed)) } private var border: some View { - RoundedRectangle(cornerRadius: 8) + Capsule() .strokeBorder() .foregroundColor(color) } private func opacity(when isPressed: Bool) -> CGFloat { - guard isEnabled else { return 0.6 } + guard isEnabled else { return 0.5 } return isPressed ? 0.6 : 1.0 } } diff --git a/DesignKit/Sources/Colors/ElementColors.swift b/DesignKit/Sources/Colors/ElementColors.swift index 601ed0b227..b2a7ed873c 100644 --- a/DesignKit/Sources/Colors/ElementColors.swift +++ b/DesignKit/Sources/Colors/ElementColors.swift @@ -38,6 +38,8 @@ public struct ElementColors { public var quinaryContent: Color { compound.quinaryContent } public var system: Color { compound.system } public var background: Color { compound.background } + // Should be the accent color + public var brand: Color { compound.accent } public var contentAndAvatars: [Color] { compound.contentAndAvatars } diff --git a/DesignKit/Sources/TextFields/ElementTextFieldStyle.swift b/DesignKit/Sources/TextFields/ElementTextFieldStyle.swift index ca2844045f..d40d76af6f 100644 --- a/DesignKit/Sources/TextFields/ElementTextFieldStyle.swift +++ b/DesignKit/Sources/TextFields/ElementTextFieldStyle.swift @@ -42,16 +42,16 @@ public struct ElementTextFieldStyle: TextFieldStyle { /// The color of the text field's border. private var borderColor: Color { guard !isError else { return .element.alert } - return isFocused ? .element.accent : .element.quinaryContent + return isFocused ? .element.tertiaryContent : .element.quinaryContent } /// The width of the text field's border. private var borderWidth: CGFloat { - isFocused || isError ? 2.0 : 1.5 + isFocused || isError ? 1.0 : 0 } private var accentColor: Color { - isError ? .element.alert : .element.accent + isError ? .element.alert : .element.brand } /// The color of the text inside the text field. @@ -68,7 +68,7 @@ public struct ElementTextFieldStyle: TextFieldStyle { if !isEnabled, colorScheme == .dark { return .element.quinaryContent } - return .element.background + return .element.system } /// The color of the placeholder text inside the text field. @@ -78,8 +78,7 @@ public struct ElementTextFieldStyle: TextFieldStyle { /// The color of the label above the text field. private var labelColor: Color { - guard colorScheme == .light else { return .element.tertiaryContent } - return isEnabled ? .element.primaryContent : .element.quaternaryContent + isEnabled ? .element.primaryContent : .element.quaternaryContent } /// The color of the footer label below the text field. @@ -99,13 +98,14 @@ public struct ElementTextFieldStyle: TextFieldStyle { } public func _body(configuration: TextField<_Label>) -> some View { - let rectangle = RoundedRectangle(cornerRadius: 8.0) + let rectangle = RoundedRectangle(cornerRadius: 14.0) return VStack(alignment: .leading, spacing: 8) { if let labelText { Text(labelText) - .font(.element.subheadline) + .font(.element.footnote) .foregroundColor(labelColor) + .padding(.horizontal, 16) } configuration @@ -113,8 +113,8 @@ public struct ElementTextFieldStyle: TextFieldStyle { .font(.element.callout) .foregroundColor(textColor) .accentColor(accentColor) - .padding(.vertical, 12.0) - .padding(.horizontal, 8.0) + .padding(.leading, 16.0) + .padding([.vertical, .trailing], 11.0) .background { ZStack { backgroundColor @@ -132,8 +132,9 @@ public struct ElementTextFieldStyle: TextFieldStyle { if let footerText { Text(footerText) - .font(.element.footnote) + .font(.element.caption1) .foregroundColor(footerColor) + .padding(.horizontal, 16) } } } diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 88688c60f4..be9bb90500 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -13,6 +13,7 @@ 02D8DF8EB7537EB4E9019DDB /* EventBasedTimelineItemProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 218AB05B4E3889731959C5F1 /* EventBasedTimelineItemProtocol.swift */; }; 03D684A3AE85A23B3DA3B43F /* Image.swift in Sources */ = {isa = PBXBuildFile; fileRef = E26747B3154A5DBC3A7E24A5 /* Image.swift */; }; 04A16B45228F7678A027C079 /* RoomHeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 422724361B6555364C43281E /* RoomHeaderView.swift */; }; + 052BE25E8C466D3D60558DA3 /* SettingsActionButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = F3F8435052B2488947B35942 /* SettingsActionButton.swift */; }; 059173B3C77056C406906B6D /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = D4DA544B2520BFA65D6DB4BB /* target.yml */; }; 05EC896A4B9AF4A56670C0BB /* SessionVerificationUITests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6D4777F0142E330A75C46FE4 /* SessionVerificationUITests.swift */; }; 066A1E9B94723EE9F3038044 /* Strings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47EBB5D698CE9A25BB553A2D /* Strings.swift */; }; @@ -147,6 +148,7 @@ 447E8580A0A2569E32529E17 /* MockRoomTimelineProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8D6094DEAAEB388E1AE118C6 /* MockRoomTimelineProvider.swift */; }; 44AE0752E001D1D10605CD88 /* Swipe.swift in Sources */ = {isa = PBXBuildFile; fileRef = A9FDA5344F7C4C6E4E863E13 /* Swipe.swift */; }; 46562110EE202E580A5FFD9C /* RoomScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 93CF7B19FFCF8EFBE0A8696A /* RoomScreenViewModelTests.swift */; }; + 48FE5F0E3921146DBF4E61E7 /* OnboardingBackgroundView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D06A27D9C70E0DCC1E199163 /* OnboardingBackgroundView.swift */; }; 492274DA6691EE985C2FCCAA /* GZIP in Frameworks */ = {isa = PBXBuildFile; productRef = 1BCD21310B997A6837B854D6 /* GZIP */; }; 49E9B99CB6A275C7744351F0 /* LoginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F2D58333B377888012740101 /* LoginViewModel.swift */; }; 49F2E7DD8CAACE09CEECE3E6 /* SeparatorRoomTimelineView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6390A6DC140CA3D6865A66FF /* SeparatorRoomTimelineView.swift */; }; @@ -279,6 +281,7 @@ 878070573C7BF19E735707B4 /* RoomTimelineItemProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5DE8D25D6A91030175D52A20 /* RoomTimelineItemProperties.swift */; }; 87BD4F95F9D603C309837378 /* UserNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = DA6B262D7584C65BC5B79A0E /* UserNotification.swift */; }; 8810A2A30A68252EBB54EE05 /* HomeScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 71BC7CA1BC1041E93077BBA1 /* HomeScreenModels.swift */; }; + 88DD7573C7D1D46C0851EF8A /* FormSectionHeaderStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 733FEDC1AE17806318A4BE56 /* FormSectionHeaderStyle.swift */; }; 890F0D453FE388756479AC97 /* AnalyticsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = C687844F60BFF532D49A994C /* AnalyticsTests.swift */; }; 8AB8ED1051216546CB35FA0E /* UserSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E5E9C044BEB7C70B1378E91 /* UserSession.swift */; }; 8B807DC963D1D4155A241BCC /* UserSessionFlowCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3F9E67AAB66638C69626866C /* UserSessionFlowCoordinator.swift */; }; @@ -329,6 +332,7 @@ A021827B528F1EDC9101CA58 /* AppCoordinatorProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = FBC776F301D374A3298C69DA /* AppCoordinatorProtocol.swift */; }; A0A0D2A9564BDA3FDE2E360F /* FormattedBodyText.swift in Sources */ = {isa = PBXBuildFile; fileRef = F73FF1A33198F5FAE9D34B1F /* FormattedBodyText.swift */; }; A23B8B27A1436A1049EEF68E /* InfoPlistReader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6A580295A56B55A856CC4084 /* InfoPlistReader.swift */; }; + A2DDFA5033B535AB2BA51F5C /* SettingsDefaultRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = BFB3D0B0E16FEC93175ABC2D /* SettingsDefaultRow.swift */; }; A32517FB1CA0BBCE2BC75249 /* BugReportCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = AD6C07DA7D3FF193F7419F55 /* BugReportCoordinator.swift */; }; A33784831AD880A670CAA9F9 /* FileManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04DF593C3F7AF4B2FBAEB05D /* FileManager.swift */; }; A371629728E597C5FCA3C2B2 /* Analytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = 73FC861755C6388F62B9280A /* Analytics.swift */; }; @@ -789,6 +793,7 @@ 71D52BAA5BADB06E5E8C295D /* Assets.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Assets.swift; sourceTree = ""; }; 72D03D36422177EF01905D20 /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/Localizable.strings; sourceTree = ""; }; 72F37B5DA798C9AE436F2C2C /* AttributedStringBuilderProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AttributedStringBuilderProtocol.swift; sourceTree = ""; }; + 733FEDC1AE17806318A4BE56 /* FormSectionHeaderStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormSectionHeaderStyle.swift; sourceTree = ""; }; 73FC861755C6388F62B9280A /* Analytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Analytics.swift; sourceTree = ""; }; 748AE77AC3B0A01223033B87 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = ""; }; 755DC0F79EF8181CC175A193 /* MessageTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageTimelineItem.swift; sourceTree = ""; }; @@ -952,6 +957,7 @@ BEA38B9851CFCC4D67F5587D /* EmojiPickerScreenCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreenCoordinator.swift; sourceTree = ""; }; BEBA759D1347CFFB3D84ED1F /* UserSessionStoreProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionStoreProtocol.swift; sourceTree = ""; }; BEE6BF9BA63FF42F8AF6EEEA /* sr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = sr; path = sr.lproj/Localizable.stringsdict; sourceTree = ""; }; + BFB3D0B0E16FEC93175ABC2D /* SettingsDefaultRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsDefaultRow.swift; sourceTree = ""; }; C024C151639C4E1B91FCC68B /* ElementXAttributeScope.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementXAttributeScope.swift; sourceTree = ""; }; C06FCD42EEFEFC220F14EAC5 /* SessionVerificationStateMachine.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SessionVerificationStateMachine.swift; sourceTree = ""; }; C070FD43DC6BF4E50217965A /* LocalizationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizationTests.swift; sourceTree = ""; }; @@ -992,6 +998,7 @@ CDE3F3911FF7CC639BDE5844 /* nl */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = nl; path = nl.lproj/Localizable.strings; sourceTree = ""; }; CED34C87277BA3CCC6B6EC7A /* th */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = th; path = th.lproj/Localizable.strings; sourceTree = ""; }; CF48AF076424DBC1615C74AD /* AuthenticationServiceProxy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationServiceProxy.swift; sourceTree = ""; }; + D06A27D9C70E0DCC1E199163 /* OnboardingBackgroundView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OnboardingBackgroundView.swift; sourceTree = ""; }; D06DFD894157A4C93A02D8B5 /* lo */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = lo; path = lo.lproj/Localizable.strings; sourceTree = ""; }; D09A267106B9585D3D0CFC0D /* ClientError.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClientError.swift; sourceTree = ""; }; D0A45283CF1DB96E583BECA6 /* ImageRoomTimelineView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImageRoomTimelineView.swift; sourceTree = ""; }; @@ -1073,6 +1080,7 @@ F3648F2FADEF2672D6A0D489 /* FileCacheTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileCacheTests.swift; sourceTree = ""; }; F36C0A6D59717193F49EA986 /* UserSessionTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserSessionTests.swift; sourceTree = ""; }; F3EAE3E9D5EF4A6D5D9C6CFD /* EmojiPickerScreenViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmojiPickerScreenViewModel.swift; sourceTree = ""; }; + F3F8435052B2488947B35942 /* SettingsActionButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsActionButton.swift; sourceTree = ""; }; F506C6ADB1E1DA6638078E11 /* UITests.xctest */ = {isa = PBXFileReference; includeInIndex = 0; lastKnownFileType = wrapper.cfbundle; path = UITests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; F5C4AF6E3885730CD560311C /* ScreenshotDetector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ScreenshotDetector.swift; sourceTree = ""; }; F73FF1A33198F5FAE9D34B1F /* FormattedBodyText.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FormattedBodyText.swift; sourceTree = ""; }; @@ -1320,10 +1328,13 @@ isa = PBXGroup; children = ( 0960A7F5C1B0B6679BDF26F9 /* ElementToggleStyle.swift */, + 733FEDC1AE17806318A4BE56 /* FormSectionHeaderStyle.swift */, B590BD4507D4F0A377FDE01A /* LoadableAvatarImage.swift */, C352359663A0E52BA20761EE /* LoadableImage.swift */, C705E605EF57C19DBE86FFA1 /* PlaceholderAvatarImage.swift */, 839E2C35DF3F9C7B54C3CE49 /* RoundedCornerShape.swift */, + F3F8435052B2488947B35942 /* SettingsActionButton.swift */, + BFB3D0B0E16FEC93175ABC2D /* SettingsDefaultRow.swift */, ); path = Views; sourceTree = ""; @@ -1839,6 +1850,7 @@ 7B14834450AE76EEFDDBCBB8 /* View */ = { isa = PBXGroup; children = ( + D06A27D9C70E0DCC1E199163 /* OnboardingBackgroundView.swift */, 09199C43BAB209C0BD89A836 /* OnboardingPageIndicator.swift */, 1DF2717AB91060260E5F4781 /* OnboardingPageView.swift */, AB8E75B9CB6C78BE8D09B1AF /* OnboardingScreen.swift */, @@ -3078,6 +3090,7 @@ 3274219F7F26A5C6C2C55630 /* FilePreviewViewModelProtocol.swift in Sources */, D33AC79A50DFC26D2498DD28 /* FileRoomTimelineItem.swift in Sources */, 1F04C63D4FA95948E3F52147 /* FileRoomTimelineView.swift in Sources */, + 88DD7573C7D1D46C0851EF8A /* FormSectionHeaderStyle.swift in Sources */, A0A0D2A9564BDA3FDE2E360F /* FormattedBodyText.swift in Sources */, 85AFBB433AD56704A880F8A0 /* FramePreferenceKey.swift in Sources */, 964B9D2EC38C488C360CE0C9 /* HomeScreen.swift in Sources */, @@ -3147,6 +3160,7 @@ 5B8B51CEC4717AF487794685 /* NotificationServiceProxy.swift in Sources */, F61AFA8BF2E739FBC30472F5 /* NotificationServiceProxyProtocol.swift in Sources */, 563A05B43207D00A6B698211 /* OIDCService.swift in Sources */, + 48FE5F0E3921146DBF4E61E7 /* OnboardingBackgroundView.swift in Sources */, 2CB6787E25B11711518E9588 /* OnboardingCoordinator.swift in Sources */, 5D7960B32C350FA93F48D02B /* OnboardingModels.swift in Sources */, 14132418A748C988B85B025E /* OnboardingPageIndicator.swift in Sources */, @@ -3225,6 +3239,8 @@ 9E8AE387FD03E4F1C1B8815A /* SessionVerificationStateMachine.swift in Sources */, A8EC7C9D886244DAE9433E37 /* SessionVerificationViewModel.swift in Sources */, D6417E5A799C3C7F14F9EC0A /* SessionVerificationViewModelProtocol.swift in Sources */, + 052BE25E8C466D3D60558DA3 /* SettingsActionButton.swift in Sources */, + A2DDFA5033B535AB2BA51F5C /* SettingsDefaultRow.swift in Sources */, 7FED310F6AB7A70CBFB7C8A3 /* SettingsScreen.swift in Sources */, 233221E32DA045018D3D3050 /* SettingsScreenCoordinator.swift in Sources */, DBAA69CC2CE4D44BC8E20105 /* SettingsScreenModels.swift in Sources */, diff --git a/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Screen Page 1.imageset/Contents.json b/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding App Logo.imageset/Contents.json similarity index 75% rename from ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Screen Page 1.imageset/Contents.json rename to ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding App Logo.imageset/Contents.json index 79f65fdf96..84bb800916 100644 --- a/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Screen Page 1.imageset/Contents.json +++ b/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding App Logo.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "OnboardingSplashScreenPage1.pdf", + "filename" : "OnboardingSplashAppLogo.pdf", "idiom" : "universal" }, { @@ -11,7 +11,7 @@ "value" : "dark" } ], - "filename" : "OnboardingSplashScreenPage1-Dark.pdf", + "filename" : "OnboardingSplashAppLogo-Dark.pdf", "idiom" : "universal" } ], diff --git a/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding App Logo.imageset/OnboardingSplashAppLogo-Dark.pdf b/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding App Logo.imageset/OnboardingSplashAppLogo-Dark.pdf new file mode 100644 index 0000000000..6d3d4cc7bb Binary files /dev/null and b/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding App Logo.imageset/OnboardingSplashAppLogo-Dark.pdf differ diff --git a/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding App Logo.imageset/OnboardingSplashAppLogo.pdf b/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding App Logo.imageset/OnboardingSplashAppLogo.pdf new file mode 100644 index 0000000000..4054363ae4 Binary files /dev/null and b/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding App Logo.imageset/OnboardingSplashAppLogo.pdf differ diff --git a/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Screen Page 3.imageset/Contents.json b/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Background Part 1.imageset/Contents.json similarity index 75% rename from ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Screen Page 3.imageset/Contents.json rename to ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Background Part 1.imageset/Contents.json index 93768124d4..dc8e14ebfb 100644 --- a/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Screen Page 3.imageset/Contents.json +++ b/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Background Part 1.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "OnboardingSplashScreenPage3.pdf", + "filename" : "OnboardingBackgroundPart1.pdf", "idiom" : "universal" }, { @@ -11,7 +11,7 @@ "value" : "dark" } ], - "filename" : "OnboardingSplashScreenPage3-Dark.pdf", + "filename" : "OnboardingBackgroundPart1-Dark.pdf", "idiom" : "universal" } ], diff --git a/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Background Part 1.imageset/OnboardingBackgroundPart1-Dark.pdf b/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Background Part 1.imageset/OnboardingBackgroundPart1-Dark.pdf new file mode 100644 index 0000000000..dc39990477 Binary files /dev/null and b/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Background Part 1.imageset/OnboardingBackgroundPart1-Dark.pdf differ diff --git a/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Background Part 1.imageset/OnboardingBackgroundPart1.pdf b/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Background Part 1.imageset/OnboardingBackgroundPart1.pdf new file mode 100644 index 0000000000..bcfc0ea2de Binary files /dev/null and b/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Background Part 1.imageset/OnboardingBackgroundPart1.pdf differ diff --git a/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Screen Page 4.imageset/Contents.json b/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Background Part 2.imageset/Contents.json similarity index 75% rename from ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Screen Page 4.imageset/Contents.json rename to ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Background Part 2.imageset/Contents.json index a405f8767b..2d2b304bda 100644 --- a/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Screen Page 4.imageset/Contents.json +++ b/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Background Part 2.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "OnboardingSplashScreenPage4.pdf", + "filename" : "OnboardingBackgroundPart2.pdf", "idiom" : "universal" }, { @@ -11,7 +11,7 @@ "value" : "dark" } ], - "filename" : "OnboardingSplashScreenPage4-Dark.pdf", + "filename" : "OnboardingBackgroundPart2-Dark.pdf", "idiom" : "universal" } ], diff --git a/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Background Part 2.imageset/OnboardingBackgroundPart2-Dark.pdf b/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Background Part 2.imageset/OnboardingBackgroundPart2-Dark.pdf new file mode 100644 index 0000000000..e9455dfa52 Binary files /dev/null and b/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Background Part 2.imageset/OnboardingBackgroundPart2-Dark.pdf differ diff --git a/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Background Part 2.imageset/OnboardingBackgroundPart2.pdf b/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Background Part 2.imageset/OnboardingBackgroundPart2.pdf new file mode 100644 index 0000000000..70d11b846b Binary files /dev/null and b/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Background Part 2.imageset/OnboardingBackgroundPart2.pdf differ diff --git a/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Screen Page 2.imageset/Contents.json b/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Background Part 3.imageset/Contents.json similarity index 75% rename from ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Screen Page 2.imageset/Contents.json rename to ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Background Part 3.imageset/Contents.json index ac814a988e..78b4bc24b2 100644 --- a/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Screen Page 2.imageset/Contents.json +++ b/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Background Part 3.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "OnboardingSplashScreenPage2.pdf", + "filename" : "OnboardingBackgroundPart3.pdf", "idiom" : "universal" }, { @@ -11,7 +11,7 @@ "value" : "dark" } ], - "filename" : "OnboardingSplashScreenPage2-Dark.pdf", + "filename" : "OnboardingBackgroundPart3-Dark.pdf", "idiom" : "universal" } ], diff --git a/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Background Part 3.imageset/OnboardingBackgroundPart3-Dark.pdf b/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Background Part 3.imageset/OnboardingBackgroundPart3-Dark.pdf new file mode 100644 index 0000000000..dc39990477 Binary files /dev/null and b/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Background Part 3.imageset/OnboardingBackgroundPart3-Dark.pdf differ diff --git a/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Background Part 3.imageset/OnboardingBackgroundPart3.pdf b/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Background Part 3.imageset/OnboardingBackgroundPart3.pdf new file mode 100644 index 0000000000..24952da26d Binary files /dev/null and b/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Background Part 3.imageset/OnboardingBackgroundPart3.pdf differ diff --git a/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Screen Page 1.imageset/OnboardingSplashScreenPage1-Dark.pdf b/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Screen Page 1.imageset/OnboardingSplashScreenPage1-Dark.pdf deleted file mode 100644 index be00814999..0000000000 Binary files a/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Screen Page 1.imageset/OnboardingSplashScreenPage1-Dark.pdf and /dev/null differ diff --git a/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Screen Page 1.imageset/OnboardingSplashScreenPage1.pdf b/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Screen Page 1.imageset/OnboardingSplashScreenPage1.pdf deleted file mode 100644 index 2afeae2947..0000000000 Binary files a/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Screen Page 1.imageset/OnboardingSplashScreenPage1.pdf and /dev/null differ diff --git a/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Screen Page 2.imageset/OnboardingSplashScreenPage2-Dark.pdf b/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Screen Page 2.imageset/OnboardingSplashScreenPage2-Dark.pdf deleted file mode 100644 index 9c1d4b2603..0000000000 Binary files a/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Screen Page 2.imageset/OnboardingSplashScreenPage2-Dark.pdf and /dev/null differ diff --git a/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Screen Page 2.imageset/OnboardingSplashScreenPage2.pdf b/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Screen Page 2.imageset/OnboardingSplashScreenPage2.pdf deleted file mode 100644 index 283dbcc94a..0000000000 Binary files a/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Screen Page 2.imageset/OnboardingSplashScreenPage2.pdf and /dev/null differ diff --git a/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Screen Page 3.imageset/OnboardingSplashScreenPage3-Dark.pdf b/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Screen Page 3.imageset/OnboardingSplashScreenPage3-Dark.pdf deleted file mode 100644 index ef2dcdbc8d..0000000000 Binary files a/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Screen Page 3.imageset/OnboardingSplashScreenPage3-Dark.pdf and /dev/null differ diff --git a/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Screen Page 3.imageset/OnboardingSplashScreenPage3.pdf b/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Screen Page 3.imageset/OnboardingSplashScreenPage3.pdf deleted file mode 100644 index 7c1c6dc767..0000000000 Binary files a/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Screen Page 3.imageset/OnboardingSplashScreenPage3.pdf and /dev/null differ diff --git a/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Screen Page 4.imageset/OnboardingSplashScreenPage4-Dark.pdf b/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Screen Page 4.imageset/OnboardingSplashScreenPage4-Dark.pdf deleted file mode 100644 index 05a60bf8e0..0000000000 Binary files a/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Screen Page 4.imageset/OnboardingSplashScreenPage4-Dark.pdf and /dev/null differ diff --git a/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Screen Page 4.imageset/OnboardingSplashScreenPage4.pdf b/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Screen Page 4.imageset/OnboardingSplashScreenPage4.pdf deleted file mode 100644 index aac8e76c03..0000000000 Binary files a/ElementX/Resources/Assets.xcassets/Images/Authentication/Onboarding Screen Page 4.imageset/OnboardingSplashScreenPage4.pdf and /dev/null differ diff --git a/ElementX/Resources/Assets.xcassets/Images/Authentication/Server Selection Icon.imageset/authentication_server_selection_icon.svg b/ElementX/Resources/Assets.xcassets/Images/Authentication/Server Selection Icon.imageset/authentication_server_selection_icon.svg index 17b23458ed..51b9699481 100644 --- a/ElementX/Resources/Assets.xcassets/Images/Authentication/Server Selection Icon.imageset/authentication_server_selection_icon.svg +++ b/ElementX/Resources/Assets.xcassets/Images/Authentication/Server Selection Icon.imageset/authentication_server_selection_icon.svg @@ -1,5 +1,3 @@ - - - - + + diff --git a/ElementX/Resources/Localizations/en.lproj/Untranslated.strings b/ElementX/Resources/Localizations/en.lproj/Untranslated.strings index 40523ae51a..2bb6fb59da 100644 --- a/ElementX/Resources/Localizations/en.lproj/Untranslated.strings +++ b/ElementX/Resources/Localizations/en.lproj/Untranslated.strings @@ -41,7 +41,7 @@ "session_verification_banner_title" = "Help keep your messages secure"; "session_verification_banner_message" = "Looks like you’re using a new device. Verify its you."; -"server_selection_server_footer" = "You can only connect to a server that has already been set up"; +"server_selection_server_footer" = "You can only connect to an existing server"; "login_mobile_device" = "Mobile"; "login_tablet_device" = "Tablet"; @@ -56,3 +56,17 @@ // Room Details "room_details_title" = "Info"; "room_details_about_section_title" = "About"; +"room_details_copy_link" = "Copy Link"; + +// Onboarding +"ftue_auth_carousel_welcome_title" = "Be in your Element"; +"ftue_auth_carousel_welcome_body" = "Welcome to the %@ Beta. Supercharged, for speed and simplicity."; + +// Sign in +"ftue_auth_sign_in_enter_details" = "Enter your details"; + +// Bug report +"bug_report_screen_title" = "Report a bug"; +"bug_report_screen_description" = "Please describe the bug. What did you do? What did you expect to happen? What actually happened. Please go into as much detail as you can."; +"bug_report_screen_include_logs" = "Send logs to help"; +"bug_report_screen_logs_description" = "To check things work as intended, logs will be sent with your message. These will be private. To just send your message, turn off this setting."; diff --git a/ElementX/Sources/Generated/Assets.swift b/ElementX/Sources/Generated/Assets.swift index f9e231a4e6..c45a0f33da 100644 --- a/ElementX/Sources/Generated/Assets.swift +++ b/ElementX/Sources/Generated/Assets.swift @@ -25,10 +25,10 @@ internal enum Asset { internal enum Images { internal static let analyticsCheckmark = ImageAsset(name: "Images/AnalyticsCheckmark") internal static let analyticsLogo = ImageAsset(name: "Images/AnalyticsLogo") - internal static let onboardingScreenPage1 = ImageAsset(name: "Images/Onboarding Screen Page 1") - internal static let onboardingScreenPage2 = ImageAsset(name: "Images/Onboarding Screen Page 2") - internal static let onboardingScreenPage3 = ImageAsset(name: "Images/Onboarding Screen Page 3") - internal static let onboardingScreenPage4 = ImageAsset(name: "Images/Onboarding Screen Page 4") + internal static let onboardingAppLogo = ImageAsset(name: "Images/Onboarding App Logo") + internal static let onboardingBackgroundPart1 = ImageAsset(name: "Images/Onboarding Background Part 1") + internal static let onboardingBackgroundPart2 = ImageAsset(name: "Images/Onboarding Background Part 2") + internal static let onboardingBackgroundPart3 = ImageAsset(name: "Images/Onboarding Background Part 3") internal static let serverSelectionIcon = ImageAsset(name: "Images/Server Selection Icon") internal static let encryptionNormal = ImageAsset(name: "Images/encryption_normal") internal static let encryptionTrusted = ImageAsset(name: "Images/encryption_trusted") diff --git a/ElementX/Sources/Generated/Strings+Untranslated.swift b/ElementX/Sources/Generated/Strings+Untranslated.swift index 0d0c604d22..c2860ed6c0 100644 --- a/ElementX/Sources/Generated/Strings+Untranslated.swift +++ b/ElementX/Sources/Generated/Strings+Untranslated.swift @@ -16,10 +16,26 @@ extension ElementL10n { public static let actionConfirm = ElementL10n.tr("Untranslated", "action_confirm") /// Match public static let actionMatch = ElementL10n.tr("Untranslated", "action_match") + /// Please describe the bug. What did you do? What did you expect to happen? What actually happened. Please go into as much detail as you can. + public static let bugReportScreenDescription = ElementL10n.tr("Untranslated", "bug_report_screen_description") + /// Send logs to help + public static let bugReportScreenIncludeLogs = ElementL10n.tr("Untranslated", "bug_report_screen_include_logs") + /// To check things work as intended, logs will be sent with your message. These will be private. To just send your message, turn off this setting. + public static let bugReportScreenLogsDescription = ElementL10n.tr("Untranslated", "bug_report_screen_logs_description") + /// Report a bug + public static let bugReportScreenTitle = ElementL10n.tr("Untranslated", "bug_report_screen_title") /// %@ iOS public static func defaultSessionDisplayName(_ p1: Any) -> String { return ElementL10n.tr("Untranslated", "default_session_display_name", String(describing: p1)) } + /// Welcome to the %@ Beta. Supercharged, for speed and simplicity. + public static func ftueAuthCarouselWelcomeBody(_ p1: Any) -> String { + return ElementL10n.tr("Untranslated", "ftue_auth_carousel_welcome_body", String(describing: p1)) + } + /// Be in your Element + public static let ftueAuthCarouselWelcomeTitle = ElementL10n.tr("Untranslated", "ftue_auth_carousel_welcome_title") + /// Enter your details + public static let ftueAuthSignInEnterDetails = ElementL10n.tr("Untranslated", "ftue_auth_sign_in_enter_details") /// Mobile public static let loginMobileDevice = ElementL10n.tr("Untranslated", "login_mobile_device") /// Tablet @@ -70,6 +86,8 @@ extension ElementL10n { public static let notification = ElementL10n.tr("Untranslated", "Notification") /// About public static let roomDetailsAboutSectionTitle = ElementL10n.tr("Untranslated", "room_details_about_section_title") + /// Copy Link + public static let roomDetailsCopyLink = ElementL10n.tr("Untranslated", "room_details_copy_link") /// Info public static let roomDetailsTitle = ElementL10n.tr("Untranslated", "room_details_title") /// Failed loading messages @@ -96,7 +114,7 @@ extension ElementL10n { public static let screenshotDetectedMessage = ElementL10n.tr("Untranslated", "screenshot_detected_message") /// You took a screenshot public static let screenshotDetectedTitle = ElementL10n.tr("Untranslated", "screenshot_detected_title") - /// You can only connect to a server that has already been set up + /// You can only connect to an existing server public static let serverSelectionServerFooter = ElementL10n.tr("Untranslated", "server_selection_server_footer") /// Looks like you’re using a new device. Verify its you. public static let sessionVerificationBannerMessage = ElementL10n.tr("Untranslated", "session_verification_banner_message") diff --git a/ElementX/Sources/Other/AvatarSize.swift b/ElementX/Sources/Other/AvatarSize.swift index 27e39ee7a3..293f26fabc 100644 --- a/ElementX/Sources/Other/AvatarSize.swift +++ b/ElementX/Sources/Other/AvatarSize.swift @@ -56,7 +56,7 @@ enum UserAvatarSizeOnScreen { case .settings: return 60 case .roomDetails: - return 32 + return 44 } } } @@ -73,7 +73,7 @@ enum RoomAvatarSizeOnScreen { case .home: return 44 case .details: - return 100 + return 70 } } } diff --git a/ElementX/Sources/Other/SwiftUI/Views/FormSectionHeaderStyle.swift b/ElementX/Sources/Other/SwiftUI/Views/FormSectionHeaderStyle.swift new file mode 100644 index 0000000000..cc89728fd5 --- /dev/null +++ b/ElementX/Sources/Other/SwiftUI/Views/FormSectionHeaderStyle.swift @@ -0,0 +1,33 @@ +// +// Copyright 2022 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +/// Style for section header +struct FormSectionHeaderStyle: ViewModifier { + func body(content: Content) -> some View { + content + .foregroundColor(.element.secondaryContent) + .font(.element.footnoteBold) + } +} + +extension View { + /// Applies the `FormSectionHeaderStyle` modifier to the view + func formSectionHeader() -> some View { + modifier(FormSectionHeaderStyle()) + } +} diff --git a/ElementX/Sources/Other/SwiftUI/Views/SettingsActionButton.swift b/ElementX/Sources/Other/SwiftUI/Views/SettingsActionButton.swift new file mode 100644 index 0000000000..e386e3a79c --- /dev/null +++ b/ElementX/Sources/Other/SwiftUI/Views/SettingsActionButton.swift @@ -0,0 +1,47 @@ +// +// Copyright 2023 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +/// Small squared action button for settings screens +struct SettingsActionButton: View { + // MARK: Public + + let title: String + let image: Image + let action: () -> Void + + // MARK: Private + + @ScaledMetric private var menuIconSize = 54.0 + + // MARK: Views + + var body: some View { + Button(action: action) { + VStack { + image + .renderingMode(.template) + .foregroundColor(.element.primaryContent) + .frame(width: menuIconSize, height: menuIconSize) + .background(RoundedRectangle(cornerRadius: 16).fill(Color.element.background)) + Text(title) + .foregroundColor(.element.secondaryContent) + .font(.element.subheadline) + } + } + } +} diff --git a/ElementX/Sources/Other/SwiftUI/Views/SettingsDefaultRow.swift b/ElementX/Sources/Other/SwiftUI/Views/SettingsDefaultRow.swift new file mode 100644 index 0000000000..743fe6b4cf --- /dev/null +++ b/ElementX/Sources/Other/SwiftUI/Views/SettingsDefaultRow.swift @@ -0,0 +1,58 @@ +// +// Copyright 2023 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import SwiftUI + +/// Default row that can be reused for settings screens +struct SettingsDefaultRow: View { + // MARK: Public + + let title: String + let image: Image + let action: () -> Void + + // MARK: Private + + @ScaledMetric private var menuIconSize = 30.0 + private let listRowInsets = EdgeInsets(top: 8, leading: 16, bottom: 8, trailing: 16) + + // MARK: Views + + var body: some View { + Button(action: action) { + HStack(spacing: 16) { + image + .foregroundColor(.element.systemGray) + .padding(4) + .background(Color.element.systemGray6) + .clipShape(RoundedRectangle(cornerRadius: 8)) + .frame(width: menuIconSize, height: menuIconSize) + + Text(title) + .font(.element.body) + .foregroundColor(.element.primaryContent) + + Spacer() + + Image(systemName: "chevron.forward") + .foregroundColor(.element.tertiaryContent) + } + } + .listRowInsets(listRowInsets) + .listRowSeparator(.hidden) + .foregroundColor(.element.primaryContent) + } +} diff --git a/ElementX/Sources/Screens/Authentication/AuthenticationIconImage.swift b/ElementX/Sources/Screens/Authentication/AuthenticationIconImage.swift index 6da243a1b1..3058ee3fe2 100644 --- a/ElementX/Sources/Screens/Authentication/AuthenticationIconImage.swift +++ b/ElementX/Sources/Screens/Authentication/AuthenticationIconImage.swift @@ -18,16 +18,17 @@ import SwiftUI /// An image that is styled for use as the screen icon in the onboarding flow. struct AuthenticationIconImage: View { - let image: ImageAsset + let image: Image var body: some View { - Image(image.name) + image .resizable() .renderingMode(.template) - .foregroundColor(.element.accent) - .frame(width: 90, height: 90) - .background(.white, in: Circle().inset(by: 2)) + .foregroundColor(.element.secondaryContent) .accessibilityHidden(true) + .padding(20) + .frame(width: 72, height: 72) + .background(RoundedRectangle(cornerRadius: 14).fill(Color.element.quinaryContent)) } } @@ -35,6 +36,6 @@ struct AuthenticationIconImage: View { struct AuthenticationIconImage_Previews: PreviewProvider { static var previews: some View { - AuthenticationIconImage(image: Asset.Images.serverSelectionIcon) + AuthenticationIconImage(image: Image(asset: Asset.Images.serverSelectionIcon)) } } diff --git a/ElementX/Sources/Screens/Authentication/LoginScreen/LoginCoordinator.swift b/ElementX/Sources/Screens/Authentication/LoginScreen/LoginCoordinator.swift index 9209d7f073..beb08c51ba 100644 --- a/ElementX/Sources/Screens/Authentication/LoginScreen/LoginCoordinator.swift +++ b/ElementX/Sources/Screens/Authentication/LoginScreen/LoginCoordinator.swift @@ -197,13 +197,11 @@ final class LoginCoordinator: CoordinatorProtocol { /// Presents the server selection screen as a modal. private func presentServerSelectionScreen() { - let serverSelectionNavigationStackCoordinator = NavigationStackCoordinator() - - let userNotificationController = UserNotificationController(rootCoordinator: serverSelectionNavigationStackCoordinator) + let userNotificationController = UserNotificationController(rootCoordinator: navigationStackCoordinator) let parameters = ServerSelectionCoordinatorParameters(authenticationService: authenticationService, userNotificationController: userNotificationController, - isModallyPresented: true) + isModallyPresented: false) let coordinator = ServerSelectionCoordinator(parameters: parameters) coordinator.callback = { [weak self, weak coordinator] action in @@ -211,9 +209,7 @@ final class LoginCoordinator: CoordinatorProtocol { self.serverSelectionCoordinator(coordinator, didCompleteWith: action) } - serverSelectionNavigationStackCoordinator.setRootCoordinator(coordinator) - - navigationStackCoordinator.setSheetCoordinator(userNotificationController) + navigationStackCoordinator.push(coordinator) } /// Handles the result from the server selection modal, dismissing it after updating the view. @@ -223,7 +219,7 @@ final class LoginCoordinator: CoordinatorProtocol { updateViewModel() } - navigationStackCoordinator.setSheetCoordinator(nil) + navigationStackCoordinator.pop() } /// Shows the forgot password screen. diff --git a/ElementX/Sources/Screens/Authentication/LoginScreen/View/LoginScreen.swift b/ElementX/Sources/Screens/Authentication/LoginScreen/View/LoginScreen.swift index 75a46cc51c..cfba183042 100644 --- a/ElementX/Sources/Screens/Authentication/LoginScreen/View/LoginScreen.swift +++ b/ElementX/Sources/Screens/Authentication/LoginScreen/View/LoginScreen.swift @@ -29,15 +29,10 @@ struct LoginScreen: View { VStack(spacing: 0) { header .padding(.top, UIConstants.topPaddingToNavigationBar) - .padding(.bottom, 36) + .padding(.bottom, 32) serverInfo - .padding(.leading, 12) - - Rectangle() - .fill(Color.element.quinaryContent) - .frame(height: 1) - .padding(.vertical, 21) + .padding(.bottom, 32) switch context.viewState.loginMode { case .password: @@ -59,7 +54,7 @@ struct LoginScreen: View { /// The header containing a Welcome Back title. var header: some View { Text(ElementL10n.ftueAuthWelcomeBackTitle) - .font(.element.title2Bold) + .font(.element.title1Bold) .multilineTextAlignment(.center) .foregroundColor(.element.primaryContent) } @@ -73,7 +68,13 @@ struct LoginScreen: View { /// The form with text fields for username and password, along with a submit button. var loginForm: some View { - VStack(spacing: 14) { + VStack(alignment: .leading, spacing: 0) { + Text(ElementL10n.ftueAuthSignInEnterDetails) + .font(.element.subheadline) + .foregroundColor(.element.primaryContent) + .padding(.horizontal, 16) + .padding(.bottom, 8) + TextField(ElementL10n.loginSigninUsernameHint, text: $context.username) .focused($isUsernameFocused) .textFieldStyle(.elementInput()) @@ -84,8 +85,7 @@ struct LoginScreen: View { .onChange(of: isUsernameFocused, perform: usernameFocusChanged) .onSubmit { isPasswordFocused = true } .accessibilityIdentifier("usernameTextField") - - Spacer().frame(height: 20) + .padding(.bottom, 20) SecureField(ElementL10n.loginSignupPasswordHint, text: $context.password) .focused($isPasswordFocused) @@ -95,15 +95,18 @@ struct LoginScreen: View { .onSubmit(submit) .accessibilityIdentifier("passwordTextField") - Button { context.send(viewAction: .forgotPassword) } label: { - Text(ElementL10n.ftueAuthForgotPassword) - .font(.element.body) - } - .frame(maxWidth: .infinity, alignment: .trailing) - .padding(.bottom, 8) + // uncomment this piece of code once forgot password will be available +// Button { context.send(viewAction: .forgotPassword) } label: { +// Text(ElementL10n.ftueAuthForgotPassword) +// .font(.element.body) +// } +// .frame(maxWidth: .infinity, alignment: .trailing) +// .padding(.bottom, 8) + Spacer().frame(height: 32) + Button(action: submit) { - Text(ElementL10n.loginSignupSubmit) + Text(ElementL10n.loginContinue) } .buttonStyle(.elementAction(.xLarge)) .disabled(!context.viewState.canSubmit) diff --git a/ElementX/Sources/Screens/Authentication/LoginScreen/View/LoginServerInfoSection.swift b/ElementX/Sources/Screens/Authentication/LoginScreen/View/LoginServerInfoSection.swift index 2a7e950fe0..7a282edce1 100644 --- a/ElementX/Sources/Screens/Authentication/LoginScreen/View/LoginServerInfoSection.swift +++ b/ElementX/Sources/Screens/Authentication/LoginScreen/View/LoginServerInfoSection.swift @@ -29,25 +29,36 @@ struct LoginServerInfoSection: View { // MARK: - Views var body: some View { - VStack(alignment: .leading, spacing: 4) { + VStack(alignment: .leading, spacing: 8) { Text(ElementL10n.ftueAuthSignInChooseServerHeader) .font(.element.subheadline) - .foregroundColor(.element.secondaryContent) + .foregroundColor(.element.primaryContent) + .padding(.horizontal, 16) - HStack { - Text(address) - .font(.element.body) - .foregroundColor(.element.primaryContent) - - Spacer() - - Button(action: editAction) { - Text(ElementL10n.edit) - .padding(.vertical, 2) + Button(action: editAction) { + HStack { + Text(address) + .font(.element.subheadlineBold) + .foregroundColor(.element.primaryContent) + .padding(.horizontal, 16) + .padding(.vertical) + + Spacer() + + Image(systemName: "chevron.right") + .foregroundColor(.element.tertiaryContent) + .padding(.trailing, 16) } - .buttonStyle(.elementGhost()) - .accessibilityIdentifier("editServerButton") + .background(RoundedRectangle(cornerRadius: 14).fill(Color.element.system)) } + .accessibilityIdentifier("editServerButton") } } } + +struct LoginServerInfoSection_Previews: PreviewProvider { + static var previews: some View { + LoginServerInfoSection(address: "matrix.org", editAction: { }) + .padding() + } +} diff --git a/ElementX/Sources/Screens/Authentication/ServerSelection/ServerSelectionModels.swift b/ElementX/Sources/Screens/Authentication/ServerSelection/ServerSelectionModels.swift index 7cb2e3fa18..4f64cb6883 100644 --- a/ElementX/Sources/Screens/Authentication/ServerSelection/ServerSelectionModels.swift +++ b/ElementX/Sources/Screens/Authentication/ServerSelection/ServerSelectionModels.swift @@ -38,7 +38,7 @@ struct ServerSelectionViewState: BindableState { /// The title shown on the confirm button. var buttonTitle: String { - isModallyPresented ? ElementL10n.actionConfirm : ElementL10n.actionNext + isModallyPresented ? ElementL10n.continue : ElementL10n.actionNext } /// The text field is showing an error. diff --git a/ElementX/Sources/Screens/Authentication/ServerSelection/View/ServerSelectionScreen.swift b/ElementX/Sources/Screens/Authentication/ServerSelection/View/ServerSelectionScreen.swift index 9ab8c448dd..b544387331 100644 --- a/ElementX/Sources/Screens/Authentication/ServerSelection/View/ServerSelectionScreen.swift +++ b/ElementX/Sources/Screens/Authentication/ServerSelection/View/ServerSelectionScreen.swift @@ -40,7 +40,7 @@ struct ServerSelectionScreen: View { /// The title, message and icon at the top of the screen. var header: some View { VStack(spacing: 8) { - AuthenticationIconImage(image: Asset.Images.serverSelectionIcon) + AuthenticationIconImage(image: Image(asset: Asset.Images.serverSelectionIcon)) .padding(.bottom, 8) Text(ElementL10n.ftueAuthChooseServerTitle) @@ -51,13 +51,14 @@ struct ServerSelectionScreen: View { Text(ElementL10n.ftueAuthChooseServerSubtitle) .font(.element.body) .multilineTextAlignment(.center) - .foregroundColor(.element.secondaryContent) + .foregroundColor(.element.tertiaryContent) } + .padding(.horizontal, 16) } /// The text field and confirm button where the user enters a server URL. var serverForm: some View { - VStack(alignment: .leading, spacing: 12) { + VStack(alignment: .leading, spacing: 24) { TextField(ElementL10n.ftueAuthChooseServerEntryHint, text: $context.homeserverAddress) .textFieldStyle(.elementInput(labelText: ElementL10n.hsUrl, footerText: context.viewState.footerMessage, @@ -70,8 +71,6 @@ struct ServerSelectionScreen: View { .onSubmit(submit) .accessibilityIdentifier("addressTextField") - Divider() - TextField(ElementL10n.ftueAuthChooseServerEntryHint, text: $context.slidingSyncProxyAddress) .textFieldStyle(.elementInput(labelText: "Sliding sync proxy URL")) .keyboardType(.URL) @@ -80,6 +79,7 @@ struct ServerSelectionScreen: View { .submitLabel(.done) .onSubmit(submit) .accessibilityIdentifier("slidingSyncProxyAddressTextField") + .padding(.bottom, 8) Button(action: submit) { Text(context.viewState.buttonTitle) diff --git a/ElementX/Sources/Screens/BugReport/BugReportModels.swift b/ElementX/Sources/Screens/BugReport/BugReportModels.swift index 4dd49bfe2d..f5242e4370 100644 --- a/ElementX/Sources/Screens/BugReport/BugReportModels.swift +++ b/ElementX/Sources/Screens/BugReport/BugReportModels.swift @@ -38,6 +38,5 @@ struct BugReportViewStateBindings { enum BugReportViewAction { case cancel case submit - case toggleSendLogs case removeScreenshot } diff --git a/ElementX/Sources/Screens/BugReport/BugReportViewModel.swift b/ElementX/Sources/Screens/BugReport/BugReportViewModel.swift index 3f4a75acf4..781f3fa919 100644 --- a/ElementX/Sources/Screens/BugReport/BugReportViewModel.swift +++ b/ElementX/Sources/Screens/BugReport/BugReportViewModel.swift @@ -41,8 +41,6 @@ class BugReportViewModel: BugReportViewModelType, BugReportViewModelProtocol { callback?(.cancel) case .submit: await submitBugReport() - case .toggleSendLogs: - context.sendingLogsEnabled.toggle() case .removeScreenshot: state.screenshot = nil } diff --git a/ElementX/Sources/Screens/BugReport/View/BugReportScreen.swift b/ElementX/Sources/Screens/BugReport/View/BugReportScreen.swift index cbca7e5aca..4d478c24e3 100644 --- a/ElementX/Sources/Screens/BugReport/View/BugReportScreen.swift +++ b/ElementX/Sources/Screens/BugReport/View/BugReportScreen.swift @@ -26,93 +26,81 @@ struct BugReportScreen: View { @ObservedObject var context: BugReportViewModel.Context var body: some View { - GeometryReader { geometry in - VStack { - ScrollView { - mainContent - .padding(.top, 50) - .padding(.horizontal, horizontalPadding) - } - .introspectScrollView { scrollView in - scrollView.keyboardDismissMode = .onDrag - } - - buttons - .padding(.horizontal, horizontalPadding) - .padding(.bottom, geometry.safeAreaInsets.bottom > 0 ? 0 : 16) - } - .navigationTitle(ElementL10n.titleActivityBugReport) - .toolbar { - if context.viewState.isModallyPresented { - ToolbarItem(placement: .cancellationAction) { - Button(ElementL10n.actionCancel) { - context.send(viewAction: .cancel) - } - } - } - } - .interactiveDismissDisabled() + ScrollView { + mainContent + .padding(.top, 50) + .padding(.horizontal, horizontalPadding) } + .scrollDismissesKeyboard(.immediately) + .background(Color.element.system) + .navigationTitle(ElementL10n.bugReportScreenTitle) + .navigationBarTitleDisplayMode(.inline) + .toolbar { toolbar } + .interactiveDismissDisabled() } - + /// The main content of the view to be shown in a scroll view. var mainContent: some View { - VStack(alignment: .leading, spacing: 12) { - Text(ElementL10n.sendBugReportDescription) - .accessibilityIdentifier("reportBugDescription") - ZStack(alignment: .topLeading) { - RoundedRectangle(cornerRadius: 8, style: .continuous) - .fill(Color.element.system) + VStack(alignment: .leading, spacing: 24) { + descriptionTextEditor + sendLogsToggle + screenshot + } + } + + @ViewBuilder + private var descriptionTextEditor: some View { + ZStack(alignment: .topLeading) { + RoundedRectangle(cornerRadius: 14, style: .continuous) + .fill(Color.element.background) - if context.reportText.isEmpty { - Text(ElementL10n.sendBugReportPlaceholder) - .foregroundColor(Color.element.secondaryContent) - .padding(.horizontal, 8) - .padding(.vertical, 12) + TextEditor(text: $context.reportText) + .tint(.element.brand) + .padding(.horizontal, 10) + .padding(.vertical, 4) + .background(Color.clear) + .cornerRadius(14) + .accessibilityIdentifier("reportTextView") + .introspectTextView { textView in + textView.backgroundColor = .clear } - TextEditor(text: $context.reportText) - .padding(4) - .background(Color.clear) - .cornerRadius(8) - .accessibilityIdentifier("reportTextView") - .introspectTextView { textView in - textView.backgroundColor = .clear - } - } - .frame(maxWidth: .infinity) - .frame(height: 300) - .font(.body) - Text(ElementL10n.sendBugReportLogsDescription) - .accessibilityIdentifier("sendLogsDescription") - HStack(spacing: 12) { - Toggle(ElementL10n.sendBugReportIncludeLogs, isOn: $context.sendingLogsEnabled) - .toggleStyle(ElementToggleStyle()) - .accessibilityIdentifier("sendLogsToggle") - Text(ElementL10n.sendBugReportIncludeLogs) - .accessibilityIdentifier("sendLogsText") - } - .onTapGesture { - context.send(viewAction: .toggleSendLogs) + + if context.reportText.isEmpty { + Text(ElementL10n.bugReportScreenDescription) + .font(.element.body) + .foregroundColor(Color.element.secondaryContent) + .padding(.horizontal, 16) + .padding(.vertical, 12) + .allowsHitTesting(false) } - screenshot + + RoundedRectangle(cornerRadius: 14, style: .continuous) + .stroke(Color.element.quaternaryContent) } + .frame(maxWidth: .infinity) + .frame(height: 300) + .font(.body) } - /// The action buttons shown at the bottom of the view. - var buttons: some View { - VStack { - Button { context.send(viewAction: .submit) } label: { - Text(ElementL10n.actionSend) - } - .frame(maxWidth: .infinity) - .padding(.vertical, 16) - .disabled(context.reportText.count < 5) - .accessibilityIdentifier("sendButton") + @ViewBuilder + private var sendLogsToggle: some View { + VStack(spacing: 8) { + Toggle(ElementL10n.bugReportScreenIncludeLogs, isOn: $context.sendingLogsEnabled) + .tint(Color.element.brand) + .accessibilityIdentifier("sendLogsToggle") + .padding(.horizontal, 16) + .padding(.vertical, 11) + .background(RoundedRectangle(cornerRadius: 14).fill(Color.element.background)) + + Text(ElementL10n.bugReportScreenLogsDescription) + .font(.element.caption1) + .foregroundColor(Color.element.secondaryContent) + .padding(.horizontal, -8) } } - + @ViewBuilder - var screenshot: some View { + private var screenshot: some View { if let screenshot = context.viewState.screenshot { ZStack(alignment: .topTrailing) { Image(uiImage: screenshot) @@ -126,7 +114,27 @@ struct BugReportScreen: View { .offset(x: 10, y: -10) .accessibilityIdentifier("removeScreenshotButton") } - .padding(.vertical, 10) + .padding(.vertical, 16) + .padding(.horizontal, 16) + } + } + + @ToolbarContentBuilder + private var toolbar: some ToolbarContent { + if context.viewState.isModallyPresented { + ToolbarItem(placement: .cancellationAction) { + Button(ElementL10n.actionCancel) { + context.send(viewAction: .cancel) + } + } + } + + ToolbarItem(placement: .confirmationAction) { + Button(ElementL10n.actionSend) { + context.send(viewAction: .submit) + } + .disabled(context.reportText.count < 5) + .accessibilityIdentifier("sendButton") } } } diff --git a/ElementX/Sources/Screens/OnboardingScreen/OnboardingModels.swift b/ElementX/Sources/Screens/OnboardingScreen/OnboardingModels.swift index 3a9b339aca..9a568a0452 100644 --- a/ElementX/Sources/Screens/OnboardingScreen/OnboardingModels.swift +++ b/ElementX/Sources/Screens/OnboardingScreen/OnboardingModels.swift @@ -60,23 +60,10 @@ struct OnboardingViewState: BindableState { } init() { - // The pun doesn't translate, so we only use it for English. - let locale = Locale.current - let page4Title = locale.identifier.hasPrefix("en") ? "Cut the slack from teams." : ElementL10n.ftueAuthCarouselWorkplaceTitle - content = [ - OnboardingPageContent(title: ElementL10n.ftueAuthCarouselSecureTitle.tinting("."), - message: ElementL10n.ftueAuthCarouselSecureBody, - image: Asset.Images.onboardingScreenPage1), - OnboardingPageContent(title: ElementL10n.ftueAuthCarouselControlTitle.tinting("."), - message: ElementL10n.ftueAuthCarouselControlBody, - image: Asset.Images.onboardingScreenPage2), - OnboardingPageContent(title: ElementL10n.ftueAuthCarouselEncryptedTitle.tinting("."), - message: ElementL10n.ftueAuthCarouselEncryptedBody, - image: Asset.Images.onboardingScreenPage3), - OnboardingPageContent(title: page4Title.tinting("."), - message: ElementL10n.ftueAuthCarouselWorkplaceBody(InfoPlistReader.target.bundleDisplayName), - image: Asset.Images.onboardingScreenPage4) + OnboardingPageContent(title: ElementL10n.ftueAuthCarouselWelcomeTitle.tinting(".", color: .element.accent), + message: ElementL10n.ftueAuthCarouselWelcomeBody(InfoPlistReader.target.bundleDisplayName), + image: Asset.Images.onboardingAppLogo) ] bindings = OnboardingBindings() } diff --git a/ElementX/Sources/Screens/OnboardingScreen/View/OnboardingBackgroundView.swift b/ElementX/Sources/Screens/OnboardingScreen/View/OnboardingBackgroundView.swift new file mode 100644 index 0000000000..626e299551 --- /dev/null +++ b/ElementX/Sources/Screens/OnboardingScreen/View/OnboardingBackgroundView.swift @@ -0,0 +1,68 @@ +// +// Copyright 2023 New Vector Ltd +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +import DesignKit +import SwiftUI + +/// A glossy animated background view designed for the onboarding screen +struct OnboardingBackgroundView: View { + @Environment(\.colorScheme) private var colorScheme + + @State private var factor = 0.0 + @State private var isReversed = false + + private let step = 0.001 + private let timer = Timer.publish(every: 0.01, on: .main, in: .common).autoconnect() + + var body: some View { + GeometryReader { geometry in + ZStack { + Image(asset: Asset.Images.onboardingBackgroundPart1) + .position(x: geometry.size.width * 0.125 - factor * geometry.size.width * 0.25, y: (1.0 - factor) * geometry.size.height * 0.55) + Image(asset: Asset.Images.onboardingBackgroundPart2) + .position(x: geometry.size.width * 1.05, y: factor * geometry.size.height * 0.45) + Image(asset: Asset.Images.onboardingBackgroundPart3) + .position(x: factor * geometry.size.width, y: geometry.size.height * 1.05 - factor * geometry.size.height * 0.08) + } + .frame(width: geometry.size.width, height: geometry.size.height) + .onReceive(timer) { _ in + if isReversed { + guard factor > 0 else { + isReversed = false + factor = step + return + } + + factor -= step + } else { + guard factor < 1 else { + isReversed = true + factor = 1 - step + return + } + + factor += 0.001 + } + } + } + } +} + +struct OnboardingBackgroundView_Previews: PreviewProvider { + static var previews: some View { + OnboardingBackgroundView() + } +} diff --git a/ElementX/Sources/Screens/OnboardingScreen/View/OnboardingPageView.swift b/ElementX/Sources/Screens/OnboardingScreen/View/OnboardingPageView.swift index f81c2316b5..7a79fc3666 100644 --- a/ElementX/Sources/Screens/OnboardingScreen/View/OnboardingPageView.swift +++ b/ElementX/Sources/Screens/OnboardingScreen/View/OnboardingPageView.swift @@ -20,27 +20,38 @@ struct OnboardingPageView: View { /// The content that this page should display. let content: OnboardingPageContent + @Environment(\.verticalSizeClass) private var verticalSizeClass + var body: some View { VStack { - Image(content.image.name) - .resizable() - .scaledToFit() - .frame(maxWidth: 310) // This value is problematic. 300 results in dropped frames - // on iPhone 12/13 Mini. 305 the same on iPhone 12/13. As of - // iOS 15, 310 seems fine on all supported screen widths 🤞. - .padding(20) - .accessibilityHidden(true) + if verticalSizeClass == .regular { + Spacer() + + Image(content.image.name) + .resizable() + .scaledToFit() + .padding(60) + .accessibilityHidden(true) + } + + Spacer() VStack(spacing: 8) { + Spacer() + Text(content.title) - .font(.element.title2Bold) + .font(.element.title1Bold) .foregroundColor(.element.primaryContent) + .multilineTextAlignment(.center) Text(content.message) .font(.element.body) .foregroundColor(.element.secondaryContent) .multilineTextAlignment(.center) } + .padding() .fixedSize(horizontal: false, vertical: true) + + Spacer() } .padding(.bottom) .padding(.horizontal, 16) diff --git a/ElementX/Sources/Screens/OnboardingScreen/View/OnboardingScreen.swift b/ElementX/Sources/Screens/OnboardingScreen/View/OnboardingScreen.swift index 60b1abd4b8..d950fc349c 100644 --- a/ElementX/Sources/Screens/OnboardingScreen/View/OnboardingScreen.swift +++ b/ElementX/Sources/Screens/OnboardingScreen/View/OnboardingScreen.swift @@ -21,7 +21,8 @@ import SwiftUI struct OnboardingScreen: View { @Environment(\.colorScheme) private var colorScheme @Environment(\.layoutDirection) private var layoutDirection - + @Environment(\.verticalSizeClass) private var verticalSizeClass + private var isLeftToRight: Bool { layoutDirection == .leftToRight } private var pageCount: Int { context.viewState.content.count } @@ -33,64 +34,68 @@ struct OnboardingScreen: View { @ObservedObject var context: OnboardingViewModel.Context var body: some View { - GeometryReader { geometry in - VStack(alignment: .leading) { - Spacer() - .frame(height: UIConstants.spacerHeight(in: geometry)) - - // The main content of the carousel - HStack(alignment: .top, spacing: 0) { - // Add a hidden page at the start of the carousel duplicating the content of the last page - OnboardingPageView(content: context.viewState.content[pageCount - 1]) - .frame(width: geometry.size.width) - .accessibilityIdentifier("hiddenPage") - - ForEach(0..