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:
Gil Eluard
2023-01-30 12:51:07 +01:00
committed by GitHub
parent f29c0a9811
commit e2e69534e3
19 changed files with 412 additions and 192 deletions
@@ -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)
+4
View File
@@ -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
+1 -1
View File
@@ -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,31 +123,35 @@ 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) HStack(spacing: 16) {
.font(.element.subheadlineBold) Text(ElementL10n.sessionVerificationBannerTitle)
.foregroundColor(.element.systemPrimaryLabel) .font(.element.headline)
.foregroundColor(.element.systemPrimaryLabel)
Spacer()
Button {
context.send(viewAction: .skipSessionVerification)
} label: {
Image(systemName: "xmark")
.foregroundColor(.element.secondaryContent)
.frame(width: 12, height: 12)
}
}
Text(ElementL10n.sessionVerificationBannerMessage) Text(ElementL10n.sessionVerificationBannerMessage)
.font(.element.footnote) .font(.element.subheadline)
.foregroundColor(.element.systemSecondaryLabel) .foregroundColor(.element.secondaryContent)
} }
HStack(spacing: 16) { Button(ElementL10n.continue) {
Button(ElementL10n.actionSkip) { context.send(viewAction: .verifySession)
context.send(viewAction: .skipSessionVerification)
}
.frame(maxWidth: .infinity)
.buttonStyle(.elementCapsule)
Button(ElementL10n.continue) {
context.send(viewAction: .verifySession)
}
.frame(maxWidth: .infinity)
.buttonStyle(.elementCapsuleProminent)
} }
.frame(maxWidth: .infinity)
.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)
) )
@@ -22,13 +22,40 @@ 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,52 +41,82 @@ 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)
case .verificationRequestAccepted: default:
StateIcon(systemName: "lock.shield") // In All other cases, we just want an empty view
case .startingSasVerification: EmptyView()
ProgressView() }
.accessibilityIdentifier("startingSasVerification") }
case .sasVerificationStarted:
ProgressView() @ViewBuilder
.accessibilityIdentifier("startedSasVerification") private func emojisPanel(with emojis: [SessionVerificationEmoji]) -> some View {
case .cancelling: HStack(spacing: 16) {
ProgressView() ForEach(emojis.prefix(4), id: \.self) { emoji in
.accessibilityIdentifier("cancellingVerificationProgressView") EmojiView(emoji: emoji)
case .acceptingChallenge:
ProgressView()
.accessibilityIdentifier("acceptingChallengeProgressView")
case .decliningChallenge:
ProgressView()
.accessibilityIdentifier("decliningChallengeProgressView")
case .showingChallenge(let emojis):
HStack(spacing: 16) {
ForEach(emojis.prefix(4), id: \.self) { emoji in
EmojiView(emoji: emoji)
}
} }
HStack(spacing: 16) { }
ForEach(emojis.suffix(from: 4), id: \.self) { emoji in HStack(spacing: 16) {
EmojiView(emoji: emoji) ForEach(emojis.suffix(from: 4), id: \.self) { emoji in
} EmojiView(emoji: emoji)
} }
case .verified:
StateIcon(systemName: "checkmark.shield")
.accessibilityIdentifier("sessionVerificationSucceededIcon")
} }
} }
@@ -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))
@@ -133,14 +158,28 @@ struct SessionVerificationScreen: View {
.font(.element.bodyBold) .font(.element.bodyBold)
.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))
.accessibilityIdentifier("challengeAcceptButton")
.disabled(true)
Button(ElementL10n.verificationSasDoNotMatch) {
context.send(viewAction: .decline)
}
.font(.element.bodyBold)
.accessibilityIdentifier("challengeDeclineButton")
.disabled(true)
} }
.buttonStyle(.elementAction(.xLarge))
.accessibilityIdentifier("finishButton")
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)
} }
@@ -91,6 +93,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)
} }
} }
@@ -31,7 +31,7 @@ class UserSessionFlowCoordinator: CoordinatorProtocol {
private let sidebarNavigationStackCoordinator: NavigationStackCoordinator private let sidebarNavigationStackCoordinator: NavigationStackCoordinator
private let detailNavigationStackCoordinator: NavigationStackCoordinator private let detailNavigationStackCoordinator: NavigationStackCoordinator
var callback: ((UserSessionFlowCoordinatorAction) -> Void)? var callback: ((UserSessionFlowCoordinatorAction) -> Void)?
init(userSession: UserSessionProtocol, init(userSession: UserSessionProtocol,
@@ -91,7 +91,7 @@ class UserSessionFlowCoordinator: CoordinatorProtocol {
self.presentSessionVerification() self.presentSessionVerification()
case (.sessionVerificationScreen, .dismissedSessionVerificationScreen, .roomList): case (.sessionVerificationScreen, .dismissedSessionVerificationScreen, .roomList):
break break
case (.roomList, .showSettingsScreen, .settingsScreen): case (.roomList, .showSettingsScreen, .settingsScreen):
self.presentSettingsScreen() self.presentSettingsScreen()
case (.settingsScreen, .dismissedSettingsScreen, .roomList): case (.settingsScreen, .dismissedSettingsScreen, .roomList):
@@ -28,7 +28,7 @@ class UserSessionFlowCoordinatorStateMachine {
/// Showing the session verification flows /// Showing the session verification flows
case sessionVerificationScreen(selectedRoomId: String?) case sessionVerificationScreen(selectedRoomId: String?)
/// Showing the session verification flows /// Showing the session verification flows
case feedbackScreen(selectedRoomId: String?) case feedbackScreen(selectedRoomId: String?)