mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-06 12:28:29 +00:00
430 - Design review for first public TestFlight Part2 (#496)
* 430 - Design review for first public TestFlight Part2 --------- Co-authored-by: Doug <douglase@element.io>
This commit is contained in:
@@ -35,7 +35,7 @@ public struct ElementCapsuleButtonStyle: ButtonStyle {
|
|||||||
configuration.label
|
configuration.label
|
||||||
.padding(7)
|
.padding(7)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.font(.element.footnote)
|
.font(.element.headline)
|
||||||
.foregroundColor(fontColor)
|
.foregroundColor(fontColor)
|
||||||
.multilineTextAlignment(.center)
|
.multilineTextAlignment(.center)
|
||||||
.background(background)
|
.background(background)
|
||||||
|
|||||||
@@ -426,6 +426,7 @@
|
|||||||
CF82143AA4A4F7BD11D22946 /* RoomTimelineViewProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACB6C5E4950B6C9842F35A38 /* RoomTimelineViewProvider.swift */; };
|
CF82143AA4A4F7BD11D22946 /* RoomTimelineViewProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = ACB6C5E4950B6C9842F35A38 /* RoomTimelineViewProvider.swift */; };
|
||||||
D034A195A3494E38BF060485 /* MockSessionVerificationControllerProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1A9CCCF53495CF3D7B19FCE /* MockSessionVerificationControllerProxy.swift */; };
|
D034A195A3494E38BF060485 /* MockSessionVerificationControllerProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = D1A9CCCF53495CF3D7B19FCE /* MockSessionVerificationControllerProxy.swift */; };
|
||||||
D05A193AE63030F2CFCE2E9C /* UITestScreenIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC6FE34A0A47D010BBB4D4D4 /* UITestScreenIdentifier.swift */; };
|
D05A193AE63030F2CFCE2E9C /* UITestScreenIdentifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = CC6FE34A0A47D010BBB4D4D4 /* UITestScreenIdentifier.swift */; };
|
||||||
|
D1DF52739551A5B2655C2D2E /* SettingsPickerRow.swift in Sources */ = {isa = PBXBuildFile; fileRef = D7428970E7B5EA449806CFA3 /* SettingsPickerRow.swift */; };
|
||||||
D2D70B5DB1A5E4AF0CD88330 /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 033DB41C51865A2E83174E87 /* target.yml */; };
|
D2D70B5DB1A5E4AF0CD88330 /* target.yml in Resources */ = {isa = PBXBuildFile; fileRef = 033DB41C51865A2E83174E87 /* target.yml */; };
|
||||||
D33AC79A50DFC26D2498DD28 /* FileRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5098DA7799946A61E34A2373 /* FileRoomTimelineItem.swift */; };
|
D33AC79A50DFC26D2498DD28 /* FileRoomTimelineItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5098DA7799946A61E34A2373 /* FileRoomTimelineItem.swift */; };
|
||||||
D59F046B15AA8E971053C1A6 /* RoomDetailsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 813B198AE8833FD12E5A9C78 /* RoomDetailsCoordinator.swift */; };
|
D59F046B15AA8E971053C1A6 /* RoomDetailsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 813B198AE8833FD12E5A9C78 /* RoomDetailsCoordinator.swift */; };
|
||||||
@@ -1014,6 +1015,7 @@
|
|||||||
D6CA5F386C7701C129398945 /* AuthenticationCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationCoordinator.swift; sourceTree = "<group>"; };
|
D6CA5F386C7701C129398945 /* AuthenticationCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationCoordinator.swift; sourceTree = "<group>"; };
|
||||||
D6D094C15E8DB424F1C6FC94 /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/Localizable.strings; sourceTree = "<group>"; };
|
D6D094C15E8DB424F1C6FC94 /* hr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hr; path = hr.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||||
D6DC38E64A5ED3FDB201029A /* BugReportService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportService.swift; sourceTree = "<group>"; };
|
D6DC38E64A5ED3FDB201029A /* BugReportService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BugReportService.swift; sourceTree = "<group>"; };
|
||||||
|
D7428970E7B5EA449806CFA3 /* SettingsPickerRow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsPickerRow.swift; sourceTree = "<group>"; };
|
||||||
D751BB69BB7C38FD247517B4 /* UITestsRootCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestsRootCoordinator.swift; sourceTree = "<group>"; };
|
D751BB69BB7C38FD247517B4 /* UITestsRootCoordinator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UITestsRootCoordinator.swift; sourceTree = "<group>"; };
|
||||||
D77B3D4950F1707E66E4A45A /* AnalyticsConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsConfiguration.swift; sourceTree = "<group>"; };
|
D77B3D4950F1707E66E4A45A /* AnalyticsConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnalyticsConfiguration.swift; sourceTree = "<group>"; };
|
||||||
D8F5F9E02B1AB5350B1815E7 /* TimelineStartRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStartRoomTimelineItem.swift; sourceTree = "<group>"; };
|
D8F5F9E02B1AB5350B1815E7 /* TimelineStartRoomTimelineItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TimelineStartRoomTimelineItem.swift; sourceTree = "<group>"; };
|
||||||
@@ -1335,6 +1337,7 @@
|
|||||||
839E2C35DF3F9C7B54C3CE49 /* RoundedCornerShape.swift */,
|
839E2C35DF3F9C7B54C3CE49 /* RoundedCornerShape.swift */,
|
||||||
F3F8435052B2488947B35942 /* SettingsActionButton.swift */,
|
F3F8435052B2488947B35942 /* SettingsActionButton.swift */,
|
||||||
BFB3D0B0E16FEC93175ABC2D /* SettingsDefaultRow.swift */,
|
BFB3D0B0E16FEC93175ABC2D /* SettingsDefaultRow.swift */,
|
||||||
|
D7428970E7B5EA449806CFA3 /* SettingsPickerRow.swift */,
|
||||||
);
|
);
|
||||||
path = Views;
|
path = Views;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -3241,6 +3244,7 @@
|
|||||||
D6417E5A799C3C7F14F9EC0A /* SessionVerificationViewModelProtocol.swift in Sources */,
|
D6417E5A799C3C7F14F9EC0A /* SessionVerificationViewModelProtocol.swift in Sources */,
|
||||||
052BE25E8C466D3D60558DA3 /* SettingsActionButton.swift in Sources */,
|
052BE25E8C466D3D60558DA3 /* SettingsActionButton.swift in Sources */,
|
||||||
A2DDFA5033B535AB2BA51F5C /* SettingsDefaultRow.swift in Sources */,
|
A2DDFA5033B535AB2BA51F5C /* SettingsDefaultRow.swift in Sources */,
|
||||||
|
D1DF52739551A5B2655C2D2E /* SettingsPickerRow.swift in Sources */,
|
||||||
7FED310F6AB7A70CBFB7C8A3 /* SettingsScreen.swift in Sources */,
|
7FED310F6AB7A70CBFB7C8A3 /* SettingsScreen.swift in Sources */,
|
||||||
233221E32DA045018D3D3050 /* SettingsScreenCoordinator.swift in Sources */,
|
233221E32DA045018D3D3050 /* SettingsScreenCoordinator.swift in Sources */,
|
||||||
DBAA69CC2CE4D44BC8E20105 /* SettingsScreenModels.swift in Sources */,
|
DBAA69CC2CE4D44BC8E20105 /* SettingsScreenModels.swift in Sources */,
|
||||||
|
|||||||
@@ -8,10 +8,11 @@
|
|||||||
"screenshot_detected_message" = "Would you like to submit a bug report?";
|
"screenshot_detected_message" = "Would you like to submit a bug report?";
|
||||||
|
|
||||||
"settings_appearance" = "Appearance";
|
"settings_appearance" = "Appearance";
|
||||||
"settings_timeline_style" = "Timeline Style";
|
"settings_timeline_style" = "Message layout";
|
||||||
|
"settings_session_verification" = "Complete verification";
|
||||||
|
|
||||||
"room_timeline_style_plain_long_description" = "Plain Timeline";
|
"room_timeline_style_plain_long_description" = "Modern";
|
||||||
"room_timeline_style_bubbled_long_description" = "Bubbled Timeline";
|
"room_timeline_style_bubbled_long_description" = "Bubbles";
|
||||||
|
|
||||||
"room_timeline_permalink_creation_failure" = "Failed creating the permalink";
|
"room_timeline_permalink_creation_failure" = "Failed creating the permalink";
|
||||||
"room_timeline_backpagination_failure" = "Failed loading messages";
|
"room_timeline_backpagination_failure" = "Failed loading messages";
|
||||||
@@ -38,8 +39,9 @@
|
|||||||
"noticeRoomUnknownMembershipChange" = "%1$@ made an unknown change to their membership";
|
"noticeRoomUnknownMembershipChange" = "%1$@ made an unknown change to their membership";
|
||||||
|
|
||||||
|
|
||||||
"session_verification_banner_title" = "Help keep your messages secure";
|
"session_verification_banner_title" = "Access your message history";
|
||||||
"session_verification_banner_message" = "Looks like you’re using a new device. Verify its you.";
|
"session_verification_banner_message" = "Looks like you’re using a new device. Verify it’s you to access your encrypted messages.";
|
||||||
|
"session_verification_start" = "Start";
|
||||||
|
|
||||||
"server_selection_server_footer" = "You can only connect to an existing server";
|
"server_selection_server_footer" = "You can only connect to an existing server";
|
||||||
|
|
||||||
@@ -70,3 +72,46 @@
|
|||||||
"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_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_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.";
|
"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.";
|
||||||
|
|
||||||
|
|
||||||
|
// Session verification
|
||||||
|
/*
|
||||||
|
"verification_open_other_to_verify" = "Use an existing session to verify this one, granting it access to encrypted messages.";
|
||||||
|
"verification_profile_verify" = "Verify";
|
||||||
|
"verification_profile_verified" = "Verified";
|
||||||
|
"verification_profile_warning" = "Warning";
|
||||||
|
"verification_conclusion_warning" = "Untrusted sign in";
|
||||||
|
"verification_sas_match" = "They match";
|
||||||
|
"verification_sas_do_not_match" = "They don\'t match";
|
||||||
|
"verification_conclusion_not_secure" = "Not secure";
|
||||||
|
"verification_conclusion_compromised" = "One of the following may be compromised:\n\n - Your homeserver\n - The homeserver the user you’re verifying is connected to\n - Yours, or the other users’ internet connection\n - Yours, or the other users’ device";
|
||||||
|
"verification_request_waiting" = "Waiting…";
|
||||||
|
"verification_request_other_cancelled" = "%@ cancelled";
|
||||||
|
"verification_request_you_cancelled" = "You cancelled";
|
||||||
|
"verification_request_other_accepted" = "%@ accepted";
|
||||||
|
"verification_request_you_accepted" = "You accepted";
|
||||||
|
"verification_sent" = "Verification Sent";
|
||||||
|
"verification_request" = "Verification Request";
|
||||||
|
"verification_verify_device" = "Verify this session";
|
||||||
|
"verification_scan_notice" = "Scan the code with the other user\'s device to securely verify each other";
|
||||||
|
"verification_scan_self_notice" = "Scan the code with your other device or switch and scan with this device";
|
||||||
|
"verification_scan_their_code" = "Scan their code";
|
||||||
|
"verification_scan_with_this_device" = "Scan with this device";
|
||||||
|
"verification_scan_emoji_title" = "Can\'t scan";
|
||||||
|
"verification_scan_emoji_subtitle" = "If you\'re not in person, compare emoji instead";
|
||||||
|
"verification_scan_self_emoji_subtitle" = "Verify by comparing emoji instead";
|
||||||
|
"verification_no_scan_emoji_title" = "Verify by comparing emojis";
|
||||||
|
"verification_verify_user" = "Verify %@";
|
||||||
|
"verification_verified_user" = "Verified %@";
|
||||||
|
"verification_request_waiting_for" = "Waiting for %@…";
|
||||||
|
*/
|
||||||
|
|
||||||
|
"verification_open_other_to_verify_title" = "Open an existing session";
|
||||||
|
"verification_open_other_to_verify_detail" = "Prove it’s you in order to access your encrypted message history.";
|
||||||
|
"verification_request_waiting_accept_request_title" = "Waiting to accept request";
|
||||||
|
"verification_request_waiting_accept_request_detail" = "Accept the request to start the verification process in your other session to continue.";
|
||||||
|
"verification_request_cancelled_title" = "Verification cancelled";
|
||||||
|
"verification_request_cancelled_detail" = "Something doesn’t seem right. Either the request timed out or the request was denied.";
|
||||||
|
"verification_compare_emojis_title" = "Compare emojis";
|
||||||
|
"verification_compare_emojis_detail" = "Confirm that the emojis below match those shown on your other session.";
|
||||||
|
"verification_conclusion_ok_self_notice_title" = "Verification complete";
|
||||||
|
|||||||
@@ -104,9 +104,9 @@ extension ElementL10n {
|
|||||||
public static func roomTimelineReplyingTo(_ p1: Any) -> String {
|
public static func roomTimelineReplyingTo(_ p1: Any) -> String {
|
||||||
return ElementL10n.tr("Untranslated", "room_timeline_replying_to", String(describing: p1))
|
return ElementL10n.tr("Untranslated", "room_timeline_replying_to", String(describing: p1))
|
||||||
}
|
}
|
||||||
/// Bubbled Timeline
|
/// Bubbles
|
||||||
public static let roomTimelineStyleBubbledLongDescription = ElementL10n.tr("Untranslated", "room_timeline_style_bubbled_long_description")
|
public static let roomTimelineStyleBubbledLongDescription = ElementL10n.tr("Untranslated", "room_timeline_style_bubbled_long_description")
|
||||||
/// Plain Timeline
|
/// Modern
|
||||||
public static let roomTimelineStylePlainLongDescription = ElementL10n.tr("Untranslated", "room_timeline_style_plain_long_description")
|
public static let roomTimelineStylePlainLongDescription = ElementL10n.tr("Untranslated", "room_timeline_style_plain_long_description")
|
||||||
/// Syncing
|
/// Syncing
|
||||||
public static let roomTimelineSyncing = ElementL10n.tr("Untranslated", "room_timeline_syncing")
|
public static let roomTimelineSyncing = ElementL10n.tr("Untranslated", "room_timeline_syncing")
|
||||||
@@ -116,13 +116,17 @@ extension ElementL10n {
|
|||||||
public static let screenshotDetectedTitle = ElementL10n.tr("Untranslated", "screenshot_detected_title")
|
public static let screenshotDetectedTitle = ElementL10n.tr("Untranslated", "screenshot_detected_title")
|
||||||
/// You can only connect to an existing server
|
/// You can only connect to an existing server
|
||||||
public static let serverSelectionServerFooter = ElementL10n.tr("Untranslated", "server_selection_server_footer")
|
public static let serverSelectionServerFooter = ElementL10n.tr("Untranslated", "server_selection_server_footer")
|
||||||
/// Looks like you’re using a new device. Verify its you.
|
/// Looks like you’re using a new device. Verify it’s you to access your encrypted messages.
|
||||||
public static let sessionVerificationBannerMessage = ElementL10n.tr("Untranslated", "session_verification_banner_message")
|
public static let sessionVerificationBannerMessage = ElementL10n.tr("Untranslated", "session_verification_banner_message")
|
||||||
/// Help keep your messages secure
|
/// Access your message history
|
||||||
public static let sessionVerificationBannerTitle = ElementL10n.tr("Untranslated", "session_verification_banner_title")
|
public static let sessionVerificationBannerTitle = ElementL10n.tr("Untranslated", "session_verification_banner_title")
|
||||||
|
/// Start
|
||||||
|
public static let sessionVerificationStart = ElementL10n.tr("Untranslated", "session_verification_start")
|
||||||
/// Appearance
|
/// Appearance
|
||||||
public static let settingsAppearance = ElementL10n.tr("Untranslated", "settings_appearance")
|
public static let settingsAppearance = ElementL10n.tr("Untranslated", "settings_appearance")
|
||||||
/// Timeline Style
|
/// Complete verification
|
||||||
|
public static let settingsSessionVerification = ElementL10n.tr("Untranslated", "settings_session_verification")
|
||||||
|
/// Message layout
|
||||||
public static let settingsTimelineStyle = ElementL10n.tr("Untranslated", "settings_timeline_style")
|
public static let settingsTimelineStyle = ElementL10n.tr("Untranslated", "settings_timeline_style")
|
||||||
/// Untranslated
|
/// Untranslated
|
||||||
public static let untranslated = ElementL10n.tr("Untranslated", "untranslated")
|
public static let untranslated = ElementL10n.tr("Untranslated", "untranslated")
|
||||||
@@ -130,6 +134,24 @@ extension ElementL10n {
|
|||||||
public static func untranslatedPlural(_ p1: Int) -> String {
|
public static func untranslatedPlural(_ p1: Int) -> String {
|
||||||
return ElementL10n.tr("Untranslated", "untranslated_plural", p1)
|
return ElementL10n.tr("Untranslated", "untranslated_plural", p1)
|
||||||
}
|
}
|
||||||
|
/// Confirm that the emojis below match those shown on your other session.
|
||||||
|
public static let verificationCompareEmojisDetail = ElementL10n.tr("Untranslated", "verification_compare_emojis_detail")
|
||||||
|
/// Compare emojis
|
||||||
|
public static let verificationCompareEmojisTitle = ElementL10n.tr("Untranslated", "verification_compare_emojis_title")
|
||||||
|
/// Verification complete
|
||||||
|
public static let verificationConclusionOkSelfNoticeTitle = ElementL10n.tr("Untranslated", "verification_conclusion_ok_self_notice_title")
|
||||||
|
/// Prove it’s you in order to access your encrypted message history.
|
||||||
|
public static let verificationOpenOtherToVerifyDetail = ElementL10n.tr("Untranslated", "verification_open_other_to_verify_detail")
|
||||||
|
/// Open an existing session
|
||||||
|
public static let verificationOpenOtherToVerifyTitle = ElementL10n.tr("Untranslated", "verification_open_other_to_verify_title")
|
||||||
|
/// Something doesn’t seem right. Either the request timed out or the request was denied.
|
||||||
|
public static let verificationRequestCancelledDetail = ElementL10n.tr("Untranslated", "verification_request_cancelled_detail")
|
||||||
|
/// Verification cancelled
|
||||||
|
public static let verificationRequestCancelledTitle = ElementL10n.tr("Untranslated", "verification_request_cancelled_title")
|
||||||
|
/// Accept the request to start the verification process in your other session to continue.
|
||||||
|
public static let verificationRequestWaitingAcceptRequestDetail = ElementL10n.tr("Untranslated", "verification_request_waiting_accept_request_detail")
|
||||||
|
/// Waiting to accept request
|
||||||
|
public static let verificationRequestWaitingAcceptRequestTitle = ElementL10n.tr("Untranslated", "verification_request_waiting_accept_request_title")
|
||||||
}
|
}
|
||||||
// swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length
|
// swiftlint:enable explicit_type_interface function_parameter_count identifier_name line_length
|
||||||
// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces
|
// swiftlint:enable nesting type_body_length type_name vertical_whitespace_opening_braces
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ enum RoomAvatarSizeOnScreen {
|
|||||||
case .timeline:
|
case .timeline:
|
||||||
return 32
|
return 32
|
||||||
case .home:
|
case .home:
|
||||||
return 44
|
return 52
|
||||||
case .details:
|
case .details:
|
||||||
return 70
|
return 70
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
//
|
||||||
|
// 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
|
||||||
|
|
||||||
|
/// Picker row that can be reused for settings screens
|
||||||
|
struct SettingsPickerRow<SelectionValue: Hashable, Content: View>: View {
|
||||||
|
// MARK: Public
|
||||||
|
|
||||||
|
let title: String
|
||||||
|
let image: Image
|
||||||
|
@Binding var selection: SelectionValue
|
||||||
|
let content: () -> Content
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
Picker(selection: $selection, content: content) {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.listRowInsets(listRowInsets)
|
||||||
|
.listRowSeparator(.hidden)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,8 +25,9 @@ struct AuthenticationIconImage: View {
|
|||||||
.resizable()
|
.resizable()
|
||||||
.renderingMode(.template)
|
.renderingMode(.template)
|
||||||
.foregroundColor(.element.secondaryContent)
|
.foregroundColor(.element.secondaryContent)
|
||||||
|
.aspectRatio(contentMode: .fit)
|
||||||
.accessibilityHidden(true)
|
.accessibilityHidden(true)
|
||||||
.padding(20)
|
.padding(16)
|
||||||
.frame(width: 72, height: 72)
|
.frame(width: 72, height: 72)
|
||||||
.background(RoundedRectangle(cornerRadius: 14).fill(Color.element.quinaryContent))
|
.background(RoundedRectangle(cornerRadius: 14).fill(Color.element.quinaryContent))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ struct HomeScreen: View {
|
|||||||
userMenuButton
|
userMenuButton
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.background(Color.element.background)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
@@ -83,15 +84,15 @@ struct HomeScreen: View {
|
|||||||
Menu {
|
Menu {
|
||||||
Section {
|
Section {
|
||||||
Button(action: settings) {
|
Button(action: settings) {
|
||||||
Label(ElementL10n.settingsUserSettings, systemImage: "gearshape")
|
Label(ElementL10n.settings, systemImage: "gearshape")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Section {
|
Section {
|
||||||
Button(action: inviteFriends) {
|
Button(action: inviteFriends) {
|
||||||
Label(ElementL10n.inviteFriends, systemImage: "square.and.arrow.up")
|
Label(ElementL10n.actionInvite, systemImage: "square.and.arrow.up")
|
||||||
}
|
}
|
||||||
Button(action: feedback) {
|
Button(action: feedback) {
|
||||||
Label(ElementL10n.feedback, systemImage: "questionmark.circle")
|
Label(ElementL10n.bugReportScreenTitle, systemImage: "questionmark.circle")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Section {
|
Section {
|
||||||
@@ -122,21 +123,26 @@ struct HomeScreen: View {
|
|||||||
|
|
||||||
private var sessionVerificationBanner: some View {
|
private var sessionVerificationBanner: some View {
|
||||||
VStack(alignment: .leading, spacing: 16) {
|
VStack(alignment: .leading, spacing: 16) {
|
||||||
VStack(alignment: .leading, spacing: 2) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
Text(ElementL10n.sessionVerificationBannerTitle)
|
|
||||||
.font(.element.subheadlineBold)
|
|
||||||
.foregroundColor(.element.systemPrimaryLabel)
|
|
||||||
Text(ElementL10n.sessionVerificationBannerMessage)
|
|
||||||
.font(.element.footnote)
|
|
||||||
.foregroundColor(.element.systemSecondaryLabel)
|
|
||||||
}
|
|
||||||
|
|
||||||
HStack(spacing: 16) {
|
HStack(spacing: 16) {
|
||||||
Button(ElementL10n.actionSkip) {
|
Text(ElementL10n.sessionVerificationBannerTitle)
|
||||||
|
.font(.element.headline)
|
||||||
|
.foregroundColor(.element.systemPrimaryLabel)
|
||||||
|
|
||||||
|
Spacer()
|
||||||
|
|
||||||
|
Button {
|
||||||
context.send(viewAction: .skipSessionVerification)
|
context.send(viewAction: .skipSessionVerification)
|
||||||
|
} label: {
|
||||||
|
Image(systemName: "xmark")
|
||||||
|
.foregroundColor(.element.secondaryContent)
|
||||||
|
.frame(width: 12, height: 12)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Text(ElementL10n.sessionVerificationBannerMessage)
|
||||||
|
.font(.element.subheadline)
|
||||||
|
.foregroundColor(.element.secondaryContent)
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity)
|
|
||||||
.buttonStyle(.elementCapsule)
|
|
||||||
|
|
||||||
Button(ElementL10n.continue) {
|
Button(ElementL10n.continue) {
|
||||||
context.send(viewAction: .verifySession)
|
context.send(viewAction: .verifySession)
|
||||||
@@ -144,9 +150,8 @@ struct HomeScreen: View {
|
|||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.buttonStyle(.elementCapsuleProminent)
|
.buttonStyle(.elementCapsuleProminent)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
.padding(16)
|
.padding(16)
|
||||||
.background(Color.element.systemSecondaryBackground)
|
.background(Color.element.system)
|
||||||
.cornerRadius(14)
|
.cornerRadius(14)
|
||||||
.padding(.horizontal, 16)
|
.padding(.horizontal, 16)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,11 +34,17 @@ struct HomeScreenRoomCell: View {
|
|||||||
footer
|
footer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(minHeight: 64.0)
|
.frame(minHeight: 84.0)
|
||||||
.accessibilityElement(children: .combine)
|
.accessibilityElement(children: .combine)
|
||||||
}
|
}
|
||||||
.buttonStyle(HomeScreenRoomCellButtonStyle())
|
.buttonStyle(HomeScreenRoomCellButtonStyle())
|
||||||
.accessibilityIdentifier("roomName:\(room.name)")
|
.accessibilityIdentifier("roomName:\(room.name)")
|
||||||
|
.overlay(alignment: .bottom) {
|
||||||
|
Divider()
|
||||||
|
.frame(height: 0.5)
|
||||||
|
.background(Color.element.quinaryContent)
|
||||||
|
.padding(.leading, 84)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
@@ -53,17 +59,17 @@ struct HomeScreenRoomCell: View {
|
|||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
var header: some View {
|
var header: some View {
|
||||||
HStack(alignment: .firstTextBaseline) {
|
HStack(alignment: .top, spacing: 16) {
|
||||||
Text(room.name)
|
Text(room.name)
|
||||||
.font(.element.callout.bold())
|
.font(.element.headline)
|
||||||
.foregroundColor(.element.primaryContent)
|
.foregroundColor(.element.primaryContent)
|
||||||
.lineLimit(1)
|
.lineLimit(1)
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
|
||||||
if let timestamp = room.timestamp {
|
if let timestamp = room.timestamp {
|
||||||
Text(timestamp)
|
Text(timestamp)
|
||||||
.font(.element.caption1)
|
.font(.element.footnote)
|
||||||
.foregroundColor(.element.secondaryContent)
|
.foregroundColor(room.hasUnreads ? .element.brand : .element.tertiaryContent)
|
||||||
.id(timestamp)
|
.id(timestamp)
|
||||||
.transition(.opacity.animation(.elementDefault))
|
.transition(.opacity.animation(.elementDefault))
|
||||||
}
|
}
|
||||||
@@ -91,9 +97,15 @@ struct HomeScreenRoomCell: View {
|
|||||||
if room.hasUnreads {
|
if room.hasUnreads {
|
||||||
Rectangle()
|
Rectangle()
|
||||||
.frame(width: 12, height: 12)
|
.frame(width: 12, height: 12)
|
||||||
.foregroundColor(.element.primaryContent)
|
.foregroundColor(.element.brand)
|
||||||
.clipShape(Circle())
|
.clipShape(Circle())
|
||||||
.transition(.opacity.animation(.elementDefault))
|
.transition(.opacity.animation(.elementDefault))
|
||||||
|
.padding(.leading, 12)
|
||||||
|
} else {
|
||||||
|
// Force extra padding between last message text and the right border of the screen if there is no unread dot
|
||||||
|
Rectangle()
|
||||||
|
.fill(Color.clear)
|
||||||
|
.frame(width: 12, height: 12)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.animation(.elementDefault, value: room)
|
.animation(.elementDefault, value: room)
|
||||||
@@ -111,18 +123,16 @@ struct HomeScreenRoomCellButtonStyle: ButtonStyle {
|
|||||||
private extension View {
|
private extension View {
|
||||||
func lastMessageFormatting() -> some View {
|
func lastMessageFormatting() -> some View {
|
||||||
font(.element.subheadline)
|
font(.element.subheadline)
|
||||||
.foregroundColor(.element.secondaryContent)
|
.foregroundColor(.element.tertiaryContent)
|
||||||
.lineLimit(2)
|
.lineLimit(2)
|
||||||
.multilineTextAlignment(.leading)
|
.multilineTextAlignment(.leading)
|
||||||
.padding(.top, 2)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// To be used to indicate the selected room too
|
// To be used to indicate the selected room too
|
||||||
func roomCellBackground(_ background: Color) -> some View {
|
func roomCellBackground(_ background: Color) -> some View {
|
||||||
padding(.horizontal, 8)
|
padding(.horizontal, 8)
|
||||||
.padding(.vertical, 6)
|
|
||||||
.background { background.clipShape(RoundedRectangle(cornerRadius: 12)) }
|
|
||||||
.padding(.horizontal, 8)
|
.padding(.horizontal, 8)
|
||||||
|
.background { background }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,8 @@ struct SplashScreenCoordinator: CoordinatorProtocol {
|
|||||||
func toPresentable() -> AnyView {
|
func toPresentable() -> AnyView {
|
||||||
AnyView(
|
AnyView(
|
||||||
ZStack {
|
ZStack {
|
||||||
Image(asset: Asset.Images.appLogo)
|
Image(asset: Asset.Images.onboardingAppLogo)
|
||||||
|
.renderingMode(.original)
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -23,12 +23,39 @@ enum SessionVerificationViewModelAction {
|
|||||||
struct SessionVerificationViewState: BindableState {
|
struct SessionVerificationViewState: BindableState {
|
||||||
var verificationState: SessionVerificationStateMachine.State = .initial
|
var verificationState: SessionVerificationStateMachine.State = .initial
|
||||||
|
|
||||||
|
var title: String? {
|
||||||
|
switch verificationState {
|
||||||
|
case .initial:
|
||||||
|
return ElementL10n.verificationOpenOtherToVerifyTitle
|
||||||
|
case .requestingVerification:
|
||||||
|
return ElementL10n.verificationRequestWaitingAcceptRequestTitle
|
||||||
|
case .verificationRequestAccepted:
|
||||||
|
return ElementL10n.verificationCompareEmojisTitle
|
||||||
|
case .startingSasVerification:
|
||||||
|
return nil
|
||||||
|
case .sasVerificationStarted:
|
||||||
|
return nil
|
||||||
|
case .showingChallenge:
|
||||||
|
return ElementL10n.verificationCompareEmojisTitle
|
||||||
|
case .acceptingChallenge:
|
||||||
|
return ElementL10n.verificationCompareEmojisTitle
|
||||||
|
case .decliningChallenge:
|
||||||
|
return nil
|
||||||
|
case .verified:
|
||||||
|
return ElementL10n.verificationConclusionOkSelfNoticeTitle
|
||||||
|
case .cancelling:
|
||||||
|
return nil
|
||||||
|
case .cancelled:
|
||||||
|
return ElementL10n.verificationCancelled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
var message: String {
|
var message: String {
|
||||||
switch verificationState {
|
switch verificationState {
|
||||||
case .initial:
|
case .initial:
|
||||||
return ElementL10n.verificationOpenOtherToVerify
|
return ElementL10n.verificationOpenOtherToVerifyDetail
|
||||||
case .requestingVerification:
|
case .requestingVerification:
|
||||||
return ElementL10n.verificationRequestWaiting
|
return ElementL10n.verificationRequestWaitingAcceptRequestDetail
|
||||||
case .verificationRequestAccepted:
|
case .verificationRequestAccepted:
|
||||||
return ElementL10n.verificationEmojiNotice
|
return ElementL10n.verificationEmojiNotice
|
||||||
case .startingSasVerification:
|
case .startingSasVerification:
|
||||||
@@ -36,17 +63,17 @@ struct SessionVerificationViewState: BindableState {
|
|||||||
case .sasVerificationStarted:
|
case .sasVerificationStarted:
|
||||||
return ElementL10n.verificationRequestWaiting
|
return ElementL10n.verificationRequestWaiting
|
||||||
case .acceptingChallenge:
|
case .acceptingChallenge:
|
||||||
return ElementL10n.verificationRequestWaiting
|
return ElementL10n.verificationCompareEmojisDetail
|
||||||
case .decliningChallenge:
|
case .decliningChallenge:
|
||||||
return ElementL10n.verificationRequestWaiting
|
return ElementL10n.verificationRequestWaiting
|
||||||
case .cancelling:
|
case .cancelling:
|
||||||
return ElementL10n.verificationRequestWaiting
|
return ElementL10n.verificationRequestWaiting
|
||||||
case .showingChallenge:
|
case .showingChallenge:
|
||||||
return ElementL10n.verificationCodeNotice
|
return ElementL10n.verificationCompareEmojisDetail
|
||||||
case .verified:
|
case .verified:
|
||||||
return ElementL10n.verificationConclusionOkSelfNotice
|
return ElementL10n.verificationConclusionOkSelfNotice
|
||||||
case .cancelled:
|
case .cancelled:
|
||||||
return ElementL10n.verificationCancelled
|
return ElementL10n.verificationRequestCancelledDetail
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -106,6 +106,12 @@ class SessionVerificationViewModel: SessionVerificationViewModelType, SessionVer
|
|||||||
self.declineChallenge()
|
self.declineChallenge()
|
||||||
case (_, .cancel, .cancelling):
|
case (_, .cancel, .cancelling):
|
||||||
self.cancelVerification()
|
self.cancelVerification()
|
||||||
|
case (_, _, .verified):
|
||||||
|
// Dismiss the success screen automatically.
|
||||||
|
Task {
|
||||||
|
try await Task.sleep(for: .seconds(2))
|
||||||
|
self.callback?(.finished)
|
||||||
|
}
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,23 +22,18 @@ struct SessionVerificationScreen: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationView {
|
NavigationView {
|
||||||
ScrollView {
|
ScrollView {
|
||||||
VStack(spacing: 32.0) {
|
VStack(spacing: 32) {
|
||||||
Text(context.viewState.message)
|
screenHeader
|
||||||
.font(.body)
|
Spacer()
|
||||||
.multilineTextAlignment(.center)
|
|
||||||
.foregroundColor(.element.systemPrimaryLabel)
|
|
||||||
.accessibilityIdentifier("titleLabel")
|
|
||||||
|
|
||||||
mainContent
|
mainContent
|
||||||
}
|
}
|
||||||
.padding()
|
.padding(.horizontal, 16)
|
||||||
.padding(.top, 64)
|
.padding(.top, 24)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.navigationTitle(ElementL10n.verificationProfileVerify)
|
|
||||||
.toolbar { toolbarContent }
|
.toolbar { toolbarContent }
|
||||||
}
|
}
|
||||||
.background(Color.element.systemSecondaryBackground)
|
.background(Color.element.background)
|
||||||
.safeAreaInset(edge: .bottom) { actionButtons.padding() }
|
.safeAreaInset(edge: .bottom) { actionButtons.padding() }
|
||||||
}
|
}
|
||||||
.navigationViewStyle(.stack)
|
.navigationViewStyle(.stack)
|
||||||
@@ -46,39 +41,73 @@ struct SessionVerificationScreen: View {
|
|||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
|
private var headerImageName: String {
|
||||||
|
switch context.viewState.verificationState {
|
||||||
|
case .initial:
|
||||||
|
return "macbook.and.iphone"
|
||||||
|
case .cancelled:
|
||||||
|
return "exclamationmark.shield"
|
||||||
|
case .requestingVerification:
|
||||||
|
return "hourglass"
|
||||||
|
case .verificationRequestAccepted:
|
||||||
|
return "face.smiling"
|
||||||
|
case .startingSasVerification:
|
||||||
|
return "hourglass"
|
||||||
|
case .sasVerificationStarted:
|
||||||
|
return "hourglass"
|
||||||
|
case .cancelling:
|
||||||
|
return "hourglass"
|
||||||
|
case .acceptingChallenge:
|
||||||
|
return "hourglass"
|
||||||
|
case .decliningChallenge:
|
||||||
|
return "hourglass"
|
||||||
|
case .showingChallenge:
|
||||||
|
return "face.smiling"
|
||||||
|
case .verified:
|
||||||
|
return "checkmark.shield"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
private var screenHeader: some View {
|
||||||
|
VStack(spacing: 0) {
|
||||||
|
AuthenticationIconImage(image: Image(systemName: headerImageName))
|
||||||
|
.padding(.bottom, 16)
|
||||||
|
|
||||||
|
Text(context.viewState.title ?? "")
|
||||||
|
.font(.title2.bold())
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
.foregroundColor(.element.primaryContent)
|
||||||
|
.accessibilityIdentifier("titleLabel")
|
||||||
|
.padding(.bottom, 8)
|
||||||
|
|
||||||
|
Text(context.viewState.message)
|
||||||
|
.font(.subheadline)
|
||||||
|
.multilineTextAlignment(.center)
|
||||||
|
.foregroundColor(.element.tertiaryContent)
|
||||||
|
.accessibilityIdentifier("detailLabel")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
private var mainContent: some View {
|
private var mainContent: some View {
|
||||||
switch context.viewState.verificationState {
|
switch context.viewState.verificationState {
|
||||||
case .initial:
|
case .showingChallenge(let emojis):
|
||||||
StateIcon(systemName: "lock.shield")
|
emojisPanel(with: emojis)
|
||||||
|
case .acceptingChallenge(let emojis):
|
||||||
case .cancelled:
|
emojisPanel(with: emojis)
|
||||||
StateIcon(systemName: "xmark.shield")
|
|
||||||
.accessibilityIdentifier("sessionVerificationFailedIcon")
|
|
||||||
|
|
||||||
case .requestingVerification:
|
case .requestingVerification:
|
||||||
ProgressView()
|
ProgressView()
|
||||||
.accessibilityIdentifier("requestingVerificationProgressView")
|
.tint(.element.secondaryContent)
|
||||||
|
.scaleEffect(2)
|
||||||
|
default:
|
||||||
|
// In All other cases, we just want an empty view
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case .verificationRequestAccepted:
|
@ViewBuilder
|
||||||
StateIcon(systemName: "lock.shield")
|
private func emojisPanel(with emojis: [SessionVerificationEmoji]) -> some View {
|
||||||
case .startingSasVerification:
|
|
||||||
ProgressView()
|
|
||||||
.accessibilityIdentifier("startingSasVerification")
|
|
||||||
case .sasVerificationStarted:
|
|
||||||
ProgressView()
|
|
||||||
.accessibilityIdentifier("startedSasVerification")
|
|
||||||
case .cancelling:
|
|
||||||
ProgressView()
|
|
||||||
.accessibilityIdentifier("cancellingVerificationProgressView")
|
|
||||||
case .acceptingChallenge:
|
|
||||||
ProgressView()
|
|
||||||
.accessibilityIdentifier("acceptingChallengeProgressView")
|
|
||||||
case .decliningChallenge:
|
|
||||||
ProgressView()
|
|
||||||
.accessibilityIdentifier("decliningChallengeProgressView")
|
|
||||||
|
|
||||||
case .showingChallenge(let emojis):
|
|
||||||
HStack(spacing: 16) {
|
HStack(spacing: 16) {
|
||||||
ForEach(emojis.prefix(4), id: \.self) { emoji in
|
ForEach(emojis.prefix(4), id: \.self) { emoji in
|
||||||
EmojiView(emoji: emoji)
|
EmojiView(emoji: emoji)
|
||||||
@@ -89,10 +118,6 @@ struct SessionVerificationScreen: View {
|
|||||||
EmojiView(emoji: emoji)
|
EmojiView(emoji: emoji)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
case .verified:
|
|
||||||
StateIcon(systemName: "checkmark.shield")
|
|
||||||
.accessibilityIdentifier("sessionVerificationSucceededIcon")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
@@ -113,7 +138,7 @@ struct SessionVerificationScreen: View {
|
|||||||
.accessibilityIdentifier("restartButton")
|
.accessibilityIdentifier("restartButton")
|
||||||
|
|
||||||
case .verificationRequestAccepted:
|
case .verificationRequestAccepted:
|
||||||
Button(ElementL10n.startVerification) {
|
Button(ElementL10n.sessionVerificationStart) {
|
||||||
context.send(viewAction: .startSasVerification)
|
context.send(viewAction: .startSasVerification)
|
||||||
}
|
}
|
||||||
.buttonStyle(.elementAction(.xLarge))
|
.buttonStyle(.elementAction(.xLarge))
|
||||||
@@ -134,12 +159,26 @@ struct SessionVerificationScreen: View {
|
|||||||
.accessibilityIdentifier("challengeDeclineButton")
|
.accessibilityIdentifier("challengeDeclineButton")
|
||||||
}
|
}
|
||||||
|
|
||||||
case .verified:
|
case .acceptingChallenge:
|
||||||
Button(ElementL10n.finish) {
|
VStack(spacing: 30) {
|
||||||
context.send(viewAction: .close)
|
Button { context.send(viewAction: .accept) } label: {
|
||||||
|
HStack(spacing: 16) {
|
||||||
|
ProgressView()
|
||||||
|
.tint(.element.background)
|
||||||
|
Label(ElementL10n.verificationSasMatch, systemImage: "checkmark")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.buttonStyle(.elementAction(.xLarge))
|
.buttonStyle(.elementAction(.xLarge))
|
||||||
.accessibilityIdentifier("finishButton")
|
.accessibilityIdentifier("challengeAcceptButton")
|
||||||
|
.disabled(true)
|
||||||
|
|
||||||
|
Button(ElementL10n.verificationSasDoNotMatch) {
|
||||||
|
context.send(viewAction: .decline)
|
||||||
|
}
|
||||||
|
.font(.element.bodyBold)
|
||||||
|
.accessibilityIdentifier("challengeDeclineButton")
|
||||||
|
.disabled(true)
|
||||||
|
}
|
||||||
|
|
||||||
default:
|
default:
|
||||||
EmptyView()
|
EmptyView()
|
||||||
@@ -149,11 +188,10 @@ struct SessionVerificationScreen: View {
|
|||||||
@ToolbarContentBuilder
|
@ToolbarContentBuilder
|
||||||
private var toolbarContent: some ToolbarContent {
|
private var toolbarContent: some ToolbarContent {
|
||||||
ToolbarItem(placement: .cancellationAction) {
|
ToolbarItem(placement: .cancellationAction) {
|
||||||
Button { context.send(viewAction: .close) } label: {
|
Button(ElementL10n.actionCancel) {
|
||||||
Image(systemName: "xmark")
|
context.send(viewAction: .close)
|
||||||
}
|
}
|
||||||
.font(.element.bodyBold)
|
.foregroundColor(.element.accent)
|
||||||
.foregroundColor(.element.systemSecondaryLabel)
|
|
||||||
.accessibilityIdentifier("closeButton")
|
.accessibilityIdentifier("closeButton")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -166,8 +204,8 @@ struct SessionVerificationScreen: View {
|
|||||||
Text(emoji.symbol)
|
Text(emoji.symbol)
|
||||||
.font(.element.largeTitleBold)
|
.font(.element.largeTitleBold)
|
||||||
Text(emoji.description)
|
Text(emoji.description)
|
||||||
.font(.element.caption2)
|
.font(.element.subheadline)
|
||||||
.foregroundColor(.element.systemSecondaryLabel)
|
.foregroundColor(.element.secondaryContent)
|
||||||
}
|
}
|
||||||
.padding(8.0)
|
.padding(8.0)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ final class SettingsScreenCoordinator: CoordinatorProtocol {
|
|||||||
self.toggleAnalytics()
|
self.toggleAnalytics()
|
||||||
case .reportBug:
|
case .reportBug:
|
||||||
self.presentBugReportScreen()
|
self.presentBugReportScreen()
|
||||||
|
case .sessionVerification:
|
||||||
|
self.verifySession()
|
||||||
case .logout:
|
case .logout:
|
||||||
self.callback?(.logout)
|
self.callback?(.logout)
|
||||||
}
|
}
|
||||||
@@ -92,6 +94,23 @@ final class SettingsScreenCoordinator: CoordinatorProtocol {
|
|||||||
parameters.navigationStackCoordinator.push(coordinator)
|
parameters.navigationStackCoordinator.push(coordinator)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func verifySession() {
|
||||||
|
guard let sessionVerificationController = parameters.userSession.sessionVerificationController else {
|
||||||
|
fatalError("The sessionVerificationController should aways be valid at this point")
|
||||||
|
}
|
||||||
|
|
||||||
|
let verificationParameters = SessionVerificationCoordinatorParameters(sessionVerificationControllerProxy: sessionVerificationController)
|
||||||
|
let coordinator = SessionVerificationCoordinator(parameters: verificationParameters)
|
||||||
|
|
||||||
|
coordinator.callback = { [weak self] in
|
||||||
|
self?.parameters.navigationStackCoordinator.setSheetCoordinator(nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
parameters.navigationStackCoordinator.setSheetCoordinator(coordinator) { [weak self] in
|
||||||
|
self?.parameters.navigationStackCoordinator.setSheetCoordinator(nil)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private func showSuccess(label: String) {
|
private func showSuccess(label: String) {
|
||||||
parameters.userNotificationController.submitNotification(UserNotification(title: label))
|
parameters.userNotificationController.submitNotification(UserNotification(title: label))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ enum SettingsScreenViewModelAction {
|
|||||||
case close
|
case close
|
||||||
case toggleAnalytics
|
case toggleAnalytics
|
||||||
case reportBug
|
case reportBug
|
||||||
|
case sessionVerification
|
||||||
case logout
|
case logout
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,6 +31,7 @@ struct SettingsScreenViewState: BindableState {
|
|||||||
var userID: String
|
var userID: String
|
||||||
var userAvatarURL: URL?
|
var userAvatarURL: URL?
|
||||||
var userDisplayName: String?
|
var userDisplayName: String?
|
||||||
|
var showSessionVerificationSection: Bool
|
||||||
}
|
}
|
||||||
|
|
||||||
struct SettingsScreenViewStateBindings {
|
struct SettingsScreenViewStateBindings {
|
||||||
@@ -40,5 +42,6 @@ enum SettingsScreenViewAction {
|
|||||||
case close
|
case close
|
||||||
case toggleAnalytics
|
case toggleAnalytics
|
||||||
case reportBug
|
case reportBug
|
||||||
|
case sessionVerification
|
||||||
case logout
|
case logout
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,10 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
|
|||||||
init(withUserSession userSession: UserSessionProtocol) {
|
init(withUserSession userSession: UserSessionProtocol) {
|
||||||
self.userSession = userSession
|
self.userSession = userSession
|
||||||
let bindings = SettingsScreenViewStateBindings()
|
let bindings = SettingsScreenViewStateBindings()
|
||||||
super.init(initialViewState: .init(bindings: bindings, deviceID: userSession.deviceId, userID: userSession.userID),
|
super.init(initialViewState: .init(bindings: bindings,
|
||||||
|
deviceID: userSession.deviceId,
|
||||||
|
userID: userSession.userID,
|
||||||
|
showSessionVerificationSection: !(userSession.sessionVerificationController?.isVerified ?? false)),
|
||||||
imageProvider: userSession.mediaProvider)
|
imageProvider: userSession.mediaProvider)
|
||||||
|
|
||||||
Task {
|
Task {
|
||||||
@@ -40,6 +43,20 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
|
|||||||
state.userDisplayName = userDisplayName
|
state.userDisplayName = userDisplayName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
userSession.callbacks
|
||||||
|
.receive(on: DispatchQueue.main)
|
||||||
|
.sink { [weak self] callback in
|
||||||
|
switch callback {
|
||||||
|
case .sessionVerificationNeeded:
|
||||||
|
self?.state.showSessionVerificationSection = true
|
||||||
|
case .didVerifySession:
|
||||||
|
self?.state.showSessionVerificationSection = false
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func process(viewAction: SettingsScreenViewAction) async {
|
override func process(viewAction: SettingsScreenViewAction) async {
|
||||||
@@ -52,6 +69,8 @@ class SettingsScreenViewModel: SettingsScreenViewModelType, SettingsScreenViewMo
|
|||||||
callback?(.reportBug)
|
callback?(.reportBug)
|
||||||
case .logout:
|
case .logout:
|
||||||
callback?(.logout)
|
callback?(.logout)
|
||||||
|
case .sessionVerification:
|
||||||
|
callback?(.sessionVerification)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -32,17 +32,16 @@ struct SettingsScreen: View {
|
|||||||
userSection
|
userSection
|
||||||
.listRowBackground(rowBackgroundColor)
|
.listRowBackground(rowBackgroundColor)
|
||||||
|
|
||||||
|
if context.viewState.showSessionVerificationSection {
|
||||||
|
sessionVerificationSection
|
||||||
|
.listRowBackground(rowBackgroundColor)
|
||||||
|
}
|
||||||
|
|
||||||
simplifiedSection
|
simplifiedSection
|
||||||
.listRowBackground(rowBackgroundColor)
|
.listRowBackground(rowBackgroundColor)
|
||||||
|
|
||||||
// analyticsSection
|
signOutSection
|
||||||
// .listRowBackground(rowBackgroundColor)
|
.listRowBackground(rowBackgroundColor)
|
||||||
|
|
||||||
// userInterfaceSection
|
|
||||||
// .listRowBackground(rowBackgroundColor)
|
|
||||||
|
|
||||||
// logoutSection
|
|
||||||
// .listRowBackground(rowBackgroundColor)
|
|
||||||
}
|
}
|
||||||
.introspectTableView { tableView in
|
.introspectTableView { tableView in
|
||||||
tableView.backgroundColor = .clear
|
tableView.backgroundColor = .clear
|
||||||
@@ -80,10 +79,10 @@ struct SettingsScreen: View {
|
|||||||
|
|
||||||
VStack(alignment: .leading, spacing: 4) {
|
VStack(alignment: .leading, spacing: 4) {
|
||||||
Text(context.viewState.userDisplayName ?? "")
|
Text(context.viewState.userDisplayName ?? "")
|
||||||
.font(.title3)
|
.font(.element.title3)
|
||||||
.foregroundColor(.element.primaryContent)
|
.foregroundColor(.element.primaryContent)
|
||||||
Text(context.viewState.userID)
|
Text(context.viewState.userID)
|
||||||
.font(.subheadline)
|
.font(.element.subheadline)
|
||||||
.foregroundColor(.element.primaryContent)
|
.foregroundColor(.element.primaryContent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -91,14 +90,38 @@ struct SettingsScreen: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private var sessionVerificationSection: some View {
|
||||||
|
Section {
|
||||||
|
SettingsDefaultRow(title: ElementL10n.settingsSessionVerification,
|
||||||
|
image: Image(systemName: "checkmark.shield")) {
|
||||||
|
context.send(viewAction: .sessionVerification)
|
||||||
|
}
|
||||||
|
.accessibilityIdentifier("sessionVerificationButton")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private var simplifiedSection: some View {
|
private var simplifiedSection: some View {
|
||||||
Section {
|
Section {
|
||||||
|
SettingsPickerRow(title: ElementL10n.settingsTimelineStyle,
|
||||||
|
image: Image(systemName: "rectangle.grid.1x2"),
|
||||||
|
selection: $settings.timelineStyle) {
|
||||||
|
ForEach(TimelineStyle.allCases, id: \.self) { style in
|
||||||
|
Text(style.description)
|
||||||
|
.tag(style)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.accessibilityIdentifier("timelineStylePicker")
|
||||||
|
|
||||||
SettingsDefaultRow(title: ElementL10n.sendBugReport,
|
SettingsDefaultRow(title: ElementL10n.sendBugReport,
|
||||||
image: Image(systemName: "questionmark.circle")) {
|
image: Image(systemName: "questionmark.circle")) {
|
||||||
context.send(viewAction: .reportBug)
|
context.send(viewAction: .reportBug)
|
||||||
}
|
}
|
||||||
.accessibilityIdentifier("reportBugButton")
|
.accessibilityIdentifier("reportBugButton")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private var signOutSection: some View {
|
||||||
|
Section {
|
||||||
SettingsDefaultRow(title: ElementL10n.actionSignOut,
|
SettingsDefaultRow(title: ElementL10n.actionSignOut,
|
||||||
image: Image(systemName: "rectangle.portrait.and.arrow.right")) {
|
image: Image(systemName: "rectangle.portrait.and.arrow.right")) {
|
||||||
showingLogoutConfirmation = true
|
showingLogoutConfirmation = true
|
||||||
@@ -112,76 +135,20 @@ struct SettingsScreen: View {
|
|||||||
} message: {
|
} message: {
|
||||||
Text(ElementL10n.actionSignOutConfirmationSimple)
|
Text(ElementL10n.actionSignOutConfirmationSimple)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var analyticsSection: some View {
|
|
||||||
Section {
|
|
||||||
Button { context.send(viewAction: .reportBug) } label: {
|
|
||||||
HStack {
|
|
||||||
Text(ElementL10n.sendBugReport)
|
|
||||||
Spacer()
|
|
||||||
Image(systemName: "chevron.forward")
|
|
||||||
.foregroundColor(.element.tertiaryContent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.listRowInsets(listRowInsets)
|
|
||||||
.listRowSeparator(.hidden)
|
|
||||||
.foregroundColor(.element.primaryContent)
|
|
||||||
.accessibilityIdentifier("reportBugButton")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ViewBuilder
|
|
||||||
private var userInterfaceSection: some View {
|
|
||||||
Section {
|
|
||||||
Picker(ElementL10n.settingsTimelineStyle, selection: $settings.timelineStyle) {
|
|
||||||
ForEach(TimelineStyle.allCases, id: \.self) { style in
|
|
||||||
Text(style.description)
|
|
||||||
.tag(style)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.listRowInsets(listRowInsets)
|
|
||||||
.accessibilityIdentifier("timelineStylePicker")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private var logoutSection: some View {
|
|
||||||
Section {
|
|
||||||
Button { showingLogoutConfirmation = true } label: {
|
|
||||||
HStack {
|
|
||||||
Image(systemName: "rectangle.portrait.and.arrow.right")
|
|
||||||
.foregroundColor(.element.systemGray)
|
|
||||||
.padding(4)
|
|
||||||
.background(Color.element.systemGray6)
|
|
||||||
.clipShape(Circle())
|
|
||||||
.frame(width: menuIconSize, height: menuIconSize)
|
|
||||||
Text(ElementL10n.actionSignOut)
|
|
||||||
Spacer()
|
|
||||||
Image(systemName: "chevron.forward")
|
|
||||||
.foregroundColor(.element.tertiaryContent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.listRowInsets(listRowInsets)
|
|
||||||
.foregroundColor(.element.primaryContent)
|
|
||||||
.accessibilityIdentifier("logoutButton")
|
|
||||||
.alert(ElementL10n.actionSignOut,
|
|
||||||
isPresented: $showingLogoutConfirmation) {
|
|
||||||
Button(ElementL10n.actionSignOut,
|
|
||||||
role: .destructive,
|
|
||||||
action: logout)
|
|
||||||
} message: {
|
|
||||||
Text(ElementL10n.actionSignOutConfirmationSimple)
|
|
||||||
}
|
|
||||||
} footer: {
|
} footer: {
|
||||||
VStack {
|
VStack {
|
||||||
versionText
|
versionText
|
||||||
|
.font(.element.caption1)
|
||||||
|
.foregroundColor(.element.tertiaryContent)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
|
|
||||||
if let deviceId = context.viewState.deviceID {
|
if let deviceId = context.viewState.deviceID {
|
||||||
Text(deviceId)
|
Text(deviceId)
|
||||||
|
.font(.element.caption1)
|
||||||
|
.foregroundColor(.element.tertiaryContent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.padding(.top, 24)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user