mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-05 11:58:32 +00:00
Reset Recovery Key Screen (#2648)
Co-authored-by: Doug <6060466+pixlwave@users.noreply.github.com>
This commit is contained in:
@@ -156,7 +156,7 @@
|
||||
"common_privacy_policy" = "Privacy policy";
|
||||
"common_reaction" = "Reaction";
|
||||
"common_reactions" = "Reactions";
|
||||
"common_recovery_key" = "Recovery key";
|
||||
"common_recovery_key" = "Recovery key or passcode";
|
||||
"common_refreshing" = "Refreshing…";
|
||||
"common_replying_to" = "Replying to %1$@";
|
||||
"common_report_a_bug" = "Report a bug";
|
||||
@@ -374,6 +374,13 @@
|
||||
"screen_chat_backup_recovery_action_confirm_description" = "Your chat backup is currently out of sync.";
|
||||
"screen_chat_backup_recovery_action_setup" = "Set up recovery";
|
||||
"screen_chat_backup_recovery_action_setup_description" = "Get access to your encrypted messages if you lose all your devices or are signed out of %1$@ everywhere.";
|
||||
"screen_create_new_recovery_key_list_item_1" = "Open Element in a desktop device";
|
||||
"screen_create_new_recovery_key_list_item_2" = "Sign into your account again";
|
||||
"screen_create_new_recovery_key_list_item_3" = "When asked to verify your device, select %1$@";
|
||||
"screen_create_new_recovery_key_list_item_3_reset_all" = "“Reset all”";
|
||||
"screen_create_new_recovery_key_list_item_4" = "Follow the instructions to create a new recovery key";
|
||||
"screen_create_new_recovery_key_list_item_5" = "Save your new recovery key in a password manager or encrypted note";
|
||||
"screen_create_new_recovery_key_title" = "Reset the encryption for your account using another device";
|
||||
"screen_create_poll_add_option_btn" = "Add option";
|
||||
"screen_create_poll_anonymous_desc" = "Show results only after poll ends";
|
||||
"screen_create_poll_anonymous_headline" = "Hide votes";
|
||||
@@ -398,6 +405,7 @@
|
||||
"screen_edit_profile_error_title" = "Unable to update profile";
|
||||
"screen_edit_profile_title" = "Edit profile";
|
||||
"screen_edit_profile_updating_details" = "Updating profile…";
|
||||
"screen_identity_confirmation_create_new_recovery_key" = "Create a new recovery key";
|
||||
"screen_identity_confirmation_subtitle" = "Verify this device to set up secure messaging.";
|
||||
"screen_identity_confirmation_title" = "Confirm that it's you";
|
||||
"screen_identity_confirmed_subtitle" = "Now you can read or send messages securely, and anyone you chat with can also trust this device.";
|
||||
@@ -473,13 +481,14 @@
|
||||
"screen_recovery_key_change_generate_key_description" = "Make sure you can store your recovery key somewhere safe";
|
||||
"screen_recovery_key_change_success" = "Recovery key changed";
|
||||
"screen_recovery_key_change_title" = "Change recovery key?";
|
||||
"screen_recovery_key_confirm_description" = "Enter your recovery key to confirm access to your chat backup.";
|
||||
"screen_recovery_key_confirm_create_new_recovery_key" = "Create new recovery key";
|
||||
"screen_recovery_key_confirm_description" = "Make sure nobody can see this screen!";
|
||||
"screen_recovery_key_confirm_error_content" = "Please try again to confirm access to your chat backup.";
|
||||
"screen_recovery_key_confirm_error_title" = "Incorrect recovery key";
|
||||
"screen_recovery_key_confirm_key_description" = "Enter the 48 character code.";
|
||||
"screen_recovery_key_confirm_key_description" = "If you have a recovery passphrase or secret passphrase/key, this will work too.";
|
||||
"screen_recovery_key_confirm_key_placeholder" = "Enter…";
|
||||
"screen_recovery_key_confirm_success" = "Recovery key confirmed";
|
||||
"screen_recovery_key_confirm_title" = "Enter your recovery key";
|
||||
"screen_recovery_key_confirm_title" = "Enter your recovery key or passcode";
|
||||
"screen_recovery_key_copied_to_clipboard" = "Copied recovery key";
|
||||
"screen_recovery_key_generating_key" = "Generating…";
|
||||
"screen_recovery_key_save_action" = "Save recovery key";
|
||||
|
||||
@@ -258,6 +258,8 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol {
|
||||
case .recoveryFixed:
|
||||
appSettings.hasRunIdentityConfirmationOnboarding = true
|
||||
stateMachine.tryEvent(.next)
|
||||
case .showResetKeyInfo:
|
||||
presentResetRecoveryKeyScreen()
|
||||
default:
|
||||
fatalError("Other flows shouldn't be possible")
|
||||
}
|
||||
@@ -267,6 +269,18 @@ class OnboardingFlowCoordinator: FlowCoordinatorProtocol {
|
||||
presentCoordinator(coordinator)
|
||||
}
|
||||
|
||||
private func presentResetRecoveryKeyScreen() {
|
||||
let coordinator = ResetRecoveryKeyScreenCoordinator()
|
||||
coordinator.actionsPublisher.sink { [weak self] action in
|
||||
switch action {
|
||||
case .cancel:
|
||||
self?.navigationStackCoordinator.setSheetCoordinator(nil)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
navigationStackCoordinator.setSheetCoordinator(coordinator)
|
||||
}
|
||||
|
||||
private func presentIdentityConfirmedScreen() {
|
||||
let coordinator = IdentityConfirmedScreenCoordinator(parameters: .init())
|
||||
coordinator.actionsPublisher
|
||||
|
||||
@@ -364,7 +364,7 @@ internal enum L10n {
|
||||
internal static var commonReaction: String { return L10n.tr("Localizable", "common_reaction") }
|
||||
/// Reactions
|
||||
internal static var commonReactions: String { return L10n.tr("Localizable", "common_reactions") }
|
||||
/// Recovery key
|
||||
/// Recovery key or passcode
|
||||
internal static var commonRecoveryKey: String { return L10n.tr("Localizable", "common_recovery_key") }
|
||||
/// Refreshing…
|
||||
internal static var commonRefreshing: String { return L10n.tr("Localizable", "common_refreshing") }
|
||||
@@ -919,6 +919,22 @@ internal enum L10n {
|
||||
internal static func screenChatBackupRecoveryActionSetupDescription(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "screen_chat_backup_recovery_action_setup_description", String(describing: p1))
|
||||
}
|
||||
/// Open Element in a desktop device
|
||||
internal static var screenCreateNewRecoveryKeyListItem1: String { return L10n.tr("Localizable", "screen_create_new_recovery_key_list_item_1") }
|
||||
/// Sign into your account again
|
||||
internal static var screenCreateNewRecoveryKeyListItem2: String { return L10n.tr("Localizable", "screen_create_new_recovery_key_list_item_2") }
|
||||
/// When asked to verify your device, select %1$@
|
||||
internal static func screenCreateNewRecoveryKeyListItem3(_ p1: Any) -> String {
|
||||
return L10n.tr("Localizable", "screen_create_new_recovery_key_list_item_3", String(describing: p1))
|
||||
}
|
||||
/// “Reset all”
|
||||
internal static var screenCreateNewRecoveryKeyListItem3ResetAll: String { return L10n.tr("Localizable", "screen_create_new_recovery_key_list_item_3_reset_all") }
|
||||
/// Follow the instructions to create a new recovery key
|
||||
internal static var screenCreateNewRecoveryKeyListItem4: String { return L10n.tr("Localizable", "screen_create_new_recovery_key_list_item_4") }
|
||||
/// Save your new recovery key in a password manager or encrypted note
|
||||
internal static var screenCreateNewRecoveryKeyListItem5: String { return L10n.tr("Localizable", "screen_create_new_recovery_key_list_item_5") }
|
||||
/// Reset the encryption for your account using another device
|
||||
internal static var screenCreateNewRecoveryKeyTitle: String { return L10n.tr("Localizable", "screen_create_new_recovery_key_title") }
|
||||
/// Add option
|
||||
internal static var screenCreatePollAddOptionBtn: String { return L10n.tr("Localizable", "screen_create_poll_add_option_btn") }
|
||||
/// Show results only after poll ends
|
||||
@@ -991,6 +1007,8 @@ internal enum L10n {
|
||||
internal static var screenEditProfileTitle: String { return L10n.tr("Localizable", "screen_edit_profile_title") }
|
||||
/// Updating profile…
|
||||
internal static var screenEditProfileUpdatingDetails: String { return L10n.tr("Localizable", "screen_edit_profile_updating_details") }
|
||||
/// Create a new recovery key
|
||||
internal static var screenIdentityConfirmationCreateNewRecoveryKey: String { return L10n.tr("Localizable", "screen_identity_confirmation_create_new_recovery_key") }
|
||||
/// Verify this device to set up secure messaging.
|
||||
internal static var screenIdentityConfirmationSubtitle: String { return L10n.tr("Localizable", "screen_identity_confirmation_subtitle") }
|
||||
/// Confirm that it's you
|
||||
@@ -1159,19 +1177,21 @@ internal enum L10n {
|
||||
internal static var screenRecoveryKeyChangeSuccess: String { return L10n.tr("Localizable", "screen_recovery_key_change_success") }
|
||||
/// Change recovery key?
|
||||
internal static var screenRecoveryKeyChangeTitle: String { return L10n.tr("Localizable", "screen_recovery_key_change_title") }
|
||||
/// Enter your recovery key to confirm access to your chat backup.
|
||||
/// Create new recovery key
|
||||
internal static var screenRecoveryKeyConfirmCreateNewRecoveryKey: String { return L10n.tr("Localizable", "screen_recovery_key_confirm_create_new_recovery_key") }
|
||||
/// Make sure nobody can see this screen!
|
||||
internal static var screenRecoveryKeyConfirmDescription: String { return L10n.tr("Localizable", "screen_recovery_key_confirm_description") }
|
||||
/// Please try again to confirm access to your chat backup.
|
||||
internal static var screenRecoveryKeyConfirmErrorContent: String { return L10n.tr("Localizable", "screen_recovery_key_confirm_error_content") }
|
||||
/// Incorrect recovery key
|
||||
internal static var screenRecoveryKeyConfirmErrorTitle: String { return L10n.tr("Localizable", "screen_recovery_key_confirm_error_title") }
|
||||
/// Enter the 48 character code.
|
||||
/// If you have a recovery passphrase or secret passphrase/key, this will work too.
|
||||
internal static var screenRecoveryKeyConfirmKeyDescription: String { return L10n.tr("Localizable", "screen_recovery_key_confirm_key_description") }
|
||||
/// Enter…
|
||||
internal static var screenRecoveryKeyConfirmKeyPlaceholder: String { return L10n.tr("Localizable", "screen_recovery_key_confirm_key_placeholder") }
|
||||
/// Recovery key confirmed
|
||||
internal static var screenRecoveryKeyConfirmSuccess: String { return L10n.tr("Localizable", "screen_recovery_key_confirm_success") }
|
||||
/// Enter your recovery key
|
||||
/// Enter your recovery key or passcode
|
||||
internal static var screenRecoveryKeyConfirmTitle: String { return L10n.tr("Localizable", "screen_recovery_key_confirm_title") }
|
||||
/// Copied recovery key
|
||||
internal static var screenRecoveryKeyCopiedToClipboard: String { return L10n.tr("Localizable", "screen_recovery_key_copied_to_clipboard") }
|
||||
|
||||
@@ -24,6 +24,7 @@ struct HeroImage: View {
|
||||
enum Style {
|
||||
case normal
|
||||
case positive
|
||||
case subtle
|
||||
|
||||
var foregroundColor: Color {
|
||||
switch self {
|
||||
@@ -31,6 +32,8 @@ struct HeroImage: View {
|
||||
return .compound.iconPrimary
|
||||
case .positive:
|
||||
return .compound.iconSuccessPrimary
|
||||
case .subtle:
|
||||
return .compound.iconSecondary
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,6 +43,8 @@ struct HeroImage: View {
|
||||
return .compound.bgSubtleSecondary
|
||||
case .positive:
|
||||
return .compound.bgSuccessSubtle
|
||||
case .subtle:
|
||||
return .compound.bgSubtlePrimary
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
// periphery:ignore:all - this is just a resetKey remove this comment once generating the final file
|
||||
|
||||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
enum ResetRecoveryKeyScreenCoordinatorAction {
|
||||
case cancel
|
||||
}
|
||||
|
||||
final class ResetRecoveryKeyScreenCoordinator: CoordinatorProtocol {
|
||||
private let viewModel: ResetRecoveryKeyScreenViewModelProtocol
|
||||
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
private let actionsSubject: PassthroughSubject<ResetRecoveryKeyScreenCoordinatorAction, Never> = .init()
|
||||
var actionsPublisher: AnyPublisher<ResetRecoveryKeyScreenCoordinatorAction, Never> {
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init() {
|
||||
viewModel = ResetRecoveryKeyScreenViewModel()
|
||||
}
|
||||
|
||||
func start() {
|
||||
viewModel.actionsPublisher.sink { [weak self] action in
|
||||
MXLog.info("Coordinator: received view model action: \(action)")
|
||||
|
||||
guard let self else { return }
|
||||
switch action {
|
||||
case .cancel:
|
||||
self.actionsSubject.send(.cancel)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
}
|
||||
|
||||
func toPresentable() -> AnyView {
|
||||
AnyView(ResetRecoveryKeyScreen(context: viewModel.context))
|
||||
}
|
||||
}
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
enum ResetRecoveryKeyScreenViewModelAction {
|
||||
case cancel
|
||||
}
|
||||
|
||||
struct ResetRecoveryKeyScreenViewState: BindableState {
|
||||
let listItem3AttributedText = {
|
||||
let boldPlaceholder = "{bold}"
|
||||
var finalString = AttributedString(L10n.screenCreateNewRecoveryKeyListItem3(boldPlaceholder))
|
||||
var boldString = AttributedString(L10n.screenCreateNewRecoveryKeyListItem3ResetAll)
|
||||
boldString.bold()
|
||||
finalString.replace(boldPlaceholder, with: boldString)
|
||||
return finalString
|
||||
}()
|
||||
}
|
||||
|
||||
enum ResetRecoveryKeyScreenViewAction {
|
||||
case cancel
|
||||
}
|
||||
+40
@@ -0,0 +1,40 @@
|
||||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Combine
|
||||
import SwiftUI
|
||||
|
||||
typealias ResetRecoveryKeyScreenViewModelType = StateStoreViewModel<ResetRecoveryKeyScreenViewState, ResetRecoveryKeyScreenViewAction>
|
||||
|
||||
class ResetRecoveryKeyScreenViewModel: ResetRecoveryKeyScreenViewModelType, ResetRecoveryKeyScreenViewModelProtocol {
|
||||
private let actionsSubject: PassthroughSubject<ResetRecoveryKeyScreenViewModelAction, Never> = .init()
|
||||
var actionsPublisher: AnyPublisher<ResetRecoveryKeyScreenViewModelAction, Never> {
|
||||
actionsSubject.eraseToAnyPublisher()
|
||||
}
|
||||
|
||||
init() {
|
||||
super.init(initialViewState: ResetRecoveryKeyScreenViewState())
|
||||
}
|
||||
|
||||
// MARK: - Public
|
||||
|
||||
override func process(viewAction: ResetRecoveryKeyScreenViewAction) {
|
||||
switch viewAction {
|
||||
case .cancel:
|
||||
actionsSubject.send(.cancel)
|
||||
}
|
||||
}
|
||||
}
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Combine
|
||||
|
||||
@MainActor
|
||||
protocol ResetRecoveryKeyScreenViewModelProtocol {
|
||||
var actionsPublisher: AnyPublisher<ResetRecoveryKeyScreenViewModelAction, Never> { get }
|
||||
var context: ResetRecoveryKeyScreenViewModelType.Context { get }
|
||||
}
|
||||
+134
@@ -0,0 +1,134 @@
|
||||
//
|
||||
// Copyright 2022 New Vector Ltd
|
||||
//
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
// Unless required by applicable law or agreed to in writing, software
|
||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
// See the License for the specific language governing permissions and
|
||||
// limitations under the License.
|
||||
//
|
||||
|
||||
import Compound
|
||||
import SwiftUI
|
||||
|
||||
struct ResetRecoveryKeyScreen: View {
|
||||
@ObservedObject var context: ResetRecoveryKeyScreenViewModel.Context
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
FullscreenDialog {
|
||||
mainContent
|
||||
} bottomContent: {
|
||||
EmptyView()
|
||||
}
|
||||
.toolbar { toolbar }
|
||||
.toolbar(.visible, for: .navigationBar)
|
||||
.background()
|
||||
.environment(\.backgroundStyle, AnyShapeStyle(Color.compound.bgSubtleSecondary))
|
||||
.interactiveDismissDisabled()
|
||||
}
|
||||
}
|
||||
|
||||
private var mainContent: some View {
|
||||
VStack(spacing: 40) {
|
||||
header
|
||||
list
|
||||
}
|
||||
}
|
||||
|
||||
private var header: some View {
|
||||
VStack(spacing: 16) {
|
||||
HeroImage(icon: \.computer, style: .subtle)
|
||||
|
||||
Text(L10n.screenCreateNewRecoveryKeyTitle)
|
||||
.foregroundColor(.compound.textPrimary)
|
||||
.font(.compound.headingMDBold)
|
||||
.multilineTextAlignment(.center)
|
||||
}
|
||||
}
|
||||
|
||||
private var list: some View {
|
||||
VStack(alignment: .leading, spacing: 24) {
|
||||
Label {
|
||||
Text(L10n.screenCreateNewRecoveryKeyListItem1)
|
||||
} icon: {
|
||||
Image(systemSymbol: ._1Circle)
|
||||
.imageScale(.large)
|
||||
.fontWeight(.light)
|
||||
.foregroundColor(.compound.textPlaceholder)
|
||||
}
|
||||
.foregroundColor(.compound.textPrimary)
|
||||
.font(.compound.bodyMD)
|
||||
|
||||
Label {
|
||||
Text(L10n.screenCreateNewRecoveryKeyListItem2)
|
||||
} icon: {
|
||||
Image(systemSymbol: ._2Circle)
|
||||
.imageScale(.large)
|
||||
.fontWeight(.light)
|
||||
.foregroundColor(.compound.textPlaceholder)
|
||||
}
|
||||
.foregroundColor(.compound.textPrimary)
|
||||
.font(.compound.bodyMD)
|
||||
|
||||
Label {
|
||||
Text(context.viewState.listItem3AttributedText)
|
||||
} icon: {
|
||||
Image(systemSymbol: ._3Circle)
|
||||
.imageScale(.large)
|
||||
.fontWeight(.light)
|
||||
.foregroundColor(.compound.textPlaceholder)
|
||||
}
|
||||
.foregroundColor(.compound.textPrimary)
|
||||
.font(.compound.bodyMD)
|
||||
|
||||
Label {
|
||||
Text(L10n.screenCreateNewRecoveryKeyListItem4)
|
||||
} icon: {
|
||||
Image(systemSymbol: ._4Circle)
|
||||
.imageScale(.large)
|
||||
.fontWeight(.light)
|
||||
.foregroundColor(.compound.textPlaceholder)
|
||||
}
|
||||
.foregroundColor(.compound.textPrimary)
|
||||
.font(.compound.bodyMD)
|
||||
|
||||
Label {
|
||||
Text(L10n.screenCreateNewRecoveryKeyListItem5)
|
||||
} icon: {
|
||||
Image(systemSymbol: ._5Circle)
|
||||
.imageScale(.large)
|
||||
.fontWeight(.light)
|
||||
.foregroundColor(.compound.textPlaceholder)
|
||||
}
|
||||
.foregroundColor(.compound.textPrimary)
|
||||
.font(.compound.bodyMD)
|
||||
}
|
||||
}
|
||||
|
||||
@ToolbarContentBuilder
|
||||
private var toolbar: some ToolbarContent {
|
||||
ToolbarItem(placement: .cancellationAction) {
|
||||
Button(L10n.actionCancel) {
|
||||
context.send(viewAction: .cancel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Previews
|
||||
|
||||
struct ResetRecoveryKeyScreen_Previews: PreviewProvider, TestablePreview {
|
||||
static let viewModel = ResetRecoveryKeyScreenViewModel()
|
||||
static var previews: some View {
|
||||
NavigationStack {
|
||||
ResetRecoveryKeyScreen(context: viewModel.context)
|
||||
}
|
||||
}
|
||||
}
|
||||
+3
@@ -28,6 +28,7 @@ enum SecureBackupRecoveryKeyScreenCoordinatorAction {
|
||||
case recoverySetUp
|
||||
case recoveryChanged
|
||||
case recoveryFixed
|
||||
case showResetKeyInfo
|
||||
}
|
||||
|
||||
final class SecureBackupRecoveryKeyScreenCoordinator: CoordinatorProtocol {
|
||||
@@ -62,6 +63,8 @@ final class SecureBackupRecoveryKeyScreenCoordinator: CoordinatorProtocol {
|
||||
case .fixRecovery:
|
||||
self.actionsSubject.send(.recoveryFixed)
|
||||
}
|
||||
case .showResetKeyInfo:
|
||||
self.actionsSubject.send(.showResetKeyInfo)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
|
||||
+2
@@ -19,6 +19,7 @@ import Foundation
|
||||
enum SecureBackupRecoveryKeyScreenViewModelAction {
|
||||
case done(mode: SecureBackupRecoveryKeyScreenViewMode)
|
||||
case cancel
|
||||
case showResetKeyInfo
|
||||
}
|
||||
|
||||
enum SecureBackupRecoveryKeyScreenViewMode {
|
||||
@@ -82,6 +83,7 @@ enum SecureBackupRecoveryKeyScreenViewAction {
|
||||
case copyKey
|
||||
case keySaved
|
||||
case confirmKey
|
||||
case resetKey
|
||||
case done
|
||||
case cancel
|
||||
}
|
||||
|
||||
+2
@@ -100,6 +100,8 @@ class SecureBackupRecoveryKeyScreenViewModel: SecureBackupRecoveryKeyScreenViewM
|
||||
guard let self else { return }
|
||||
actionsSubject.send(.done(mode: context.viewState.mode))
|
||||
}))
|
||||
case .resetKey:
|
||||
actionsSubject.send(.showResetKeyInfo)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+16
-2
@@ -64,6 +64,12 @@ struct SecureBackupRecoveryKeyScreen: View {
|
||||
case .setupRecovery, .changeRecovery:
|
||||
recoveryCreatedActionButtons
|
||||
case .fixRecovery:
|
||||
incompleteVerificationActionButtons
|
||||
}
|
||||
}
|
||||
|
||||
private var incompleteVerificationActionButtons: some View {
|
||||
VStack(spacing: 16) {
|
||||
Button {
|
||||
context.send(viewAction: .confirmKey)
|
||||
} label: {
|
||||
@@ -71,6 +77,14 @@ struct SecureBackupRecoveryKeyScreen: View {
|
||||
}
|
||||
.buttonStyle(.compound(.primary))
|
||||
.disabled(context.confirmationRecoveryKey.isEmpty)
|
||||
|
||||
Button {
|
||||
context.send(viewAction: .resetKey)
|
||||
} label: {
|
||||
Text(L10n.screenIdentityConfirmationCreateNewRecoveryKey)
|
||||
.padding(.vertical, 14)
|
||||
}
|
||||
.buttonStyle(.compound(.plain))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,7 +141,7 @@ struct SecureBackupRecoveryKeyScreen: View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text(L10n.commonRecoveryKey)
|
||||
.foregroundColor(.compound.textPrimary)
|
||||
.font(.compound.bodySM)
|
||||
.font(.compound.bodySMSemibold)
|
||||
|
||||
Group {
|
||||
if context.viewState.recoveryKey == nil {
|
||||
@@ -180,7 +194,7 @@ struct SecureBackupRecoveryKeyScreen: View {
|
||||
VStack(alignment: .leading, spacing: 8) {
|
||||
Text(L10n.commonRecoveryKey)
|
||||
.foregroundColor(.compound.textPrimary)
|
||||
.font(.compound.bodySM)
|
||||
.font(.compound.bodySMSemibold)
|
||||
|
||||
SecureField(L10n.screenRecoveryKeyConfirmKeyPlaceholder, text: $context.confirmationRecoveryKey)
|
||||
.textContentType(.password) // Not ideal but stops random suggestions
|
||||
|
||||
+17
-3
@@ -51,18 +51,20 @@ final class SecureBackupScreenCoordinator: CoordinatorProtocol {
|
||||
|
||||
recoveryKeyCoordinator.actions.sink { [weak self] action in
|
||||
guard let self else { return }
|
||||
|
||||
parameters.navigationStackCoordinator?.setSheetCoordinator(nil)
|
||||
|
||||
switch action {
|
||||
case .cancel:
|
||||
break
|
||||
case .recoverySetUp:
|
||||
showSuccessIndicator(title: L10n.screenRecoveryKeySetupSuccess)
|
||||
parameters.navigationStackCoordinator?.setSheetCoordinator(nil)
|
||||
case .recoveryChanged:
|
||||
showSuccessIndicator(title: L10n.screenRecoveryKeyChangeSuccess)
|
||||
parameters.navigationStackCoordinator?.setSheetCoordinator(nil)
|
||||
case .recoveryFixed:
|
||||
showSuccessIndicator(title: L10n.screenRecoveryKeyConfirmSuccess)
|
||||
parameters.navigationStackCoordinator?.setSheetCoordinator(nil)
|
||||
case .showResetKeyInfo:
|
||||
showResetRecoveryKeyScreen(navigationStackCoordinator: navigationStackCoordinator)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
@@ -105,4 +107,16 @@ final class SecureBackupScreenCoordinator: CoordinatorProtocol {
|
||||
iconName: "checkmark",
|
||||
persistent: false))
|
||||
}
|
||||
|
||||
private func showResetRecoveryKeyScreen(navigationStackCoordinator: NavigationStackCoordinator) {
|
||||
let coordinator = ResetRecoveryKeyScreenCoordinator()
|
||||
coordinator.actionsPublisher.sink { action in
|
||||
switch action {
|
||||
case .cancel:
|
||||
navigationStackCoordinator.setSheetCoordinator(nil)
|
||||
}
|
||||
}
|
||||
.store(in: &cancellables)
|
||||
navigationStackCoordinator.setSheetCoordinator(coordinator)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user