From 5ff6f05fae35f9a31744a5e8ac4638c06895ac46 Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Thu, 2 Jan 2025 08:20:29 +0100 Subject: [PATCH 001/107] feat: add BackupViewMode and new stringsl --- .../Views/FolderPicker/FolderPicker.swift | 2 +- wire-ios/Wire-iOS.xcodeproj/project.pbxproj | 48 +++ .../Generated/Strings+Generated.swift | 16 +- .../Resources/Base.lproj/Localizable.strings | 6 +- .../DeveloperDebugActionsView.swift | 2 +- .../Backup/BackupViewController.swift | 2 +- .../Backup/Models/BackupSection.swift | 69 ++++ .../BackupHostingController.swift | 36 ++ .../Backup/ViewModels/BackupViewModel.swift | 72 ++++ .../Settings/Backup/Views/BackupView.swift | 355 ++++++++++++++++++ ...ettingsCellDescriptorFactory+Account.swift | 6 +- 11 files changed, 602 insertions(+), 12 deletions(-) create mode 100644 wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/Models/BackupSection.swift create mode 100644 wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/ViewControllers/BackupHostingController.swift create mode 100644 wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/ViewModels/BackupViewModel.swift create mode 100644 wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/Views/BackupView.swift diff --git a/WireUI/Sources/WireMoveToFolderUI/Views/FolderPicker/FolderPicker.swift b/WireUI/Sources/WireMoveToFolderUI/Views/FolderPicker/FolderPicker.swift index e5b2bccee07..f28bbb9203f 100644 --- a/WireUI/Sources/WireMoveToFolderUI/Views/FolderPicker/FolderPicker.swift +++ b/WireUI/Sources/WireMoveToFolderUI/Views/FolderPicker/FolderPicker.swift @@ -88,7 +88,7 @@ public struct FolderPicker: View { } } - private var folderList: some View { + private var folderList: some View {// List(viewModel.folders, id: \.identifier) { folder in FolderRow( folder: folder, diff --git a/wire-ios/Wire-iOS.xcodeproj/project.pbxproj b/wire-ios/Wire-iOS.xcodeproj/project.pbxproj index 6298e0a2081..0573af0895e 100644 --- a/wire-ios/Wire-iOS.xcodeproj/project.pbxproj +++ b/wire-ios/Wire-iOS.xcodeproj/project.pbxproj @@ -101,6 +101,10 @@ 06C412AC238FCAA80018866F /* Int+Const.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06C412AB238FCAA80018866F /* Int+Const.swift */; }; 06C412B0239103910018866F /* ConversationInputBarViewController+DragAndDrop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06C412AF239103910018866F /* ConversationInputBarViewController+DragAndDrop.swift */; }; 06CDC6F62A2DDBCE00EB518D /* FailedUsersSystemMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06CDC6F52A2DDBCE00EB518D /* FailedUsersSystemMessageCell.swift */; }; + 06D4B85B2D22DDF2004627EB /* BackupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06D4B85A2D22DDF2004627EB /* BackupView.swift */; }; + 06D4B8632D230537004627EB /* BackupSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06D4B8622D230535004627EB /* BackupSection.swift */; }; + 06D720222D2357DF00D36510 /* BackupHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06D720212D2357C000D36510 /* BackupHostingController.swift */; }; + 06D720242D2359EC00D36510 /* BackupViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06D720232D2359EA00D36510 /* BackupViewModel.swift */; }; 06D93B472B56F64F00A0A512 /* E2EIFeatureChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06D93B462B56F64F00A0A512 /* E2EIFeatureChange.swift */; }; 06DF42D12757DA56009C8A99 /* GuestLinkInfoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06DF42D02757DA55009C8A99 /* GuestLinkInfoCell.swift */; }; 06E097982A20BEE000B38C4A /* ZMConversation+IncompleteMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06E097972A20BEE000B38C4A /* ZMConversation+IncompleteMetadata.swift */; }; @@ -2052,6 +2056,10 @@ 06C412AB238FCAA80018866F /* Int+Const.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Int+Const.swift"; sourceTree = ""; }; 06C412AF239103910018866F /* ConversationInputBarViewController+DragAndDrop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConversationInputBarViewController+DragAndDrop.swift"; sourceTree = ""; }; 06CDC6F52A2DDBCE00EB518D /* FailedUsersSystemMessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FailedUsersSystemMessageCell.swift; sourceTree = ""; }; + 06D4B85A2D22DDF2004627EB /* BackupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupView.swift; sourceTree = ""; }; + 06D4B8622D230535004627EB /* BackupSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupSection.swift; sourceTree = ""; }; + 06D720212D2357C000D36510 /* BackupHostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupHostingController.swift; sourceTree = ""; }; + 06D720232D2359EA00D36510 /* BackupViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupViewModel.swift; sourceTree = ""; }; 06D93B462B56F64F00A0A512 /* E2EIFeatureChange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = E2EIFeatureChange.swift; sourceTree = ""; }; 06DF42D02757DA55009C8A99 /* GuestLinkInfoCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuestLinkInfoCell.swift; sourceTree = ""; }; 06E097972A20BEE000B38C4A /* ZMConversation+IncompleteMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ZMConversation+IncompleteMetadata.swift"; sourceTree = ""; }; @@ -4095,6 +4103,38 @@ path = UIAlertController; sourceTree = ""; }; + 06D4B85C2D2302F3004627EB /* Models */ = { + isa = PBXGroup; + children = ( + 06D4B8622D230535004627EB /* BackupSection.swift */, + ); + path = Models; + sourceTree = ""; + }; + 06D4B85D2D230304004627EB /* Views */ = { + isa = PBXGroup; + children = ( + 06D4B85A2D22DDF2004627EB /* BackupView.swift */, + ); + path = Views; + sourceTree = ""; + }; + 06D4B8602D23031D004627EB /* ViewModels */ = { + isa = PBXGroup; + children = ( + 06D720232D2359EA00D36510 /* BackupViewModel.swift */, + ); + path = ViewModels; + sourceTree = ""; + }; + 06D4B8612D230327004627EB /* ViewControllers */ = { + isa = PBXGroup; + children = ( + 06D720212D2357C000D36510 /* BackupHostingController.swift */, + ); + path = ViewControllers; + sourceTree = ""; + }; 06F3E73124EFA9E600B1083C /* Notification Extension */ = { isa = PBXGroup; children = ( @@ -7502,6 +7542,10 @@ E666EDD42B73E5F500C03E2B /* Backup */ = { isa = PBXGroup; children = ( + 06D4B8612D230327004627EB /* ViewControllers */, + 06D4B8602D23031D004627EB /* ViewModels */, + 06D4B85D2D230304004627EB /* Views */, + 06D4B85C2D2302F3004627EB /* Models */, E666EDD52B73E62800C03E2B /* BackupSource.swift */, E666EDDB2B73EA3500C03E2B /* BackupActionCell.swift */, E666EDD92B73E9C400C03E2B /* BackupStatusCell.swift */, @@ -9296,6 +9340,7 @@ 064AD4C425DD31A600143D74 /* UIApplication+OpenURL.swift in Sources */, 87E4D2CA1FC6E39800F3C664 /* CharacterInputField.swift in Sources */, F15650D921DD107800210504 /* RoundedViewProtocol.swift in Sources */, + 06D4B85B2D22DDF2004627EB /* BackupView.swift in Sources */, A9F57E3D2419A83100EEA912 /* TokenContainer.swift in Sources */, E95F1CA12C3D472A005E80BC /* AnalytitcsServiceConfigurationBuilder.swift in Sources */, F1CB5D252158FE1F001CCF5D /* MentionsHandler+TextView.swift in Sources */, @@ -9490,6 +9535,7 @@ EFF3DA452097587F007A3358 /* UIPasteboard+MediaAsset.swift in Sources */, 1682AEB620480C80003A052A /* UIViewController+Navigation.swift in Sources */, 5E33F0D020AD629D00414EA6 /* CallPermissionsConfiguration.swift in Sources */, + 06D720242D2359EC00D36510 /* BackupViewModel.swift in Sources */, EF2388862211A67900331C07 /* UserRight.swift in Sources */, 5405444F253842C200FAE77A /* LaunchSequenceOperation.swift in Sources */, 871BC2551D34F8F800DF0793 /* CrossfadeTransition.swift in Sources */, @@ -9827,6 +9873,7 @@ 06ADE9F12BC7C515008BA0B3 /* RemoveClientsViewController.swift in Sources */, EF17B0D6223A70D1006252A8 /* SketchColorPickerController.swift in Sources */, 16829EDF2010D60800F579A0 /* InviteTeamMemberSection.swift in Sources */, + 06D720222D2357DF00D36510 /* BackupHostingController.swift in Sources */, 594C4E2B2CCA98F000F13D03 /* AccountImageSourceMapping.swift in Sources */, 87E8D0BD21821E6F00BD26AC /* ConversationContentViewController+Reply.swift in Sources */, 63238C6B2B860E2B00951467 /* DeveloperE2eiView.swift in Sources */, @@ -9934,6 +9981,7 @@ 5EE73BF3212323C70032986D /* RegistrationCredentialsVerifiedEventHandler.swift in Sources */, EE8E926E2024F085000F4752 /* MarkdownTextView.swift in Sources */, 63E128B726208BAD009D9AF2 /* CallGridHintNotificationLabel.swift in Sources */, + 06D4B8632D230537004627EB /* BackupSection.swift in Sources */, A9E674D124F40C550058FC72 /* KeyboardAvoidingAuthenticationCoordinatedViewController.swift in Sources */, F12CDAE01E43868200CEFCEB /* ConfirmEmailViewController.swift in Sources */, 7CCFC10F228AFCC0007C365B /* ConversationLegalHoldCell.swift in Sources */, diff --git a/wire-ios/Wire-iOS/Generated/Strings+Generated.swift b/wire-ios/Wire-iOS/Generated/Strings+Generated.swift index a1855b2f0b6..19e2871a32b 100644 --- a/wire-ios/Wire-iOS/Generated/Strings+Generated.swift +++ b/wire-ios/Wire-iOS/Generated/Strings+Generated.swift @@ -5560,12 +5560,10 @@ internal enum L10n { internal enum HistoryBackup { /// Back Up Now internal static let action = L10n.tr("Localizable", "self.settings.history_backup.action", fallback: "Back Up Now") - /// Create a backup to preserve your conversation history. You can use this to restore history if you lose your device or switch to a new one. - /// - /// Choose a strong password to protect the backup file. - internal static let description = L10n.tr("Localizable", "self.settings.history_backup.description", fallback: "Create a backup to preserve your conversation history. You can use this to restore history if you lose your device or switch to a new one.\n\nChoose a strong password to protect the backup file.") - /// Back Up Conversations - internal static let title = L10n.tr("Localizable", "self.settings.history_backup.title", fallback: "Back Up Conversations") + /// Create a backup to preserve your conversation history. You can use this to restore history if you lose your computer or switch to a new one. The backup file is not protected by Wire end-to-end encryption, so store it in a safe place. + internal static let description = L10n.tr("Localizable", "self.settings.history_backup.description", fallback: "Create a backup to preserve your conversation history. You can use this to restore history if you lose your computer or switch to a new one. The backup file is not protected by Wire end-to-end encryption, so store it in a safe place.") + /// Back up or Restore + internal static let title = L10n.tr("Localizable", "self.settings.history_backup.title", fallback: "Back up or Restore") internal enum Error { /// Error internal static let title = L10n.tr("Localizable", "self.settings.history_backup.error.title", fallback: "Error") @@ -5763,6 +5761,12 @@ internal enum L10n { internal static let title = L10n.tr("Localizable", "self.settings.receiveNews_and_offers.description.title", fallback: "Receive news and product updates from Wire via email.") } } + internal enum RestoreFromBackup { + /// Restore from Backup + internal static let action = L10n.tr("Localizable", "self.settings.restore_from_backup.action", fallback: "Restore from Backup") + /// The existing history on this device remains and will be completed by the new backup. You can restore history from all your devices and different platforms but not from another account. + internal static let description = L10n.tr("Localizable", "self.settings.restore_from_backup.description", fallback: "The existing history on this device remains and will be completed by the new backup. You can restore history from all your devices and different platforms but not from another account.") + } internal enum SoundMenu { /// Sound Alerts internal static let title = L10n.tr("Localizable", "self.settings.sound_menu.title", fallback: "Sound Alerts") diff --git a/wire-ios/Wire-iOS/Resources/Base.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/Base.lproj/Localizable.strings index 4d3b5bb371c..a33e4cbfd36 100644 --- a/wire-ios/Wire-iOS/Resources/Base.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/Base.lproj/Localizable.strings @@ -1356,9 +1356,11 @@ // History backup "self.settings.conversations.title" = "History"; -"self.settings.history_backup.title" = "Back Up Conversations"; -"self.settings.history_backup.description" = "Create a backup to preserve your conversation history. You can use this to restore history if you lose your device or switch to a new one.\n\nChoose a strong password to protect the backup file."; +"self.settings.history_backup.title" = "Back up or Restore"; +"self.settings.history_backup.description" = "Create a backup to preserve your conversation history. You can use this to restore history if you lose your computer or switch to a new one. The backup file is not protected by Wire end-to-end encryption, so store it in a safe place."; "self.settings.history_backup.action" = "Back Up Now"; +"self.settings.restore_from_backup.description" = "The existing history on this device remains and will be completed by the new backup. You can restore history from all your devices and different platforms but not from another account."; +"self.settings.restore_from_backup.action" = "Restore from Backup"; "self.settings.history_backup.error.title" = "Error"; "self.settings.history_backup.set_email.title" = "Set an email and password."; diff --git a/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DebugActions/DeveloperDebugActionsView.swift b/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DebugActions/DeveloperDebugActionsView.swift index 3aea2d6d0a4..e9178296e8c 100644 --- a/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DebugActions/DeveloperDebugActionsView.swift +++ b/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DebugActions/DeveloperDebugActionsView.swift @@ -22,7 +22,7 @@ struct DeveloperDebugActionsView: View { @ObservedObject var viewModel: DeveloperDebugActionsViewModel - var body: some View { + var body: some View {// List(viewModel.buttons) { button in Button(action: button.action) { Text(button.title) diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupViewController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupViewController.swift index f559ec8ccd5..793ce77e293 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupViewController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupViewController.swift @@ -20,7 +20,7 @@ import UIKit import WireDesign import WireReusableUIComponents -final class BackupViewController: UIViewController { +final class BackupViewController: UIViewController {// private let tableView = UITableView(frame: .zero) private var cells: [UITableViewCell.Type] = [] diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/Models/BackupSection.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/Models/BackupSection.swift new file mode 100644 index 00000000000..5183ae4fc64 --- /dev/null +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/Models/BackupSection.swift @@ -0,0 +1,69 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import Foundation + +/// The section that will be displayed in the backup settings +struct BackupSection: Identifiable { + + typealias SettingsString = L10n.Localizable.Self.Settings + + enum Section { + case backup + case restore + + var title: String { + switch self { + case .backup: + return SettingsString.HistoryBackup.action + case .restore: + return SettingsString.RestoreFromBackup.action + } + } + + var footer: String { + switch self { + case .backup: + return SettingsString.HistoryBackup.description + case .restore: + return SettingsString.RestoreFromBackup.description + } + } + } + + /// Unique identifier for the section + let id: UUID + + /// The section type + let type: Section + + /// OnTap section action + let action: () -> Void + + init( + id: UUID = UUID(), + type: Section, + action: @escaping () -> Void + ) { + self.id = id + self.type = type + self.action = action + } + +} + diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/ViewControllers/BackupHostingController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/ViewControllers/BackupHostingController.swift new file mode 100644 index 00000000000..06f0e7904dd --- /dev/null +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/ViewControllers/BackupHostingController.swift @@ -0,0 +1,36 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import SwiftUI +import UIKit + +public final class BackupHostingController: UIHostingController { + private let viewModel: BackupViewModel + + public init( + viewModel: BackupViewModel + ) { + self.viewModel = viewModel + super.init(rootView: BackupView(viewModel: viewModel)) + } + + @available(*, unavailable) + dynamic required init?(coder aDecoder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } +} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/ViewModels/BackupViewModel.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/ViewModels/BackupViewModel.swift new file mode 100644 index 00000000000..49589e9c86a --- /dev/null +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/ViewModels/BackupViewModel.swift @@ -0,0 +1,72 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import SwiftUI + +public final class BackupViewModel: ObservableObject { + @Published var sections: [BackupSection] = [] + + init() { + sections = [ + BackupSection( + type: .backup, + action: { + print("Action for Section 1 triggered!") + } + ), + BackupSection( + type: .restore, + action: { + print("Action for Section 2 triggered!") + } + ) + ] + } + +// private func backupActiveAccount() { +// requestBackupPassword { [weak self] result in +// guard let self, let password = result else { return } +// // activityIndicator.start() +// +// backupSource.backupActiveAccount(password: password) { backupResult in +// // self.activityIndicator.stop() +// +// switch backupResult { +// case let .failure(error): +// self.presentAlert(for: error) +// case let .success(url): +// self.presentShareSheet(with: url, from: indexPath) +// } +// } +// } +// } + +// private func requestBackupPassword(completion: @escaping (String?) -> Void) { +// let passwordController = BackupPasswordViewController() +// passwordController.onCompletion = { [weak passwordController] password in +// passwordController?.dismiss(animated: true) { +// completion(password) +// } +// } +// let navigationController = KeyboardAvoidingViewController(viewController: passwordController) +// .wrapInNavigationController() +// navigationController.modalPresentationStyle = .formSheet +// present(navigationController, animated: true) +// } + +} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/Views/BackupView.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/Views/BackupView.swift new file mode 100644 index 00000000000..1bf5c7588d4 --- /dev/null +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/Views/BackupView.swift @@ -0,0 +1,355 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import SwiftUI +import WireDesign +import WireReusableUIComponents + +public struct BackupView: View { + @ObservedObject private var viewModel: BackupViewModel + @State private var isSheetPresented: Bool = false + @State private var selectedOption: String = "" +// private lazy var activityIndicator = BlockingActivityIndicator(view: self) + + public init( + viewModel: BackupViewModel + ) { + self.viewModel = viewModel + } + + public var body: some View { + List { + ForEach(viewModel.sections) { section in + Section( + footer: Text(section.type.footer) + ) { + Button(action: { + print("Action for Section 1 triggered!") + withAnimation { + isSheetPresented = true + } + }) { + //Button(action: section.action) { + HStack { + Text(section.type.title) + .font(.textStyle(.body2)) + .foregroundStyle(Color.primaryText) + Spacer() + Image(.chevronRight).foregroundStyle(Color.primary) + } + } +// BackupPasswordPickerView( +// isPresented: $isSheetPresented, +// onCancel: { +// print("Picker canceled") +// }, +// onConfirm: { password in +// print("Password entered: \(password)") +// } +// ) + .sheet(isPresented: $isSheetPresented) { +// FolderPicker11(showCloseButton: true, +// helpLink: WireURLs.shared.howToAddConversationToCustomFolder) +// BackupPasswordView() + NavigationStack { + PickerTestView( + title: "Choose an Option", + options: ["Option 1", "Option 2", "Option 3"], + selectedOption: $selectedOption, + onDone: { + print("Selected Option: \(selectedOption)") + isSheetPresented = false + } + ) + } + } + .presentationDragIndicator(.visible) + .presentationDetents([.medium, .large]) + } + } + } + .listStyle(.grouped) + .listRowBackground(Color(ColorTheme.Backgrounds.background)) + } +} + +#Preview { + BackupView(viewModel: BackupViewModel()) +} + +struct PickerTestView: View { + let title: String + let options: [String] + @Binding var selectedOption: String + let onDone: () -> Void + + var body: some View { + NavigationView { + VStack { + Picker(selection: $selectedOption, label: Text(title)) { + ForEach(options, id: \.self) { option in + Text(option).tag(option) + } + } + .pickerStyle(WheelPickerStyle()) + .padding() + + Button(action: onDone) { + Text("Done") + .padding() + .frame(maxWidth: .infinity) + .background(Color.blue) + .foregroundColor(.white) + .cornerRadius(10) + } + .padding() + } + .navigationTitle(title) + .navigationBarTitleDisplayMode(.inline) + } + } +} + + +struct BackupPasswordPickerView: View { + @Binding var isPresented: Bool + @State private var password: String = "" + + var onCancel: () -> Void + var onConfirm: (String) -> Void + + var body: some View { + ZStack { + // Dimmed background + if isPresented { + Color.black.opacity(0.4) + .edgesIgnoringSafeArea(.all) + .onTapGesture { + dismiss() + } + } + + // Bottom sheet content + if isPresented { + VStack(spacing: 16) { + // Header + HStack { + Button("Cancel") { + dismiss() + onCancel() + } + Spacer() + Text("Set password") + .font(.headline) + Spacer() + Button(" ") {} // Placeholder to balance layout + .hidden() + } + .padding(.horizontal) + + // Description + Text("The backup will be compressed and encrypted with a password. Make sure to store it in a secure place.") + .font(.body) + .multilineTextAlignment(.center) + .padding(.horizontal) + + // Password Field + VStack(alignment: .leading, spacing: 8) { + Text("Password (OPTIONAL)") + .font(.subheadline) + .foregroundColor(.gray) + HStack { + SecureField("Enter password", text: $password) + .textFieldStyle(RoundedBorderTextFieldStyle()) + Button(action: { + // Add "show/hide password" logic here + }) { + Image(systemName: "eye") + .foregroundColor(.gray) + } + } + Text("Use at least 8 characters, with one lowercase letter, one capital letter, a number, and a special character.") + .font(.caption) + .foregroundColor(.gray) + } + .padding(.horizontal) + + // Backup Button + Button(action: { + dismiss() + onConfirm(password) + }) { + Text("Back Up Now") + .frame(maxWidth: .infinity) + .padding() + .background(Color.blue) + .foregroundColor(.white) + .cornerRadius(8) + } + .padding(.horizontal) + + Spacer() + } + .padding(.top, 20) + .background(Color(UIColor.systemBackground)) + .cornerRadius(16) + .shadow(radius: 10) + .transition(.move(edge: .bottom)) + .animation(.easeInOut, value: isPresented) + } + } + } + + private func dismiss() { + withAnimation { + isPresented = false + } + } +} + + + +struct FolderPicker11: View { + + @Environment(\.dismiss) private var dismiss + + private let showCloseButton: Bool + private let helpLink: URL + + /// Creates a new instance of `FolderPicker` + /// - Parameters: + /// - showCloseButton: Whether to show a close button in the navigation bar + /// - options: An array of `FolderPickerOption` to display in the picker + /// - helpLink: A URL to a help page that explains how to add conversations to a folder + /// - selected: The `id` of the selected `FolderPickerOption` + + public init( + showCloseButton: Bool, + helpLink: URL + ) { + self.showCloseButton = showCloseButton + self.helpLink = helpLink + } + + public var body: some View { + content() + .background(Color.viewBackground) + .scrollContentBackground(.hidden) + .navigationTitle( + Text("Folders") + ) + .navigationBarTitleDisplayMode(.inline) + .toolbar { + if showCloseButton { + ToolbarItem(placement: .topBarTrailing) { + CloseButton( + action: didTapClose, accessibilityLabel: "CloseButton" + ) + } + } + } + } + + private func didTapClose() { + dismiss() + } + + @ViewBuilder + private func content() -> some View { + EmptyState1( + image: Image(systemName: "folder"), + description: Text("EmptyState"), + linkText: Text("Link"), + url: helpLink + ) + + } + +} + +// MARK: - Previews + +@available(iOS 17.0, *) +#Preview("With Data") { + FolderPickerPreview(showCloseButton: true) +} + +private struct FolderPickerPreview: View { + @State private var isPresented = true + + let showCloseButton: Bool + + var body: some View { + Button { + isPresented.toggle() + } label: { + Text(verbatim: "Open Folder Picker") + } + .sheet(isPresented: $isPresented) { + NavigationStack { + FolderPicker11( + showCloseButton: showCloseButton, + helpLink: URL(string: "https://www.example.com")! + ) + } + .presentationDragIndicator(.visible) + .presentationDetents([.medium, .large]) + } + } +} + +struct EmptyState1: View { + let image: Image + let description: Text + let linkText: Text + let url: URL + + var body: some View { + Group { + VStack { + image + .font(.system(size: 40)) + .foregroundStyle(Color.secondaryText) + .padding(.bottom, 16) + + description + .multilineTextAlignment(.center) + .padding(.bottom, 16) + + Link(destination: url) { + linkText + .multilineTextAlignment(.center) + .underline() + } + } + .font(.textStyle(.body1)) + .foregroundStyle(Color.primaryText) + .frame(maxWidth: 272) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + } +} + +#Preview { + EmptyState1( + image: Image(systemName: "folder"), + description: Text(verbatim: "Add your conversations to folders to stay organized."), + linkText: Text(verbatim: "How to add a conversation to a folder"), + url: URL(string: "http://example.com")! + ) +} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift index 7f56b11efbe..a236a0c9077 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift @@ -377,7 +377,11 @@ extension SettingsCellDescriptorFactory { return .none } if selfUser.hasValidEmail || selfUser.usesCompanyLogin { - return BackupViewController(backupSource: SessionManager.shared!) + let viewModel = BackupViewModel() + let backupController = BackupHostingController(viewModel: viewModel) + backupController.setupNavigationBarTitle(L10n.Localizable.Self.Settings.HistoryBackup.title) + return backupController + //return BackupViewController(backupSource: SessionManager.shared!) } else { let alert = UIAlertController( title: L10n.Localizable.Self.Settings.HistoryBackup.SetEmail.title, From d4a21e1653ab946f1fbb6d535d44be8521df8c94 Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Thu, 2 Jan 2025 08:47:36 +0100 Subject: [PATCH 002/107] feat: add WireBackupUI --- WireUI/Package.swift | 3 + .../WireBackupUI/Models/BackupSection.swift | 68 +++++++++++++++++++ 2 files changed, 71 insertions(+) create mode 100644 WireUI/Sources/WireBackupUI/Models/BackupSection.swift diff --git a/WireUI/Package.swift b/WireUI/Package.swift index 6ae379791ba..b8a8b4d1add 100644 --- a/WireUI/Package.swift +++ b/WireUI/Package.swift @@ -11,6 +11,7 @@ let package = Package( platforms: [.iOS(.v16), .macOS(.v12)], products: [ .library(name: "WireAccountImageUI", targets: ["WireAccountImageUI"]), + .library(name: "WireBackupUI", targets: ["WireBackupUI"]), .library(name: "WireConversationListUI", targets: ["WireConversationListUI"]), .library(name: "WireDesign", targets: ["WireDesign"]), .library(name: "WireFolderPickerUI", targets: ["WireFolderPickerUI"]), @@ -39,6 +40,8 @@ let package = Package( ), .testTarget(name: "WireAccountImageUITests", dependencies: ["WireAccountImageUI", "WireFoundation"]), + .target(name: "WireBackupUI", dependencies: ["WireFoundation", "WireReusableUIComponents"]), + .target(name: "WireConversationListUI"), .testTarget(name: "WireConversationListUITests", dependencies: ["WireConversationListUI", "WireSettingsUI"]), diff --git a/WireUI/Sources/WireBackupUI/Models/BackupSection.swift b/WireUI/Sources/WireBackupUI/Models/BackupSection.swift new file mode 100644 index 00000000000..93f93f00d88 --- /dev/null +++ b/WireUI/Sources/WireBackupUI/Models/BackupSection.swift @@ -0,0 +1,68 @@ +// +// Wire +// Copyright (C) 2025 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import Foundation + +/// The section that will be displayed in the backup settings +struct BackupSection: Identifiable { + + //typealias SettingsString = L10n.Localizable.Self.Settings + + enum Section { + case backup + case restore + + var title: String { + switch self { + case .backup: + return "SettingsString.HistoryBackup.action" + case .restore: + return "SettingsString.RestoreFromBackup.action" + } + } + + var footer: String { + switch self { + case .backup: + return "SettingsString.HistoryBackup.description" + case .restore: + return "SettingsString.RestoreFromBackup.description" + } + } + } + + /// Unique identifier for the section + let id: UUID + + /// The section type + let type: Section + + /// OnTap section action + let action: () -> Void + + init( + id: UUID = UUID(), + type: Section, + action: @escaping () -> Void + ) { + self.id = id + self.type = type + self.action = action + } + +} From a768468eba0412da244ce14302bf67759fe59677 Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Thu, 2 Jan 2025 16:25:58 +0100 Subject: [PATCH 003/107] feat: add SetBackupPassword --- .../Views/PasswordFieldView.swift | 106 ++++++ .../Views/SetBackupPassword.swift | 121 +++++++ ...tionListViewController+NavigationBar.swift | 2 +- .../Backup/BackupPasswordViewController.swift | 2 +- .../Settings/Backup/Views/BackupView.swift | 303 +----------------- 5 files changed, 238 insertions(+), 296 deletions(-) create mode 100644 WireUI/Sources/WireBackupUI/Views/PasswordFieldView.swift create mode 100644 WireUI/Sources/WireBackupUI/Views/SetBackupPassword.swift diff --git a/WireUI/Sources/WireBackupUI/Views/PasswordFieldView.swift b/WireUI/Sources/WireBackupUI/Views/PasswordFieldView.swift new file mode 100644 index 00000000000..9d00becae2b --- /dev/null +++ b/WireUI/Sources/WireBackupUI/Views/PasswordFieldView.swift @@ -0,0 +1,106 @@ +// +// Wire +// Copyright (C) 2025 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import SwiftUI +import WireDesign + +struct PasswordFieldView: View { + @Binding var password: String + var isPasswordValid: Bool + @Binding var isPasswordVisible: Bool + + var body: some View { + VStack(alignment: .leading, spacing: 8) { + Text("Password (OPTIONAL)") + .font(.subheadline) + .foregroundColor(isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color) + + ZStack { + if isPasswordVisible { + TextField("Enter password", text: $password) + .textFieldStyle(RoundedBorderTextFieldStyle()) + .overlay( + RoundedRectangle(cornerRadius: 5) + .stroke(isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color, lineWidth: 1) + ) + } else { + SecureField("Enter password", text: $password) + .textFieldStyle(RoundedBorderTextFieldStyle()) + .overlay( + RoundedRectangle(cornerRadius: 5) + .stroke(isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color, lineWidth: 1) + ) + } + + HStack { + Spacer() + Button(action: { + isPasswordVisible.toggle() + }) { + Image(systemName: isPasswordVisible ? "eye" : "eye.slash") + .foregroundColor(.gray) + } + .padding(.trailing, 10) + } + } + + Text("Use at least 8 characters, with one lowercase letter, one capital letter, a number, and a special character.") + .font(.caption) + .foregroundColor(isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color) + } + .padding(.horizontal) + } +} + +// MARK: - Previews + +@available(iOS 17, *) +#Preview("Invalid Password - Hidden") { + PasswordFieldView( + password: .constant(""), + isPasswordValid: false, + isPasswordVisible: .constant(false) + ) +} + +@available(iOS 17, *) +#Preview("Invalid Password - Visible") { + PasswordFieldView( + password: .constant("ValidPassword1!"), + isPasswordValid: false, + isPasswordVisible: .constant(true) + ) +} + +@available(iOS 17, *) +#Preview("Valid Password - Hidden") { + PasswordFieldView( + password: .constant("ValidPassword1!"), + isPasswordValid: true, + isPasswordVisible: .constant(false) + ) +} + +@available(iOS 17, *) +#Preview("Valid Password - Visible") { + PasswordFieldView( + password: .constant("ValidPassword1!"), + isPasswordValid: true, + isPasswordVisible: .constant(true) + ) +} diff --git a/WireUI/Sources/WireBackupUI/Views/SetBackupPassword.swift b/WireUI/Sources/WireBackupUI/Views/SetBackupPassword.swift new file mode 100644 index 00000000000..d7465a9f690 --- /dev/null +++ b/WireUI/Sources/WireBackupUI/Views/SetBackupPassword.swift @@ -0,0 +1,121 @@ +// +// Wire +// Copyright (C) 2025 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import SwiftUI +import WireDesign +import WireFoundation +import WireReusableUIComponents + +/// A view that allows to set a password for the backup. + +public struct SetBackupPassword: View { + + @Environment(\.dismiss) private var dismiss + + public var body: some View { + BackupSheetView() + .background(Color.viewBackground) + .scrollContentBackground(.hidden) + .navigationTitle( + Text("Set password") + ) + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + CloseButton( + action: didTapClose, + accessibilityLabel: "Close" + ) + } + } + } + + private func didTapClose() { + dismiss() + } + +} + +struct BackupSheetView: View { + @Environment(\.dismiss) var dismiss + @State private var password: String = "" + @State private var isPasswordVisible: Bool = false + + var body: some View { + VStack(spacing: 20) { + Text("The backup will be compressed and encrypted with a password. Make sure to store it in a secure place.") + .font(.textStyle(.body1)) + .foregroundStyle(Color.primaryText) + .multilineTextAlignment(.leading) + .padding(.horizontal) + + PasswordFieldView( + password: $password, + isPasswordValid: isPasswordValid(), + isPasswordVisible: $isPasswordVisible) + + Spacer() + + Button( + action: { + print("Back up initiated with password") + dismiss() + }, + label: { + Text("Back Up Now") + } + ) + .wireButtonStyle(.primary) + .padding() + } + .frame(maxHeight: .infinity) + } + + func isPasswordValid() -> Bool { + guard !password.isEmpty else { + return true + } + let passwordRegex = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[!@#$%^&*()_+{}:<>?]).{8,}$" + let predicate = NSPredicate(format: "SELF MATCHES %@", passwordRegex) + return predicate.evaluate(with: password) + } +} + +// MARK: - Previews + +@available(iOS 17.0, *) +#Preview("With Data") { + SetBackupPasswordPreview() +} + +private struct SetBackupPasswordPreview: View { + @State private var isPresented = true + + var body: some View { + Button("Open Sheet") { + isPresented.toggle() + } + .sheet(isPresented: $isPresented) { + NavigationStack { + SetBackupPassword() + } + .presentationDragIndicator(.visible) + .presentationDetents([.medium, .large]) + } + } +} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/ConversationList/Container/ConversationListViewController+NavigationBar/ConversationListViewController+NavigationBar.swift b/wire-ios/Wire-iOS/Sources/UserInterface/ConversationList/Container/ConversationListViewController+NavigationBar/ConversationListViewController+NavigationBar.swift index 6385b016343..ab10ce21938 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/ConversationList/Container/ConversationListViewController+NavigationBar/ConversationListViewController+NavigationBar.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/ConversationList/Container/ConversationListViewController+NavigationBar/ConversationListViewController+NavigationBar.swift @@ -466,7 +466,7 @@ extension ConversationListViewController: ConversationListContainerViewModelDele mainCoordinator: mainCoordinator, showCloseButton: true ) - if let sheet = viewController.sheetPresentationController { + if let sheet = viewController.sheetPresentationController {// sheet.detents = [.medium(), .large()] sheet.prefersGrabberVisible = true } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupPasswordViewController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupPasswordViewController.swift index 5a810cff7b4..17188cf23c5 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupPasswordViewController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupPasswordViewController.swift @@ -140,7 +140,7 @@ final class BackupPasswordViewController: UIViewController { navigationItem.rightBarButtonItem = nextButtonItem } - private func updateState(with text: String) { + private func updateState(with text: String) {// switch PasswordRuleSet.shared.validatePassword(text) { case .valid: password = text diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/Views/BackupView.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/Views/BackupView.swift index 1bf5c7588d4..facfc2b6b43 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/Views/BackupView.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/Views/BackupView.swift @@ -19,16 +19,13 @@ import SwiftUI import WireDesign import WireReusableUIComponents +import WireSettingsUI public struct BackupView: View { @ObservedObject private var viewModel: BackupViewModel @State private var isSheetPresented: Bool = false - @State private var selectedOption: String = "" -// private lazy var activityIndicator = BlockingActivityIndicator(view: self) - public init( - viewModel: BackupViewModel - ) { + public init(viewModel: BackupViewModel) { self.viewModel = viewModel } @@ -44,7 +41,6 @@ public struct BackupView: View { isSheetPresented = true } }) { - //Button(action: section.action) { HStack { Text(section.type.title) .font(.textStyle(.body2)) @@ -53,38 +49,18 @@ public struct BackupView: View { Image(.chevronRight).foregroundStyle(Color.primary) } } -// BackupPasswordPickerView( -// isPresented: $isSheetPresented, -// onCancel: { -// print("Picker canceled") -// }, -// onConfirm: { password in -// print("Password entered: \(password)") -// } -// ) - .sheet(isPresented: $isSheetPresented) { -// FolderPicker11(showCloseButton: true, -// helpLink: WireURLs.shared.howToAddConversationToCustomFolder) -// BackupPasswordView() - NavigationStack { - PickerTestView( - title: "Choose an Option", - options: ["Option 1", "Option 2", "Option 3"], - selectedOption: $selectedOption, - onDone: { - print("Selected Option: \(selectedOption)") - isSheetPresented = false - } - ) - } - } - .presentationDragIndicator(.visible) - .presentationDetents([.medium, .large]) + // .sheet(isPresented: $isSheetPresented) { + // } +// .presentationDragIndicator(.visible) +// .presentationDetents([.medium, .large]) } } } .listStyle(.grouped) .listRowBackground(Color(ColorTheme.Backgrounds.background)) + .sheet(isPresented: $isSheetPresented) { + //SetBackupPassword() + } } } @@ -92,264 +68,3 @@ public struct BackupView: View { BackupView(viewModel: BackupViewModel()) } -struct PickerTestView: View { - let title: String - let options: [String] - @Binding var selectedOption: String - let onDone: () -> Void - - var body: some View { - NavigationView { - VStack { - Picker(selection: $selectedOption, label: Text(title)) { - ForEach(options, id: \.self) { option in - Text(option).tag(option) - } - } - .pickerStyle(WheelPickerStyle()) - .padding() - - Button(action: onDone) { - Text("Done") - .padding() - .frame(maxWidth: .infinity) - .background(Color.blue) - .foregroundColor(.white) - .cornerRadius(10) - } - .padding() - } - .navigationTitle(title) - .navigationBarTitleDisplayMode(.inline) - } - } -} - - -struct BackupPasswordPickerView: View { - @Binding var isPresented: Bool - @State private var password: String = "" - - var onCancel: () -> Void - var onConfirm: (String) -> Void - - var body: some View { - ZStack { - // Dimmed background - if isPresented { - Color.black.opacity(0.4) - .edgesIgnoringSafeArea(.all) - .onTapGesture { - dismiss() - } - } - - // Bottom sheet content - if isPresented { - VStack(spacing: 16) { - // Header - HStack { - Button("Cancel") { - dismiss() - onCancel() - } - Spacer() - Text("Set password") - .font(.headline) - Spacer() - Button(" ") {} // Placeholder to balance layout - .hidden() - } - .padding(.horizontal) - - // Description - Text("The backup will be compressed and encrypted with a password. Make sure to store it in a secure place.") - .font(.body) - .multilineTextAlignment(.center) - .padding(.horizontal) - - // Password Field - VStack(alignment: .leading, spacing: 8) { - Text("Password (OPTIONAL)") - .font(.subheadline) - .foregroundColor(.gray) - HStack { - SecureField("Enter password", text: $password) - .textFieldStyle(RoundedBorderTextFieldStyle()) - Button(action: { - // Add "show/hide password" logic here - }) { - Image(systemName: "eye") - .foregroundColor(.gray) - } - } - Text("Use at least 8 characters, with one lowercase letter, one capital letter, a number, and a special character.") - .font(.caption) - .foregroundColor(.gray) - } - .padding(.horizontal) - - // Backup Button - Button(action: { - dismiss() - onConfirm(password) - }) { - Text("Back Up Now") - .frame(maxWidth: .infinity) - .padding() - .background(Color.blue) - .foregroundColor(.white) - .cornerRadius(8) - } - .padding(.horizontal) - - Spacer() - } - .padding(.top, 20) - .background(Color(UIColor.systemBackground)) - .cornerRadius(16) - .shadow(radius: 10) - .transition(.move(edge: .bottom)) - .animation(.easeInOut, value: isPresented) - } - } - } - - private func dismiss() { - withAnimation { - isPresented = false - } - } -} - - - -struct FolderPicker11: View { - - @Environment(\.dismiss) private var dismiss - - private let showCloseButton: Bool - private let helpLink: URL - - /// Creates a new instance of `FolderPicker` - /// - Parameters: - /// - showCloseButton: Whether to show a close button in the navigation bar - /// - options: An array of `FolderPickerOption` to display in the picker - /// - helpLink: A URL to a help page that explains how to add conversations to a folder - /// - selected: The `id` of the selected `FolderPickerOption` - - public init( - showCloseButton: Bool, - helpLink: URL - ) { - self.showCloseButton = showCloseButton - self.helpLink = helpLink - } - - public var body: some View { - content() - .background(Color.viewBackground) - .scrollContentBackground(.hidden) - .navigationTitle( - Text("Folders") - ) - .navigationBarTitleDisplayMode(.inline) - .toolbar { - if showCloseButton { - ToolbarItem(placement: .topBarTrailing) { - CloseButton( - action: didTapClose, accessibilityLabel: "CloseButton" - ) - } - } - } - } - - private func didTapClose() { - dismiss() - } - - @ViewBuilder - private func content() -> some View { - EmptyState1( - image: Image(systemName: "folder"), - description: Text("EmptyState"), - linkText: Text("Link"), - url: helpLink - ) - - } - -} - -// MARK: - Previews - -@available(iOS 17.0, *) -#Preview("With Data") { - FolderPickerPreview(showCloseButton: true) -} - -private struct FolderPickerPreview: View { - @State private var isPresented = true - - let showCloseButton: Bool - - var body: some View { - Button { - isPresented.toggle() - } label: { - Text(verbatim: "Open Folder Picker") - } - .sheet(isPresented: $isPresented) { - NavigationStack { - FolderPicker11( - showCloseButton: showCloseButton, - helpLink: URL(string: "https://www.example.com")! - ) - } - .presentationDragIndicator(.visible) - .presentationDetents([.medium, .large]) - } - } -} - -struct EmptyState1: View { - let image: Image - let description: Text - let linkText: Text - let url: URL - - var body: some View { - Group { - VStack { - image - .font(.system(size: 40)) - .foregroundStyle(Color.secondaryText) - .padding(.bottom, 16) - - description - .multilineTextAlignment(.center) - .padding(.bottom, 16) - - Link(destination: url) { - linkText - .multilineTextAlignment(.center) - .underline() - } - } - .font(.textStyle(.body1)) - .foregroundStyle(Color.primaryText) - .frame(maxWidth: 272) - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - } -} - -#Preview { - EmptyState1( - image: Image(systemName: "folder"), - description: Text(verbatim: "Add your conversations to folders to stay organized."), - linkText: Text(verbatim: "How to add a conversation to a folder"), - url: URL(string: "http://example.com")! - ) -} From 85c5107aa791409f2b286c6ba2456e070de33f8e Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Thu, 2 Jan 2025 23:51:41 +0100 Subject: [PATCH 004/107] move to WireSettingsUI --- WireUI/Package.swift | 5 +- .../WireBackupUI/Models/BackupSection.swift | 68 ------------------- .../Backup}/Views/PasswordFieldView.swift | 9 +-- .../Backup}/Views/SetBackupPassword.swift | 8 ++- .../BackupHostingController.swift | 4 +- .../Backup/ViewModels/BackupViewModel.swift | 12 ++-- .../Settings/Backup/Views/BackupView.swift | 20 +++--- 7 files changed, 26 insertions(+), 100 deletions(-) delete mode 100644 WireUI/Sources/WireBackupUI/Models/BackupSection.swift rename WireUI/Sources/{WireBackupUI => WireSettingsUI/Account/Backup}/Views/PasswordFieldView.swift (89%) rename WireUI/Sources/{WireBackupUI => WireSettingsUI/Account/Backup}/Views/SetBackupPassword.swift (93%) diff --git a/WireUI/Package.swift b/WireUI/Package.swift index b8a8b4d1add..52400c0d480 100644 --- a/WireUI/Package.swift +++ b/WireUI/Package.swift @@ -11,7 +11,6 @@ let package = Package( platforms: [.iOS(.v16), .macOS(.v12)], products: [ .library(name: "WireAccountImageUI", targets: ["WireAccountImageUI"]), - .library(name: "WireBackupUI", targets: ["WireBackupUI"]), .library(name: "WireConversationListUI", targets: ["WireConversationListUI"]), .library(name: "WireDesign", targets: ["WireDesign"]), .library(name: "WireFolderPickerUI", targets: ["WireFolderPickerUI"]), @@ -40,8 +39,6 @@ let package = Package( ), .testTarget(name: "WireAccountImageUITests", dependencies: ["WireAccountImageUI", "WireFoundation"]), - .target(name: "WireBackupUI", dependencies: ["WireFoundation", "WireReusableUIComponents"]), - .target(name: "WireConversationListUI"), .testTarget(name: "WireConversationListUITests", dependencies: ["WireConversationListUI", "WireSettingsUI"]), @@ -77,7 +74,7 @@ let package = Package( .target(name: "WireReusableUIComponents", dependencies: ["WireDesign", "WireFoundation"]), .testTarget(name: "WireReusableUIComponentsTests", dependencies: ["WireReusableUIComponents"]), - .target(name: "WireSettingsUI"), + .target(name: "WireSettingsUI", dependencies: ["WireDesign", "WireFoundation", "WireReusableUIComponents"]), .testTarget(name: "WireSettingsUITests", dependencies: ["WireSettingsUI"]), .target( diff --git a/WireUI/Sources/WireBackupUI/Models/BackupSection.swift b/WireUI/Sources/WireBackupUI/Models/BackupSection.swift deleted file mode 100644 index 93f93f00d88..00000000000 --- a/WireUI/Sources/WireBackupUI/Models/BackupSection.swift +++ /dev/null @@ -1,68 +0,0 @@ -// -// Wire -// Copyright (C) 2025 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import Foundation - -/// The section that will be displayed in the backup settings -struct BackupSection: Identifiable { - - //typealias SettingsString = L10n.Localizable.Self.Settings - - enum Section { - case backup - case restore - - var title: String { - switch self { - case .backup: - return "SettingsString.HistoryBackup.action" - case .restore: - return "SettingsString.RestoreFromBackup.action" - } - } - - var footer: String { - switch self { - case .backup: - return "SettingsString.HistoryBackup.description" - case .restore: - return "SettingsString.RestoreFromBackup.description" - } - } - } - - /// Unique identifier for the section - let id: UUID - - /// The section type - let type: Section - - /// OnTap section action - let action: () -> Void - - init( - id: UUID = UUID(), - type: Section, - action: @escaping () -> Void - ) { - self.id = id - self.type = type - self.action = action - } - -} diff --git a/WireUI/Sources/WireBackupUI/Views/PasswordFieldView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift similarity index 89% rename from WireUI/Sources/WireBackupUI/Views/PasswordFieldView.swift rename to WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift index 9d00becae2b..8d169627404 100644 --- a/WireUI/Sources/WireBackupUI/Views/PasswordFieldView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift @@ -26,20 +26,21 @@ struct PasswordFieldView: View { var body: some View { VStack(alignment: .leading, spacing: 8) { - Text("Password (OPTIONAL)") + Text(String("Password (OPTIONAL)")) .font(.subheadline) .foregroundColor(isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color) ZStack { if isPasswordVisible { - TextField("Enter password", text: $password) + TextField(String("Enter password"), text: $password) + .font(.textStyle(.body1)) .textFieldStyle(RoundedBorderTextFieldStyle()) .overlay( RoundedRectangle(cornerRadius: 5) .stroke(isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color, lineWidth: 1) ) } else { - SecureField("Enter password", text: $password) + SecureField(String("Enter password"), text: $password) .textFieldStyle(RoundedBorderTextFieldStyle()) .overlay( RoundedRectangle(cornerRadius: 5) @@ -59,7 +60,7 @@ struct PasswordFieldView: View { } } - Text("Use at least 8 characters, with one lowercase letter, one capital letter, a number, and a special character.") + Text(String("Use at least 8 characters, with one lowercase letter, one capital letter, a number, and a special character.")) .font(.caption) .foregroundColor(isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color) } diff --git a/WireUI/Sources/WireBackupUI/Views/SetBackupPassword.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/SetBackupPassword.swift similarity index 93% rename from WireUI/Sources/WireBackupUI/Views/SetBackupPassword.swift rename to WireUI/Sources/WireSettingsUI/Account/Backup/Views/SetBackupPassword.swift index d7465a9f690..265325b011c 100644 --- a/WireUI/Sources/WireBackupUI/Views/SetBackupPassword.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/SetBackupPassword.swift @@ -27,12 +27,14 @@ public struct SetBackupPassword: View { @Environment(\.dismiss) private var dismiss + public init(){} + public var body: some View { BackupSheetView() .background(Color.viewBackground) .scrollContentBackground(.hidden) .navigationTitle( - Text("Set password") + Text(String("Set password")) ) .navigationBarTitleDisplayMode(.inline) .toolbar { @@ -58,7 +60,7 @@ struct BackupSheetView: View { var body: some View { VStack(spacing: 20) { - Text("The backup will be compressed and encrypted with a password. Make sure to store it in a secure place.") + Text(String("The backup will be compressed and encrypted with a password. Make sure to store it in a secure place.")) .font(.textStyle(.body1)) .foregroundStyle(Color.primaryText) .multilineTextAlignment(.leading) @@ -77,7 +79,7 @@ struct BackupSheetView: View { dismiss() }, label: { - Text("Back Up Now") + Text(String("Back Up Now")) } ) .wireButtonStyle(.primary) diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/ViewControllers/BackupHostingController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/ViewControllers/BackupHostingController.swift index 06f0e7904dd..94b50c5eb53 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/ViewControllers/BackupHostingController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/ViewControllers/BackupHostingController.swift @@ -22,9 +22,7 @@ import UIKit public final class BackupHostingController: UIHostingController { private let viewModel: BackupViewModel - public init( - viewModel: BackupViewModel - ) { + public init(viewModel: BackupViewModel) { self.viewModel = viewModel super.init(rootView: BackupView(viewModel: viewModel)) } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/ViewModels/BackupViewModel.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/ViewModels/BackupViewModel.swift index 49589e9c86a..12f01301b93 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/ViewModels/BackupViewModel.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/ViewModels/BackupViewModel.swift @@ -28,13 +28,13 @@ public final class BackupViewModel: ObservableObject { action: { print("Action for Section 1 triggered!") } - ), - BackupSection( - type: .restore, - action: { - print("Action for Section 2 triggered!") - } ) +// BackupSection( +// type: .restore, +// action: { +// print("Action for Section 2 triggered!") +// } +// ) ] } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/Views/BackupView.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/Views/BackupView.swift index facfc2b6b43..b947cabb6c6 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/Views/BackupView.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/Views/BackupView.swift @@ -36,10 +36,8 @@ public struct BackupView: View { footer: Text(section.type.footer) ) { Button(action: { - print("Action for Section 1 triggered!") - withAnimation { - isSheetPresented = true - } + print("Action for Section!") + isSheetPresented.toggle() }) { HStack { Text(section.type.title) @@ -49,22 +47,20 @@ public struct BackupView: View { Image(.chevronRight).foregroundStyle(Color.primary) } } - // .sheet(isPresented: $isSheetPresented) { - // } -// .presentationDragIndicator(.visible) -// .presentationDetents([.medium, .large]) + .sheet(isPresented: $isSheetPresented) { + NavigationStack { + SetBackupPassword() + } + .presentationDetents([.medium, .large]) + } } } } .listStyle(.grouped) .listRowBackground(Color(ColorTheme.Backgrounds.background)) - .sheet(isPresented: $isSheetPresented) { - //SetBackupPassword() - } } } #Preview { BackupView(viewModel: BackupViewModel()) } - From 7a74f68b89bdc6d5afdc22368f1b3264863ea93b Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Fri, 3 Jan 2025 00:19:19 +0100 Subject: [PATCH 005/107] add Localizable --- .../Backup/Views/PasswordFieldView.swift | 16 +++- .../Backup/Views/SetBackupPassword.swift | 20 +++-- .../Resources/Accessibility.xcstrings | 17 +++++ .../Resources/Localizable.xcstrings | 75 +++++++++++++++++++ 4 files changed, 116 insertions(+), 12 deletions(-) create mode 100644 WireUI/Sources/WireSettingsUI/Resources/Accessibility.xcstrings create mode 100644 WireUI/Sources/WireSettingsUI/Resources/Localizable.xcstrings diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift index 8d169627404..3ea3eb42604 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift @@ -26,13 +26,17 @@ struct PasswordFieldView: View { var body: some View { VStack(alignment: .leading, spacing: 8) { - Text(String("Password (OPTIONAL)")) + Text("passwordField.title", tableName: "Localizable", bundle: .module) .font(.subheadline) .foregroundColor(isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color) ZStack { if isPasswordVisible { - TextField(String("Enter password"), text: $password) + TextField(String( + localized: "passwordField.placeholder", + table: "Localizable", + bundle: .module), + text: $password) .font(.textStyle(.body1)) .textFieldStyle(RoundedBorderTextFieldStyle()) .overlay( @@ -40,7 +44,11 @@ struct PasswordFieldView: View { .stroke(isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color, lineWidth: 1) ) } else { - SecureField(String("Enter password"), text: $password) + SecureField(String( + localized: "passwordField.placeholder", + table: "Localizable", + bundle: .module), + text: $password) .textFieldStyle(RoundedBorderTextFieldStyle()) .overlay( RoundedRectangle(cornerRadius: 5) @@ -60,7 +68,7 @@ struct PasswordFieldView: View { } } - Text(String("Use at least 8 characters, with one lowercase letter, one capital letter, a number, and a special character.")) + Text("passwordField.rules", tableName: "Localizable", bundle: .module) .font(.caption) .foregroundColor(isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color) } diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/SetBackupPassword.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/SetBackupPassword.swift index 265325b011c..6686f62dc17 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/SetBackupPassword.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/SetBackupPassword.swift @@ -34,14 +34,18 @@ public struct SetBackupPassword: View { .background(Color.viewBackground) .scrollContentBackground(.hidden) .navigationTitle( - Text(String("Set password")) + Text("setBackupPassword.title", tableName: "Localizable", bundle: .module) ) .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .topBarTrailing) { CloseButton( action: didTapClose, - accessibilityLabel: "Close" + accessibilityLabel: String( + localized: "setBackupPassword.close.label", + table: "Accessibility", + bundle: .module + ) ) } } @@ -60,7 +64,7 @@ struct BackupSheetView: View { var body: some View { VStack(spacing: 20) { - Text(String("The backup will be compressed and encrypted with a password. Make sure to store it in a secure place.")) + Text("setBackupPassword.description", tableName: "Localizable", bundle: .module) .font(.textStyle(.body1)) .foregroundStyle(Color.primaryText) .multilineTextAlignment(.leading) @@ -75,11 +79,10 @@ struct BackupSheetView: View { Button( action: { - print("Back up initiated with password") dismiss() }, label: { - Text(String("Back Up Now")) + Text("setBackupPassword.button", tableName: "Localizable", bundle: .module) } ) .wireButtonStyle(.primary) @@ -88,7 +91,8 @@ struct BackupSheetView: View { .frame(maxHeight: .infinity) } - func isPasswordValid() -> Bool { + // TODO: remove it + private func isPasswordValid() -> Bool { guard !password.isEmpty else { return true } @@ -101,7 +105,7 @@ struct BackupSheetView: View { // MARK: - Previews @available(iOS 17.0, *) -#Preview("With Data") { +#Preview("Set password sheet") { SetBackupPasswordPreview() } @@ -109,7 +113,7 @@ private struct SetBackupPasswordPreview: View { @State private var isPresented = true var body: some View { - Button("Open Sheet") { + Button("Back Up Now") { isPresented.toggle() } .sheet(isPresented: $isPresented) { diff --git a/WireUI/Sources/WireSettingsUI/Resources/Accessibility.xcstrings b/WireUI/Sources/WireSettingsUI/Resources/Accessibility.xcstrings new file mode 100644 index 00000000000..407fdfc0221 --- /dev/null +++ b/WireUI/Sources/WireSettingsUI/Resources/Accessibility.xcstrings @@ -0,0 +1,17 @@ +{ + "sourceLanguage" : "en", + "strings" : { + "setBackupPassword.close.label" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Close setup backup password" + } + } + } + } + }, + "version" : "1.0" +} \ No newline at end of file diff --git a/WireUI/Sources/WireSettingsUI/Resources/Localizable.xcstrings b/WireUI/Sources/WireSettingsUI/Resources/Localizable.xcstrings new file mode 100644 index 00000000000..a3bb42b3a0c --- /dev/null +++ b/WireUI/Sources/WireSettingsUI/Resources/Localizable.xcstrings @@ -0,0 +1,75 @@ +{ + "sourceLanguage" : "en", + "strings" : { + "Open Sheet" : { + + }, + "passwordField.placeholder" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Enter password" + } + } + } + }, + "passwordField.rules" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Use at least 8 characters, with one lowercase letter, one capital letter, a number, and a special character." + } + } + } + }, + "passwordField.title" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Password (OPTIONAL)" + } + } + } + }, + "setBackupPassword.button" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Back Up Now" + } + } + } + }, + "setBackupPassword.description" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The backup will be compressed and encrypted with a password. Make sure to store it in a secure place." + } + } + } + }, + "setBackupPassword.title" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Set password" + } + } + } + } + }, + "version" : "1.0" +} \ No newline at end of file From 832f44dd5f1fe7fa8f17866f3b73bf2daefefbd5 Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Fri, 3 Jan 2025 16:27:01 +0100 Subject: [PATCH 006/107] move all files to WireUI --- .../Backup/Models/BackupActionsSection.swift | 62 ++++++++++++ .../Backup/Protocols/BackupSource.swift | 26 +++++ .../BackupActionsHostingController.swift | 10 +- .../ViewModels/BackupActionsViewModel.swift | 53 ++++++++++ .../Backup/Views/BackupActionsView.swift | 50 +++++++--- ...ackupPassword.swift => ExportBackup.swift} | 43 +++++--- .../Resources/Localizable.xcstrings | 45 ++++++++- .../generated/AutoMockable.generated.swift | 38 -------- wire-ios/Wire-iOS.xcodeproj/project.pbxproj | 24 ++--- .../Settings/Backup/BackupSource.swift | 18 ++-- .../Backup/Models/BackupSection.swift | 97 +++++++++---------- .../BackupActionsHostingController.swift | 34 +++++++ .../ViewModels/BackupActionsViewModel11.swift | 55 +++++++++++ .../Backup/ViewModels/BackupViewModel.swift | 72 -------------- .../Backup/Views/BackupActionsView.swift | 88 +++++++++++++++++ ...ettingsCellDescriptorFactory+Account.swift | 52 ++++++++-- 16 files changed, 546 insertions(+), 221 deletions(-) create mode 100644 WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupActionsSection.swift create mode 100644 WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/BackupSource.swift rename wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/ViewControllers/BackupHostingController.swift => WireUI/Sources/WireSettingsUI/Account/Backup/ViewControllers/BackupActionsHostingController.swift (74%) create mode 100644 WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift rename wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/Views/BackupView.swift => WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift (50%) rename WireUI/Sources/WireSettingsUI/Account/Backup/Views/{SetBackupPassword.swift => ExportBackup.swift} (76%) create mode 100644 wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/ViewControllers/BackupActionsHostingController.swift create mode 100644 wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/ViewModels/BackupActionsViewModel11.swift delete mode 100644 wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/ViewModels/BackupViewModel.swift create mode 100644 wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/Views/BackupActionsView.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupActionsSection.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupActionsSection.swift new file mode 100644 index 00000000000..197b915f892 --- /dev/null +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupActionsSection.swift @@ -0,0 +1,62 @@ +// +// Wire +// Copyright (C) 2025 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import Foundation +import SwiftUI + +/// The section that will be displayed in the backup actions +struct BackupActionsSection: Identifiable { + + enum Section { + case backup + case restore + + var title: Text { + switch self { + case .backup: + return Text("historyBackup.action", tableName: "Localizable", bundle: .module) + case .restore: + return Text("restoreFromBackup.action", tableName: "Localizable", bundle: .module) + } + } + + var footer: Text { + switch self { + case .backup: + return Text("historyBackup.description", tableName: "Localizable", bundle: .module) + case .restore: + return Text("restoreFromBackup.description", tableName: "Localizable", bundle: .module) + } + } + } + + /// Unique identifier for the section + let id: UUID + + /// The section type + let type: Section + + init( + id: UUID = UUID(), + type: Section + ) { + self.id = id + self.type = type + } + +} diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/BackupSource.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/BackupSource.swift new file mode 100644 index 00000000000..d637a50f018 --- /dev/null +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/BackupSource.swift @@ -0,0 +1,26 @@ +// +// Wire +// Copyright (C) 2025 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import Foundation + +public protocol BackupSource { + // TODO: make password optional + func backupActiveAccount(password: String) throws -> URL + + func clearPreviousBackups() +} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/ViewControllers/BackupHostingController.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewControllers/BackupActionsHostingController.swift similarity index 74% rename from wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/ViewControllers/BackupHostingController.swift rename to WireUI/Sources/WireSettingsUI/Account/Backup/ViewControllers/BackupActionsHostingController.swift index 94b50c5eb53..d7ac231510c 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/ViewControllers/BackupHostingController.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewControllers/BackupActionsHostingController.swift @@ -1,6 +1,6 @@ // // Wire -// Copyright (C) 2024 Wire Swiss GmbH +// Copyright (C) 2025 Wire Swiss GmbH // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -19,12 +19,12 @@ import SwiftUI import UIKit -public final class BackupHostingController: UIHostingController { - private let viewModel: BackupViewModel +public final class BackupActionsHostingController: UIHostingController { + private let viewModel: BackupActionsViewModel - public init(viewModel: BackupViewModel) { + public init(viewModel: BackupActionsViewModel) { self.viewModel = viewModel - super.init(rootView: BackupView(viewModel: viewModel)) + super.init(rootView: BackupActionsView(viewModel: viewModel)) } @available(*, unavailable) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift new file mode 100644 index 00000000000..2736f78db5a --- /dev/null +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift @@ -0,0 +1,53 @@ +// +// Wire +// Copyright (C) 2025 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import SwiftUI + +public final class BackupActionsViewModel: ObservableObject { + @Published var sections: [BackupActionsSection] = [] + + private let backupSource: any BackupSource + private let onSuccessHandler: ((URL, @escaping () -> Void) -> Void) + private let onFailureHandler: ((any Error) -> Void) + + public init( + backupSource: any BackupSource, + onSuccessHandler: @escaping ((URL, @escaping () -> Void) -> Void), + onFailureHandler: @escaping ((any Error) -> Void) + ) { + self.backupSource = backupSource + self.onSuccessHandler = onSuccessHandler + self.onFailureHandler = onFailureHandler + + sections = [ + BackupActionsSection(type: .backup) + //BackupActionsSection(type: .restore) + ] + } + + func backupActiveAccount(password: String) { + do { + let backupPath = try backupSource.backupActiveAccount(password: password) + onSuccessHandler(backupPath) { [weak self] in + self?.backupSource.clearPreviousBackups() + } + } catch { + onFailureHandler(error) + } + } +} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/Views/BackupView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift similarity index 50% rename from wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/Views/BackupView.swift rename to WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift index b947cabb6c6..cca7d06046c 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/Views/BackupView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift @@ -1,6 +1,6 @@ // // Wire -// Copyright (C) 2024 Wire Swiss GmbH +// Copyright (C) 2025 Wire Swiss GmbH // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by @@ -19,13 +19,15 @@ import SwiftUI import WireDesign import WireReusableUIComponents -import WireSettingsUI -public struct BackupView: View { - @ObservedObject private var viewModel: BackupViewModel - @State private var isSheetPresented: Bool = false +public struct BackupActionsView: View { + // TODO: or @StateObject + @ObservedObject private var viewModel: BackupActionsViewModel + @State private var isBackupPresented: Bool = false + //TODO: use `isRestorePresented` + @State private var isRestorePresented: Bool = false - public init(viewModel: BackupViewModel) { + public init(viewModel: BackupActionsViewModel) { self.viewModel = viewModel } @@ -33,23 +35,32 @@ public struct BackupView: View { List { ForEach(viewModel.sections) { section in Section( - footer: Text(section.type.footer) + footer: section.type.footer ) { Button(action: { - print("Action for Section!") - isSheetPresented.toggle() + switch section.type { + case .backup: + isRestorePresented = false + isBackupPresented.toggle() + case .restore: + isBackupPresented = false + isRestorePresented.toggle() + } + }) { HStack { - Text(section.type.title) + section.type.title .font(.textStyle(.body2)) .foregroundStyle(Color.primaryText) Spacer() - Image(.chevronRight).foregroundStyle(Color.primary) + Image("chevron.right").foregroundStyle(Color.primary) } } - .sheet(isPresented: $isSheetPresented) { + .sheet(isPresented: $isBackupPresented) { NavigationStack { - SetBackupPassword() + ExportBackup { password in + viewModel.backupActiveAccount(password: password) + } } .presentationDetents([.medium, .large]) } @@ -62,5 +73,16 @@ public struct BackupView: View { } #Preview { - BackupView(viewModel: BackupViewModel()) + BackupActionsView(viewModel: BackupActionsViewModel( + backupSource: MockBackupSource(), + onSuccessHandler: {_,_ in}, + onFailureHandler: {_ in})) +} + +private class MockBackupSource: BackupSource { + func backupActiveAccount(password: String) throws -> URL { + return URL(fileURLWithPath: "path") + } + + func clearPreviousBackups() {} } diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/SetBackupPassword.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackup.swift similarity index 76% rename from WireUI/Sources/WireSettingsUI/Account/Backup/Views/SetBackupPassword.swift rename to WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackup.swift index 6686f62dc17..cb091155a69 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/SetBackupPassword.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackup.swift @@ -21,20 +21,25 @@ import WireDesign import WireFoundation import WireReusableUIComponents -/// A view that allows to set a password for the backup. +/// A view that allows to export the backup. -public struct SetBackupPassword: View { +public struct ExportBackup: View { @Environment(\.dismiss) private var dismiss + private let onAction: (String) -> Void - public init(){} + public init(onAction: @escaping (String) -> Void) { + self.onAction = onAction + } public var body: some View { - BackupSheetView() + SetBackupPasswordView(onAction: onAction) .background(Color.viewBackground) .scrollContentBackground(.hidden) .navigationTitle( - Text("setBackupPassword.title", tableName: "Localizable", bundle: .module) + Text("setBackupPassword.title", + tableName: "Localizable", + bundle: .module) ) .navigationBarTitleDisplayMode(.inline) .toolbar { @@ -57,11 +62,17 @@ public struct SetBackupPassword: View { } -struct BackupSheetView: View { +private struct SetBackupPasswordView: View { @Environment(\.dismiss) var dismiss @State private var password: String = "" @State private var isPasswordVisible: Bool = false + private let onAction: (String) -> Void + + init(onAction: @escaping (String) -> Void) { + self.onAction = onAction + } + var body: some View { VStack(spacing: 20) { Text("setBackupPassword.description", tableName: "Localizable", bundle: .module) @@ -79,6 +90,7 @@ struct BackupSheetView: View { Button( action: { + onAction(password) dismiss() }, label: { @@ -105,20 +117,25 @@ struct BackupSheetView: View { // MARK: - Previews @available(iOS 17.0, *) -#Preview("Set password sheet") { - SetBackupPasswordPreview() +#Preview("Export Backup sheet") { + ExportBackupPreview() } -private struct SetBackupPasswordPreview: View { +private struct ExportBackupPreview: View { @State private var isPresented = true var body: some View { - Button("Back Up Now") { - isPresented.toggle() - } + Button( + action: { + isPresented.toggle() + }, + label: { + Text("setBackupPassword.button", tableName: "Localizable", bundle: .module) + } + ) .sheet(isPresented: $isPresented) { NavigationStack { - SetBackupPassword() + ExportBackup(onAction: {_ in }) } .presentationDragIndicator(.visible) .presentationDetents([.medium, .large]) diff --git a/WireUI/Sources/WireSettingsUI/Resources/Localizable.xcstrings b/WireUI/Sources/WireSettingsUI/Resources/Localizable.xcstrings index a3bb42b3a0c..8e3dfeeb16b 100644 --- a/WireUI/Sources/WireSettingsUI/Resources/Localizable.xcstrings +++ b/WireUI/Sources/WireSettingsUI/Resources/Localizable.xcstrings @@ -1,8 +1,27 @@ { "sourceLanguage" : "en", "strings" : { - "Open Sheet" : { - + "historyBackup.action" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Back Up Now" + } + } + } + }, + "historyBackup.description" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Create a backup to preserve your conversation history. You can use this to restore history if you lose your computer or switch to a new one. The backup file is not protected by Wire end-to-end encryption, so store it in a safe place." + } + } + } }, "passwordField.placeholder" : { "extractionState" : "manual", @@ -37,6 +56,28 @@ } } }, + "restoreFromBackup.action" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Restore from Backup" + } + } + } + }, + "restoreFromBackup.description" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The existing history on this device remains and will be completed by the new backup. You can restore history from all your devices and different platforms but not from another account." + } + } + } + }, "setBackupPassword.button" : { "extractionState" : "manual", "localizations" : { diff --git a/wire-ios/Tests/Sourcery/generated/AutoMockable.generated.swift b/wire-ios/Tests/Sourcery/generated/AutoMockable.generated.swift index 92fd417865f..f2a5fcadb38 100644 --- a/wire-ios/Tests/Sourcery/generated/AutoMockable.generated.swift +++ b/wire-ios/Tests/Sourcery/generated/AutoMockable.generated.swift @@ -193,44 +193,6 @@ class MockAppStateCalculatorDelegate: AppStateCalculatorDelegate { } -class MockBackupSource: BackupSource { - - // MARK: - Life cycle - - - - // MARK: - backupActiveAccount - - var backupActiveAccountPasswordCompletion_Invocations: [(password: String, completion: (Result) -> Void)] = [] - var backupActiveAccountPasswordCompletion_MockMethod: ((String, @escaping (Result) -> Void) -> Void)? - - func backupActiveAccount(password: String, completion: @escaping (Result) -> Void) { - backupActiveAccountPasswordCompletion_Invocations.append((password: password, completion: completion)) - - guard let mock = backupActiveAccountPasswordCompletion_MockMethod else { - fatalError("no mock for `backupActiveAccountPasswordCompletion`") - } - - mock(password, completion) - } - - // MARK: - clearPreviousBackups - - var clearPreviousBackups_Invocations: [Void] = [] - var clearPreviousBackups_MockMethod: (() -> Void)? - - func clearPreviousBackups() { - clearPreviousBackups_Invocations.append(()) - - guard let mock = clearPreviousBackups_MockMethod else { - fatalError("no mock for `clearPreviousBackups`") - } - - mock() - } - -} - class MockCallQualityRouterProtocol: CallQualityRouterProtocol { // MARK: - Life cycle diff --git a/wire-ios/Wire-iOS.xcodeproj/project.pbxproj b/wire-ios/Wire-iOS.xcodeproj/project.pbxproj index 0573af0895e..1b9d840aef7 100644 --- a/wire-ios/Wire-iOS.xcodeproj/project.pbxproj +++ b/wire-ios/Wire-iOS.xcodeproj/project.pbxproj @@ -101,10 +101,10 @@ 06C412AC238FCAA80018866F /* Int+Const.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06C412AB238FCAA80018866F /* Int+Const.swift */; }; 06C412B0239103910018866F /* ConversationInputBarViewController+DragAndDrop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06C412AF239103910018866F /* ConversationInputBarViewController+DragAndDrop.swift */; }; 06CDC6F62A2DDBCE00EB518D /* FailedUsersSystemMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06CDC6F52A2DDBCE00EB518D /* FailedUsersSystemMessageCell.swift */; }; - 06D4B85B2D22DDF2004627EB /* BackupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06D4B85A2D22DDF2004627EB /* BackupView.swift */; }; + 06D4B85B2D22DDF2004627EB /* BackupActionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06D4B85A2D22DDF2004627EB /* BackupActionsView.swift */; }; 06D4B8632D230537004627EB /* BackupSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06D4B8622D230535004627EB /* BackupSection.swift */; }; - 06D720222D2357DF00D36510 /* BackupHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06D720212D2357C000D36510 /* BackupHostingController.swift */; }; - 06D720242D2359EC00D36510 /* BackupViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06D720232D2359EA00D36510 /* BackupViewModel.swift */; }; + 06D720222D2357DF00D36510 /* BackupActionsHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06D720212D2357C000D36510 /* BackupActionsHostingController.swift */; }; + 06D720242D2359EC00D36510 /* BackupActionsViewModel11.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06D720232D2359EA00D36510 /* BackupActionsViewModel11.swift */; }; 06D93B472B56F64F00A0A512 /* E2EIFeatureChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06D93B462B56F64F00A0A512 /* E2EIFeatureChange.swift */; }; 06DF42D12757DA56009C8A99 /* GuestLinkInfoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06DF42D02757DA55009C8A99 /* GuestLinkInfoCell.swift */; }; 06E097982A20BEE000B38C4A /* ZMConversation+IncompleteMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06E097972A20BEE000B38C4A /* ZMConversation+IncompleteMetadata.swift */; }; @@ -2056,10 +2056,10 @@ 06C412AB238FCAA80018866F /* Int+Const.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Int+Const.swift"; sourceTree = ""; }; 06C412AF239103910018866F /* ConversationInputBarViewController+DragAndDrop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConversationInputBarViewController+DragAndDrop.swift"; sourceTree = ""; }; 06CDC6F52A2DDBCE00EB518D /* FailedUsersSystemMessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FailedUsersSystemMessageCell.swift; sourceTree = ""; }; - 06D4B85A2D22DDF2004627EB /* BackupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupView.swift; sourceTree = ""; }; + 06D4B85A2D22DDF2004627EB /* BackupActionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupActionsView.swift; sourceTree = ""; }; 06D4B8622D230535004627EB /* BackupSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupSection.swift; sourceTree = ""; }; - 06D720212D2357C000D36510 /* BackupHostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupHostingController.swift; sourceTree = ""; }; - 06D720232D2359EA00D36510 /* BackupViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupViewModel.swift; sourceTree = ""; }; + 06D720212D2357C000D36510 /* BackupActionsHostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupActionsHostingController.swift; sourceTree = ""; }; + 06D720232D2359EA00D36510 /* BackupActionsViewModel11.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupActionsViewModel11.swift; sourceTree = ""; }; 06D93B462B56F64F00A0A512 /* E2EIFeatureChange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = E2EIFeatureChange.swift; sourceTree = ""; }; 06DF42D02757DA55009C8A99 /* GuestLinkInfoCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuestLinkInfoCell.swift; sourceTree = ""; }; 06E097972A20BEE000B38C4A /* ZMConversation+IncompleteMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ZMConversation+IncompleteMetadata.swift"; sourceTree = ""; }; @@ -4114,7 +4114,7 @@ 06D4B85D2D230304004627EB /* Views */ = { isa = PBXGroup; children = ( - 06D4B85A2D22DDF2004627EB /* BackupView.swift */, + 06D4B85A2D22DDF2004627EB /* BackupActionsView.swift */, ); path = Views; sourceTree = ""; @@ -4122,7 +4122,7 @@ 06D4B8602D23031D004627EB /* ViewModels */ = { isa = PBXGroup; children = ( - 06D720232D2359EA00D36510 /* BackupViewModel.swift */, + 06D720232D2359EA00D36510 /* BackupActionsViewModel11.swift */, ); path = ViewModels; sourceTree = ""; @@ -4130,7 +4130,7 @@ 06D4B8612D230327004627EB /* ViewControllers */ = { isa = PBXGroup; children = ( - 06D720212D2357C000D36510 /* BackupHostingController.swift */, + 06D720212D2357C000D36510 /* BackupActionsHostingController.swift */, ); path = ViewControllers; sourceTree = ""; @@ -9340,7 +9340,7 @@ 064AD4C425DD31A600143D74 /* UIApplication+OpenURL.swift in Sources */, 87E4D2CA1FC6E39800F3C664 /* CharacterInputField.swift in Sources */, F15650D921DD107800210504 /* RoundedViewProtocol.swift in Sources */, - 06D4B85B2D22DDF2004627EB /* BackupView.swift in Sources */, + 06D4B85B2D22DDF2004627EB /* BackupActionsView.swift in Sources */, A9F57E3D2419A83100EEA912 /* TokenContainer.swift in Sources */, E95F1CA12C3D472A005E80BC /* AnalytitcsServiceConfigurationBuilder.swift in Sources */, F1CB5D252158FE1F001CCF5D /* MentionsHandler+TextView.swift in Sources */, @@ -9535,7 +9535,7 @@ EFF3DA452097587F007A3358 /* UIPasteboard+MediaAsset.swift in Sources */, 1682AEB620480C80003A052A /* UIViewController+Navigation.swift in Sources */, 5E33F0D020AD629D00414EA6 /* CallPermissionsConfiguration.swift in Sources */, - 06D720242D2359EC00D36510 /* BackupViewModel.swift in Sources */, + 06D720242D2359EC00D36510 /* BackupActionsViewModel11.swift in Sources */, EF2388862211A67900331C07 /* UserRight.swift in Sources */, 5405444F253842C200FAE77A /* LaunchSequenceOperation.swift in Sources */, 871BC2551D34F8F800DF0793 /* CrossfadeTransition.swift in Sources */, @@ -9873,7 +9873,7 @@ 06ADE9F12BC7C515008BA0B3 /* RemoveClientsViewController.swift in Sources */, EF17B0D6223A70D1006252A8 /* SketchColorPickerController.swift in Sources */, 16829EDF2010D60800F579A0 /* InviteTeamMemberSection.swift in Sources */, - 06D720222D2357DF00D36510 /* BackupHostingController.swift in Sources */, + 06D720222D2357DF00D36510 /* BackupActionsHostingController.swift in Sources */, 594C4E2B2CCA98F000F13D03 /* AccountImageSourceMapping.swift in Sources */, 87E8D0BD21821E6F00BD26AC /* ConversationContentViewController+Reply.swift in Sources */, 63238C6B2B860E2B00951467 /* DeveloperE2eiView.swift in Sources */, diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupSource.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupSource.swift index 1d562395f1e..61f07c23678 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupSource.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupSource.swift @@ -18,15 +18,17 @@ import Foundation import class WireSyncEngine.SessionManager +import WireSettingsUI +// TODO: replace it // sourcery: AutoMockable -protocol BackupSource { - func backupActiveAccount( - password: String, - completion: @escaping (Result) -> Void - ) - - func clearPreviousBackups() -} +//protocol BackupSource { +// func backupActiveAccount( +// password: String, +// completion: @escaping (Result) -> Void +// ) +// +// func clearPreviousBackups() +//} extension SessionManager: BackupSource {} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/Models/BackupSection.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/Models/BackupSection.swift index 5183ae4fc64..b760a3ee743 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/Models/BackupSection.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/Models/BackupSection.swift @@ -16,54 +16,49 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import Foundation - -/// The section that will be displayed in the backup settings -struct BackupSection: Identifiable { - - typealias SettingsString = L10n.Localizable.Self.Settings - - enum Section { - case backup - case restore - - var title: String { - switch self { - case .backup: - return SettingsString.HistoryBackup.action - case .restore: - return SettingsString.RestoreFromBackup.action - } - } - - var footer: String { - switch self { - case .backup: - return SettingsString.HistoryBackup.description - case .restore: - return SettingsString.RestoreFromBackup.description - } - } - } - - /// Unique identifier for the section - let id: UUID - - /// The section type - let type: Section - - /// OnTap section action - let action: () -> Void - - init( - id: UUID = UUID(), - type: Section, - action: @escaping () -> Void - ) { - self.id = id - self.type = type - self.action = action - } - -} - +//import Foundation +// +///// The section that will be displayed in the backup actions +//struct BackupActionsSection: Identifiable { +// +// typealias SettingsString = L10n.Localizable.Self.Settings +// +// enum Section { +// case backup +// case restore +// +// var title: String { +// switch self { +// case .backup: +// return SettingsString.HistoryBackup.action +// case .restore: +// return SettingsString.RestoreFromBackup.action +// } +// } +// +// var footer: String { +// switch self { +// case .backup: +// return SettingsString.HistoryBackup.description +// case .restore: +// return SettingsString.RestoreFromBackup.description +// } +// } +// } +// +// /// Unique identifier for the section +// let id: UUID +// +// /// The section type +// let type: Section +// +// init( +// id: UUID = UUID(), +// type: Section +// ) { +// self.id = id +// self.type = type +// } +// +//} +// diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/ViewControllers/BackupActionsHostingController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/ViewControllers/BackupActionsHostingController.swift new file mode 100644 index 00000000000..e8d07b00ab8 --- /dev/null +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/ViewControllers/BackupActionsHostingController.swift @@ -0,0 +1,34 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +//import SwiftUI +//import UIKit +// +//public final class BackupActionsHostingController: UIHostingController { +// private let viewModel: BackupActionsViewModel +// +// public init(viewModel: BackupActionsViewModel) { +// self.viewModel = viewModel +// super.init(rootView: BackupActionsView(viewModel: viewModel)) +// } +// +// @available(*, unavailable) +// dynamic required init?(coder aDecoder: NSCoder) { +// fatalError("init(coder:) has not been implemented") +// } +//} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/ViewModels/BackupActionsViewModel11.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/ViewModels/BackupActionsViewModel11.swift new file mode 100644 index 00000000000..48cce544d02 --- /dev/null +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/ViewModels/BackupActionsViewModel11.swift @@ -0,0 +1,55 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +//import SwiftUI +// +//public final class BackupActionsViewModel: ObservableObject { +// @Published var sections: [BackupActionsSection] = [] +// +// private let backupSource: BackupSource +// private let onSuccessHandler: ((URL, @escaping Completion) -> Void) +// private let onFailureHandler: ((Error) -> Void) +// +// init( +// backupSource: BackupSource, +// onSuccessHandler: @escaping ((URL, @escaping Completion) -> Void), +// onFailureHandler: @escaping ((Error) -> Void) +// ) { +// self.backupSource = backupSource +// self.onSuccessHandler = onSuccessHandler +// self.onFailureHandler = onFailureHandler +// +// sections = [ +// BackupActionsSection(type: .backup) +// //BackupActionsSection(type: .restore) +// ] +// } +// +// func backupActiveAccount(password: String) { +// backupSource.backupActiveAccount(password: password) { [weak self] backupResult in +// switch backupResult { +// case let .failure(error): +// self?.onFailureHandler(error) +// case let .success(url): +// self?.onSuccessHandler(url) { [weak self] in +// self?.backupSource.clearPreviousBackups() +// } +// } +// } +// } +//} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/ViewModels/BackupViewModel.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/ViewModels/BackupViewModel.swift deleted file mode 100644 index 12f01301b93..00000000000 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/ViewModels/BackupViewModel.swift +++ /dev/null @@ -1,72 +0,0 @@ -// -// Wire -// Copyright (C) 2024 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import SwiftUI - -public final class BackupViewModel: ObservableObject { - @Published var sections: [BackupSection] = [] - - init() { - sections = [ - BackupSection( - type: .backup, - action: { - print("Action for Section 1 triggered!") - } - ) -// BackupSection( -// type: .restore, -// action: { -// print("Action for Section 2 triggered!") -// } -// ) - ] - } - -// private func backupActiveAccount() { -// requestBackupPassword { [weak self] result in -// guard let self, let password = result else { return } -// // activityIndicator.start() -// -// backupSource.backupActiveAccount(password: password) { backupResult in -// // self.activityIndicator.stop() -// -// switch backupResult { -// case let .failure(error): -// self.presentAlert(for: error) -// case let .success(url): -// self.presentShareSheet(with: url, from: indexPath) -// } -// } -// } -// } - -// private func requestBackupPassword(completion: @escaping (String?) -> Void) { -// let passwordController = BackupPasswordViewController() -// passwordController.onCompletion = { [weak passwordController] password in -// passwordController?.dismiss(animated: true) { -// completion(password) -// } -// } -// let navigationController = KeyboardAvoidingViewController(viewController: passwordController) -// .wrapInNavigationController() -// navigationController.modalPresentationStyle = .formSheet -// present(navigationController, animated: true) -// } - -} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/Views/BackupActionsView.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/Views/BackupActionsView.swift new file mode 100644 index 00000000000..fc3c7eddef9 --- /dev/null +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/Views/BackupActionsView.swift @@ -0,0 +1,88 @@ +// +// Wire +// Copyright (C) 2024 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +//import SwiftUI +//import WireDesign +//import WireReusableUIComponents +//import WireSettingsUI +// +//public struct BackupActionsView: View { +// // TODO: or @StateObject +// @ObservedObject private var viewModel: BackupActionsViewModel +// @State private var isBackupPresented: Bool = false +// //TODO: use `isRestorePresented` +// @State private var isRestorePresented: Bool = false +// +// public init(viewModel: BackupActionsViewModel) { +// self.viewModel = viewModel +// } +// +// public var body: some View { +// List { +// ForEach(viewModel.sections) { section in +// Section( +// footer: Text(section.type.footer) +// ) { +// Button(action: { +// switch section.type { +// case .backup: +// isRestorePresented = false +// isBackupPresented.toggle() +// case .restore: +// isBackupPresented = false +// isRestorePresented.toggle() +// } +// +// }) { +// HStack { +// Text(section.type.title) +// .font(.textStyle(.body2)) +// .foregroundStyle(Color.primaryText) +// Spacer() +// Image(.chevronRight).foregroundStyle(Color.primary) +// } +// } +// .sheet(isPresented: $isBackupPresented) { +// NavigationStack { +// ExportBackup { password in +// viewModel.backupActiveAccount(password: password) +// } +// } +// .presentationDetents([.medium, .large]) +// } +// } +// } +// } +// .listStyle(.grouped) +// .listRowBackground(Color(ColorTheme.Backgrounds.background)) +// } +//} +// +//#Preview { +// BackupActionsView(viewModel: BackupActionsViewModel( +// backupSource: MockBackupSource(), +// onSuccessHandler: {_,_ in}, +// onFailureHandler: {_ in})) +//} +// +//private class MockBackupSource: BackupSource { +// func backupActiveAccount(password: String, completion: @escaping (Result) -> Void) {} +// +// func clearPreviousBackups() {} +// +//} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift index a236a0c9077..190d00f20fe 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift @@ -21,6 +21,7 @@ import WireCommonComponents import WireDataModel import WireDesign import WireSyncEngine +import WireSettingsUI extension ZMUser { var hasValidEmail: Bool { @@ -374,14 +375,17 @@ extension SettingsCellDescriptorFactory { presentationAction: { guard let selfUser = ZMUser.selfUser() else { assertionFailure("ZMUser.selfUser() is nil") - return .none + return UIViewController() } if selfUser.hasValidEmail || selfUser.usesCompanyLogin { - let viewModel = BackupViewModel() - let backupController = BackupHostingController(viewModel: viewModel) - backupController.setupNavigationBarTitle(L10n.Localizable.Self.Settings.HistoryBackup.title) - return backupController - //return BackupViewController(backupSource: SessionManager.shared!) + let viewModel = BackupActionsViewModel( + backupSource: SessionManager.shared!, + onSuccessHandler: presentShareSheet, + onFailureHandler: presentAlert) + let backupActionsController = BackupActionsHostingController(viewModel: viewModel) + backupActionsController.setupNavigationBarTitle(L10n.Localizable.Self.Settings.HistoryBackup.title) + return backupActionsController +// return BackupViewController(backupSource: SessionManager.shared!) } else { let alert = UIAlertController( title: L10n.Localizable.Self.Settings.HistoryBackup.SetEmail.title, @@ -449,3 +453,39 @@ extension SettingsCellDescriptorFactory { } } + +// TODO: 1. Change names, 2. Move somewhere +extension SettingsCellDescriptorFactory { + + private func presentAlert(for error: Error) { + guard let controller = UIApplication.shared.topmostViewController(onlyFullScreen: false) else { + return + } + + let alert = UIAlertController( + title: L10n.Localizable.Self.Settings.HistoryBackup.Error.title, + message: error.localizedDescription, + preferredStyle: .alert + ) + alert.addAction(UIAlertAction( + title: L10n.Localizable.General.ok, + style: .cancel + )) + + controller.present(alert, animated: true) + } + + private func presentShareSheet(with url: URL, completion: @escaping Completion) { + guard let controller = UIApplication.shared.topmostViewController(onlyFullScreen: false) else { + return + } + + let activityController = UIActivityViewController(activityItems: [url], applicationActivities: nil) + activityController.completionWithItemsHandler = { _, _, _, _ in + //self?.backupSource.clearPreviousBackups() + completion() + } + controller.present(activityController, animated: true) + } + +} From 7aa58ba5b66911f2596d9cb5d0bfd423cfead491 Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Fri, 3 Jan 2025 18:11:03 +0100 Subject: [PATCH 007/107] add strings --- WireUI/Package.swift | 5 +- .../Views/FolderPicker/FolderPicker.swift | 2 +- .../Backup/Models/BackupActionsSection.swift | 8 +- .../Backup/Views/BackupActionsView.swift | 2 +- .../Account/Backup/Views/ExportBackup.swift | 10 +- .../Backup/Views/PasswordFieldView.swift | 14 +- .../Resources/Accessibility.xcstrings | 17 -- .../Resources/Localizable.xcstrings | 116 -------- .../Resources/en.lproj/Accessibility.strings | 19 ++ .../Resources/en.lproj/Localizable.strings | 28 ++ .../CoreDataStack+Backup.swift | 173 +++++++---- .../SessionManager+Backup.swift | 120 +++++--- .../Backup/BackupViewController.swift | 278 +++++++++--------- ...ettingsCellDescriptorFactory+Account.swift | 3 +- 14 files changed, 405 insertions(+), 390 deletions(-) delete mode 100644 WireUI/Sources/WireSettingsUI/Resources/Accessibility.xcstrings delete mode 100644 WireUI/Sources/WireSettingsUI/Resources/Localizable.xcstrings create mode 100644 WireUI/Sources/WireSettingsUI/Resources/en.lproj/Accessibility.strings create mode 100644 WireUI/Sources/WireSettingsUI/Resources/en.lproj/Localizable.strings diff --git a/WireUI/Package.swift b/WireUI/Package.swift index 52400c0d480..bb17b0058a9 100644 --- a/WireUI/Package.swift +++ b/WireUI/Package.swift @@ -74,7 +74,10 @@ let package = Package( .target(name: "WireReusableUIComponents", dependencies: ["WireDesign", "WireFoundation"]), .testTarget(name: "WireReusableUIComponentsTests", dependencies: ["WireReusableUIComponents"]), - .target(name: "WireSettingsUI", dependencies: ["WireDesign", "WireFoundation", "WireReusableUIComponents"]), + .target( + name: "WireSettingsUI", + dependencies: ["WireDesign", "WireFoundation", "WireReusableUIComponents"], + plugins: [.plugin(name: "SwiftGenPlugin", package: "WirePlugins")]), .testTarget(name: "WireSettingsUITests", dependencies: ["WireSettingsUI"]), .target( diff --git a/WireUI/Sources/WireMoveToFolderUI/Views/FolderPicker/FolderPicker.swift b/WireUI/Sources/WireMoveToFolderUI/Views/FolderPicker/FolderPicker.swift index f28bbb9203f..e5b2bccee07 100644 --- a/WireUI/Sources/WireMoveToFolderUI/Views/FolderPicker/FolderPicker.swift +++ b/WireUI/Sources/WireMoveToFolderUI/Views/FolderPicker/FolderPicker.swift @@ -88,7 +88,7 @@ public struct FolderPicker: View { } } - private var folderList: some View {// + private var folderList: some View { List(viewModel.folders, id: \.identifier) { folder in FolderRow( folder: folder, diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupActionsSection.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupActionsSection.swift index 197b915f892..937b5a51a39 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupActionsSection.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupActionsSection.swift @@ -29,18 +29,18 @@ struct BackupActionsSection: Identifiable { var title: Text { switch self { case .backup: - return Text("historyBackup.action", tableName: "Localizable", bundle: .module) + return Text(L10n.HistoryBackup.action) case .restore: - return Text("restoreFromBackup.action", tableName: "Localizable", bundle: .module) + return Text(L10n.RestoreFromBackup.action) } } var footer: Text { switch self { case .backup: - return Text("historyBackup.description", tableName: "Localizable", bundle: .module) + return Text(L10n.HistoryBackup.description) case .restore: - return Text("restoreFromBackup.description", tableName: "Localizable", bundle: .module) + return Text(L10n.RestoreFromBackup.description) } } } diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift index cca7d06046c..87f57219b1e 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift @@ -53,7 +53,7 @@ public struct BackupActionsView: View { .font(.textStyle(.body2)) .foregroundStyle(Color.primaryText) Spacer() - Image("chevron.right").foregroundStyle(Color.primary) + Image(systemName: "chevron.right").foregroundStyle(Color.primary) } } .sheet(isPresented: $isBackupPresented) { diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackup.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackup.swift index cb091155a69..1de0be1a2ac 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackup.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackup.swift @@ -37,9 +37,7 @@ public struct ExportBackup: View { .background(Color.viewBackground) .scrollContentBackground(.hidden) .navigationTitle( - Text("setBackupPassword.title", - tableName: "Localizable", - bundle: .module) + Text(L10n.SetBackupPassword.title) ) .navigationBarTitleDisplayMode(.inline) .toolbar { @@ -75,7 +73,7 @@ private struct SetBackupPasswordView: View { var body: some View { VStack(spacing: 20) { - Text("setBackupPassword.description", tableName: "Localizable", bundle: .module) + Text(L10n.SetBackupPassword.description) .font(.textStyle(.body1)) .foregroundStyle(Color.primaryText) .multilineTextAlignment(.leading) @@ -94,7 +92,7 @@ private struct SetBackupPasswordView: View { dismiss() }, label: { - Text("setBackupPassword.button", tableName: "Localizable", bundle: .module) + Text(L10n.SetBackupPassword.button) } ) .wireButtonStyle(.primary) @@ -130,7 +128,7 @@ private struct ExportBackupPreview: View { isPresented.toggle() }, label: { - Text("setBackupPassword.button", tableName: "Localizable", bundle: .module) + Text(L10n.SetBackupPassword.button) } ) .sheet(isPresented: $isPresented) { diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift index 3ea3eb42604..308b67bdfb2 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift @@ -26,16 +26,13 @@ struct PasswordFieldView: View { var body: some View { VStack(alignment: .leading, spacing: 8) { - Text("passwordField.title", tableName: "Localizable", bundle: .module) + Text(L10n.PasswordField.title) .font(.subheadline) .foregroundColor(isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color) ZStack { if isPasswordVisible { - TextField(String( - localized: "passwordField.placeholder", - table: "Localizable", - bundle: .module), + TextField(L10n.PasswordField.placeholder, text: $password) .font(.textStyle(.body1)) .textFieldStyle(RoundedBorderTextFieldStyle()) @@ -44,10 +41,7 @@ struct PasswordFieldView: View { .stroke(isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color, lineWidth: 1) ) } else { - SecureField(String( - localized: "passwordField.placeholder", - table: "Localizable", - bundle: .module), + SecureField(L10n.PasswordField.placeholder, text: $password) .textFieldStyle(RoundedBorderTextFieldStyle()) .overlay( @@ -68,7 +62,7 @@ struct PasswordFieldView: View { } } - Text("passwordField.rules", tableName: "Localizable", bundle: .module) + Text(L10n.PasswordField.rules) .font(.caption) .foregroundColor(isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color) } diff --git a/WireUI/Sources/WireSettingsUI/Resources/Accessibility.xcstrings b/WireUI/Sources/WireSettingsUI/Resources/Accessibility.xcstrings deleted file mode 100644 index 407fdfc0221..00000000000 --- a/WireUI/Sources/WireSettingsUI/Resources/Accessibility.xcstrings +++ /dev/null @@ -1,17 +0,0 @@ -{ - "sourceLanguage" : "en", - "strings" : { - "setBackupPassword.close.label" : { - "extractionState" : "manual", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Close setup backup password" - } - } - } - } - }, - "version" : "1.0" -} \ No newline at end of file diff --git a/WireUI/Sources/WireSettingsUI/Resources/Localizable.xcstrings b/WireUI/Sources/WireSettingsUI/Resources/Localizable.xcstrings deleted file mode 100644 index 8e3dfeeb16b..00000000000 --- a/WireUI/Sources/WireSettingsUI/Resources/Localizable.xcstrings +++ /dev/null @@ -1,116 +0,0 @@ -{ - "sourceLanguage" : "en", - "strings" : { - "historyBackup.action" : { - "extractionState" : "manual", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Back Up Now" - } - } - } - }, - "historyBackup.description" : { - "extractionState" : "manual", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Create a backup to preserve your conversation history. You can use this to restore history if you lose your computer or switch to a new one. The backup file is not protected by Wire end-to-end encryption, so store it in a safe place." - } - } - } - }, - "passwordField.placeholder" : { - "extractionState" : "manual", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Enter password" - } - } - } - }, - "passwordField.rules" : { - "extractionState" : "manual", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Use at least 8 characters, with one lowercase letter, one capital letter, a number, and a special character." - } - } - } - }, - "passwordField.title" : { - "extractionState" : "manual", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Password (OPTIONAL)" - } - } - } - }, - "restoreFromBackup.action" : { - "extractionState" : "manual", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Restore from Backup" - } - } - } - }, - "restoreFromBackup.description" : { - "extractionState" : "manual", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The existing history on this device remains and will be completed by the new backup. You can restore history from all your devices and different platforms but not from another account." - } - } - } - }, - "setBackupPassword.button" : { - "extractionState" : "manual", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Back Up Now" - } - } - } - }, - "setBackupPassword.description" : { - "extractionState" : "manual", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "The backup will be compressed and encrypted with a password. Make sure to store it in a secure place." - } - } - } - }, - "setBackupPassword.title" : { - "extractionState" : "manual", - "localizations" : { - "en" : { - "stringUnit" : { - "state" : "translated", - "value" : "Set password" - } - } - } - } - }, - "version" : "1.0" -} \ No newline at end of file diff --git a/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Accessibility.strings b/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Accessibility.strings new file mode 100644 index 00000000000..9131729c59c --- /dev/null +++ b/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Accessibility.strings @@ -0,0 +1,19 @@ +// +// Wire +// Copyright (C) 2025 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +"setBackupPassword.close.label" = "Close setup backup password" diff --git a/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Localizable.strings b/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Localizable.strings new file mode 100644 index 00000000000..a3a34d79ca6 --- /dev/null +++ b/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Localizable.strings @@ -0,0 +1,28 @@ +// +// Wire +// Copyright (C) 2025 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +"historyBackup.action" = "Back Up Now"; +"historyBackup.description" = "Create a backup to preserve your conversation history. You can use this to restore history if you lose your computer or switch to a new one. The backup file is not protected by Wire end-to-end encryption, so store it in a safe place."; +"passwordField.placeholder" = "Enter password"; +"passwordField.rules" = "Use at least 8 characters, with one lowercase letter, one capital letter, a number, and a special character."; +"passwordField.title" = "Password (OPTIONAL)"; +"restoreFromBackup.action" = "Restore from Backup"; +"restoreFromBackup.description" = "The existing history on this device remains and will be completed by the new backup. You can restore history from all your devices and different platforms but not from another account."; +"setBackupPassword.button" = "Back Up Now"; +"setBackupPassword.description" = "The backup will be compressed and encrypted with a password. Make sure to store it in a secure place."; +"setBackupPassword.title" = "Set password"; diff --git a/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack+Backup.swift b/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack+Backup.swift index 1847139daf0..f7a76c70793 100644 --- a/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack+Backup.swift +++ b/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack+Backup.swift @@ -88,80 +88,141 @@ public extension CoreDataStack { /// - dispatchGroup: group for testing /// - databaseKey: EAR database key /// - completion: called on main thread when done. Result will contain the folder where all data was written to. +// static func backupLocalStorage( +// accountIdentifier: UUID, +// clientIdentifier: String, +// applicationContainer: URL, +// dispatchGroup: ZMSDispatchGroup, +// databaseKey: VolatileData? = nil, +// completion: @escaping (Result) -> Void +// ) { +// func fail(_ error: BackupError) { +// log.debug("error backing up local store: \(error)") +// DispatchQueue.main.async(group: dispatchGroup) { +// completion(.failure(error)) +// } +// } +// +// let accountDirectory = Self.accountDataFolder( +// accountIdentifier: accountIdentifier, +// applicationContainer: applicationContainer +// ) +// let storeFile = accountDirectory.appendingPersistentStoreLocation() +// +// guard fileManager.fileExists(atPath: accountDirectory.path) else { return fail(.failedToRead) } +// +// let backupDirectory = backupsDirectory.appendingPathComponent(UUID().uuidString) +// let databaseDirectory = backupDirectory.appendingPathComponent(databaseDirectoryName) +// let metadataURL = backupDirectory.appendingPathComponent(metadataFilename) +// +// workQueue.async(group: dispatchGroup) { +// do { +// let model = CoreDataStack.loadMessagingModel() +// let coordinator = NSPersistentStoreCoordinator(managedObjectModel: model) +// +// // Create target directory +// try fileManager.createDirectory( +// at: databaseDirectory, +// withIntermediateDirectories: true, +// attributes: nil +// ) +// let backupLocation = databaseDirectory.appendingStoreFile() +// let options = NSPersistentStoreCoordinator.persistentStoreOptions(supportsMigration: false) +// +// // Recreate the persistent store inside a new location +// WireLogger.localStorage.debug("backup: Recreate the persistent store inside a new location") +// try coordinator.replacePersistentStore( +// at: backupLocation, +// destinationOptions: options, +// withPersistentStoreFrom: storeFile, +// sourceOptions: options, +// ofType: NSSQLiteStoreType +// ) +// +// WireLogger.localStorage.debug("backup: prepareStoreForBackupExport") +// try prepareStoreForBackupExport( +// coordinator: coordinator, +// location: backupLocation, +// options: options, +// databaseKey: databaseKey +// ) +// +// // Create & write metadata +// WireLogger.localStorage.debug("backup: Create & write metadata") +// let metadata = BackupMetadata(userIdentifier: accountIdentifier, clientIdentifier: clientIdentifier) +// try metadata.write(to: metadataURL) +// log.info("successfully created backup at: \(backupDirectory.path), metadata: \(metadata)") +// +// DispatchQueue.main.async(group: dispatchGroup) { +// completion(.success(.init(url: backupDirectory, metadata: metadata))) +// } +// } catch { +// fail(.failedToWrite(error)) +// } +// } +// } static func backupLocalStorage( accountIdentifier: UUID, clientIdentifier: String, applicationContainer: URL, dispatchGroup: ZMSDispatchGroup, - databaseKey: VolatileData? = nil, - completion: @escaping (Result) -> Void - ) { - func fail(_ error: BackupError) { - log.debug("error backing up local store: \(error)") - DispatchQueue.main.async(group: dispatchGroup) { - completion(.failure(error)) - } - } - + databaseKey: VolatileData? = nil + ) throws -> BackupInfo { let accountDirectory = Self.accountDataFolder( accountIdentifier: accountIdentifier, applicationContainer: applicationContainer ) let storeFile = accountDirectory.appendingPersistentStoreLocation() - guard fileManager.fileExists(atPath: accountDirectory.path) else { return fail(.failedToRead) } + guard fileManager.fileExists(atPath: accountDirectory.path) else { + throw BackupError.failedToRead + } let backupDirectory = backupsDirectory.appendingPathComponent(UUID().uuidString) let databaseDirectory = backupDirectory.appendingPathComponent(databaseDirectoryName) let metadataURL = backupDirectory.appendingPathComponent(metadataFilename) - workQueue.async(group: dispatchGroup) { - do { - let model = CoreDataStack.loadMessagingModel() - let coordinator = NSPersistentStoreCoordinator(managedObjectModel: model) - - // Create target directory - try fileManager.createDirectory( - at: databaseDirectory, - withIntermediateDirectories: true, - attributes: nil - ) - let backupLocation = databaseDirectory.appendingStoreFile() - let options = NSPersistentStoreCoordinator.persistentStoreOptions(supportsMigration: false) - - // Recreate the persistent store inside a new location - WireLogger.localStorage.debug("backup: Recreate the persistent store inside a new location") - try coordinator.replacePersistentStore( - at: backupLocation, - destinationOptions: options, - withPersistentStoreFrom: storeFile, - sourceOptions: options, - ofType: NSSQLiteStoreType - ) - - WireLogger.localStorage.debug("backup: prepareStoreForBackupExport") - try prepareStoreForBackupExport( - coordinator: coordinator, - location: backupLocation, - options: options, - databaseKey: databaseKey - ) - - // Create & write metadata - WireLogger.localStorage.debug("backup: Create & write metadata") - let metadata = BackupMetadata(userIdentifier: accountIdentifier, clientIdentifier: clientIdentifier) - try metadata.write(to: metadataURL) - log.info("successfully created backup at: \(backupDirectory.path), metadata: \(metadata)") - - DispatchQueue.main.async(group: dispatchGroup) { - completion(.success(.init(url: backupDirectory, metadata: metadata))) - } - } catch { - fail(.failedToWrite(error)) - } + return try workQueue.sync { + let model = CoreDataStack.loadMessagingModel() + let coordinator = NSPersistentStoreCoordinator(managedObjectModel: model) + + // Create target directory + try fileManager.createDirectory( + at: databaseDirectory, + withIntermediateDirectories: true, + attributes: nil + ) + let backupLocation = databaseDirectory.appendingStoreFile() + let options = NSPersistentStoreCoordinator.persistentStoreOptions(supportsMigration: false) + + // Recreate the persistent store inside a new location + WireLogger.localStorage.debug("backup: Recreate the persistent store inside a new location") + try coordinator.replacePersistentStore( + at: backupLocation, + destinationOptions: options, + withPersistentStoreFrom: storeFile, + sourceOptions: options, + ofType: NSSQLiteStoreType + ) + + WireLogger.localStorage.debug("backup: prepareStoreForBackupExport") + try prepareStoreForBackupExport( + coordinator: coordinator, + location: backupLocation, + options: options, + databaseKey: databaseKey + ) + + // Create & write metadata + WireLogger.localStorage.debug("backup: Create & write metadata") + let metadata = BackupMetadata(userIdentifier: accountIdentifier, clientIdentifier: clientIdentifier) + try metadata.write(to: metadataURL) + log.info("successfully created backup at: \(backupDirectory.path), metadata: \(metadata)") + + return BackupInfo(url: backupDirectory, metadata: metadata) } } - + /// Will import a backup for a given account /// /// - Parameters: diff --git a/wire-ios-sync-engine/Source/SessionManager/SessionManager+Backup.swift b/wire-ios-sync-engine/Source/SessionManager/SessionManager+Backup.swift index 9b099963b98..6be4d9b978c 100644 --- a/wire-ios-sync-engine/Source/SessionManager/SessionManager+Backup.swift +++ b/wire-ios-sync-engine/Source/SessionManager/SessionManager+Backup.swift @@ -40,52 +40,71 @@ extension SessionManager { case unknown } - public func backupActiveAccount(password: String, completion: @escaping (Result) -> Void) { + public func backupActiveAccount(password: String) throws -> URL { guard let userId = accountManager.selectedAccount?.userIdentifier, let clientId = activeUserSession?.selfUserClient?.remoteIdentifier, let handle = activeUserSession.flatMap(ZMUser.selfUser)?.handle, let activeUserSession else { - return completion(.failure(BackupError.noActiveAccount)) + throw BackupError.noActiveAccount } - CoreDataStack.backupLocalStorage( - accountIdentifier: userId, - clientIdentifier: clientId, - applicationContainer: sharedContainerURL, - dispatchGroup: dispatchGroup, - databaseKey: activeUserSession.managedObjectContext.databaseKey, - completion: { [dispatchGroup] result in - switch result { - case .success: - break - case .failure: - activeUserSession.analyticsEventTracker?.trackEvent(.Backup.exportFailed) - } - - SessionManager.handle( - result: result, - password: password, - accountId: userId, - dispatchGroup: dispatchGroup, - completion: completion, - handle: handle - ) - } - ) + do { + let backupInfo = try CoreDataStack.backupLocalStorage( + accountIdentifier: userId, + clientIdentifier: clientId, + applicationContainer: sharedContainerURL, + dispatchGroup: dispatchGroup, + databaseKey: activeUserSession.managedObjectContext.databaseKey + ) + + return try SessionManager.handle( + result: .success(backupInfo), + password: password, + accountId: userId, + dispatchGroup: dispatchGroup, + handle: handle + ) + } catch { + activeUserSession.analyticsEventTracker?.trackEvent(.Backup.exportFailed) + throw error + } +// CoreDataStack.backupLocalStorage( +// accountIdentifier: userId, +// clientIdentifier: clientId, +// applicationContainer: sharedContainerURL, +// dispatchGroup: dispatchGroup, +// databaseKey: activeUserSession.managedObjectContext.databaseKey, +// completion: { [dispatchGroup] result in +// switch result { +// case .success: +// break +// case .failure: +// activeUserSession.analyticsEventTracker?.trackEvent(.Backup.exportFailed) +// } +// +// SessionManager.handle( +// result: result, +// password: password, +// accountId: userId, +// dispatchGroup: dispatchGroup, +// completion: completion, +// handle: handle +// ) +// } +// ) } - private static func handle( result: Result, password: String, accountId: UUID, dispatchGroup: ZMSDispatchGroup, - completion: @escaping (Result) -> Void, handle: String - ) { - workerQueue.async(group: dispatchGroup) { - let encrypted = result.flatMap { info in + ) throws -> URL { + return try workerQueue.sync { + switch result { + case .success(let info): do { // 1. Compress the backup let compressed = try compress(backup: info) @@ -93,18 +112,45 @@ extension SessionManager { // 2. Encrypt the backup let url = targetBackupURL(for: info, handle: handle) try encrypt(from: compressed, to: url, password: password, accountId: accountId) - return .success(url) + return url } catch { - return .failure(error) + throw error } - } - - DispatchQueue.main.async(group: dispatchGroup) { - completion(encrypted) + case .failure(let error): + throw error } } } +// private static func handle( +// result: Result, +// password: String, +// accountId: UUID, +// dispatchGroup: ZMSDispatchGroup, +// completion: @escaping (Result) -> Void, +// handle: String +// ) { +// workerQueue.async(group: dispatchGroup) { +// let encrypted = result.flatMap { info in +// do { +// // 1. Compress the backup +// let compressed = try compress(backup: info) +// +// // 2. Encrypt the backup +// let url = targetBackupURL(for: info, handle: handle) +// try encrypt(from: compressed, to: url, password: password, accountId: accountId) +// return .success(url) +// } catch { +// return .failure(error) +// } +// } +// +// DispatchQueue.main.async(group: dispatchGroup) { +// completion(encrypted) +// } +// } +// } + // MARK: - Import /// Restores the account database from the Wire iOS database back up file. diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupViewController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupViewController.swift index 793ce77e293..1d919b14c03 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupViewController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupViewController.swift @@ -20,142 +20,142 @@ import UIKit import WireDesign import WireReusableUIComponents -final class BackupViewController: UIViewController {// - - private let tableView = UITableView(frame: .zero) - private var cells: [UITableViewCell.Type] = [] - private let backupSource: BackupSource - private lazy var activityIndicator = BlockingActivityIndicator(view: navigationController?.view ?? view) - - init(backupSource: BackupSource) { - self.backupSource = backupSource - super.init(nibName: nil, bundle: nil) - } - - @available(*, unavailable) - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewDidLoad() { - super.viewDidLoad() - setupViews() - setupLayout() - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - setupNavigationBarTitle(L10n.Localizable.Self.Settings.HistoryBackup.title.capitalized) - } - - private func setupViews() { - view.backgroundColor = ColorTheme.Backgrounds.background - - tableView.isScrollEnabled = false - tableView.rowHeight = UITableView.automaticDimension - tableView.estimatedRowHeight = 80 - tableView.backgroundColor = .clear - tableView.separatorColor = UIColor(white: 1, alpha: 0.1) - tableView.translatesAutoresizingMaskIntoConstraints = false - view.addSubview(tableView) - tableView.delegate = self - tableView.dataSource = self - - // this is necessary to remove the placeholder cells - tableView.tableFooterView = UIView() - cells = [BackupStatusCell.self, BackupActionCell.self] - - cells.forEach { - tableView.register($0.self, forCellReuseIdentifier: $0.reuseIdentifier) - } - } - - private func setupLayout() { - tableView.fitIn(view: view) - } -} - -// MARK: - UITableViewDataSource & UITableViewDelegate - -extension BackupViewController: UITableViewDataSource, UITableViewDelegate { - - func numberOfSections(in tableView: UITableView) -> Int { - 1 - } - - func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - cells.count - } - - func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { - tableView.dequeueReusableCell(withIdentifier: cells[indexPath.row].reuseIdentifier, for: indexPath) - } - - func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { - tableView.deselectRow(at: indexPath, animated: true) - guard indexPath.row == 1 else { return } - backupActiveAccount(indexPath: indexPath) - } -} - -// MARK: - Backup Logic - -private extension BackupViewController { - - func backupActiveAccount(indexPath: IndexPath) { - requestBackupPassword { [weak self] result in - guard let self, let password = result else { return } - activityIndicator.start() - - backupSource.backupActiveAccount(password: password) { backupResult in - self.activityIndicator.stop() - - switch backupResult { - case let .failure(error): - self.presentAlert(for: error) - case let .success(url): - self.presentShareSheet(with: url, from: indexPath) - } - } - } - } - - private func requestBackupPassword(completion: @escaping (String?) -> Void) { - let passwordController = BackupPasswordViewController() - passwordController.onCompletion = { [weak passwordController] password in - passwordController?.dismiss(animated: true) { - completion(password) - } - } - let navigationController = KeyboardAvoidingViewController(viewController: passwordController) - .wrapInNavigationController() - navigationController.modalPresentationStyle = .formSheet - present(navigationController, animated: true) - } - - private func presentAlert(for error: Error) { - let alert = UIAlertController( - title: L10n.Localizable.Self.Settings.HistoryBackup.Error.title, - message: error.localizedDescription, - preferredStyle: .alert - ) - alert.addAction(UIAlertAction( - title: L10n.Localizable.General.ok, - style: .cancel - )) - - present(alert, animated: true) - } - - private func presentShareSheet(with url: URL, from indexPath: IndexPath) { - let activityController = UIActivityViewController(activityItems: [url], applicationActivities: nil) - activityController.completionWithItemsHandler = { [weak self] _, _, _, _ in - self?.backupSource.clearPreviousBackups() - } - activityController.popoverPresentationController.map { - $0.sourceView = tableView - $0.sourceRect = tableView.rectForRow(at: indexPath) - } - present(activityController, animated: true) - } -} +//final class BackupViewController: UIViewController { +// +// private let tableView = UITableView(frame: .zero) +// private var cells: [UITableViewCell.Type] = [] +// private let backupSource: BackupSource +// private lazy var activityIndicator = BlockingActivityIndicator(view: navigationController?.view ?? view) +// +// init(backupSource: BackupSource) { +// self.backupSource = backupSource +// super.init(nibName: nil, bundle: nil) +// } +// +// @available(*, unavailable) +// required init?(coder aDecoder: NSCoder) { +// fatalError("init(coder:) has not been implemented") +// } +// +// override func viewDidLoad() { +// super.viewDidLoad() +// setupViews() +// setupLayout() +// } +// +// override func viewWillAppear(_ animated: Bool) { +// super.viewWillAppear(animated) +// setupNavigationBarTitle(L10n.Localizable.Self.Settings.HistoryBackup.title.capitalized) +// } +// +// private func setupViews() { +// view.backgroundColor = ColorTheme.Backgrounds.background +// +// tableView.isScrollEnabled = false +// tableView.rowHeight = UITableView.automaticDimension +// tableView.estimatedRowHeight = 80 +// tableView.backgroundColor = .clear +// tableView.separatorColor = UIColor(white: 1, alpha: 0.1) +// tableView.translatesAutoresizingMaskIntoConstraints = false +// view.addSubview(tableView) +// tableView.delegate = self +// tableView.dataSource = self +// +// // this is necessary to remove the placeholder cells +// tableView.tableFooterView = UIView() +// cells = [BackupStatusCell.self, BackupActionCell.self] +// +// cells.forEach { +// tableView.register($0.self, forCellReuseIdentifier: $0.reuseIdentifier) +// } +// } +// +// private func setupLayout() { +// tableView.fitIn(view: view) +// } +//} +// +//// MARK: - UITableViewDataSource & UITableViewDelegate +// +//extension BackupViewController: UITableViewDataSource, UITableViewDelegate { +// +// func numberOfSections(in tableView: UITableView) -> Int { +// 1 +// } +// +// func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { +// cells.count +// } +// +// func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { +// tableView.dequeueReusableCell(withIdentifier: cells[indexPath.row].reuseIdentifier, for: indexPath) +// } +// +// func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { +// tableView.deselectRow(at: indexPath, animated: true) +// guard indexPath.row == 1 else { return } +// backupActiveAccount(indexPath: indexPath) +// } +//} +// +//// MARK: - Backup Logic +// +//private extension BackupViewController { +// +// func backupActiveAccount(indexPath: IndexPath) { +// requestBackupPassword { [weak self] result in +// guard let self, let password = result else { return } +// activityIndicator.start() +// +// backupSource.backupActiveAccount(password: password) { backupResult in +// self.activityIndicator.stop() +// +// switch backupResult { +// case let .failure(error): +// self.presentAlert(for: error) +// case let .success(url): +// self.presentShareSheet(with: url, from: indexPath) +// } +// } +// } +// } +// +// private func requestBackupPassword(completion: @escaping (String?) -> Void) { +// let passwordController = BackupPasswordViewController() +// passwordController.onCompletion = { [weak passwordController] password in +// passwordController?.dismiss(animated: true) { +// completion(password) +// } +// } +// let navigationController = KeyboardAvoidingViewController(viewController: passwordController) +// .wrapInNavigationController() +// navigationController.modalPresentationStyle = .formSheet +// present(navigationController, animated: true) +// } +// +// private func presentAlert(for error: Error) { +// let alert = UIAlertController( +// title: L10n.Localizable.Self.Settings.HistoryBackup.Error.title, +// message: error.localizedDescription, +// preferredStyle: .alert +// ) +// alert.addAction(UIAlertAction( +// title: L10n.Localizable.General.ok, +// style: .cancel +// )) +// +// present(alert, animated: true) +// } +// +// private func presentShareSheet(with url: URL, from indexPath: IndexPath) { +// let activityController = UIActivityViewController(activityItems: [url], applicationActivities: nil) +// activityController.completionWithItemsHandler = { [weak self] _, _, _, _ in +// self?.backupSource.clearPreviousBackups() +// } +// activityController.popoverPresentationController.map { +// $0.sourceView = tableView +// $0.sourceRect = tableView.rectForRow(at: indexPath) +// } +// present(activityController, animated: true) +// } +//} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift index 190d00f20fe..13450b9f8d7 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift @@ -375,7 +375,7 @@ extension SettingsCellDescriptorFactory { presentationAction: { guard let selfUser = ZMUser.selfUser() else { assertionFailure("ZMUser.selfUser() is nil") - return UIViewController() + return .none } if selfUser.hasValidEmail || selfUser.usesCompanyLogin { let viewModel = BackupActionsViewModel( @@ -385,7 +385,6 @@ extension SettingsCellDescriptorFactory { let backupActionsController = BackupActionsHostingController(viewModel: viewModel) backupActionsController.setupNavigationBarTitle(L10n.Localizable.Self.Settings.HistoryBackup.title) return backupActionsController -// return BackupViewController(backupSource: SessionManager.shared!) } else { let alert = UIAlertController( title: L10n.Localizable.Self.Settings.HistoryBackup.SetEmail.title, From 2a46189d3507229764c9f982bb4185fc05871ff8 Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Fri, 3 Jan 2025 18:47:32 +0100 Subject: [PATCH 008/107] remove files --- .../CoreDataStack+Backup.swift | 77 +--- .../SessionManager+Backup.swift | 54 +-- wire-ios/Wire-iOS.xcodeproj/project.pbxproj | 48 --- .../Generated/Strings+Generated.swift | 22 -- .../Resources/Base.lproj/Localizable.strings | 13 - .../Settings/Backup/BackupActionCell.swift | 72 ++-- .../Backup/BackupPasswordViewController.swift | 336 +++++++++--------- .../Settings/Backup/BackupSource.swift | 11 - .../Settings/Backup/BackupStatusCell.swift | 100 +++--- .../Backup/BackupViewController.swift | 6 +- .../Backup/Models/BackupSection.swift | 64 ---- .../BackupActionsHostingController.swift | 34 -- .../ViewModels/BackupActionsViewModel11.swift | 55 --- .../Backup/Views/BackupActionsView.swift | 88 ----- 14 files changed, 260 insertions(+), 720 deletions(-) delete mode 100644 wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/Models/BackupSection.swift delete mode 100644 wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/ViewControllers/BackupActionsHostingController.swift delete mode 100644 wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/ViewModels/BackupActionsViewModel11.swift delete mode 100644 wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/Views/BackupActionsView.swift diff --git a/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack+Backup.swift b/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack+Backup.swift index b42765857fe..5b8269562d4 100644 --- a/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack+Backup.swift +++ b/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack+Backup.swift @@ -81,86 +81,13 @@ public extension CoreDataStack { } /// Will make a copy of account storage and place in a unique directory - /// /// - Parameters: /// - accountIdentifier: identifier of account being backed up + /// - clientIdentifier: client ID /// - applicationContainer: shared application container /// - dispatchGroup: group for testing /// - databaseKey: EAR database key - /// - completion: called on main thread when done. Result will contain the folder where all data was written to. -// static func backupLocalStorage( -// accountIdentifier: UUID, -// clientIdentifier: String, -// applicationContainer: URL, -// dispatchGroup: ZMSDispatchGroup, -// databaseKey: VolatileData? = nil, -// completion: @escaping (Result) -> Void -// ) { -// func fail(_ error: BackupError) { -// log.debug("error backing up local store: \(error)") -// DispatchQueue.main.async(group: dispatchGroup) { -// completion(.failure(error)) -// } -// } -// -// let accountDirectory = Self.accountDataFolder( -// accountIdentifier: accountIdentifier, -// applicationContainer: applicationContainer -// ) -// let storeFile = accountDirectory.appendingPersistentStoreLocation() -// -// guard fileManager.fileExists(atPath: accountDirectory.path) else { return fail(.failedToRead) } -// -// let backupDirectory = backupsDirectory.appendingPathComponent(UUID().uuidString) -// let databaseDirectory = backupDirectory.appendingPathComponent(databaseDirectoryName) -// let metadataURL = backupDirectory.appendingPathComponent(metadataFilename) -// -// workQueue.async(group: dispatchGroup) { -// do { -// let model = CoreDataStack.loadMessagingModel() -// let coordinator = NSPersistentStoreCoordinator(managedObjectModel: model) -// -// // Create target directory -// try fileManager.createDirectory( -// at: databaseDirectory, -// withIntermediateDirectories: true, -// attributes: nil -// ) -// let backupLocation = databaseDirectory.appendingStoreFile() -// let options = NSPersistentStoreCoordinator.persistentStoreOptions(supportsMigration: false) -// -// // Recreate the persistent store inside a new location -// WireLogger.localStorage.debug("backup: Recreate the persistent store inside a new location") -// try coordinator.replacePersistentStore( -// at: backupLocation, -// destinationOptions: options, -// withPersistentStoreFrom: storeFile, -// sourceOptions: options, -// ofType: NSSQLiteStoreType -// ) -// -// WireLogger.localStorage.debug("backup: prepareStoreForBackupExport") -// try prepareStoreForBackupExport( -// coordinator: coordinator, -// location: backupLocation, -// options: options, -// databaseKey: databaseKey -// ) -// -// // Create & write metadata -// WireLogger.localStorage.debug("backup: Create & write metadata") -// let metadata = BackupMetadata(userIdentifier: accountIdentifier, clientIdentifier: clientIdentifier) -// try metadata.write(to: metadataURL) -// log.info("successfully created backup at: \(backupDirectory.path), metadata: \(metadata)") -// -// DispatchQueue.main.async(group: dispatchGroup) { -// completion(.success(.init(url: backupDirectory, metadata: metadata))) -// } -// } catch { -// fail(.failedToWrite(error)) -// } -// } -// } + /// - Returns: contains the folder where all data was written to static func backupLocalStorage( accountIdentifier: UUID, clientIdentifier: String, diff --git a/wire-ios-sync-engine/Source/SessionManager/SessionManager+Backup.swift b/wire-ios-sync-engine/Source/SessionManager/SessionManager+Backup.swift index df4f7ca8f43..7718876c1df 100644 --- a/wire-ios-sync-engine/Source/SessionManager/SessionManager+Backup.swift +++ b/wire-ios-sync-engine/Source/SessionManager/SessionManager+Backup.swift @@ -70,31 +70,8 @@ extension SessionManager { activeUserSession.analyticsEventTracker?.trackEvent(.Backup.exportFailed) throw error } -// CoreDataStack.backupLocalStorage( -// accountIdentifier: userId, -// clientIdentifier: clientId, -// applicationContainer: sharedContainerURL, -// dispatchGroup: dispatchGroup, -// databaseKey: activeUserSession.managedObjectContext.databaseKey, -// completion: { [dispatchGroup] result in -// switch result { -// case .success: -// break -// case .failure: -// activeUserSession.analyticsEventTracker?.trackEvent(.Backup.exportFailed) -// } -// -// SessionManager.handle( -// result: result, -// password: password, -// accountId: userId, -// dispatchGroup: dispatchGroup, -// completion: completion, -// handle: handle -// ) -// } -// ) } + private static func handle( result: Result, password: String, @@ -122,35 +99,6 @@ extension SessionManager { } } -// private static func handle( -// result: Result, -// password: String, -// accountId: UUID, -// dispatchGroup: ZMSDispatchGroup, -// completion: @escaping (Result) -> Void, -// handle: String -// ) { -// workerQueue.async(group: dispatchGroup) { -// let encrypted = result.flatMap { info in -// do { -// // 1. Compress the backup -// let compressed = try compress(backup: info) -// -// // 2. Encrypt the backup -// let url = targetBackupURL(for: info, handle: handle) -// try encrypt(from: compressed, to: url, password: password, accountId: accountId) -// return .success(url) -// } catch { -// return .failure(error) -// } -// } -// -// DispatchQueue.main.async(group: dispatchGroup) { -// completion(encrypted) -// } -// } -// } - // MARK: - Import /// Restores the account database from the Wire iOS database back up file. diff --git a/wire-ios/Wire-iOS.xcodeproj/project.pbxproj b/wire-ios/Wire-iOS.xcodeproj/project.pbxproj index ab3b3855234..a40074ac1ad 100644 --- a/wire-ios/Wire-iOS.xcodeproj/project.pbxproj +++ b/wire-ios/Wire-iOS.xcodeproj/project.pbxproj @@ -101,10 +101,6 @@ 06C412AC238FCAA80018866F /* Int+Const.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06C412AB238FCAA80018866F /* Int+Const.swift */; }; 06C412B0239103910018866F /* ConversationInputBarViewController+DragAndDrop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06C412AF239103910018866F /* ConversationInputBarViewController+DragAndDrop.swift */; }; 06CDC6F62A2DDBCE00EB518D /* FailedUsersSystemMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06CDC6F52A2DDBCE00EB518D /* FailedUsersSystemMessageCell.swift */; }; - 06D4B85B2D22DDF2004627EB /* BackupActionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06D4B85A2D22DDF2004627EB /* BackupActionsView.swift */; }; - 06D4B8632D230537004627EB /* BackupSection.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06D4B8622D230535004627EB /* BackupSection.swift */; }; - 06D720222D2357DF00D36510 /* BackupActionsHostingController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06D720212D2357C000D36510 /* BackupActionsHostingController.swift */; }; - 06D720242D2359EC00D36510 /* BackupActionsViewModel11.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06D720232D2359EA00D36510 /* BackupActionsViewModel11.swift */; }; 06D93B472B56F64F00A0A512 /* E2EIFeatureChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06D93B462B56F64F00A0A512 /* E2EIFeatureChange.swift */; }; 06DF42D12757DA56009C8A99 /* GuestLinkInfoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06DF42D02757DA55009C8A99 /* GuestLinkInfoCell.swift */; }; 06E097982A20BEE000B38C4A /* ZMConversation+IncompleteMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06E097972A20BEE000B38C4A /* ZMConversation+IncompleteMetadata.swift */; }; @@ -2056,10 +2052,6 @@ 06C412AB238FCAA80018866F /* Int+Const.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Int+Const.swift"; sourceTree = ""; }; 06C412AF239103910018866F /* ConversationInputBarViewController+DragAndDrop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConversationInputBarViewController+DragAndDrop.swift"; sourceTree = ""; }; 06CDC6F52A2DDBCE00EB518D /* FailedUsersSystemMessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FailedUsersSystemMessageCell.swift; sourceTree = ""; }; - 06D4B85A2D22DDF2004627EB /* BackupActionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupActionsView.swift; sourceTree = ""; }; - 06D4B8622D230535004627EB /* BackupSection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupSection.swift; sourceTree = ""; }; - 06D720212D2357C000D36510 /* BackupActionsHostingController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupActionsHostingController.swift; sourceTree = ""; }; - 06D720232D2359EA00D36510 /* BackupActionsViewModel11.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupActionsViewModel11.swift; sourceTree = ""; }; 06D93B462B56F64F00A0A512 /* E2EIFeatureChange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = E2EIFeatureChange.swift; sourceTree = ""; }; 06DF42D02757DA55009C8A99 /* GuestLinkInfoCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuestLinkInfoCell.swift; sourceTree = ""; }; 06E097972A20BEE000B38C4A /* ZMConversation+IncompleteMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ZMConversation+IncompleteMetadata.swift"; sourceTree = ""; }; @@ -4103,38 +4095,6 @@ path = UIAlertController; sourceTree = ""; }; - 06D4B85C2D2302F3004627EB /* Models */ = { - isa = PBXGroup; - children = ( - 06D4B8622D230535004627EB /* BackupSection.swift */, - ); - path = Models; - sourceTree = ""; - }; - 06D4B85D2D230304004627EB /* Views */ = { - isa = PBXGroup; - children = ( - 06D4B85A2D22DDF2004627EB /* BackupActionsView.swift */, - ); - path = Views; - sourceTree = ""; - }; - 06D4B8602D23031D004627EB /* ViewModels */ = { - isa = PBXGroup; - children = ( - 06D720232D2359EA00D36510 /* BackupActionsViewModel11.swift */, - ); - path = ViewModels; - sourceTree = ""; - }; - 06D4B8612D230327004627EB /* ViewControllers */ = { - isa = PBXGroup; - children = ( - 06D720212D2357C000D36510 /* BackupActionsHostingController.swift */, - ); - path = ViewControllers; - sourceTree = ""; - }; 06F3E73124EFA9E600B1083C /* Notification Extension */ = { isa = PBXGroup; children = ( @@ -7542,10 +7502,6 @@ E666EDD42B73E5F500C03E2B /* Backup */ = { isa = PBXGroup; children = ( - 06D4B8612D230327004627EB /* ViewControllers */, - 06D4B8602D23031D004627EB /* ViewModels */, - 06D4B85D2D230304004627EB /* Views */, - 06D4B85C2D2302F3004627EB /* Models */, E666EDD52B73E62800C03E2B /* BackupSource.swift */, E666EDDB2B73EA3500C03E2B /* BackupActionCell.swift */, E666EDD92B73E9C400C03E2B /* BackupStatusCell.swift */, @@ -9340,7 +9296,6 @@ 064AD4C425DD31A600143D74 /* UIApplication+OpenURL.swift in Sources */, 87E4D2CA1FC6E39800F3C664 /* CharacterInputField.swift in Sources */, F15650D921DD107800210504 /* RoundedViewProtocol.swift in Sources */, - 06D4B85B2D22DDF2004627EB /* BackupActionsView.swift in Sources */, A9F57E3D2419A83100EEA912 /* TokenContainer.swift in Sources */, E95F1CA12C3D472A005E80BC /* AnalytitcsServiceConfigurationBuilder.swift in Sources */, F1CB5D252158FE1F001CCF5D /* MentionsHandler+TextView.swift in Sources */, @@ -9535,7 +9490,6 @@ EFF3DA452097587F007A3358 /* UIPasteboard+MediaAsset.swift in Sources */, 1682AEB620480C80003A052A /* UIViewController+Navigation.swift in Sources */, 5E33F0D020AD629D00414EA6 /* CallPermissionsConfiguration.swift in Sources */, - 06D720242D2359EC00D36510 /* BackupActionsViewModel11.swift in Sources */, EF2388862211A67900331C07 /* UserRight.swift in Sources */, 5405444F253842C200FAE77A /* LaunchSequenceOperation.swift in Sources */, 871BC2551D34F8F800DF0793 /* CrossfadeTransition.swift in Sources */, @@ -9873,7 +9827,6 @@ 06ADE9F12BC7C515008BA0B3 /* RemoveClientsViewController.swift in Sources */, EF17B0D6223A70D1006252A8 /* SketchColorPickerController.swift in Sources */, 16829EDF2010D60800F579A0 /* InviteTeamMemberSection.swift in Sources */, - 06D720222D2357DF00D36510 /* BackupActionsHostingController.swift in Sources */, 594C4E2B2CCA98F000F13D03 /* AccountImageSourceMapping.swift in Sources */, 87E8D0BD21821E6F00BD26AC /* ConversationContentViewController+Reply.swift in Sources */, 63238C6B2B860E2B00951467 /* DeveloperE2eiView.swift in Sources */, @@ -9981,7 +9934,6 @@ 5EE73BF3212323C70032986D /* RegistrationCredentialsVerifiedEventHandler.swift in Sources */, EE8E926E2024F085000F4752 /* MarkdownTextView.swift in Sources */, 63E128B726208BAD009D9AF2 /* CallGridHintNotificationLabel.swift in Sources */, - 06D4B8632D230537004627EB /* BackupSection.swift in Sources */, A9E674D124F40C550058FC72 /* KeyboardAvoidingAuthenticationCoordinatedViewController.swift in Sources */, F12CDAE01E43868200CEFCEB /* ConfirmEmailViewController.swift in Sources */, 7CCFC10F228AFCC0007C365B /* ConversationLegalHoldCell.swift in Sources */, diff --git a/wire-ios/Wire-iOS/Generated/Strings+Generated.swift b/wire-ios/Wire-iOS/Generated/Strings+Generated.swift index bb324c83527..2f1e2556866 100644 --- a/wire-ios/Wire-iOS/Generated/Strings+Generated.swift +++ b/wire-ios/Wire-iOS/Generated/Strings+Generated.swift @@ -5558,28 +5558,12 @@ internal enum L10n { internal static let header = L10n.tr("Localizable", "self.settings.external_apps.header", fallback: "Open With") } internal enum HistoryBackup { - /// Back Up Now - internal static let action = L10n.tr("Localizable", "self.settings.history_backup.action", fallback: "Back Up Now") - /// Create a backup to preserve your conversation history. You can use this to restore history if you lose your computer or switch to a new one. The backup file is not protected by Wire end-to-end encryption, so store it in a safe place. - internal static let description = L10n.tr("Localizable", "self.settings.history_backup.description", fallback: "Create a backup to preserve your conversation history. You can use this to restore history if you lose your computer or switch to a new one. The backup file is not protected by Wire end-to-end encryption, so store it in a safe place.") /// Back up or Restore internal static let title = L10n.tr("Localizable", "self.settings.history_backup.title", fallback: "Back up or Restore") internal enum Error { /// Error internal static let title = L10n.tr("Localizable", "self.settings.history_backup.error.title", fallback: "Error") } - internal enum Password { - /// Cancel - internal static let cancel = L10n.tr("Localizable", "self.settings.history_backup.password.cancel", fallback: "Cancel") - /// The backup will be compressed and encrypted with the password you set here. - internal static let description = L10n.tr("Localizable", "self.settings.history_backup.password.description", fallback: "The backup will be compressed and encrypted with the password you set here.") - /// Next - internal static let next = L10n.tr("Localizable", "self.settings.history_backup.password.next", fallback: "Next") - /// Password - internal static let placeholder = L10n.tr("Localizable", "self.settings.history_backup.password.placeholder", fallback: "Password") - /// Set Password - internal static let title = L10n.tr("Localizable", "self.settings.history_backup.password.title", fallback: "Set Password") - } internal enum SetEmail { /// You need an email and a password in order to back up your conversation history. You can do it from the account page in Settings. internal static let message = L10n.tr("Localizable", "self.settings.history_backup.set_email.message", fallback: "You need an email and a password in order to back up your conversation history. You can do it from the account page in Settings.") @@ -5761,12 +5745,6 @@ internal enum L10n { internal static let title = L10n.tr("Localizable", "self.settings.receiveNews_and_offers.description.title", fallback: "Receive news and product updates from Wire via email.") } } - internal enum RestoreFromBackup { - /// Restore from Backup - internal static let action = L10n.tr("Localizable", "self.settings.restore_from_backup.action", fallback: "Restore from Backup") - /// The existing history on this device remains and will be completed by the new backup. You can restore history from all your devices and different platforms but not from another account. - internal static let description = L10n.tr("Localizable", "self.settings.restore_from_backup.description", fallback: "The existing history on this device remains and will be completed by the new backup. You can restore history from all your devices and different platforms but not from another account.") - } internal enum SoundMenu { /// Sound Alerts internal static let title = L10n.tr("Localizable", "self.settings.sound_menu.title", fallback: "Sound Alerts") diff --git a/wire-ios/Wire-iOS/Resources/Base.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/Base.lproj/Localizable.strings index 7f8bed552a9..644440bfc29 100644 --- a/wire-ios/Wire-iOS/Resources/Base.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/Base.lproj/Localizable.strings @@ -1357,23 +1357,10 @@ "self.settings.conversations.title" = "History"; "self.settings.history_backup.title" = "Back up or Restore"; -"self.settings.history_backup.description" = "Create a backup to preserve your conversation history. You can use this to restore history if you lose your computer or switch to a new one. The backup file is not protected by Wire end-to-end encryption, so store it in a safe place."; -"self.settings.history_backup.action" = "Back Up Now"; -"self.settings.restore_from_backup.description" = "The existing history on this device remains and will be completed by the new backup. You can restore history from all your devices and different platforms but not from another account."; -"self.settings.restore_from_backup.action" = "Restore from Backup"; "self.settings.history_backup.error.title" = "Error"; - "self.settings.history_backup.set_email.title" = "Set an email and password."; "self.settings.history_backup.set_email.message" = "You need an email and a password in order to back up your conversation history. You can do it from the account page in Settings."; -// History backup password - -"self.settings.history_backup.password.title" = "Set Password"; -"self.settings.history_backup.password.cancel" = "Cancel"; -"self.settings.history_backup.password.next" = "Next"; -"self.settings.history_backup.password.placeholder" = "Password"; -"self.settings.history_backup.password.description" = "The backup will be compressed and encrypted with the password you set here."; - // +++++++++++++++++++++++++++++++++++++++++++++++++++++ // System status "system_status_bar.no_internet.title" = "No Internet"; diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupActionCell.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupActionCell.swift index 12397d4bdbd..729170e76f9 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupActionCell.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupActionCell.swift @@ -19,39 +19,39 @@ import UIKit import WireDesign -final class BackupActionCell: UITableViewCell { - let actionTitleLabel: DynamicFontLabel = { - let text = L10n.Localizable.Self.Settings.HistoryBackup.action - let label = DynamicFontLabel( - text: text, - style: .body2, - color: SemanticColors.Label.textDefault - ) - label.textAlignment = .left - return label - }() - - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) - selectionStyle = .none - backgroundColor = SemanticColors.View.backgroundUserCell - accessibilityTraits = .button - contentView.backgroundColor = .clear - - actionTitleLabel.translatesAutoresizingMaskIntoConstraints = false - contentView.addSubview(actionTitleLabel) - NSLayoutConstraint.activate([ - actionTitleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 24), - actionTitleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -24), - actionTitleLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 0), - actionTitleLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0) - ]) - actionTitleLabel.heightAnchor.constraint(equalToConstant: 44).isActive = true - addBorder(for: .bottom) - } - - @available(*, unavailable) - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} +//final class BackupActionCell: UITableViewCell { +// let actionTitleLabel: DynamicFontLabel = { +// let text = L10n.Localizable.Self.Settings.HistoryBackup.action +// let label = DynamicFontLabel( +// text: text, +// style: .body2, +// color: SemanticColors.Label.textDefault +// ) +// label.textAlignment = .left +// return label +// }() +// +// override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { +// super.init(style: style, reuseIdentifier: reuseIdentifier) +// selectionStyle = .none +// backgroundColor = SemanticColors.View.backgroundUserCell +// accessibilityTraits = .button +// contentView.backgroundColor = .clear +// +// actionTitleLabel.translatesAutoresizingMaskIntoConstraints = false +// contentView.addSubview(actionTitleLabel) +// NSLayoutConstraint.activate([ +// actionTitleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 24), +// actionTitleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -24), +// actionTitleLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 0), +// actionTitleLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0) +// ]) +// actionTitleLabel.heightAnchor.constraint(equalToConstant: 44).isActive = true +// addBorder(for: .bottom) +// } +// +// @available(*, unavailable) +// required init?(coder aDecoder: NSCoder) { +// fatalError("init(coder:) has not been implemented") +// } +//} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupPasswordViewController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupPasswordViewController.swift index 38b104fd7bd..71095d453a2 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupPasswordViewController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupPasswordViewController.swift @@ -16,172 +16,172 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import UIKit -import WireDesign +//import UIKit +//import WireDesign -final class BackupPasswordViewController: UIViewController { - - typealias ViewColors = SemanticColors.View - typealias LabelColors = SemanticColors.Label - typealias HistoryBackup = L10n.Localizable.Self.Settings.HistoryBackup - - var onCompletion: ((_ password: String?) -> Void)? - - private var password: String? - private let passwordView = SimpleTextField() - - private let subtitleLabel: DynamicFontLabel = { - let label = DynamicFontLabel( - text: HistoryBackup.Password.description, - style: .subline1, - color: LabelColors.textSectionHeader - ) - label.numberOfLines = 0 - return label - }() - - private let passwordRulesLabel: DynamicFontLabel = { - let label = DynamicFontLabel( - style: .subline1, - color: LabelColors.textSectionHeader - ) - label.numberOfLines = 0 - return label - }() - - init() { - super.init(nibName: nil, bundle: nil) - - setupViews() - createConstraints() - } - - @available(*, unavailable) - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } - - override func viewWillAppear(_ animated: Bool) { - super.viewWillAppear(animated) - setupNavigationBar() - } - - override func viewDidAppear(_ animated: Bool) { - super.viewDidAppear(animated) - passwordView.becomeFirstResponder() - } - - override var supportedInterfaceOrientations: UIInterfaceOrientationMask { - wr_supportedInterfaceOrientations - } - - private func setupViews() { - view.backgroundColor = ViewColors.backgroundDefault - passwordRulesLabel.text = PasswordRuleSet.localizedErrorMessage - - [passwordView, subtitleLabel, passwordRulesLabel].forEach { - view.addSubview($0) - $0.translatesAutoresizingMaskIntoConstraints = false - } - - passwordView.placeholder = HistoryBackup.Password.placeholder - passwordView.accessibilityIdentifier = "password input" - passwordView.accessibilityHint = PasswordRuleSet.localizedErrorMessage - passwordView.returnKeyType = .done - passwordView.isSecureTextEntry = true - passwordView.delegate = self - passwordView.textColor = LabelColors.textSectionHeader - passwordView.backgroundColor = ViewColors.backgroundUserCell - let attributes: [NSAttributedString.Key: Any] = [ - .foregroundColor: SemanticColors.SearchBar.textInputViewPlaceholder, - .font: UIFont.font(for: .body1) - ] - passwordView.updatePlaceholderAttributedText(attributes: attributes) - } - - private func createConstraints() { - NSLayoutConstraint.activate([ - passwordView.leadingAnchor.constraint(equalTo: view.leadingAnchor), - passwordView.trailingAnchor.constraint(equalTo: view.trailingAnchor), - passwordView.centerYAnchor.constraint(equalTo: view.centerYAnchor), - passwordView.heightAnchor.constraint(equalToConstant: 56), - subtitleLabel.bottomAnchor.constraint(equalTo: passwordView.topAnchor, constant: -16), - subtitleLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16), - subtitleLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16), - passwordRulesLabel.topAnchor.constraint(equalTo: passwordView.bottomAnchor, constant: 16), - passwordRulesLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16), - passwordRulesLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16) - ]) - } - - private func setupNavigationBar() { - navigationController?.navigationBar.backgroundColor = ViewColors.backgroundDefault - - setupNavigationBarTitle(HistoryBackup.Password.title) - - let cancelButtonItem = UIBarButtonItem.createNavigationLeftBarButtonItem( - title: HistoryBackup.Password.cancel, - action: UIAction { [weak self] _ in - self?.onCompletion?(nil) - } - ) - - let nextButtonItem = UIBarButtonItem.createNavigationRightBarButtonItem( - title: HistoryBackup.Password.next, - action: UIAction { [weak self] _ in - self?.onCompletion?(self?.password) - } - ) - - nextButtonItem.tintColor = UIColor.accent() - nextButtonItem.isEnabled = false - - navigationItem.leftBarButtonItem = cancelButtonItem - navigationItem.rightBarButtonItem = nextButtonItem - } - - private func updateState(with text: String) {// - switch PasswordRuleSet.shared.validatePassword(text) { - case .valid: - password = text - navigationItem.rightBarButtonItem?.isEnabled = true - case .invalid: - password = nil - navigationItem.rightBarButtonItem?.isEnabled = false - } - } - - @objc - private dynamic func completeWithCurrentResult() { - onCompletion?(password) - } -} - -// MARK: - UITextFieldDelegate - -extension BackupPasswordViewController: UITextFieldDelegate { - - func textField( - _ textField: UITextField, - shouldChangeCharactersIn range: NSRange, - replacementString string: String - ) -> Bool { - - if string.containsCharacters(from: .whitespaces) { - return false - } - - if string.containsCharacters(from: .newlines) { - if password != nil { - completeWithCurrentResult() - } - return false - } - - let newString = ((textField.text ?? "") as NSString).replacingCharacters(in: range, with: string) - - updateState(with: newString) - - return true - } -} +//final class BackupPasswordViewController: UIViewController { +// +// typealias ViewColors = SemanticColors.View +// typealias LabelColors = SemanticColors.Label +// typealias HistoryBackup = L10n.Localizable.Self.Settings.HistoryBackup +// +// var onCompletion: ((_ password: String?) -> Void)? +// +// private var password: String? +// private let passwordView = SimpleTextField() +// +// private let subtitleLabel: DynamicFontLabel = { +// let label = DynamicFontLabel( +// text: HistoryBackup.Password.description, +// style: .subline1, +// color: LabelColors.textSectionHeader +// ) +// label.numberOfLines = 0 +// return label +// }() +// +// private let passwordRulesLabel: DynamicFontLabel = { +// let label = DynamicFontLabel( +// style: .subline1, +// color: LabelColors.textSectionHeader +// ) +// label.numberOfLines = 0 +// return label +// }() +// +// init() { +// super.init(nibName: nil, bundle: nil) +// +// setupViews() +// createConstraints() +// } +// +// @available(*, unavailable) +// required init?(coder aDecoder: NSCoder) { +// fatalError("init(coder:) has not been implemented") +// } +// +// override func viewWillAppear(_ animated: Bool) { +// super.viewWillAppear(animated) +// setupNavigationBar() +// } +// +// override func viewDidAppear(_ animated: Bool) { +// super.viewDidAppear(animated) +// passwordView.becomeFirstResponder() +// } +// +// override var supportedInterfaceOrientations: UIInterfaceOrientationMask { +// wr_supportedInterfaceOrientations +// } +// +// private func setupViews() { +// view.backgroundColor = ViewColors.backgroundDefault +// passwordRulesLabel.text = PasswordRuleSet.localizedErrorMessage +// +// [passwordView, subtitleLabel, passwordRulesLabel].forEach { +// view.addSubview($0) +// $0.translatesAutoresizingMaskIntoConstraints = false +// } +// +// passwordView.placeholder = HistoryBackup.Password.placeholder +// passwordView.accessibilityIdentifier = "password input" +// passwordView.accessibilityHint = PasswordRuleSet.localizedErrorMessage +// passwordView.returnKeyType = .done +// passwordView.isSecureTextEntry = true +// passwordView.delegate = self +// passwordView.textColor = LabelColors.textSectionHeader +// passwordView.backgroundColor = ViewColors.backgroundUserCell +// let attributes: [NSAttributedString.Key: Any] = [ +// .foregroundColor: SemanticColors.SearchBar.textInputViewPlaceholder, +// .font: UIFont.font(for: .body1) +// ] +// passwordView.updatePlaceholderAttributedText(attributes: attributes) +// } +// +// private func createConstraints() { +// NSLayoutConstraint.activate([ +// passwordView.leadingAnchor.constraint(equalTo: view.leadingAnchor), +// passwordView.trailingAnchor.constraint(equalTo: view.trailingAnchor), +// passwordView.centerYAnchor.constraint(equalTo: view.centerYAnchor), +// passwordView.heightAnchor.constraint(equalToConstant: 56), +// subtitleLabel.bottomAnchor.constraint(equalTo: passwordView.topAnchor, constant: -16), +// subtitleLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16), +// subtitleLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16), +// passwordRulesLabel.topAnchor.constraint(equalTo: passwordView.bottomAnchor, constant: 16), +// passwordRulesLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16), +// passwordRulesLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16) +// ]) +// } +// +// private func setupNavigationBar() { +// navigationController?.navigationBar.backgroundColor = ViewColors.backgroundDefault +// +// setupNavigationBarTitle(HistoryBackup.Password.title) +// +// let cancelButtonItem = UIBarButtonItem.createNavigationLeftBarButtonItem( +// title: HistoryBackup.Password.cancel, +// action: UIAction { [weak self] _ in +// self?.onCompletion?(nil) +// } +// ) +// +// let nextButtonItem = UIBarButtonItem.createNavigationRightBarButtonItem( +// title: HistoryBackup.Password.next, +// action: UIAction { [weak self] _ in +// self?.onCompletion?(self?.password) +// } +// ) +// +// nextButtonItem.tintColor = UIColor.accent() +// nextButtonItem.isEnabled = false +// +// navigationItem.leftBarButtonItem = cancelButtonItem +// navigationItem.rightBarButtonItem = nextButtonItem +// } +// +// private func updateState(with text: String) { +// switch PasswordRuleSet.shared.validatePassword(text) { +// case .valid: +// password = text +// navigationItem.rightBarButtonItem?.isEnabled = true +// case .invalid: +// password = nil +// navigationItem.rightBarButtonItem?.isEnabled = false +// } +// } +// +// @objc +// private dynamic func completeWithCurrentResult() { +// onCompletion?(password) +// } +//} +// +//// MARK: - UITextFieldDelegate +// +//extension BackupPasswordViewController: UITextFieldDelegate { +// +// func textField( +// _ textField: UITextField, +// shouldChangeCharactersIn range: NSRange, +// replacementString string: String +// ) -> Bool { +// +// if string.containsCharacters(from: .whitespaces) { +// return false +// } +// +// if string.containsCharacters(from: .newlines) { +// if password != nil { +// completeWithCurrentResult() +// } +// return false +// } +// +// let newString = ((textField.text ?? "") as NSString).replacingCharacters(in: range, with: string) +// +// updateState(with: newString) +// +// return true +// } +//} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupSource.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupSource.swift index 7ce036f12de..5ae63013f3a 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupSource.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupSource.swift @@ -20,15 +20,4 @@ import Foundation import class WireSyncEngine.SessionManager import WireSettingsUI -// TODO: replace it -// sourcery: AutoMockable -//protocol BackupSource { -// func backupActiveAccount( -// password: String, -// completion: @escaping (Result) -> Void -// ) -// -// func clearPreviousBackups() -//} - extension SessionManager: BackupSource {} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupStatusCell.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupStatusCell.swift index d4e856ff07c..1e3cad3e159 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupStatusCell.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupStatusCell.swift @@ -19,53 +19,53 @@ import UIKit import WireDesign -final class BackupStatusCell: UITableViewCell { - - let descriptionLabel: DynamicFontLabel = { - let label = DynamicFontLabel( - style: .body1, - color: SemanticColors.Label.textDefault - ) - label.textAlignment = .left - label.numberOfLines = 0 - return label - }() - - let iconView = UIImageView() - - override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { - super.init(style: style, reuseIdentifier: reuseIdentifier) - - selectionStyle = .none - backgroundColor = .clear - contentView.backgroundColor = .clear - - iconView.setTemplateIcon(.restore, size: .large) - iconView.tintColor = SemanticColors.Label.textDefault - iconView.contentMode = .center - iconView.translatesAutoresizingMaskIntoConstraints = false - contentView.addSubview(iconView) - - descriptionLabel.translatesAutoresizingMaskIntoConstraints = false - contentView.addSubview(descriptionLabel) - - NSLayoutConstraint.activate([ - iconView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 24), - iconView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor), - iconView.heightAnchor.constraint(equalTo: iconView.widthAnchor), - iconView.widthAnchor.constraint(equalToConstant: 48), - descriptionLabel.topAnchor.constraint(equalTo: iconView.bottomAnchor, constant: 24), - descriptionLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 24), - descriptionLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -24), - descriptionLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -24) - ]) - - let description = L10n.Localizable.Self.Settings.HistoryBackup.description - descriptionLabel.attributedText = description && .paragraphSpacing(2) - } - - @available(*, unavailable) - required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } -} +//final class BackupStatusCell: UITableViewCell { +// +// let descriptionLabel: DynamicFontLabel = { +// let label = DynamicFontLabel( +// style: .body1, +// color: SemanticColors.Label.textDefault +// ) +// label.textAlignment = .left +// label.numberOfLines = 0 +// return label +// }() +// +// let iconView = UIImageView() +// +// override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { +// super.init(style: style, reuseIdentifier: reuseIdentifier) +// +// selectionStyle = .none +// backgroundColor = .clear +// contentView.backgroundColor = .clear +// +// iconView.setTemplateIcon(.restore, size: .large) +// iconView.tintColor = SemanticColors.Label.textDefault +// iconView.contentMode = .center +// iconView.translatesAutoresizingMaskIntoConstraints = false +// contentView.addSubview(iconView) +// +// descriptionLabel.translatesAutoresizingMaskIntoConstraints = false +// contentView.addSubview(descriptionLabel) +// +// NSLayoutConstraint.activate([ +// iconView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 24), +// iconView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor), +// iconView.heightAnchor.constraint(equalTo: iconView.widthAnchor), +// iconView.widthAnchor.constraint(equalToConstant: 48), +// descriptionLabel.topAnchor.constraint(equalTo: iconView.bottomAnchor, constant: 24), +// descriptionLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 24), +// descriptionLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -24), +// descriptionLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -24) +// ]) +// +// let description = L10n.Localizable.Self.Settings.HistoryBackup.description +// descriptionLabel.attributedText = description && .paragraphSpacing(2) +// } +// +// @available(*, unavailable) +// required init?(coder aDecoder: NSCoder) { +// fatalError("init(coder:) has not been implemented") +// } +//} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupViewController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupViewController.swift index 034d674eb62..dac534fa1cc 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupViewController.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupViewController.swift @@ -16,9 +16,9 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import UIKit -import WireDesign -import WireReusableUIComponents +//import UIKit +//import WireDesign +//import WireReusableUIComponents //final class BackupViewController: UIViewController { // diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/Models/BackupSection.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/Models/BackupSection.swift deleted file mode 100644 index b760a3ee743..00000000000 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/Models/BackupSection.swift +++ /dev/null @@ -1,64 +0,0 @@ -// -// Wire -// Copyright (C) 2024 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -//import Foundation -// -///// The section that will be displayed in the backup actions -//struct BackupActionsSection: Identifiable { -// -// typealias SettingsString = L10n.Localizable.Self.Settings -// -// enum Section { -// case backup -// case restore -// -// var title: String { -// switch self { -// case .backup: -// return SettingsString.HistoryBackup.action -// case .restore: -// return SettingsString.RestoreFromBackup.action -// } -// } -// -// var footer: String { -// switch self { -// case .backup: -// return SettingsString.HistoryBackup.description -// case .restore: -// return SettingsString.RestoreFromBackup.description -// } -// } -// } -// -// /// Unique identifier for the section -// let id: UUID -// -// /// The section type -// let type: Section -// -// init( -// id: UUID = UUID(), -// type: Section -// ) { -// self.id = id -// self.type = type -// } -// -//} -// diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/ViewControllers/BackupActionsHostingController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/ViewControllers/BackupActionsHostingController.swift deleted file mode 100644 index e8d07b00ab8..00000000000 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/ViewControllers/BackupActionsHostingController.swift +++ /dev/null @@ -1,34 +0,0 @@ -// -// Wire -// Copyright (C) 2024 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -//import SwiftUI -//import UIKit -// -//public final class BackupActionsHostingController: UIHostingController { -// private let viewModel: BackupActionsViewModel -// -// public init(viewModel: BackupActionsViewModel) { -// self.viewModel = viewModel -// super.init(rootView: BackupActionsView(viewModel: viewModel)) -// } -// -// @available(*, unavailable) -// dynamic required init?(coder aDecoder: NSCoder) { -// fatalError("init(coder:) has not been implemented") -// } -//} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/ViewModels/BackupActionsViewModel11.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/ViewModels/BackupActionsViewModel11.swift deleted file mode 100644 index 48cce544d02..00000000000 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/ViewModels/BackupActionsViewModel11.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// Wire -// Copyright (C) 2024 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -//import SwiftUI -// -//public final class BackupActionsViewModel: ObservableObject { -// @Published var sections: [BackupActionsSection] = [] -// -// private let backupSource: BackupSource -// private let onSuccessHandler: ((URL, @escaping Completion) -> Void) -// private let onFailureHandler: ((Error) -> Void) -// -// init( -// backupSource: BackupSource, -// onSuccessHandler: @escaping ((URL, @escaping Completion) -> Void), -// onFailureHandler: @escaping ((Error) -> Void) -// ) { -// self.backupSource = backupSource -// self.onSuccessHandler = onSuccessHandler -// self.onFailureHandler = onFailureHandler -// -// sections = [ -// BackupActionsSection(type: .backup) -// //BackupActionsSection(type: .restore) -// ] -// } -// -// func backupActiveAccount(password: String) { -// backupSource.backupActiveAccount(password: password) { [weak self] backupResult in -// switch backupResult { -// case let .failure(error): -// self?.onFailureHandler(error) -// case let .success(url): -// self?.onSuccessHandler(url) { [weak self] in -// self?.backupSource.clearPreviousBackups() -// } -// } -// } -// } -//} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/Views/BackupActionsView.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/Views/BackupActionsView.swift deleted file mode 100644 index fc3c7eddef9..00000000000 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/Views/BackupActionsView.swift +++ /dev/null @@ -1,88 +0,0 @@ -// -// Wire -// Copyright (C) 2024 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -//import SwiftUI -//import WireDesign -//import WireReusableUIComponents -//import WireSettingsUI -// -//public struct BackupActionsView: View { -// // TODO: or @StateObject -// @ObservedObject private var viewModel: BackupActionsViewModel -// @State private var isBackupPresented: Bool = false -// //TODO: use `isRestorePresented` -// @State private var isRestorePresented: Bool = false -// -// public init(viewModel: BackupActionsViewModel) { -// self.viewModel = viewModel -// } -// -// public var body: some View { -// List { -// ForEach(viewModel.sections) { section in -// Section( -// footer: Text(section.type.footer) -// ) { -// Button(action: { -// switch section.type { -// case .backup: -// isRestorePresented = false -// isBackupPresented.toggle() -// case .restore: -// isBackupPresented = false -// isRestorePresented.toggle() -// } -// -// }) { -// HStack { -// Text(section.type.title) -// .font(.textStyle(.body2)) -// .foregroundStyle(Color.primaryText) -// Spacer() -// Image(.chevronRight).foregroundStyle(Color.primary) -// } -// } -// .sheet(isPresented: $isBackupPresented) { -// NavigationStack { -// ExportBackup { password in -// viewModel.backupActiveAccount(password: password) -// } -// } -// .presentationDetents([.medium, .large]) -// } -// } -// } -// } -// .listStyle(.grouped) -// .listRowBackground(Color(ColorTheme.Backgrounds.background)) -// } -//} -// -//#Preview { -// BackupActionsView(viewModel: BackupActionsViewModel( -// backupSource: MockBackupSource(), -// onSuccessHandler: {_,_ in}, -// onFailureHandler: {_ in})) -//} -// -//private class MockBackupSource: BackupSource { -// func backupActiveAccount(password: String, completion: @escaping (Result) -> Void) {} -// -// func clearPreviousBackups() {} -// -//} From beac109d4e1d543598dd330075dccff199374041 Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Fri, 3 Jan 2025 18:49:50 +0100 Subject: [PATCH 009/107] clean up --- WireUI/Sources/WireSettingsUI/.swiftgen.yml | 14 ++++++++++++++ .../DebugActions/DeveloperDebugActionsView.swift | 2 +- ...versationListViewController+NavigationBar.swift | 2 +- 3 files changed, 16 insertions(+), 2 deletions(-) create mode 100644 WireUI/Sources/WireSettingsUI/.swiftgen.yml diff --git a/WireUI/Sources/WireSettingsUI/.swiftgen.yml b/WireUI/Sources/WireSettingsUI/.swiftgen.yml new file mode 100644 index 00000000000..668e9ff4faf --- /dev/null +++ b/WireUI/Sources/WireSettingsUI/.swiftgen.yml @@ -0,0 +1,14 @@ +# Every input/output paths in the rest of the config will then be expressed relative to these. + +input_dir: ./ +output_dir: ${GENERATED}/ + +# Generate constants for your localized strings. + +strings: + inputs: + - Resources/en.lproj/Localizable.strings + filter: + outputs: + - templateName: structured-swift5 + output: Strings+Generated.swift diff --git a/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DebugActions/DeveloperDebugActionsView.swift b/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DebugActions/DeveloperDebugActionsView.swift index 9a20617def5..592982a5264 100644 --- a/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DebugActions/DeveloperDebugActionsView.swift +++ b/wire-ios/Wire-iOS/Sources/Developer/DeveloperTools/DebugActions/DeveloperDebugActionsView.swift @@ -22,7 +22,7 @@ struct DeveloperDebugActionsView: View { @ObservedObject var viewModel: DeveloperDebugActionsViewModel - var body: some View {// + var body: some View { List(viewModel.buttons) { button in Button(action: button.action) { Text(button.title) diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/ConversationList/Container/ConversationListViewController+NavigationBar/ConversationListViewController+NavigationBar.swift b/wire-ios/Wire-iOS/Sources/UserInterface/ConversationList/Container/ConversationListViewController+NavigationBar/ConversationListViewController+NavigationBar.swift index 9765e811bd3..b9337ff7eb2 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/ConversationList/Container/ConversationListViewController+NavigationBar/ConversationListViewController+NavigationBar.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/ConversationList/Container/ConversationListViewController+NavigationBar/ConversationListViewController+NavigationBar.swift @@ -466,7 +466,7 @@ extension ConversationListViewController: ConversationListContainerViewModelDele mainCoordinator: mainCoordinator, showCloseButton: true ) - if let sheet = viewController.sheetPresentationController {// + if let sheet = viewController.sheetPresentationController { sheet.detents = [.medium(), .large()] sheet.prefersGrabberVisible = true } From ab35e0f80743ad644e15c3288782e7d8bd1e9cfd Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Mon, 6 Jan 2025 09:04:12 +0100 Subject: [PATCH 010/107] add BackupHandler --- .../Account/Backup/Models/BackupHandler.swift | 32 +++++++++++++++++++ .../ViewModels/BackupActionsViewModel.swift | 13 +++----- .../Backup/Views/BackupActionsView.swift | 6 ++-- .../Account/Backup/Views/ExportBackup.swift | 18 +++++------ .../CoreDataStack+Backup.swift | 2 +- ...ettingsCellDescriptorFactory+Account.swift | 11 ++++--- 6 files changed, 58 insertions(+), 24 deletions(-) create mode 100644 WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupHandler.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupHandler.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupHandler.swift new file mode 100644 index 00000000000..37d53f6ee3b --- /dev/null +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupHandler.swift @@ -0,0 +1,32 @@ +// +// Wire +// Copyright (C) 2025 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import Foundation + +public struct BackupHandler { + let onSuccess: (URL, @escaping () -> Void) -> Void + let onFailure: (any Error) -> Void + + public init( + onSuccess: @escaping (URL, @escaping () -> Void) -> Void, + onFailure: @escaping (any Error) -> Void + ) { + self.onSuccess = onSuccess + self.onFailure = onFailure + } +} diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift index 2736f78db5a..341d95c23f4 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift @@ -22,17 +22,14 @@ public final class BackupActionsViewModel: ObservableObject { @Published var sections: [BackupActionsSection] = [] private let backupSource: any BackupSource - private let onSuccessHandler: ((URL, @escaping () -> Void) -> Void) - private let onFailureHandler: ((any Error) -> Void) + private var backupHandler: BackupHandler public init( backupSource: any BackupSource, - onSuccessHandler: @escaping ((URL, @escaping () -> Void) -> Void), - onFailureHandler: @escaping ((any Error) -> Void) + backupHandler: BackupHandler ) { self.backupSource = backupSource - self.onSuccessHandler = onSuccessHandler - self.onFailureHandler = onFailureHandler + self.backupHandler = backupHandler sections = [ BackupActionsSection(type: .backup) @@ -43,11 +40,11 @@ public final class BackupActionsViewModel: ObservableObject { func backupActiveAccount(password: String) { do { let backupPath = try backupSource.backupActiveAccount(password: password) - onSuccessHandler(backupPath) { [weak self] in + backupHandler.onSuccess(backupPath) { [weak self] in self?.backupSource.clearPreviousBackups() } } catch { - onFailureHandler(error) + backupHandler.onFailure(error) } } } diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift index 87f57219b1e..f7038ad0448 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift @@ -75,8 +75,10 @@ public struct BackupActionsView: View { #Preview { BackupActionsView(viewModel: BackupActionsViewModel( backupSource: MockBackupSource(), - onSuccessHandler: {_,_ in}, - onFailureHandler: {_ in})) + backupHandler: BackupHandler( + onSuccess: {_,_ in}, + onFailure: {_ in}) + )) } private class MockBackupSource: BackupSource { diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackup.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackup.swift index 1de0be1a2ac..51c1f39a7c0 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackup.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackup.swift @@ -26,14 +26,14 @@ import WireReusableUIComponents public struct ExportBackup: View { @Environment(\.dismiss) private var dismiss - private let onAction: (String) -> Void + private let exportBackup: (String) -> Void - public init(onAction: @escaping (String) -> Void) { - self.onAction = onAction + public init(exportBackup: @escaping (String) -> Void) { + self.exportBackup = exportBackup } public var body: some View { - SetBackupPasswordView(onAction: onAction) + SetBackupPasswordView(exportBackup: exportBackup) .background(Color.viewBackground) .scrollContentBackground(.hidden) .navigationTitle( @@ -65,10 +65,10 @@ private struct SetBackupPasswordView: View { @State private var password: String = "" @State private var isPasswordVisible: Bool = false - private let onAction: (String) -> Void + private let exportBackup: (String) -> Void - init(onAction: @escaping (String) -> Void) { - self.onAction = onAction + init(exportBackup: @escaping (String) -> Void) { + self.exportBackup = exportBackup } var body: some View { @@ -88,7 +88,7 @@ private struct SetBackupPasswordView: View { Button( action: { - onAction(password) + exportBackup(password) dismiss() }, label: { @@ -133,7 +133,7 @@ private struct ExportBackupPreview: View { ) .sheet(isPresented: $isPresented) { NavigationStack { - ExportBackup(onAction: {_ in }) + ExportBackup(exportBackup: {_ in }) } .presentationDragIndicator(.visible) .presentationDetents([.medium, .large]) diff --git a/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack+Backup.swift b/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack+Backup.swift index 5b8269562d4..511ccaa2d9b 100644 --- a/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack+Backup.swift +++ b/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack+Backup.swift @@ -149,7 +149,7 @@ public extension CoreDataStack { return BackupInfo(url: backupDirectory, metadata: metadata) } } - + /// Will import a backup for a given account /// /// - Parameters: diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift index 4e49dc8fe3b..b48bbcfc5c3 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift @@ -380,8 +380,11 @@ extension SettingsCellDescriptorFactory { if selfUser.hasValidEmail || selfUser.usesCompanyLogin { let viewModel = BackupActionsViewModel( backupSource: SessionManager.shared!, - onSuccessHandler: presentShareSheet, - onFailureHandler: presentAlert) + backupHandler: BackupHandler( + onSuccess: presentShareSheet, + onFailure: presentAlert + ) + ) let backupActionsController = BackupActionsHostingController(viewModel: viewModel) backupActionsController.setupNavigationBarTitle(L10n.Localizable.Self.Settings.HistoryBackup.title) return backupActionsController @@ -453,7 +456,8 @@ extension SettingsCellDescriptorFactory { } -// TODO: 1. Change names, 2. Move somewhere +// TODO: Backup action handler + extension SettingsCellDescriptorFactory { private func presentAlert(for error: Error) { @@ -481,7 +485,6 @@ extension SettingsCellDescriptorFactory { let activityController = UIActivityViewController(activityItems: [url], applicationActivities: nil) activityController.completionWithItemsHandler = { _, _, _, _ in - //self?.backupSource.clearPreviousBackups() completion() } controller.present(activityController, animated: true) From dc2836a20e4a601023b88281a016f120958660d4 Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Mon, 6 Jan 2025 11:12:35 +0100 Subject: [PATCH 011/107] remove ToDos --- .../Account/Backup/Protocols/BackupSource.swift | 1 - .../Account/Backup/Views/BackupActionsView.swift | 16 +++++++--------- .../SettingsCellDescriptorFactory+Account.swift | 2 +- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/BackupSource.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/BackupSource.swift index d637a50f018..0bda7e9e7b7 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/BackupSource.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/BackupSource.swift @@ -19,7 +19,6 @@ import Foundation public protocol BackupSource { - // TODO: make password optional func backupActiveAccount(password: String) throws -> URL func clearPreviousBackups() diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift index f7038ad0448..db4a3b3a240 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift @@ -21,11 +21,9 @@ import WireDesign import WireReusableUIComponents public struct BackupActionsView: View { - // TODO: or @StateObject @ObservedObject private var viewModel: BackupActionsViewModel - @State private var isBackupPresented: Bool = false - //TODO: use `isRestorePresented` - @State private var isRestorePresented: Bool = false + @State private var isBackupSheetPresented: Bool = false + @State private var isRestoreSheetPresented: Bool = false public init(viewModel: BackupActionsViewModel) { self.viewModel = viewModel @@ -40,11 +38,11 @@ public struct BackupActionsView: View { Button(action: { switch section.type { case .backup: - isRestorePresented = false - isBackupPresented.toggle() + isRestoreSheetPresented = false + isBackupSheetPresented.toggle() case .restore: - isBackupPresented = false - isRestorePresented.toggle() + isBackupSheetPresented = false + isRestoreSheetPresented.toggle() } }) { @@ -56,7 +54,7 @@ public struct BackupActionsView: View { Image(systemName: "chevron.right").foregroundStyle(Color.primary) } } - .sheet(isPresented: $isBackupPresented) { + .sheet(isPresented: $isBackupSheetPresented) { NavigationStack { ExportBackup { password in viewModel.backupActiveAccount(password: password) diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift index b48bbcfc5c3..c3345c9a565 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift @@ -456,7 +456,7 @@ extension SettingsCellDescriptorFactory { } -// TODO: Backup action handler +// MARK: - Backup action handler extension SettingsCellDescriptorFactory { From 33d009dbcfd260ac55b34ea175581b7224473300 Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Mon, 6 Jan 2025 15:20:24 +0100 Subject: [PATCH 012/107] add BackupPasswordValidator --- .../Protocols/BackupPasswordValidator.swift | 25 ++++++++++++ .../ViewModels/BackupActionsViewModel.swift | 7 +++- .../Backup/Views/BackupActionsView.swift | 20 ++++++++-- .../Account/Backup/Views/ExportBackup.swift | 37 ++++++++++------- .../Backup/Views/PasswordFieldView.swift | 17 +++++--- wire-ios/Wire-iOS.xcodeproj/project.pbxproj | 4 ++ .../Backup/BackupPasswordValidator.swift | 40 +++++++++++++++++++ ...ettingsCellDescriptorFactory+Account.swift | 3 +- 8 files changed, 125 insertions(+), 28 deletions(-) create mode 100644 WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/BackupPasswordValidator.swift create mode 100644 wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupPasswordValidator.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/BackupPasswordValidator.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/BackupPasswordValidator.swift new file mode 100644 index 00000000000..fe5397fe9ac --- /dev/null +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/BackupPasswordValidator.swift @@ -0,0 +1,25 @@ +// +// Wire +// Copyright (C) 2025 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +public protocol BackupPasswordValidatorProtocol { + + func isValid(password: String) -> Bool + + var localizedRulesDescription: String { get } + +} diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift index 341d95c23f4..ca288727623 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift @@ -22,14 +22,17 @@ public final class BackupActionsViewModel: ObservableObject { @Published var sections: [BackupActionsSection] = [] private let backupSource: any BackupSource - private var backupHandler: BackupHandler + private let backupHandler: BackupHandler + let passwordValidator: any BackupPasswordValidatorProtocol public init( backupSource: any BackupSource, - backupHandler: BackupHandler + backupHandler: BackupHandler, + passwordValidator: any BackupPasswordValidatorProtocol ) { self.backupSource = backupSource self.backupHandler = backupHandler + self.passwordValidator = passwordValidator sections = [ BackupActionsSection(type: .backup) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift index db4a3b3a240..b136469f004 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift @@ -56,9 +56,10 @@ public struct BackupActionsView: View { } .sheet(isPresented: $isBackupSheetPresented) { NavigationStack { - ExportBackup { password in - viewModel.backupActiveAccount(password: password) - } + ExportBackup( + passwordValidator: viewModel.passwordValidator) { password in + viewModel.backupActiveAccount(password: password) + } } .presentationDetents([.medium, .large]) } @@ -75,7 +76,8 @@ public struct BackupActionsView: View { backupSource: MockBackupSource(), backupHandler: BackupHandler( onSuccess: {_,_ in}, - onFailure: {_ in}) + onFailure: {_ in}), + passwordValidator: MockBackupPasswordValidator() )) } @@ -86,3 +88,13 @@ private class MockBackupSource: BackupSource { func clearPreviousBackups() {} } + +class MockBackupPasswordValidator: BackupPasswordValidatorProtocol { + func isValid(password: String) -> Bool { + true + } + + var localizedRulesDescription: String { + "Use at least 8 characters, with one lowercase letter, one capital letter, a number, and a special character." + } +} diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackup.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackup.swift index 51c1f39a7c0..f9b2e9748d2 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackup.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackup.swift @@ -27,13 +27,20 @@ public struct ExportBackup: View { @Environment(\.dismiss) private var dismiss private let exportBackup: (String) -> Void + private let passwordValidator: any BackupPasswordValidatorProtocol - public init(exportBackup: @escaping (String) -> Void) { + public init( + passwordValidator: any BackupPasswordValidatorProtocol, + exportBackup: @escaping (String) -> Void + ) { self.exportBackup = exportBackup + self.passwordValidator = passwordValidator } public var body: some View { - SetBackupPasswordView(exportBackup: exportBackup) + SetBackupPasswordView( + passwordValidator: passwordValidator, + exportBackup: exportBackup) .background(Color.viewBackground) .scrollContentBackground(.hidden) .navigationTitle( @@ -65,9 +72,14 @@ private struct SetBackupPasswordView: View { @State private var password: String = "" @State private var isPasswordVisible: Bool = false + private let passwordValidator: any BackupPasswordValidatorProtocol private let exportBackup: (String) -> Void - init(exportBackup: @escaping (String) -> Void) { + init( + passwordValidator: any BackupPasswordValidatorProtocol, + exportBackup: @escaping (String) -> Void + ) { + self.passwordValidator = passwordValidator self.exportBackup = exportBackup } @@ -81,8 +93,9 @@ private struct SetBackupPasswordView: View { PasswordFieldView( password: $password, - isPasswordValid: isPasswordValid(), - isPasswordVisible: $isPasswordVisible) + isPasswordValid: passwordValidator.isValid(password: password), + isPasswordVisible: $isPasswordVisible, + passwordRules: Text(passwordValidator.localizedRulesDescription)) Spacer() @@ -101,15 +114,6 @@ private struct SetBackupPasswordView: View { .frame(maxHeight: .infinity) } - // TODO: remove it - private func isPasswordValid() -> Bool { - guard !password.isEmpty else { - return true - } - let passwordRegex = "^(?=.*[a-z])(?=.*[A-Z])(?=.*\\d)(?=.*[!@#$%^&*()_+{}:<>?]).{8,}$" - let predicate = NSPredicate(format: "SELF MATCHES %@", passwordRegex) - return predicate.evaluate(with: password) - } } // MARK: - Previews @@ -133,7 +137,10 @@ private struct ExportBackupPreview: View { ) .sheet(isPresented: $isPresented) { NavigationStack { - ExportBackup(exportBackup: {_ in }) + ExportBackup( + passwordValidator: MockBackupPasswordValidator(), + exportBackup: {_ in } + ) } .presentationDragIndicator(.visible) .presentationDetents([.medium, .large]) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift index 308b67bdfb2..4031a2ae5ae 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift @@ -21,8 +21,9 @@ import WireDesign struct PasswordFieldView: View { @Binding var password: String - var isPasswordValid: Bool + let isPasswordValid: Bool @Binding var isPasswordVisible: Bool + let passwordRules: Text var body: some View { VStack(alignment: .leading, spacing: 8) { @@ -62,7 +63,7 @@ struct PasswordFieldView: View { } } - Text(L10n.PasswordField.rules) + passwordRules .font(.caption) .foregroundColor(isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color) } @@ -77,7 +78,8 @@ struct PasswordFieldView: View { PasswordFieldView( password: .constant(""), isPasswordValid: false, - isPasswordVisible: .constant(false) + isPasswordVisible: .constant(false), + passwordRules: Text("Use at least 8 characters, with one lowercase letter, one capital letter, a number, and a special character.") ) } @@ -86,7 +88,8 @@ struct PasswordFieldView: View { PasswordFieldView( password: .constant("ValidPassword1!"), isPasswordValid: false, - isPasswordVisible: .constant(true) + isPasswordVisible: .constant(true), + passwordRules: Text("Use at least 8 characters, with one lowercase letter, one capital letter, a number, and a special character.") ) } @@ -95,7 +98,8 @@ struct PasswordFieldView: View { PasswordFieldView( password: .constant("ValidPassword1!"), isPasswordValid: true, - isPasswordVisible: .constant(false) + isPasswordVisible: .constant(false), + passwordRules: Text("Use at least 8 characters, with one lowercase letter, one capital letter, a number, and a special character.") ) } @@ -104,6 +108,7 @@ struct PasswordFieldView: View { PasswordFieldView( password: .constant("ValidPassword1!"), isPasswordValid: true, - isPasswordVisible: .constant(true) + isPasswordVisible: .constant(true), + passwordRules: Text("Use at least 8 characters, with one lowercase letter, one capital letter, a number, and a special character.") ) } diff --git a/wire-ios/Wire-iOS.xcodeproj/project.pbxproj b/wire-ios/Wire-iOS.xcodeproj/project.pbxproj index a40074ac1ad..177917498e0 100644 --- a/wire-ios/Wire-iOS.xcodeproj/project.pbxproj +++ b/wire-ios/Wire-iOS.xcodeproj/project.pbxproj @@ -47,6 +47,7 @@ 060A36902CBCF1010066908C /* ConversationListViewController+EmptyState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 060A368F2CBCF1010066908C /* ConversationListViewController+EmptyState.swift */; }; 060C06652B73DFC700B484C6 /* E2EINotificationActionsHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 060C06642B73DFC700B484C6 /* E2EINotificationActionsHandler.swift */; }; 060E5336257668EE00BDDEBB /* SettingsPropertyFactory+AppLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = 060E5335257668EE00BDDEBB /* SettingsPropertyFactory+AppLock.swift */; }; + 060F032B2D2C12930016431F /* BackupPasswordValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 060F032A2D2C12910016431F /* BackupPasswordValidator.swift */; }; 061275DE26F304CB006E8D4C /* DragInteractionRestrictionTextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 061275DD26F304CB006E8D4C /* DragInteractionRestrictionTextView.swift */; }; 0617001523E48B66005C262D /* VerticalTransition.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0617001423E48B66005C262D /* VerticalTransition.swift */; }; 0619FBBB258A1DF700A0F3E2 /* ZClientViewController+FeatureChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0619FBBA258A1DF700A0F3E2 /* ZClientViewController+FeatureChange.swift */; }; @@ -1981,6 +1982,7 @@ 060A368F2CBCF1010066908C /* ConversationListViewController+EmptyState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConversationListViewController+EmptyState.swift"; sourceTree = ""; }; 060C06642B73DFC700B484C6 /* E2EINotificationActionsHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = E2EINotificationActionsHandler.swift; sourceTree = ""; }; 060E5335257668EE00BDDEBB /* SettingsPropertyFactory+AppLock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "SettingsPropertyFactory+AppLock.swift"; sourceTree = ""; }; + 060F032A2D2C12910016431F /* BackupPasswordValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupPasswordValidator.swift; sourceTree = ""; }; 061275DD26F304CB006E8D4C /* DragInteractionRestrictionTextView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DragInteractionRestrictionTextView.swift; sourceTree = ""; }; 061282622379C25500C1A53C /* UITextView+ReplaceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UITextView+ReplaceTests.swift"; sourceTree = ""; }; 0617001423E48B66005C262D /* VerticalTransition.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VerticalTransition.swift; sourceTree = ""; }; @@ -7502,6 +7504,7 @@ E666EDD42B73E5F500C03E2B /* Backup */ = { isa = PBXGroup; children = ( + 060F032A2D2C12910016431F /* BackupPasswordValidator.swift */, E666EDD52B73E62800C03E2B /* BackupSource.swift */, E666EDDB2B73EA3500C03E2B /* BackupActionCell.swift */, E666EDD92B73E9C400C03E2B /* BackupStatusCell.swift */, @@ -9742,6 +9745,7 @@ 8750B1CB2124236200B807A9 /* SecondaryTextButton.swift in Sources */, 5E62802A2224090D0039A8AB /* SeparatorTableViewCell.swift in Sources */, 596841CC2BD14E550009C6B8 /* CoreImageBasedImageTransformer.swift in Sources */, + 060F032B2D2C12930016431F /* BackupPasswordValidator.swift in Sources */, EF25F7DE1FC45F8E0040C3CC /* ValidatedTextField.swift in Sources */, F15650DA21DD107B00210504 /* RoundedView.swift in Sources */, BFE65AB320A047EB00689063 /* CallAccessoryViewController.swift in Sources */, diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupPasswordValidator.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupPasswordValidator.swift new file mode 100644 index 00000000000..21c64652aff --- /dev/null +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupPasswordValidator.swift @@ -0,0 +1,40 @@ +// +// Wire +// Copyright (C) 2025 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import WireSettingsUI + +struct BackupPasswordValidator: BackupPasswordValidatorProtocol { + + func isValid(password: String) -> Bool { + guard !password.isEmpty else { + return true + } + + switch PasswordRuleSet.shared.validatePassword(password) { + case .valid: + return true + case .invalid: + return false + } + } + + var localizedRulesDescription: String { + return PasswordRuleSet.localizedErrorMessage + } + +} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift index c3345c9a565..3cd89827477 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift @@ -383,7 +383,8 @@ extension SettingsCellDescriptorFactory { backupHandler: BackupHandler( onSuccess: presentShareSheet, onFailure: presentAlert - ) + ), + passwordValidator: BackupPasswordValidator() ) let backupActionsController = BackupActionsHostingController(viewModel: viewModel) backupActionsController.setupNavigationBarTitle(L10n.Localizable.Self.Settings.HistoryBackup.title) From 5557bf9a6945f8da5d2ae0db3e7224ae4f658e7f Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Mon, 6 Jan 2025 17:28:17 +0100 Subject: [PATCH 013/107] fix SwiftFormat issues --- WireUI/Package.swift | 3 ++- .../Source/SessionManager/SessionManager+Backup.swift | 6 +++--- .../Settings/Backup/BackupPasswordValidator.swift | 2 +- .../UserInterface/Settings/Backup/BackupSource.swift | 2 +- 4 files changed, 7 insertions(+), 6 deletions(-) diff --git a/WireUI/Package.swift b/WireUI/Package.swift index bb17b0058a9..ba5c7b447eb 100644 --- a/WireUI/Package.swift +++ b/WireUI/Package.swift @@ -77,7 +77,8 @@ let package = Package( .target( name: "WireSettingsUI", dependencies: ["WireDesign", "WireFoundation", "WireReusableUIComponents"], - plugins: [.plugin(name: "SwiftGenPlugin", package: "WirePlugins")]), + plugins: [.plugin(name: "SwiftGenPlugin", package: "WirePlugins")] + ), .testTarget(name: "WireSettingsUITests", dependencies: ["WireSettingsUI"]), .target( diff --git a/wire-ios-sync-engine/Source/SessionManager/SessionManager+Backup.swift b/wire-ios-sync-engine/Source/SessionManager/SessionManager+Backup.swift index 7718876c1df..713948895b8 100644 --- a/wire-ios-sync-engine/Source/SessionManager/SessionManager+Backup.swift +++ b/wire-ios-sync-engine/Source/SessionManager/SessionManager+Backup.swift @@ -79,9 +79,9 @@ extension SessionManager { dispatchGroup: ZMSDispatchGroup, handle: String ) throws -> URL { - return try workerQueue.sync { + try workerQueue.sync { switch result { - case .success(let info): + case let .success(info): do { // 1. Compress the backup let compressed = try compress(backup: info) @@ -93,7 +93,7 @@ extension SessionManager { } catch { throw error } - case .failure(let error): + case let .failure(error): throw error } } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupPasswordValidator.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupPasswordValidator.swift index 21c64652aff..c2306f30500 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupPasswordValidator.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupPasswordValidator.swift @@ -34,7 +34,7 @@ struct BackupPasswordValidator: BackupPasswordValidatorProtocol { } var localizedRulesDescription: String { - return PasswordRuleSet.localizedErrorMessage + PasswordRuleSet.localizedErrorMessage } } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupSource.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupSource.swift index 5ae63013f3a..7eb0568a497 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupSource.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupSource.swift @@ -17,7 +17,7 @@ // import Foundation -import class WireSyncEngine.SessionManager import WireSettingsUI +import class WireSyncEngine.SessionManager extension SessionManager: BackupSource {} From a4a70de579273082cf11a8c10b9091bd38d964a2 Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Tue, 7 Jan 2025 09:42:51 +0100 Subject: [PATCH 014/107] UI for backup restore --- .../ViewModels/BackupActionsViewModel.swift | 7 +- .../Backup/Views/BackupActionsView.swift | 55 ++++++- .../Backup/Views/RestoreBackupView.swift | 144 ++++++++++++++++++ .../Resources/en.lproj/Accessibility.strings | 1 + .../Resources/en.lproj/Localizable.strings | 4 + .../Backup/BackupRestoreController.swift | 2 +- 6 files changed, 207 insertions(+), 6 deletions(-) create mode 100644 WireUI/Sources/WireSettingsUI/Account/Backup/Views/RestoreBackupView.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift index ca288727623..7db5d4c8c5a 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift @@ -35,8 +35,8 @@ public final class BackupActionsViewModel: ObservableObject { self.passwordValidator = passwordValidator sections = [ - BackupActionsSection(type: .backup) - //BackupActionsSection(type: .restore) + BackupActionsSection(type: .backup), + BackupActionsSection(type: .restore) ] } @@ -50,4 +50,7 @@ public final class BackupActionsViewModel: ObservableObject { backupHandler.onFailure(error) } } + + func restoreBackup(password: String, from url: URL) { + } } diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift index b136469f004..3cf37b5793b 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift @@ -24,6 +24,7 @@ public struct BackupActionsView: View { @ObservedObject private var viewModel: BackupActionsViewModel @State private var isBackupSheetPresented: Bool = false @State private var isRestoreSheetPresented: Bool = false + @State private var isBackupPickerPresented: Bool = false public init(viewModel: BackupActionsViewModel) { self.viewModel = viewModel @@ -38,11 +39,9 @@ public struct BackupActionsView: View { Button(action: { switch section.type { case .backup: - isRestoreSheetPresented = false isBackupSheetPresented.toggle() case .restore: - isBackupSheetPresented = false - isRestoreSheetPresented.toggle() + isBackupPickerPresented.toggle() } }) { @@ -63,6 +62,23 @@ public struct BackupActionsView: View { } .presentationDetents([.medium, .large]) } + // .sheet(isPresented: $isRestoreSheetPresented) { + // NavigationStack { + // RestoreBackupView(backupPath: <#T##URL#>) { password, url in + // viewModel.restoreBackup(password: password, from: url) + // } + // } + // .presentationDetents([.medium, .large]) + // } + .fullScreenCover(isPresented: $isBackupPickerPresented) { + DocumentPicker { url in + if let fileURL = url { + // Handle the file URL in your viewModel or further logic + print("Selected file URL: \(fileURL)") + // You can now call viewModel.restoreAccount(with: fileURL) + } + } + } } } } @@ -71,6 +87,39 @@ public struct BackupActionsView: View { } } +struct DocumentPicker: UIViewControllerRepresentable { + var completion: (URL?) -> Void + + func makeUIViewController(context: Context) -> UIDocumentPickerViewController { + let picker = UIDocumentPickerViewController(forOpeningContentTypes: [.item]) + picker.allowsMultipleSelection = false + picker.delegate = context.coordinator + return picker + } + + func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: Context) {} + + func makeCoordinator() -> Coordinator { + Coordinator(completion: completion) + } + + class Coordinator: NSObject, UIDocumentPickerDelegate { + var completion: (URL?) -> Void + + init(completion: @escaping (URL?) -> Void) { + self.completion = completion + } + + func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { + completion(urls.first) + } + + func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) { + completion(nil) + } + } +} + #Preview { BackupActionsView(viewModel: BackupActionsViewModel( backupSource: MockBackupSource(), diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/RestoreBackupView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/RestoreBackupView.swift new file mode 100644 index 00000000000..2417e3f9f37 --- /dev/null +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/RestoreBackupView.swift @@ -0,0 +1,144 @@ +// +// Wire +// Copyright (C) 2025 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import SwiftUI +import WireDesign +import WireFoundation +import WireReusableUIComponents + +struct RestoreBackupView: View { + @Environment(\.dismiss) private var dismiss + private let backupPath: URL + private let importBackup: (String, URL) -> Void + + public init( + backupPath: URL, + importBackup: @escaping (String, URL) -> Void + ) { + self.backupPath = backupPath + self.importBackup = importBackup + } + + public var body: some View { + PpasswordBackupView( + backupPath: backupPath, importBackup: importBackup) + .background(Color.viewBackground) + .scrollContentBackground(.hidden) + .navigationTitle( + Text("Enter password") + ) + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + CloseButton( + action: didTapClose, + accessibilityLabel: String( + localized: "restoreBackup.close.label", + table: "Accessibility", + bundle: .module + ) + ) + } + } + } + + private func didTapClose() { + dismiss() + } +} + +private struct PpasswordBackupView: View { + @Environment(\.dismiss) var dismiss + @State private var password: String = "" + @State private var isPasswordVisible: Bool = false + + private let backupPath: URL + private let importBackup: (String, URL) -> Void + + init( + backupPath: URL, + importBackup: @escaping (String, URL) -> Void + ) { + self.backupPath = backupPath + self.importBackup = importBackup + } + + var body: some View { + VStack(spacing: 20) { + Text(L10n.RestoreFromBackup.title) + .font(.textStyle(.body1)) + .foregroundStyle(Color.primaryText) + .multilineTextAlignment(.leading) + .padding(.horizontal) + +// PasswordFieldView( +// password: $password, +// isPasswordValid: passwordValidator.isValid(password: password), +// isPasswordVisible: $isPasswordVisible, +// passwordRules: Text(passwordValidator.localizedRulesDescription)) + + Spacer() + + Button( + action: { + importBackup(password, backupPath) + dismiss() + }, + label: { + Text(L10n.RestoreFromBackup.button) + } + ) + .wireButtonStyle(.primary) + .padding() + } + .frame(maxHeight: .infinity) + } + +} + +// MARK: - Previews + +@available(iOS 17.0, *) +#Preview("Export Backup sheet") { + RestoreBackupPreview() +} + +private struct RestoreBackupPreview: View { + @State private var isPresented = true + + var body: some View { + Button( + action: { + isPresented.toggle() + }, + label: { + Text(L10n.RestoreFromBackup.button) + } + ) + .sheet(isPresented: $isPresented) { + NavigationStack { + RestoreBackupView( + backupPath: URL(fileURLWithPath: ""), + importBackup: { _, _ in } + ) + } + .presentationDragIndicator(.visible) + .presentationDetents([.medium, .large]) + } + } +} diff --git a/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Accessibility.strings b/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Accessibility.strings index 9131729c59c..bdd7b3a1bb1 100644 --- a/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Accessibility.strings +++ b/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Accessibility.strings @@ -17,3 +17,4 @@ // "setBackupPassword.close.label" = "Close setup backup password" +"restoreBackup.close.label" = "Close restore backup" diff --git a/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Localizable.strings b/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Localizable.strings index a3a34d79ca6..eb9ee22f884 100644 --- a/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Localizable.strings +++ b/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Localizable.strings @@ -26,3 +26,7 @@ "setBackupPassword.button" = "Back Up Now"; "setBackupPassword.description" = "The backup will be compressed and encrypted with a password. Make sure to store it in a secure place."; "setBackupPassword.title" = "Set password"; +"enterBackupPassword.title" = "Enter password"; +"restoreFromBackup.title" = "This backup is password protected."; +"restoreFromBackup.button" = "Continue"; +"enterBackupPassword.error" = "Wrong password. Please verify your input and try again"; diff --git a/wire-ios/Wire-iOS/Sources/Authentication/Backup/BackupRestoreController.swift b/wire-ios/Wire-iOS/Sources/Authentication/Backup/BackupRestoreController.swift index eaf69d0d819..62066c9f0e0 100644 --- a/wire-ios/Wire-iOS/Sources/Authentication/Backup/BackupRestoreController.swift +++ b/wire-ios/Wire-iOS/Sources/Authentication/Backup/BackupRestoreController.swift @@ -111,7 +111,7 @@ final class BackupRestoreController: NSObject { } Task { @MainActor in activityIndicator.start() } - +// sessionManager.restoreFromBackup(at: url, password: password) { [weak self] result in guard let self else { BackgroundActivityFactory.shared.endBackgroundActivity(activity) From 3d89bfb72d8df23f54d2b413f3f525a2c3eb4558 Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Tue, 7 Jan 2025 11:23:36 +0100 Subject: [PATCH 015/107] remove old backup code --- .../Backup/Models/BackupActionsSection.swift | 8 +- .../Backup/Protocols/BackupSource.swift | 4 +- .../ViewModels/BackupActionsViewModel.swift | 4 +- .../Backup/Views/BackupActionsView.swift | 4 +- ...ortBackup.swift => ExportBackupView.swift} | 12 +- .../Backup/Views/PasswordFieldView.swift | 6 +- .../Resources/en.lproj/Localizable.strings | 20 +- wire-ios/Wire-iOS.xcodeproj/project.pbxproj | 16 -- .../Settings/Backup/BackupActionCell.swift | 57 ------ .../Backup/BackupPasswordViewController.swift | 187 ------------------ .../Settings/Backup/BackupSource.swift | 19 +- .../Settings/Backup/BackupStatusCell.swift | 71 ------- .../Backup/BackupViewController.swift | 161 --------------- ...ettingsCellDescriptorFactory+Account.swift | 2 +- 14 files changed, 49 insertions(+), 522 deletions(-) rename WireUI/Sources/WireSettingsUI/Account/Backup/Views/{ExportBackup.swift => ExportBackupView.swift} (93%) delete mode 100644 wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupActionCell.swift delete mode 100644 wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupPasswordViewController.swift delete mode 100644 wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupStatusCell.swift delete mode 100644 wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupViewController.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupActionsSection.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupActionsSection.swift index 937b5a51a39..2cd1b238813 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupActionsSection.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupActionsSection.swift @@ -29,18 +29,18 @@ struct BackupActionsSection: Identifiable { var title: Text { switch self { case .backup: - return Text(L10n.HistoryBackup.action) + return Text(L10n.Settings.ExportBackup.action) case .restore: - return Text(L10n.RestoreFromBackup.action) + return Text(L10n.Settings.RestoreFromBackup.action) } } var footer: Text { switch self { case .backup: - return Text(L10n.HistoryBackup.description) + return Text(L10n.Settings.ExportBackup.description) case .restore: - return Text(L10n.RestoreFromBackup.description) + return Text(L10n.Settings.RestoreFromBackup.description) } } } diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/BackupSource.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/BackupSource.swift index 0bda7e9e7b7..f611ca99225 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/BackupSource.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/BackupSource.swift @@ -18,8 +18,10 @@ import Foundation -public protocol BackupSource { +public protocol BackupSourceProtocol { + func backupActiveAccount(password: String) throws -> URL func clearPreviousBackups() + } diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift index ca288727623..2c5e3b1080a 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift @@ -21,12 +21,12 @@ import SwiftUI public final class BackupActionsViewModel: ObservableObject { @Published var sections: [BackupActionsSection] = [] - private let backupSource: any BackupSource + private let backupSource: any BackupSourceProtocol private let backupHandler: BackupHandler let passwordValidator: any BackupPasswordValidatorProtocol public init( - backupSource: any BackupSource, + backupSource: any BackupSourceProtocol, backupHandler: BackupHandler, passwordValidator: any BackupPasswordValidatorProtocol ) { diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift index b136469f004..dbc01136159 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift @@ -56,7 +56,7 @@ public struct BackupActionsView: View { } .sheet(isPresented: $isBackupSheetPresented) { NavigationStack { - ExportBackup( + ExportBackupView( passwordValidator: viewModel.passwordValidator) { password in viewModel.backupActiveAccount(password: password) } @@ -81,7 +81,7 @@ public struct BackupActionsView: View { )) } -private class MockBackupSource: BackupSource { +private class MockBackupSource: BackupSourceProtocol { func backupActiveAccount(password: String) throws -> URL { return URL(fileURLWithPath: "path") } diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackup.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift similarity index 93% rename from WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackup.swift rename to WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift index f9b2e9748d2..f0a0650863a 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackup.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift @@ -23,7 +23,7 @@ import WireReusableUIComponents /// A view that allows to export the backup. -public struct ExportBackup: View { +public struct ExportBackupView: View { @Environment(\.dismiss) private var dismiss private let exportBackup: (String) -> Void @@ -44,7 +44,7 @@ public struct ExportBackup: View { .background(Color.viewBackground) .scrollContentBackground(.hidden) .navigationTitle( - Text(L10n.SetBackupPassword.title) + Text(L10n.ExportBackup.title) ) .navigationBarTitleDisplayMode(.inline) .toolbar { @@ -85,7 +85,7 @@ private struct SetBackupPasswordView: View { var body: some View { VStack(spacing: 20) { - Text(L10n.SetBackupPassword.description) + Text(L10n.ExportBackup.description) .font(.textStyle(.body1)) .foregroundStyle(Color.primaryText) .multilineTextAlignment(.leading) @@ -105,7 +105,7 @@ private struct SetBackupPasswordView: View { dismiss() }, label: { - Text(L10n.SetBackupPassword.button) + Text(L10n.ExportBackup.button) } ) .wireButtonStyle(.primary) @@ -132,12 +132,12 @@ private struct ExportBackupPreview: View { isPresented.toggle() }, label: { - Text(L10n.SetBackupPassword.button) + Text(L10n.ExportBackup.button) } ) .sheet(isPresented: $isPresented) { NavigationStack { - ExportBackup( + ExportBackupView( passwordValidator: MockBackupPasswordValidator(), exportBackup: {_ in } ) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift index 4031a2ae5ae..9866d3adb8f 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift @@ -27,13 +27,13 @@ struct PasswordFieldView: View { var body: some View { VStack(alignment: .leading, spacing: 8) { - Text(L10n.PasswordField.title) + Text(L10n.SetBackupPassword.title) .font(.subheadline) .foregroundColor(isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color) ZStack { if isPasswordVisible { - TextField(L10n.PasswordField.placeholder, + TextField(L10n.SetBackupPassword.placeholder, text: $password) .font(.textStyle(.body1)) .textFieldStyle(RoundedBorderTextFieldStyle()) @@ -42,7 +42,7 @@ struct PasswordFieldView: View { .stroke(isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color, lineWidth: 1) ) } else { - SecureField(L10n.PasswordField.placeholder, + SecureField(L10n.SetBackupPassword.placeholder, text: $password) .textFieldStyle(RoundedBorderTextFieldStyle()) .overlay( diff --git a/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Localizable.strings b/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Localizable.strings index a3a34d79ca6..8e7df5bfbab 100644 --- a/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Localizable.strings +++ b/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Localizable.strings @@ -16,13 +16,13 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -"historyBackup.action" = "Back Up Now"; -"historyBackup.description" = "Create a backup to preserve your conversation history. You can use this to restore history if you lose your computer or switch to a new one. The backup file is not protected by Wire end-to-end encryption, so store it in a safe place."; -"passwordField.placeholder" = "Enter password"; -"passwordField.rules" = "Use at least 8 characters, with one lowercase letter, one capital letter, a number, and a special character."; -"passwordField.title" = "Password (OPTIONAL)"; -"restoreFromBackup.action" = "Restore from Backup"; -"restoreFromBackup.description" = "The existing history on this device remains and will be completed by the new backup. You can restore history from all your devices and different platforms but not from another account."; -"setBackupPassword.button" = "Back Up Now"; -"setBackupPassword.description" = "The backup will be compressed and encrypted with a password. Make sure to store it in a secure place."; -"setBackupPassword.title" = "Set password"; +"settings.exportBackup.action" = "Back Up Now"; +"settings.exportBackup.description" = "Create a backup to preserve your conversation history. You can use this to restore history if you lose your computer or switch to a new one. The backup file is not protected by Wire end-to-end encryption, so store it in a safe place."; +"settings.restoreFromBackup.action" = "Restore from Backup"; +"settings.restoreFromBackup.description" = "The existing history on this device remains and will be completed by the new backup. You can restore history from all your devices and different platforms but not from another account."; +"exportBackup.title" = "Set password"; +"exportBackup.description" = "The backup will be compressed and encrypted with a password. Make sure to store it in a secure place."; +"exportBackup.button" = "Back Up Now"; +"setBackupPassword.title" = "Password (OPTIONAL)"; +"setBackupPassword.placeholder" = "Enter password"; + diff --git a/wire-ios/Wire-iOS.xcodeproj/project.pbxproj b/wire-ios/Wire-iOS.xcodeproj/project.pbxproj index 177917498e0..abb43bca2c5 100644 --- a/wire-ios/Wire-iOS.xcodeproj/project.pbxproj +++ b/wire-ios/Wire-iOS.xcodeproj/project.pbxproj @@ -785,7 +785,6 @@ 8750A0112195BEE800DC8DB6 /* UpsideDownTableView+Scrolling.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8750A0102195BEE800DC8DB6 /* UpsideDownTableView+Scrolling.swift */; }; 8750B1CB2124236200B807A9 /* SecondaryTextButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8750B1CA2124236200B807A9 /* SecondaryTextButton.swift */; }; 8751A6CB1FF6573D00804A58 /* QuickActionsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8751A6CA1FF6573D00804A58 /* QuickActionsManager.swift */; }; - 8751BA442069455E00DF8667 /* BackupViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8751BA432069455E00DF8667 /* BackupViewController.swift */; }; 875753D1211DC61C00A80F5E /* EmptySearchResultsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 875753D0211DC61C00A80F5E /* EmptySearchResultsView.swift */; }; 8758CDB92191A7D60031BE0F /* ReplyComposingViewTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8758CDB82191A7D60031BE0F /* ReplyComposingViewTests.swift */; }; 8759E2C51FC86090008E17C9 /* UIAlertController+TOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8759E2C41FC86090008E17C9 /* UIAlertController+TOS.swift */; }; @@ -1218,7 +1217,6 @@ D5C34E93203D7B2E004A0986 /* CellConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5C34E92203D7B2E004A0986 /* CellConfiguration.swift */; }; D5CEBFC3202CA3BA00AFBD3A /* AddParticipantsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5CEBFC2202CA3BA00AFBD3A /* AddParticipantsViewModel.swift */; }; D5D65A0F2074CFBB00D7F3C3 /* AuthenticationType.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5D65A0E2074CFBB00D7F3C3 /* AuthenticationType.swift */; }; - D5D65A11207509F300D7F3C3 /* BackupPasswordViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5D65A10207509F300D7F3C3 /* BackupPasswordViewController.swift */; }; D5D89780201A12D300FAF69C /* Account+ShareExtensionDisplayName.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5D8977E201A121900FAF69C /* Account+ShareExtensionDisplayName.swift */; }; D5F22D4C2048052900879444 /* UIAlertController+RemoveAction.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F22D4B2048052900879444 /* UIAlertController+RemoveAction.swift */; }; D5F8BFF42007AC84008F8C3D /* UIView+TableViewHeaderFooterSizing.swift in Sources */ = {isa = PBXBuildFile; fileRef = D5F8BFF32007AC84008F8C3D /* UIView+TableViewHeaderFooterSizing.swift */; }; @@ -1292,8 +1290,6 @@ E662588B2B4D3C9D00C23E79 /* DeveloperDebugActionsDisplayModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = E662588A2B4D3C9D00C23E79 /* DeveloperDebugActionsDisplayModel.swift */; }; E66258942B4D661900C23E79 /* DeveloperDebugActionsViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E66258932B4D661900C23E79 /* DeveloperDebugActionsViewModelTests.swift */; }; E666EDD62B73E62800C03E2B /* BackupSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = E666EDD52B73E62800C03E2B /* BackupSource.swift */; }; - E666EDDA2B73E9C400C03E2B /* BackupStatusCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E666EDD92B73E9C400C03E2B /* BackupStatusCell.swift */; }; - E666EDDC2B73EA3500C03E2B /* BackupActionCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E666EDDB2B73EA3500C03E2B /* BackupActionCell.swift */; }; E66C51D52C13375700F82F88 /* WireAnalytics.swift in Sources */ = {isa = PBXBuildFile; fileRef = E66C51D42C13375700F82F88 /* WireAnalytics.swift */; }; E66C51D92C13434B00F82F88 /* WireDatadog+LoggerProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E66C51D82C13434B00F82F88 /* WireDatadog+LoggerProtocol.swift */; }; E66D4E822BE525DF00C7F374 /* AVSVideoContainerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = E66D4E812BE525DF00C7F374 /* AVSVideoContainerView.swift */; }; @@ -2725,7 +2721,6 @@ 8750A0102195BEE800DC8DB6 /* UpsideDownTableView+Scrolling.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UpsideDownTableView+Scrolling.swift"; sourceTree = ""; }; 8750B1CA2124236200B807A9 /* SecondaryTextButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecondaryTextButton.swift; sourceTree = ""; }; 8751A6CA1FF6573D00804A58 /* QuickActionsManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickActionsManager.swift; sourceTree = ""; }; - 8751BA432069455E00DF8667 /* BackupViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupViewController.swift; sourceTree = ""; }; 8756CA121D2EBE1F002E7CB7 /* LaunchImageViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LaunchImageViewController.swift; sourceTree = ""; }; 875753D0211DC61C00A80F5E /* EmptySearchResultsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EmptySearchResultsView.swift; sourceTree = ""; }; 8758CDB82191A7D60031BE0F /* ReplyComposingViewTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyComposingViewTests.swift; sourceTree = ""; }; @@ -3245,7 +3240,6 @@ D5C34E92203D7B2E004A0986 /* CellConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CellConfiguration.swift; sourceTree = ""; }; D5CEBFC2202CA3BA00AFBD3A /* AddParticipantsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddParticipantsViewModel.swift; sourceTree = ""; }; D5D65A0E2074CFBB00D7F3C3 /* AuthenticationType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationType.swift; sourceTree = ""; }; - D5D65A10207509F300D7F3C3 /* BackupPasswordViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupPasswordViewController.swift; sourceTree = ""; }; D5D8977E201A121900FAF69C /* Account+ShareExtensionDisplayName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Account+ShareExtensionDisplayName.swift"; sourceTree = ""; }; D5F22D4B2048052900879444 /* UIAlertController+RemoveAction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIAlertController+RemoveAction.swift"; sourceTree = ""; }; D5F8BFF32007AC84008F8C3D /* UIView+TableViewHeaderFooterSizing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+TableViewHeaderFooterSizing.swift"; sourceTree = ""; }; @@ -3271,8 +3265,6 @@ E662588A2B4D3C9D00C23E79 /* DeveloperDebugActionsDisplayModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperDebugActionsDisplayModel.swift; sourceTree = ""; }; E66258932B4D661900C23E79 /* DeveloperDebugActionsViewModelTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeveloperDebugActionsViewModelTests.swift; sourceTree = ""; }; E666EDD52B73E62800C03E2B /* BackupSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupSource.swift; sourceTree = ""; }; - E666EDD92B73E9C400C03E2B /* BackupStatusCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupStatusCell.swift; sourceTree = ""; }; - E666EDDB2B73EA3500C03E2B /* BackupActionCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupActionCell.swift; sourceTree = ""; }; E66C51D42C13375700F82F88 /* WireAnalytics.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WireAnalytics.swift; sourceTree = ""; }; E66C51D82C13434B00F82F88 /* WireDatadog+LoggerProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WireDatadog+LoggerProtocol.swift"; sourceTree = ""; }; E66D4E812BE525DF00C7F374 /* AVSVideoContainerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AVSVideoContainerView.swift; sourceTree = ""; }; @@ -7506,10 +7498,6 @@ children = ( 060F032A2D2C12910016431F /* BackupPasswordValidator.swift */, E666EDD52B73E62800C03E2B /* BackupSource.swift */, - E666EDDB2B73EA3500C03E2B /* BackupActionCell.swift */, - E666EDD92B73E9C400C03E2B /* BackupStatusCell.swift */, - 8751BA432069455E00DF8667 /* BackupViewController.swift */, - D5D65A10207509F300D7F3C3 /* BackupPasswordViewController.swift */, ); path = Backup; sourceTree = ""; @@ -9078,7 +9066,6 @@ D5168F532010F20B00F8222A /* BlurEffectTransition.swift in Sources */, 1671D6D1200CDC380022D289 /* InviteTeamMemberCell.swift in Sources */, E95B0A142B55991E0088B778 /* ConversationNewDeviceSystemMessageCellDescription.swift in Sources */, - D5D65A11207509F300D7F3C3 /* BackupPasswordViewController.swift in Sources */, 5EC7C87A219038F6004662AC /* ImageResourceThumbnailView.swift in Sources */, EE0768EE2A9FB99700BA47A3 /* EmojiRepository.swift in Sources */, 87AC77DB1DE48C01009F6D56 /* Copyable.swift in Sources */, @@ -9272,7 +9259,6 @@ 06E097982A20BEE000B38C4A /* ZMConversation+IncompleteMetadata.swift in Sources */, 8775BF1C2007C6B900A8AD93 /* SearchGroupSelector.swift in Sources */, 5915B9582BF4BA9700215817 /* DidPresentNotificationPermissionHintUseCaseProtocol.swift in Sources */, - E666EDDC2B73EA3500C03E2B /* BackupActionCell.swift in Sources */, E66258892B4D39D900C23E79 /* DeveloperDebugActionsViewModel.swift in Sources */, BF5DF5C720F4C1C5002BCB67 /* GroupParticipantsDetailViewModel.swift in Sources */, EEE81E9823E0815600A1D035 /* ProfilePresenter.swift in Sources */, @@ -9716,7 +9702,6 @@ 7CED307C1FD97895009F0DAC /* AvailabilityStringBuilder.swift in Sources */, F1CB5D38215CD462001CCF5D /* MarkdownTextView+Recognizers.swift in Sources */, 5E8DA75C211B085A00360979 /* AnyAuthenticationEventHandler.swift in Sources */, - E666EDDA2B73E9C400C03E2B /* BackupStatusCell.swift in Sources */, 1682AEBD20483DA5003A052A /* ServicesSectionController.swift in Sources */, E90A2F0D28EEC674005AF571 /* TextFieldStyle.swift in Sources */, 7C6878DB201B3785003A0C7A /* StartUIViewController+SearchResults.swift in Sources */, @@ -9903,7 +9888,6 @@ 5E8DA758211B055100360979 /* AuthenticationClientLimitErrorHandler.swift in Sources */, BF10B58D1E6452B800E7036E /* ZMConversationMessage+Reactions.swift in Sources */, 5E2945992190A1680045ACFA /* SenderNameCellComponent.swift in Sources */, - 8751BA442069455E00DF8667 /* BackupViewController.swift in Sources */, A9859D6A23FEDD7400DC3F36 /* FullscreenImageViewController.swift in Sources */, A9076C8223B1047E004FD3C9 /* ConversationContentViewController+Header.swift in Sources */, A96A21F123D5B865005B5579 /* ConversationInputBarViewController+Files.swift in Sources */, diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupActionCell.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupActionCell.swift deleted file mode 100644 index 729170e76f9..00000000000 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupActionCell.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// Wire -// Copyright (C) 2025 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import UIKit -import WireDesign - -//final class BackupActionCell: UITableViewCell { -// let actionTitleLabel: DynamicFontLabel = { -// let text = L10n.Localizable.Self.Settings.HistoryBackup.action -// let label = DynamicFontLabel( -// text: text, -// style: .body2, -// color: SemanticColors.Label.textDefault -// ) -// label.textAlignment = .left -// return label -// }() -// -// override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { -// super.init(style: style, reuseIdentifier: reuseIdentifier) -// selectionStyle = .none -// backgroundColor = SemanticColors.View.backgroundUserCell -// accessibilityTraits = .button -// contentView.backgroundColor = .clear -// -// actionTitleLabel.translatesAutoresizingMaskIntoConstraints = false -// contentView.addSubview(actionTitleLabel) -// NSLayoutConstraint.activate([ -// actionTitleLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 24), -// actionTitleLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -24), -// actionTitleLabel.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 0), -// actionTitleLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: 0) -// ]) -// actionTitleLabel.heightAnchor.constraint(equalToConstant: 44).isActive = true -// addBorder(for: .bottom) -// } -// -// @available(*, unavailable) -// required init?(coder aDecoder: NSCoder) { -// fatalError("init(coder:) has not been implemented") -// } -//} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupPasswordViewController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupPasswordViewController.swift deleted file mode 100644 index 71095d453a2..00000000000 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupPasswordViewController.swift +++ /dev/null @@ -1,187 +0,0 @@ -// -// Wire -// Copyright (C) 2025 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -//import UIKit -//import WireDesign - -//final class BackupPasswordViewController: UIViewController { -// -// typealias ViewColors = SemanticColors.View -// typealias LabelColors = SemanticColors.Label -// typealias HistoryBackup = L10n.Localizable.Self.Settings.HistoryBackup -// -// var onCompletion: ((_ password: String?) -> Void)? -// -// private var password: String? -// private let passwordView = SimpleTextField() -// -// private let subtitleLabel: DynamicFontLabel = { -// let label = DynamicFontLabel( -// text: HistoryBackup.Password.description, -// style: .subline1, -// color: LabelColors.textSectionHeader -// ) -// label.numberOfLines = 0 -// return label -// }() -// -// private let passwordRulesLabel: DynamicFontLabel = { -// let label = DynamicFontLabel( -// style: .subline1, -// color: LabelColors.textSectionHeader -// ) -// label.numberOfLines = 0 -// return label -// }() -// -// init() { -// super.init(nibName: nil, bundle: nil) -// -// setupViews() -// createConstraints() -// } -// -// @available(*, unavailable) -// required init?(coder aDecoder: NSCoder) { -// fatalError("init(coder:) has not been implemented") -// } -// -// override func viewWillAppear(_ animated: Bool) { -// super.viewWillAppear(animated) -// setupNavigationBar() -// } -// -// override func viewDidAppear(_ animated: Bool) { -// super.viewDidAppear(animated) -// passwordView.becomeFirstResponder() -// } -// -// override var supportedInterfaceOrientations: UIInterfaceOrientationMask { -// wr_supportedInterfaceOrientations -// } -// -// private func setupViews() { -// view.backgroundColor = ViewColors.backgroundDefault -// passwordRulesLabel.text = PasswordRuleSet.localizedErrorMessage -// -// [passwordView, subtitleLabel, passwordRulesLabel].forEach { -// view.addSubview($0) -// $0.translatesAutoresizingMaskIntoConstraints = false -// } -// -// passwordView.placeholder = HistoryBackup.Password.placeholder -// passwordView.accessibilityIdentifier = "password input" -// passwordView.accessibilityHint = PasswordRuleSet.localizedErrorMessage -// passwordView.returnKeyType = .done -// passwordView.isSecureTextEntry = true -// passwordView.delegate = self -// passwordView.textColor = LabelColors.textSectionHeader -// passwordView.backgroundColor = ViewColors.backgroundUserCell -// let attributes: [NSAttributedString.Key: Any] = [ -// .foregroundColor: SemanticColors.SearchBar.textInputViewPlaceholder, -// .font: UIFont.font(for: .body1) -// ] -// passwordView.updatePlaceholderAttributedText(attributes: attributes) -// } -// -// private func createConstraints() { -// NSLayoutConstraint.activate([ -// passwordView.leadingAnchor.constraint(equalTo: view.leadingAnchor), -// passwordView.trailingAnchor.constraint(equalTo: view.trailingAnchor), -// passwordView.centerYAnchor.constraint(equalTo: view.centerYAnchor), -// passwordView.heightAnchor.constraint(equalToConstant: 56), -// subtitleLabel.bottomAnchor.constraint(equalTo: passwordView.topAnchor, constant: -16), -// subtitleLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16), -// subtitleLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16), -// passwordRulesLabel.topAnchor.constraint(equalTo: passwordView.bottomAnchor, constant: 16), -// passwordRulesLabel.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 16), -// passwordRulesLabel.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -16) -// ]) -// } -// -// private func setupNavigationBar() { -// navigationController?.navigationBar.backgroundColor = ViewColors.backgroundDefault -// -// setupNavigationBarTitle(HistoryBackup.Password.title) -// -// let cancelButtonItem = UIBarButtonItem.createNavigationLeftBarButtonItem( -// title: HistoryBackup.Password.cancel, -// action: UIAction { [weak self] _ in -// self?.onCompletion?(nil) -// } -// ) -// -// let nextButtonItem = UIBarButtonItem.createNavigationRightBarButtonItem( -// title: HistoryBackup.Password.next, -// action: UIAction { [weak self] _ in -// self?.onCompletion?(self?.password) -// } -// ) -// -// nextButtonItem.tintColor = UIColor.accent() -// nextButtonItem.isEnabled = false -// -// navigationItem.leftBarButtonItem = cancelButtonItem -// navigationItem.rightBarButtonItem = nextButtonItem -// } -// -// private func updateState(with text: String) { -// switch PasswordRuleSet.shared.validatePassword(text) { -// case .valid: -// password = text -// navigationItem.rightBarButtonItem?.isEnabled = true -// case .invalid: -// password = nil -// navigationItem.rightBarButtonItem?.isEnabled = false -// } -// } -// -// @objc -// private dynamic func completeWithCurrentResult() { -// onCompletion?(password) -// } -//} -// -//// MARK: - UITextFieldDelegate -// -//extension BackupPasswordViewController: UITextFieldDelegate { -// -// func textField( -// _ textField: UITextField, -// shouldChangeCharactersIn range: NSRange, -// replacementString string: String -// ) -> Bool { -// -// if string.containsCharacters(from: .whitespaces) { -// return false -// } -// -// if string.containsCharacters(from: .newlines) { -// if password != nil { -// completeWithCurrentResult() -// } -// return false -// } -// -// let newString = ((textField.text ?? "") as NSString).replacingCharacters(in: range, with: string) -// -// updateState(with: newString) -// -// return true -// } -//} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupSource.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupSource.swift index 7eb0568a497..04485359937 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupSource.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupSource.swift @@ -20,4 +20,21 @@ import Foundation import WireSettingsUI import class WireSyncEngine.SessionManager -extension SessionManager: BackupSource {} +struct BackupSource: BackupSourceProtocol { + + enum BackupSourceError: Error { + case missingSessionManager + } + + func backupActiveAccount(password: String) throws -> URL { + guard let sessionManager = SessionManager.shared else { + throw BackupSourceError.missingSessionManager + } + return try sessionManager.backupActiveAccount(password: password) + } + + func clearPreviousBackups() { + SessionManager.shared?.clearPreviousBackups() + } + +} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupStatusCell.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupStatusCell.swift deleted file mode 100644 index 1e3cad3e159..00000000000 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupStatusCell.swift +++ /dev/null @@ -1,71 +0,0 @@ -// -// Wire -// Copyright (C) 2025 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import UIKit -import WireDesign - -//final class BackupStatusCell: UITableViewCell { -// -// let descriptionLabel: DynamicFontLabel = { -// let label = DynamicFontLabel( -// style: .body1, -// color: SemanticColors.Label.textDefault -// ) -// label.textAlignment = .left -// label.numberOfLines = 0 -// return label -// }() -// -// let iconView = UIImageView() -// -// override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { -// super.init(style: style, reuseIdentifier: reuseIdentifier) -// -// selectionStyle = .none -// backgroundColor = .clear -// contentView.backgroundColor = .clear -// -// iconView.setTemplateIcon(.restore, size: .large) -// iconView.tintColor = SemanticColors.Label.textDefault -// iconView.contentMode = .center -// iconView.translatesAutoresizingMaskIntoConstraints = false -// contentView.addSubview(iconView) -// -// descriptionLabel.translatesAutoresizingMaskIntoConstraints = false -// contentView.addSubview(descriptionLabel) -// -// NSLayoutConstraint.activate([ -// iconView.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 24), -// iconView.centerXAnchor.constraint(equalTo: contentView.centerXAnchor), -// iconView.heightAnchor.constraint(equalTo: iconView.widthAnchor), -// iconView.widthAnchor.constraint(equalToConstant: 48), -// descriptionLabel.topAnchor.constraint(equalTo: iconView.bottomAnchor, constant: 24), -// descriptionLabel.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 24), -// descriptionLabel.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -24), -// descriptionLabel.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -24) -// ]) -// -// let description = L10n.Localizable.Self.Settings.HistoryBackup.description -// descriptionLabel.attributedText = description && .paragraphSpacing(2) -// } -// -// @available(*, unavailable) -// required init?(coder aDecoder: NSCoder) { -// fatalError("init(coder:) has not been implemented") -// } -//} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupViewController.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupViewController.swift deleted file mode 100644 index dac534fa1cc..00000000000 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupViewController.swift +++ /dev/null @@ -1,161 +0,0 @@ -// -// Wire -// Copyright (C) 2025 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -//import UIKit -//import WireDesign -//import WireReusableUIComponents - -//final class BackupViewController: UIViewController { -// -// private let tableView = UITableView(frame: .zero) -// private var cells: [UITableViewCell.Type] = [] -// private let backupSource: BackupSource -// private lazy var activityIndicator = BlockingActivityIndicator(view: navigationController?.view ?? view) -// -// init(backupSource: BackupSource) { -// self.backupSource = backupSource -// super.init(nibName: nil, bundle: nil) -// } -// -// @available(*, unavailable) -// required init?(coder aDecoder: NSCoder) { -// fatalError("init(coder:) has not been implemented") -// } -// -// override func viewDidLoad() { -// super.viewDidLoad() -// setupViews() -// setupLayout() -// } -// -// override func viewWillAppear(_ animated: Bool) { -// super.viewWillAppear(animated) -// setupNavigationBarTitle(L10n.Localizable.Self.Settings.HistoryBackup.title.capitalized) -// } -// -// private func setupViews() { -// view.backgroundColor = ColorTheme.Backgrounds.background -// -// tableView.isScrollEnabled = false -// tableView.rowHeight = UITableView.automaticDimension -// tableView.estimatedRowHeight = 80 -// tableView.backgroundColor = .clear -// tableView.separatorColor = UIColor(white: 1, alpha: 0.1) -// tableView.translatesAutoresizingMaskIntoConstraints = false -// view.addSubview(tableView) -// tableView.delegate = self -// tableView.dataSource = self -// -// // this is necessary to remove the placeholder cells -// tableView.tableFooterView = UIView() -// cells = [BackupStatusCell.self, BackupActionCell.self] -// -// cells.forEach { -// tableView.register($0.self, forCellReuseIdentifier: $0.reuseIdentifier) -// } -// } -// -// private func setupLayout() { -// tableView.fitIn(view: view) -// } -//} -// -//// MARK: - UITableViewDataSource & UITableViewDelegate -// -//extension BackupViewController: UITableViewDataSource, UITableViewDelegate { -// -// func numberOfSections(in tableView: UITableView) -> Int { -// 1 -// } -// -// func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { -// cells.count -// } -// -// func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { -// tableView.dequeueReusableCell(withIdentifier: cells[indexPath.row].reuseIdentifier, for: indexPath) -// } -// -// func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { -// tableView.deselectRow(at: indexPath, animated: true) -// guard indexPath.row == 1 else { return } -// backupActiveAccount(indexPath: indexPath) -// } -//} -// -//// MARK: - Backup Logic -// -//private extension BackupViewController { -// -// func backupActiveAccount(indexPath: IndexPath) { -// requestBackupPassword { [weak self] result in -// guard let self, let password = result else { return } -// activityIndicator.start() -// -// backupSource.backupActiveAccount(password: password) { backupResult in -// self.activityIndicator.stop() -// -// switch backupResult { -// case let .failure(error): -// self.presentAlert(for: error) -// case let .success(url): -// self.presentShareSheet(with: url, from: indexPath) -// } -// } -// } -// } -// -// private func requestBackupPassword(completion: @escaping (String?) -> Void) { -// let passwordController = BackupPasswordViewController() -// passwordController.onCompletion = { [weak passwordController] password in -// passwordController?.dismiss(animated: true) { -// completion(password) -// } -// } -// let navigationController = KeyboardAvoidingViewController(viewController: passwordController) -// .wrapInNavigationController() -// navigationController.modalPresentationStyle = .formSheet -// present(navigationController, animated: true) -// } -// -// private func presentAlert(for error: Error) { -// let alert = UIAlertController( -// title: L10n.Localizable.Self.Settings.HistoryBackup.Error.title, -// message: error.localizedDescription, -// preferredStyle: .alert -// ) -// alert.addAction(UIAlertAction( -// title: L10n.Localizable.General.ok, -// style: .cancel -// )) -// -// present(alert, animated: true) -// } -// -// private func presentShareSheet(with url: URL, from indexPath: IndexPath) { -// let activityController = UIActivityViewController(activityItems: [url], applicationActivities: nil) -// activityController.completionWithItemsHandler = { [weak self] _, _, _, _ in -// self?.backupSource.clearPreviousBackups() -// } -// activityController.popoverPresentationController.map { -// $0.sourceView = tableView -// $0.sourceRect = tableView.rectForRow(at: indexPath) -// } -// present(activityController, animated: true) -// } -//} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift index 3cd89827477..3f47be22077 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift @@ -379,7 +379,7 @@ extension SettingsCellDescriptorFactory { } if selfUser.hasValidEmail || selfUser.usesCompanyLogin { let viewModel = BackupActionsViewModel( - backupSource: SessionManager.shared!, + backupSource: BackupSource(), backupHandler: BackupHandler( onSuccess: presentShareSheet, onFailure: presentAlert From a2761c3a1dfe84180a94351309d71bae26be8b92 Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Tue, 7 Jan 2025 11:39:44 +0100 Subject: [PATCH 016/107] fix SwiftFormat issues --- .../Backup/Models/BackupActionsSection.swift | 8 ++++---- .../ViewModels/BackupActionsViewModel.swift | 1 - .../Account/Backup/Views/BackupActionsView.swift | 15 ++++++++------- .../Account/Backup/Views/ExportBackupView.swift | 2 +- .../Account/Backup/Views/PasswordFieldView.swift | 4 +--- 5 files changed, 14 insertions(+), 16 deletions(-) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupActionsSection.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupActionsSection.swift index 2cd1b238813..702e67ac634 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupActionsSection.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupActionsSection.swift @@ -29,18 +29,18 @@ struct BackupActionsSection: Identifiable { var title: Text { switch self { case .backup: - return Text(L10n.Settings.ExportBackup.action) + Text(L10n.Settings.ExportBackup.action) case .restore: - return Text(L10n.Settings.RestoreFromBackup.action) + Text(L10n.Settings.RestoreFromBackup.action) } } var footer: Text { switch self { case .backup: - return Text(L10n.Settings.ExportBackup.description) + Text(L10n.Settings.ExportBackup.description) case .restore: - return Text(L10n.Settings.RestoreFromBackup.description) + Text(L10n.Settings.RestoreFromBackup.description) } } } diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift index 2c5e3b1080a..a91cff5739a 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift @@ -36,7 +36,6 @@ public final class BackupActionsViewModel: ObservableObject { sections = [ BackupActionsSection(type: .backup) - //BackupActionsSection(type: .restore) ] } diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift index dbc01136159..fcdc0764dd7 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift @@ -44,8 +44,8 @@ public struct BackupActionsView: View { isBackupSheetPresented = false isRestoreSheetPresented.toggle() } - - }) { + } + ) { HStack { section.type.title .font(.textStyle(.body2)) @@ -57,9 +57,10 @@ public struct BackupActionsView: View { .sheet(isPresented: $isBackupSheetPresented) { NavigationStack { ExportBackupView( - passwordValidator: viewModel.passwordValidator) { password in - viewModel.backupActiveAccount(password: password) - } + passwordValidator: + viewModel.passwordValidator) { password in + viewModel.backupActiveAccount(password: password) + } } .presentationDetents([.medium, .large]) } @@ -75,8 +76,8 @@ public struct BackupActionsView: View { BackupActionsView(viewModel: BackupActionsViewModel( backupSource: MockBackupSource(), backupHandler: BackupHandler( - onSuccess: {_,_ in}, - onFailure: {_ in}), + onSuccess: { _, _ in }, + onFailure: { _ in }), passwordValidator: MockBackupPasswordValidator() )) } diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift index f0a0650863a..22addca868a 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift @@ -139,7 +139,7 @@ private struct ExportBackupPreview: View { NavigationStack { ExportBackupView( passwordValidator: MockBackupPasswordValidator(), - exportBackup: {_ in } + exportBackup: { _ in } ) } .presentationDragIndicator(.visible) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift index 9866d3adb8f..f0b7f4bd3a1 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift @@ -53,9 +53,7 @@ struct PasswordFieldView: View { HStack { Spacer() - Button(action: { - isPasswordVisible.toggle() - }) { + Button(action: { isPasswordVisible.toggle() }) { Image(systemName: isPasswordVisible ? "eye" : "eye.slash") .foregroundColor(.gray) } From f0bdfc7e362b1e8441810d2f483962cd6fdbed04 Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Tue, 7 Jan 2025 11:49:29 +0100 Subject: [PATCH 017/107] fix SwiftLint issues --- .../Account/Backup/Views/BackupActionsView.swift | 7 ++----- .../Account/Backup/Views/PasswordFieldView.swift | 6 ++++-- .../SettingsCellDescriptorFactory+Account.swift | 2 +- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift index fcdc0764dd7..db921dd50dd 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift @@ -38,14 +38,11 @@ public struct BackupActionsView: View { Button(action: { switch section.type { case .backup: - isRestoreSheetPresented = false isBackupSheetPresented.toggle() case .restore: - isBackupSheetPresented = false isRestoreSheetPresented.toggle() } - } - ) { + }, label: { HStack { section.type.title .font(.textStyle(.body2)) @@ -53,7 +50,7 @@ public struct BackupActionsView: View { Spacer() Image(systemName: "chevron.right").foregroundStyle(Color.primary) } - } + }) .sheet(isPresented: $isBackupSheetPresented) { NavigationStack { ExportBackupView( diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift index f0b7f4bd3a1..705752d14e2 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift @@ -53,10 +53,12 @@ struct PasswordFieldView: View { HStack { Spacer() - Button(action: { isPasswordVisible.toggle() }) { + Button(action: { + isPasswordVisible.toggle() + }, label: { Image(systemName: isPasswordVisible ? "eye" : "eye.slash") .foregroundColor(.gray) - } + }) .padding(.trailing, 10) } } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift index 3f47be22077..560c7834edc 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift @@ -20,8 +20,8 @@ import SwiftUI import WireCommonComponents import WireDataModel import WireDesign -import WireSyncEngine import WireSettingsUI +import WireSyncEngine extension ZMUser { var hasValidEmail: Bool { From def249538509f66c239f74c28ff0ddab6a3eeeec Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Tue, 7 Jan 2025 11:58:48 +0100 Subject: [PATCH 018/107] fix SwiftFormat issues --- .../Account/Backup/Views/BackupActionsView.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift index db921dd50dd..59575080906 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift @@ -54,10 +54,10 @@ public struct BackupActionsView: View { .sheet(isPresented: $isBackupSheetPresented) { NavigationStack { ExportBackupView( - passwordValidator: - viewModel.passwordValidator) { password in - viewModel.backupActiveAccount(password: password) - } + passwordValidator: viewModel.passwordValidator, + exportBackup: { password in + viewModel.backupActiveAccount(password: password) + }) } .presentationDetents([.medium, .large]) } @@ -81,7 +81,7 @@ public struct BackupActionsView: View { private class MockBackupSource: BackupSourceProtocol { func backupActiveAccount(password: String) throws -> URL { - return URL(fileURLWithPath: "path") + URL(fileURLWithPath: "path") } func clearPreviousBackups() {} From f98c390fc173cd77e11184441e6155df8a48851f Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Tue, 7 Jan 2025 13:32:59 +0100 Subject: [PATCH 019/107] fix SwiftFormat issues --- .../Account/Backup/ViewModels/BackupActionsViewModel.swift | 2 +- .../Account/Backup/Views/BackupActionsView.swift | 6 ++++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift index a91cff5739a..fc480c5df2a 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift @@ -34,7 +34,7 @@ public final class BackupActionsViewModel: ObservableObject { self.backupHandler = backupHandler self.passwordValidator = passwordValidator - sections = [ + self.sections = [ BackupActionsSection(type: .backup) ] } diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift index 59575080906..9673c950e00 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift @@ -57,7 +57,8 @@ public struct BackupActionsView: View { passwordValidator: viewModel.passwordValidator, exportBackup: { password in viewModel.backupActiveAccount(password: password) - }) + } + ) } .presentationDetents([.medium, .large]) } @@ -74,7 +75,8 @@ public struct BackupActionsView: View { backupSource: MockBackupSource(), backupHandler: BackupHandler( onSuccess: { _, _ in }, - onFailure: { _ in }), + onFailure: { _ in } + ), passwordValidator: MockBackupPasswordValidator() )) } From 52c51ec79c44ba523954bec4d5d1c8224f87b98a Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Tue, 7 Jan 2025 13:49:11 +0100 Subject: [PATCH 020/107] attempt to fix linker errors --- wire-ios/Wire-iOS.xcodeproj/project.pbxproj | 94 +++++++-------------- 1 file changed, 30 insertions(+), 64 deletions(-) diff --git a/wire-ios/Wire-iOS.xcodeproj/project.pbxproj b/wire-ios/Wire-iOS.xcodeproj/project.pbxproj index abb43bca2c5..f1fd38d48ef 100644 --- a/wire-ios/Wire-iOS.xcodeproj/project.pbxproj +++ b/wire-ios/Wire-iOS.xcodeproj/project.pbxproj @@ -314,18 +314,15 @@ 5902F8D72BF78FC200F1D392 /* ArchivedListViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5902F8D62BF78FC200F1D392 /* ArchivedListViewControllerDelegate.swift */; }; 5902F8E32BF7B18B00F1D392 /* ArchivedListViewControllerSnapshotTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5902F8DF2BF7B14F00F1D392 /* ArchivedListViewControllerSnapshotTests.swift */; }; 59076F952C934A9800AE7529 /* WireAccountImageUI in Frameworks */ = {isa = PBXBuildFile; productRef = 59076F942C934A9800AE7529 /* WireAccountImageUI */; }; - 59076F972C934AA100AE7529 /* WireAccountImageUI in Frameworks */ = {isa = PBXBuildFile; productRef = 59076F962C934AA100AE7529 /* WireAccountImageUI */; }; 590A5F092BF4CB6B008E87D8 /* PermissionDeniedViewController+notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 590A5F082BF4CB6B008E87D8 /* PermissionDeniedViewController+notifications.swift */; }; 590A5F0B2BF4CBCE008E87D8 /* PermissionDeniedViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 590A5F0A2BF4CBCE008E87D8 /* PermissionDeniedViewControllerDelegate.swift */; }; 590DCA082C971A56002D0A2C /* WireSidebarUI in Frameworks */ = {isa = PBXBuildFile; productRef = 590DCA072C971A56002D0A2C /* WireSidebarUI */; }; - 590DCA0A2C971AFF002D0A2C /* WireFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = 590DCA092C971AFF002D0A2C /* WireFoundation */; }; 5915B94B2BF4A70900215817 /* ShouldPresentNotificationPermissionHintUseCaseProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5915B94A2BF4A70900215817 /* ShouldPresentNotificationPermissionHintUseCaseProtocol.swift */; }; 5915B94D2BF4A76C00215817 /* ShouldPresentNotificationPermissionHintUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5915B94C2BF4A76C00215817 /* ShouldPresentNotificationPermissionHintUseCase.swift */; }; 5915B9562BF4B8AC00215817 /* ShouldPresentNotificationPermissionHintUseCaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5915B9552BF4B8AC00215817 /* ShouldPresentNotificationPermissionHintUseCaseTests.swift */; }; 5915B9582BF4BA9700215817 /* DidPresentNotificationPermissionHintUseCaseProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5915B9572BF4BA9700215817 /* DidPresentNotificationPermissionHintUseCaseProtocol.swift */; }; 5915B95A2BF4BAFF00215817 /* DidPresentNotificationPermissionHintUseCase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5915B9592BF4BAFE00215817 /* DidPresentNotificationPermissionHintUseCase.swift */; }; 5915B95C2BF4BB5300215817 /* NativelySupportedUserDefaultsKey+lastTimeNotificationPermissionHintWasShown.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5915B95B2BF4BB5300215817 /* NativelySupportedUserDefaultsKey+lastTimeNotificationPermissionHintWasShown.swift */; }; - 59191A652D0051C7001AB388 /* WireLogging in Frameworks */ = {isa = PBXBuildFile; productRef = 59191A642D0051C7001AB388 /* WireLogging */; }; 591B6E172C8B095B009F8A7B /* WireNotificationEngine.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE9A8585298B0A3B00064A9C /* WireNotificationEngine.framework */; }; 591B6E182C8B095B009F8A7B /* WireNotificationEngine.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = EE9A8585298B0A3B00064A9C /* WireNotificationEngine.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 591B6E192C8B0960009F8A7B /* WireCommonComponents.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F1FEA14A21DCEB1700790A54 /* WireCommonComponents.framework */; }; @@ -396,17 +393,17 @@ 59AADE272BB429B200D9E658 /* WireRequestStrategySupport.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 59AADE262BB429B200D9E658 /* WireRequestStrategySupport.framework */; }; 59AADE282BB429D300D9E658 /* AutoMockable.generated.swift in Sources */ = {isa = PBXBuildFile; fileRef = E644B79A2B7CBBA3005D0BFD /* AutoMockable.generated.swift */; }; 59AF77A12CC7FB3B002438D1 /* AnyMainCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59AF77A02CC7FB39002438D1 /* AnyMainCoordinator.swift */; }; - 59AF77A32CC7FF89002438D1 /* WireMainNavigationUI in Frameworks */ = {isa = PBXBuildFile; productRef = 59AF77A22CC7FF89002438D1 /* WireMainNavigationUI */; }; 59B404512CAA937400CC33BF /* SettingsContent+MainSettingsContentRepresentable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59B404502CAA936E00CC33BF /* SettingsContent+MainSettingsContentRepresentable.swift */; }; 59B404652CAAC3AC00CC33BF /* SettingsViewControllerBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59B404642CAAC3A700CC33BF /* SettingsViewControllerBuilder.swift */; }; 59B404672CAB05CA00CC33BF /* SettingsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59B404662CAB05C900CC33BF /* SettingsCoordinator.swift */; }; 59B404692CAB13D400CC33BF /* MockSettingsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59B404682CAB13CF00CC33BF /* MockSettingsCoordinator.swift */; }; 59B4046A2CAB13D400CC33BF /* MockSettingsCoordinator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59B404682CAB13CF00CC33BF /* MockSettingsCoordinator.swift */; }; - 59B4046C2CAB140A00CC33BF /* WireSettingsUI in Frameworks */ = {isa = PBXBuildFile; productRef = 59B4046B2CAB140A00CC33BF /* WireSettingsUI */; }; - 59B4046E2CAB141000CC33BF /* WireSettingsUI in Frameworks */ = {isa = PBXBuildFile; productRef = 59B4046D2CAB141000CC33BF /* WireSettingsUI */; }; - 59B48C5E2C89CCAB00EA7999 /* WireFoundationSupport in Frameworks */ = {isa = PBXBuildFile; productRef = 59B48C5D2C89CCAB00EA7999 /* WireFoundationSupport */; }; - 59B48C602C89CCCC00EA7999 /* WireFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = 59B48C5F2C89CCCC00EA7999 /* WireFoundation */; }; - 59B48C622C89CD3D00EA7999 /* WireTestingPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 59B48C612C89CD3D00EA7999 /* WireTestingPackage */; }; + 59B768412D2D586E007B5F1E /* WireTestingPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 59B768402D2D586E007B5F1E /* WireTestingPackage */; }; + 59B768432D2D58BE007B5F1E /* WireFoundationSupport in Frameworks */ = {isa = PBXBuildFile; productRef = 59B768422D2D58BE007B5F1E /* WireFoundationSupport */; }; + 59B768452D2D58EF007B5F1E /* WireTestingPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 59B768442D2D58EF007B5F1E /* WireTestingPackage */; }; + 59B768472D2D59E3007B5F1E /* WireLoggingSupport in Frameworks */ = {isa = PBXBuildFile; productRef = 59B768462D2D59E3007B5F1E /* WireLoggingSupport */; }; + 59B768492D2D59E8007B5F1E /* WireLoggingSupport in Frameworks */ = {isa = PBXBuildFile; productRef = 59B768482D2D59E8007B5F1E /* WireLoggingSupport */; }; + 59B7684B2D2D5A17007B5F1E /* WireFoundationSupport in Frameworks */ = {isa = PBXBuildFile; productRef = 59B7684A2D2D5A17007B5F1E /* WireFoundationSupport */; }; 59B99FAA2C89DE8600201827 /* WireFoundation in Frameworks */ = {isa = PBXBuildFile; productRef = 59B99FA92C89DE8600201827 /* WireFoundation */; }; 59B99FAC2C89DF2100201827 /* WireAPI in Frameworks */ = {isa = PBXBuildFile; productRef = 59B99FAB2C89DF2100201827 /* WireAPI */; }; 59B99FB02C89DFB700201827 /* WireDomainPackage in Frameworks */ = {isa = PBXBuildFile; productRef = 59B99FAF2C89DFB700201827 /* WireDomainPackage */; }; @@ -415,7 +412,6 @@ 59BFBFC92CB7E0F7005C3375 /* SidebarViewControllerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59BFBFC72CB7E0F7005C3375 /* SidebarViewControllerDelegate.swift */; }; 59BFBFCA2CB7E0F7005C3375 /* SidebarAccountInfo+initWithUserSessionAndAccount.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59BFBFC82CB7E0F7005C3375 /* SidebarAccountInfo+initWithUserSessionAndAccount.swift */; }; 59BFBFCE2CB7E25B005C3375 /* ConversationListViewController+NavigationBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 59BFBFCD2CB7E25B005C3375 /* ConversationListViewController+NavigationBar.swift */; }; - 59C2F09F2CA54E4900B25E5D /* WireMainNavigationUI in Frameworks */ = {isa = PBXBuildFile; productRef = 59C2F09E2CA54E4900B25E5D /* WireMainNavigationUI */; }; 59C4FBF12C45B7130037030B /* WireShareEngine.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = EE33C48C296485FA00C058D1 /* WireShareEngine.framework */; }; 59CD83E52C22CE9800186022 /* WireDesign in Frameworks */ = {isa = PBXBuildFile; productRef = 59CD83E42C22CE9800186022 /* WireDesign */; }; 59CDB3F62C4EA08F0049D1AB /* WireReusableUIComponents in Frameworks */ = {isa = PBXBuildFile; productRef = 59CDB3F52C4EA08F0049D1AB /* WireReusableUIComponents */; }; @@ -1363,7 +1359,6 @@ E97661812BDA4D1E0033AACC /* SecureLinkHeaderCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = E97661802BDA4D1E0033AACC /* SecureLinkHeaderCell.swift */; }; E9816C902CC9244700D77F22 /* WireSyncEngine.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = E9816C8F2CC9244700D77F22 /* WireSyncEngine.framework */; }; E9816C962CC9281000D77F22 /* LaunchScreenViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = E9816C952CC9280400D77F22 /* LaunchScreenViewController.swift */; }; - E9816C9A2CC929FC00D77F22 /* WireFoundationSupport in Frameworks */ = {isa = PBXBuildFile; productRef = E9816C992CC929FC00D77F22 /* WireFoundationSupport */; }; E985CB8F2CEB4FCB0075DAD6 /* WireDatadog in Frameworks */ = {isa = PBXBuildFile; productRef = 016A141C2CE6BFC4006A7EF5 /* WireDatadog */; }; E989695728EC430B0088F0CE /* SecondaryButtonDescription.swift in Sources */ = {isa = PBXBuildFile; fileRef = E989695628EC430B0088F0CE /* SecondaryButtonDescription.swift */; }; E98B61102B5820BD0030E021 /* SwiftMockConversation+ConversationCreation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E98B610F2B5820BD0030E021 /* SwiftMockConversation+ConversationCreation.swift */; }; @@ -3893,21 +3888,16 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 59B48C622C89CD3D00EA7999 /* WireTestingPackage in Frameworks */, - 59076F972C934AA100AE7529 /* WireAccountImageUI in Frameworks */, - 59B4046E2CAB141000CC33BF /* WireSettingsUI in Frameworks */, + 59B768412D2D586E007B5F1E /* WireTestingPackage in Frameworks */, 01C1A7C72A54C45A0058D578 /* SnapshotTesting in Frameworks */, CB4870F22C7F4FE5001E9151 /* WireTransportSupport.framework in Frameworks */, + 59B7684B2D2D5A17007B5F1E /* WireFoundationSupport in Frameworks */, + 59B768492D2D59E8007B5F1E /* WireLoggingSupport in Frameworks */, 5996E8A82C19D09D007A52F0 /* WireSyncEngineSupport.framework in Frameworks */, 5996E8A42C19D074007A52F0 /* WireRequestStrategySupport.framework in Frameworks */, 598E86F72BF4DD5C00FC5438 /* WireSystemSupport.framework in Frameworks */, 598E86D12BF4D97800FC5438 /* WireUtilitiesSupport.framework in Frameworks */, - 59191A652D0051C7001AB388 /* WireLogging in Frameworks */, - E9816C9A2CC929FC00D77F22 /* WireFoundationSupport in Frameworks */, - 590DCA0A2C971AFF002D0A2C /* WireFoundation in Frameworks */, 591B6E1C2C8B0964009F8A7B /* WireDataModelSupport.framework in Frameworks */, - 59C2F09F2CA54E4900B25E5D /* WireMainNavigationUI in Frameworks */, - 59B48C5E2C89CCAB00EA7999 /* WireFoundationSupport in Frameworks */, 5996E8AD2C19D0DF007A52F0 /* WireTesting.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -3918,11 +3908,11 @@ files = ( 59AADE272BB429B200D9E658 /* WireRequestStrategySupport.framework in Frameworks */, 5996E8AA2C19D0D6007A52F0 /* WireSystemSupport.framework in Frameworks */, - 59B4046C2CAB140A00CC33BF /* WireSettingsUI in Frameworks */, - 59B48C602C89CCCC00EA7999 /* WireFoundation in Frameworks */, + 59B768452D2D58EF007B5F1E /* WireTestingPackage in Frameworks */, 0145AE992B1156FC0097E3B8 /* WireSyncEngineSupport.framework in Frameworks */, E6C25FC52AFFAAC300406A1C /* WireTesting.framework in Frameworks */, - 59AF77A32CC7FF89002438D1 /* WireMainNavigationUI in Frameworks */, + 59B768472D2D59E3007B5F1E /* WireLoggingSupport in Frameworks */, + 59B768432D2D58BE007B5F1E /* WireFoundationSupport in Frameworks */, 591B6E242C8B0970009F8A7B /* WireDataModelSupport.framework in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; @@ -8453,13 +8443,9 @@ name = "Wire-iOS-Tests"; packageProductDependencies = ( 01C1A7C62A54C45A0058D578 /* SnapshotTesting */, - 59B48C612C89CD3D00EA7999 /* WireTestingPackage */, - 59076F962C934AA100AE7529 /* WireAccountImageUI */, - 590DCA092C971AFF002D0A2C /* WireFoundation */, - E9816C992CC929FC00D77F22 /* WireFoundationSupport */, - 59C2F09E2CA54E4900B25E5D /* WireMainNavigationUI */, - 59B4046D2CAB141000CC33BF /* WireSettingsUI */, - 59191A642D0051C7001AB388 /* WireLogging */, + 59B768402D2D586E007B5F1E /* WireTestingPackage */, + 59B768482D2D59E8007B5F1E /* WireLoggingSupport */, + 59B7684A2D2D5A17007B5F1E /* WireFoundationSupport */, ); productName = "ZClient-iOS Tests"; productReference = BACB88511AF7C48900DDCDB0 /* Wire-iOS-Tests.xctest */; @@ -8480,9 +8466,9 @@ ); name = "Wire-iOS UnitTests"; packageProductDependencies = ( - 59B48C5F2C89CCCC00EA7999 /* WireFoundation */, - 59B4046B2CAB140A00CC33BF /* WireSettingsUI */, - 59AF77A22CC7FF89002438D1 /* WireMainNavigationUI */, + 59B768422D2D58BE007B5F1E /* WireFoundationSupport */, + 59B768442D2D58EF007B5F1E /* WireTestingPackage */, + 59B768462D2D59E3007B5F1E /* WireLoggingSupport */, ); productName = "Wire-iOS UnitTests"; productReference = E6579E362AFF9B30004E7FD8 /* Wire-iOS UnitTests.xctest */; @@ -11391,22 +11377,10 @@ isa = XCSwiftPackageProductDependency; productName = WireAccountImageUI; }; - 59076F962C934AA100AE7529 /* WireAccountImageUI */ = { - isa = XCSwiftPackageProductDependency; - productName = WireAccountImageUI; - }; 590DCA072C971A56002D0A2C /* WireSidebarUI */ = { isa = XCSwiftPackageProductDependency; productName = WireSidebarUI; }; - 590DCA092C971AFF002D0A2C /* WireFoundation */ = { - isa = XCSwiftPackageProductDependency; - productName = WireFoundation; - }; - 59191A642D0051C7001AB388 /* WireLogging */ = { - isa = XCSwiftPackageProductDependency; - productName = WireLogging; - }; 5929CB212CA6C9C800070488 /* WireConversationListUI */ = { isa = XCSwiftPackageProductDependency; productName = WireConversationListUI; @@ -11447,29 +11421,29 @@ isa = XCSwiftPackageProductDependency; productName = WireSettingsUI; }; - 59AF77A22CC7FF89002438D1 /* WireMainNavigationUI */ = { + 59B768402D2D586E007B5F1E /* WireTestingPackage */ = { isa = XCSwiftPackageProductDependency; - productName = WireMainNavigationUI; + productName = WireTestingPackage; }; - 59B4046B2CAB140A00CC33BF /* WireSettingsUI */ = { + 59B768422D2D58BE007B5F1E /* WireFoundationSupport */ = { isa = XCSwiftPackageProductDependency; - productName = WireSettingsUI; + productName = WireFoundationSupport; }; - 59B4046D2CAB141000CC33BF /* WireSettingsUI */ = { + 59B768442D2D58EF007B5F1E /* WireTestingPackage */ = { isa = XCSwiftPackageProductDependency; - productName = WireSettingsUI; + productName = WireTestingPackage; }; - 59B48C5D2C89CCAB00EA7999 /* WireFoundationSupport */ = { + 59B768462D2D59E3007B5F1E /* WireLoggingSupport */ = { isa = XCSwiftPackageProductDependency; - productName = WireFoundationSupport; + productName = WireLoggingSupport; }; - 59B48C5F2C89CCCC00EA7999 /* WireFoundation */ = { + 59B768482D2D59E8007B5F1E /* WireLoggingSupport */ = { isa = XCSwiftPackageProductDependency; - productName = WireFoundation; + productName = WireLoggingSupport; }; - 59B48C612C89CD3D00EA7999 /* WireTestingPackage */ = { + 59B7684A2D2D5A17007B5F1E /* WireFoundationSupport */ = { isa = XCSwiftPackageProductDependency; - productName = WireTestingPackage; + productName = WireFoundationSupport; }; 59B99FA92C89DE8600201827 /* WireFoundation */ = { isa = XCSwiftPackageProductDependency; @@ -11483,10 +11457,6 @@ isa = XCSwiftPackageProductDependency; productName = WireDomainPackage; }; - 59C2F09E2CA54E4900B25E5D /* WireMainNavigationUI */ = { - isa = XCSwiftPackageProductDependency; - productName = WireMainNavigationUI; - }; 59CD83E42C22CE9800186022 /* WireDesign */ = { isa = XCSwiftPackageProductDependency; productName = WireDesign; @@ -11512,10 +11482,6 @@ package = CB4E15102C81CC81005DDEC8 /* XCRemoteSwiftPackageReference "Down" */; productName = Down; }; - E9816C992CC929FC00D77F22 /* WireFoundationSupport */ = { - isa = XCSwiftPackageProductDependency; - productName = WireFoundationSupport; - }; E9BA75C52CD51DF100F6EDDF /* WireMoveToFolderUI */ = { isa = XCSwiftPackageProductDependency; productName = WireMoveToFolderUI; From 76282127b8fcdc6c3093a3bc093b22f7f4bdd74e Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Tue, 7 Jan 2025 14:21:39 +0100 Subject: [PATCH 021/107] remove BackupPasswordViewControllerTests and BackupViewControllerTests --- .../BackupPasswordViewControllerTests.swift | 103 ------------------ .../BackupViewControllerTests.swift | 55 ---------- wire-ios/Wire-iOS.xcodeproj/project.pbxproj | 8 -- 3 files changed, 166 deletions(-) delete mode 100644 wire-ios/Wire-iOS Tests/BackupPasswordViewControllerTests.swift delete mode 100644 wire-ios/Wire-iOS Tests/BackupViewControllerTests.swift diff --git a/wire-ios/Wire-iOS Tests/BackupPasswordViewControllerTests.swift b/wire-ios/Wire-iOS Tests/BackupPasswordViewControllerTests.swift deleted file mode 100644 index 4df2d1cb4ab..00000000000 --- a/wire-ios/Wire-iOS Tests/BackupPasswordViewControllerTests.swift +++ /dev/null @@ -1,103 +0,0 @@ -// -// Wire -// Copyright (C) 2025 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import WireTestingPackage -import XCTest - -@testable import Wire - -final class BackupPasswordViewControllerTests: XCTestCase { - - private var snapshotHelper: SnapshotHelper! - - override func setUp() { - super.setUp() - snapshotHelper = SnapshotHelper() - } - - override func tearDown() { - snapshotHelper = nil - super.tearDown() - } - - func testDefaultState() { - // GIVEN - let sut = makeViewController() - - // WHEN & THEN - snapshotHelper.verify(matching: sut.view) - } - - func testThatItCallsTheCallback() { - // GIVEN - let validPassword = "Password123!" - let expectation = expectation(description: "Callback called") - let sut = makeViewController() - sut.onCompletion = { password in - XCTAssertEqual(password, validPassword) - expectation.fulfill() - } - - // WHEN - XCTAssertTrue( - sut.textField( - UITextField(), - shouldChangeCharactersIn: NSRange(location: 0, length: 0), - replacementString: validPassword - ) - ) - XCTAssertFalse( - sut.textField( - UITextField(), - shouldChangeCharactersIn: NSRange(location: 0, length: 0), - replacementString: "\n" - ) - ) - - // THEN - waitForExpectations(timeout: 2) { error in - XCTAssertNil(error) - } - } - - func testThatWhitespacesPasswordIsNotGood() { - // GIVEN - let sut = makeViewController() - sut.onCompletion = { _ in - XCTFail("Sut is nil") - } - - // WHEN - XCTAssertFalse(sut.textField( - UITextField(), - shouldChangeCharactersIn: NSRange(location: 0, length: 0), - replacementString: " " - )) - XCTAssertFalse(sut.textField( - UITextField(), - shouldChangeCharactersIn: NSRange(location: 0, length: 0), - replacementString: "\n" - )) - } - - // MARK: - Helpers - - private func makeViewController() -> BackupPasswordViewController { - BackupPasswordViewController() - } -} diff --git a/wire-ios/Wire-iOS Tests/BackupViewControllerTests.swift b/wire-ios/Wire-iOS Tests/BackupViewControllerTests.swift deleted file mode 100644 index 1e1f71484b1..00000000000 --- a/wire-ios/Wire-iOS Tests/BackupViewControllerTests.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// Wire -// Copyright (C) 2025 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import WireDesign -import WireTestingPackage -import XCTest - -@testable import Wire - -final class BackupViewControllerTests: XCTestCase { - - private var snapshotHelper: SnapshotHelper! - - override func setUp() { - super.setUp() - snapshotHelper = SnapshotHelper() - } - - override func tearDown() { - snapshotHelper = nil - super.tearDown() - } - - func testInitialState() { - // GIVEN - let sut = makeViewController() - - // WHEN && THEN - snapshotHelper.verify(matching: sut.view) - } - - // MARK: Helpers - - private func makeViewController() -> BackupViewController { - let backupSource = MockBackupSource() - let vc = BackupViewController(backupSource: backupSource) - vc.view.backgroundColor = SemanticColors.View.backgroundDefault - return vc - } -} diff --git a/wire-ios/Wire-iOS.xcodeproj/project.pbxproj b/wire-ios/Wire-iOS.xcodeproj/project.pbxproj index f1fd38d48ef..e71fb7021c8 100644 --- a/wire-ios/Wire-iOS.xcodeproj/project.pbxproj +++ b/wire-ios/Wire-iOS.xcodeproj/project.pbxproj @@ -836,7 +836,6 @@ 87AC33CD2182064F00069C79 /* ReplyComposingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87AC33CC2182064F00069C79 /* ReplyComposingView.swift */; }; 87AC77DB1DE48C01009F6D56 /* Copyable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87AC77DA1DE48C01009F6D56 /* Copyable.swift */; }; 87AC9F561C0749FB00E1ED6F /* TailEditingTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87AC9F551C0749FB00E1ED6F /* TailEditingTextField.swift */; }; - 87AE8BDC207B99540058715E /* BackupPasswordViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87AE8BDB207B99540058715E /* BackupPasswordViewControllerTests.swift */; }; 87B8C33F1DF9788B0015EC89 /* BrowserOpening.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87B8C33B1DF9788B0015EC89 /* BrowserOpening.swift */; }; 87B8C3401DF9788B0015EC89 /* LinkOpener.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87B8C33C1DF9788B0015EC89 /* LinkOpener.swift */; }; 87B8C3411DF9788B0015EC89 /* MapOpening.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87B8C33D1DF9788B0015EC89 /* MapOpening.swift */; }; @@ -850,7 +849,6 @@ 87BEB0E01C734DA60094BFE9 /* MockLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 87BEB0DF1C734DA60094BFE9 /* MockLoader.m */; }; 87BEB0E21C734DB60094BFE9 /* MockMessage.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87BEB0E11C734DB60094BFE9 /* MockMessage.swift */; }; 87C39A8A206947A1008DA100 /* UIView+Constraints.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87C39A89206947A1008DA100 /* UIView+Constraints.swift */; }; - 87C39A90206BF00A008DA100 /* BackupViewControllerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87C39A8F206BF00A008DA100 /* BackupViewControllerTests.swift */; }; 87C539561E8E516400084F94 /* BoundsAwareFlowLayout.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87C539551E8E516400084F94 /* BoundsAwareFlowLayout.swift */; }; 87CA886E1DDF0075004101B6 /* UserConnectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87CA886D1DDF0075004101B6 /* UserConnectionViewController.swift */; }; 87D21AD71D8A98620075AB7A /* AccentColorPickerController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 87D21AD61D8A98620075AB7A /* AccentColorPickerController.swift */; }; @@ -2787,7 +2785,6 @@ 87AC33CC2182064F00069C79 /* ReplyComposingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ReplyComposingView.swift; sourceTree = ""; }; 87AC77DA1DE48C01009F6D56 /* Copyable.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Copyable.swift; sourceTree = ""; }; 87AC9F551C0749FB00E1ED6F /* TailEditingTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TailEditingTextField.swift; sourceTree = ""; }; - 87AE8BDB207B99540058715E /* BackupPasswordViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupPasswordViewControllerTests.swift; sourceTree = ""; }; 87B8C33B1DF9788B0015EC89 /* BrowserOpening.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BrowserOpening.swift; sourceTree = ""; }; 87B8C33C1DF9788B0015EC89 /* LinkOpener.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LinkOpener.swift; sourceTree = ""; }; 87B8C33D1DF9788B0015EC89 /* MapOpening.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MapOpening.swift; sourceTree = ""; }; @@ -2804,7 +2801,6 @@ 87BEB0DF1C734DA60094BFE9 /* MockLoader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = MockLoader.m; sourceTree = ""; }; 87BEB0E11C734DB60094BFE9 /* MockMessage.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MockMessage.swift; sourceTree = ""; }; 87C39A89206947A1008DA100 /* UIView+Constraints.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Constraints.swift"; sourceTree = ""; }; - 87C39A8F206BF00A008DA100 /* BackupViewControllerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackupViewControllerTests.swift; sourceTree = ""; }; 87C539551E8E516400084F94 /* BoundsAwareFlowLayout.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BoundsAwareFlowLayout.swift; sourceTree = ""; }; 87C8D7FB1BA8261C00B0530B /* Entitlements-Dev.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = "Entitlements-Dev.entitlements"; path = "Wire-iOS/Entitlements-Dev.entitlements"; sourceTree = ""; }; 87C8D7FC1BA8261C00B0530B /* Entitlements-Prod.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; name = "Entitlements-Prod.entitlements"; path = "Wire-iOS/Entitlements-Prod.entitlements"; sourceTree = ""; }; @@ -6893,8 +6889,6 @@ 8732670C1D2D0C49005A62C1 /* AudioRecordKeyboardViewControllerTests.swift */, BFAF4CAE1CEB791B00780537 /* AudioRecordViewControllerTests.swift */, 7C6A69041FDFD80E007EBE41 /* AvailabilityLabelTests.swift */, - 87AE8BDB207B99540058715E /* BackupPasswordViewControllerTests.swift */, - 87C39A8F206BF00A008DA100 /* BackupViewControllerTests.swift */, 543F26CC2562AC2F0097F5EB /* CallControllerTests.swift */, 5EE115E0204EEC7D002AD6C2 /* CallQualityControllerTests.swift */, BFE3FEEB20975D1E003D9AB5 /* CallStatusViewTests.swift */, @@ -10072,7 +10066,6 @@ BF16FC4C1DAFCA0000FF4325 /* EphemeralKeyboardViewControllerTests.swift in Sources */, D30E113C2A98CE3000D8C62D /* EmoliRepositoryTests.swift in Sources */, 63F239352694A151000BFFC6 /* Array+Prefix.swift in Sources */, - 87AE8BDC207B99540058715E /* BackupPasswordViewControllerTests.swift in Sources */, EE52378423F5567300D4FE79 /* MockUserType+Creation.swift in Sources */, A90800752372F0F600A530FC /* ConversationListHeaderViewTests.swift in Sources */, EF1FDC7E21DE255C00C9CEB1 /* NSString+EmoticonSubstitutionTests.swift in Sources */, @@ -10232,7 +10225,6 @@ F1F5E57C216CA6D4006BF3D5 /* ConversationStatusTests+Icon.swift in Sources */, EF5741EE2102001A0041AD47 /* MessageTests.swift in Sources */, 5EF7BA61221AB89300815625 /* ProfileViewTests.swift in Sources */, - 87C39A90206BF00A008DA100 /* BackupViewControllerTests.swift in Sources */, EF2A8DE7214A816D002C9058 /* StartUIViewControllerSnapshotTests.swift in Sources */, 164B533C20A0A5E800EC8265 /* CallInfoConfigurationTests.swift in Sources */, 5950467D2C6F3DA9005315DE /* NetworkStatusViewSnapshotTests.swift in Sources */, From e2dfc6f003a140bda9fedd0105d6a162076b634f Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Tue, 7 Jan 2025 16:26:34 +0100 Subject: [PATCH 022/107] fix dada model tests --- .../CoreDataStack+Backup.swift | 82 ++++++++++--------- .../CoreDataStackTests+Backup.swift | 63 ++++++-------- 2 files changed, 67 insertions(+), 78 deletions(-) diff --git a/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack+Backup.swift b/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack+Backup.swift index 511ccaa2d9b..66311cd99d1 100644 --- a/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack+Backup.swift +++ b/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack+Backup.swift @@ -110,43 +110,47 @@ public extension CoreDataStack { let metadataURL = backupDirectory.appendingPathComponent(metadataFilename) return try workQueue.sync { - let model = CoreDataStack.loadMessagingModel() - let coordinator = NSPersistentStoreCoordinator(managedObjectModel: model) - - // Create target directory - try fileManager.createDirectory( - at: databaseDirectory, - withIntermediateDirectories: true, - attributes: nil - ) - let backupLocation = databaseDirectory.appendingStoreFile() - let options = NSPersistentStoreCoordinator.persistentStoreOptions(supportsMigration: false) - - // Recreate the persistent store inside a new location - WireLogger.localStorage.debug("backup: Recreate the persistent store inside a new location") - try coordinator.replacePersistentStore( - at: backupLocation, - destinationOptions: options, - withPersistentStoreFrom: storeFile, - sourceOptions: options, - ofType: NSSQLiteStoreType - ) - - WireLogger.localStorage.debug("backup: prepareStoreForBackupExport") - try prepareStoreForBackupExport( - coordinator: coordinator, - location: backupLocation, - options: options, - databaseKey: databaseKey - ) - - // Create & write metadata - WireLogger.localStorage.debug("backup: Create & write metadata") - let metadata = BackupMetadata(userIdentifier: accountIdentifier, clientIdentifier: clientIdentifier) - try metadata.write(to: metadataURL) - log.info("successfully created backup at: \(backupDirectory.path), metadata: \(metadata)") - - return BackupInfo(url: backupDirectory, metadata: metadata) + do { + let model = CoreDataStack.loadMessagingModel() + let coordinator = NSPersistentStoreCoordinator(managedObjectModel: model) + + // Create target directory + try fileManager.createDirectory( + at: databaseDirectory, + withIntermediateDirectories: true, + attributes: nil + ) + let backupLocation = databaseDirectory.appendingStoreFile() + let options = NSPersistentStoreCoordinator.persistentStoreOptions(supportsMigration: false) + + // Recreate the persistent store inside a new location + WireLogger.localStorage.debug("backup: Recreate the persistent store inside a new location") + try coordinator.replacePersistentStore( + at: backupLocation, + destinationOptions: options, + withPersistentStoreFrom: storeFile, + sourceOptions: options, + ofType: NSSQLiteStoreType + ) + + WireLogger.localStorage.debug("backup: prepareStoreForBackupExport") + try prepareStoreForBackupExport( + coordinator: coordinator, + location: backupLocation, + options: options, + databaseKey: databaseKey + ) + + // Create & write metadata + WireLogger.localStorage.debug("backup: Create & write metadata") + let metadata = BackupMetadata(userIdentifier: accountIdentifier, clientIdentifier: clientIdentifier) + try metadata.write(to: metadataURL) + log.info("successfully created backup at: \(backupDirectory.path), metadata: \(metadata)") + + return BackupInfo(url: backupDirectory, metadata: metadata) + } catch { + throw BackupError.failedToWrite(error) + } } } @@ -307,7 +311,9 @@ public extension CoreDataStack { try context.performGroupedAndWait { if context.encryptMessagesAtRest { - guard let databaseKey else { throw BackupError.missingEAREncryptionKey } + guard let databaseKey else { + throw BackupError.missingEAREncryptionKey + } try context.migrateAwayFromEncryptionAtRest(databaseKey: databaseKey) context.encryptMessagesAtRest = false _ = context.makeMetadataPersistent() diff --git a/wire-ios-data-model/Tests/Source/ManagedObjectContext/CoreDataStackTests+Backup.swift b/wire-ios-data-model/Tests/Source/ManagedObjectContext/CoreDataStackTests+Backup.swift index 296bfea4d39..dbd5d2101db 100644 --- a/wire-ios-data-model/Tests/Source/ManagedObjectContext/CoreDataStackTests+Backup.swift +++ b/wire-ios-data-model/Tests/Source/ManagedObjectContext/CoreDataStackTests+Backup.swift @@ -52,21 +52,14 @@ final class CoreDataStackTests_Backup: DatabaseBaseTest { databaseKey: VolatileData? = nil, file: StaticString = #filePath, line: UInt = #line - ) -> Result { - var result: Result? - - CoreDataStack.backupLocalStorage( + ) throws -> CoreDataStack.BackupInfo { + try CoreDataStack.backupLocalStorage( accountIdentifier: accountIdentifier, clientIdentifier: name, applicationContainer: DatabaseBaseTest.applicationContainer, dispatchGroup: dispatchGroup, databaseKey: databaseKey - ) { - result = $0.map(\.url) - } - XCTAssert(waitForAllGroupsToBeEmpty(withTimeout: 1), file: file, line: line) - - return result ?? .failure(CoreDataStackTests.timedOut) + ) } func importBackup( @@ -105,8 +98,8 @@ final class CoreDataStackTests_Backup: DatabaseBaseTest { _ = ZMConversation.insertGroupConversation(moc: directory.viewContext, participants: [ZMUser]()) directory.viewContext.saveOrRollback() - let backup = createBackup(accountIdentifier: accountIdentifier) - return try backup.get() + let backup = try createBackup(accountIdentifier: accountIdentifier) + return backup.url } // MARK: - Export @@ -115,11 +108,8 @@ final class CoreDataStackTests_Backup: DatabaseBaseTest { // given _ = createStorageStackAndWaitForCompletion(userID: UUID()) - // when - let result = createBackup(accountIdentifier: UUID()) - - // then - XCTAssertThrowsError(try result.get()) { error in + // when / then + XCTAssertThrowsError(try createBackup(accountIdentifier: UUID())) { error in switch error as? CoreDataStack.BackupError { case .failedToRead: break default: XCTFail("unexpected error type") @@ -133,10 +123,10 @@ final class CoreDataStackTests_Backup: DatabaseBaseTest { _ = createStorageStackAndWaitForCompletion(userID: uuid) // when - let result = createBackup(accountIdentifier: uuid) + let result = try createBackup(accountIdentifier: uuid) // then - let url = try result.get() + let url = result.url let fm = FileManager.default XCTAssertTrue(fm.fileExists(atPath: url.path)) @@ -156,14 +146,12 @@ final class CoreDataStackTests_Backup: DatabaseBaseTest { // create empty file where backup needs to be saved to try Data().write(to: CoreDataStack.backupsDirectory) - // when - let result = createBackup(accountIdentifier: uuid) - - guard case let .failure(error) = result else { return XCTFail() } - - switch error as? CoreDataStack.BackupError { - case .failedToWrite?: break - default: XCTFail("unexpected error type") + // when / then + XCTAssertThrowsError(try createBackup(accountIdentifier: uuid)) { error in + switch error as? CoreDataStack.BackupError { + case .failedToWrite: break + default: XCTFail("unexpected error type") + } } } @@ -177,11 +165,11 @@ final class CoreDataStackTests_Backup: DatabaseBaseTest { directory.viewContext.saveOrRollback() // when - let result = createBackup(accountIdentifier: uuid, databaseKey: directory.viewContext.databaseKey) + let result = try createBackup(accountIdentifier: uuid, databaseKey: directory.viewContext.databaseKey) directory.viewContext.saveOrRollback() // then - let backup = try result.get() + let backup = result.url let model = CoreDataStack.loadMessagingModel() let coordinator = NSPersistentStoreCoordinator(managedObjectModel: model) let storeFile = backup.appendingPathComponent("data").appendingStoreFile() @@ -200,11 +188,8 @@ final class CoreDataStackTests_Backup: DatabaseBaseTest { directory.viewContext.databaseKey = nil directory.viewContext.saveOrRollback() - // when - let result = createBackup(accountIdentifier: uuid, databaseKey: nil) - - // then - XCTAssertThrowsError(try result.get()) { error in + // when then + XCTAssertThrowsError(try createBackup(accountIdentifier: uuid, databaseKey: nil)) { error in switch error as? CoreDataStack.BackupError { case let .failedToWrite(failureError): @@ -223,7 +208,7 @@ final class CoreDataStackTests_Backup: DatabaseBaseTest { } } - func testThatItPreservesOriginalDataAfterBackup() { + func testThatItPreservesOriginalDataAfterBackup() throws { // given let uuid = UUID() let directory = createStorageStackAndWaitForCompletion(userID: uuid) @@ -231,10 +216,9 @@ final class CoreDataStackTests_Backup: DatabaseBaseTest { directory.viewContext.saveOrRollback() // when - let result = createBackup(accountIdentifier: uuid) + let _ = try createBackup(accountIdentifier: uuid) // then - guard case .success = result else { return XCTFail() } let fetchConversations = ZMConversation.sortedFetchRequest() XCTAssertEqual(try directory.viewContext.count(for: fetchConversations), 1) } @@ -247,10 +231,9 @@ final class CoreDataStackTests_Backup: DatabaseBaseTest { directory.viewContext.saveOrRollback() // when - let result = createBackup(accountIdentifier: uuid) + let _ = try createBackup(accountIdentifier: uuid) // then - guard case .success = result else { return XCTFail() } let anotherDirectory = createStorageStackAndWaitForCompletion(userID: uuid) let fetchConversations = ZMConversation.sortedFetchRequest() XCTAssertEqual(try anotherDirectory.viewContext.count(for: fetchConversations), 1) @@ -292,7 +275,7 @@ final class CoreDataStackTests_Backup: DatabaseBaseTest { ) directory.viewContext.forceSaveOrRollback() - let backup = try createBackup(accountIdentifier: uuid).get() + let backup = try createBackup(accountIdentifier: uuid).url // Delete account clearStorageFolder() From 5eaaa40b1f745a4ac1a2c554b773d6885f92595d Mon Sep 17 00:00:00 2001 From: KaterinaWire <57407805+KaterinaWire@users.noreply.github.com> Date: Tue, 7 Jan 2025 16:27:21 +0100 Subject: [PATCH 023/107] Update WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/BackupPasswordValidator.swift Co-authored-by: Christoph Aldrian --- .../Account/Backup/Protocols/BackupPasswordValidator.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/BackupPasswordValidator.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/BackupPasswordValidator.swift index fe5397fe9ac..311fb67560a 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/BackupPasswordValidator.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/BackupPasswordValidator.swift @@ -18,7 +18,7 @@ public protocol BackupPasswordValidatorProtocol { - func isValid(password: String) -> Bool + func isPasswordValid(_ password: String) -> Bool var localizedRulesDescription: String { get } From 25776e34f1902d10af4de315150e31b5f532bff5 Mon Sep 17 00:00:00 2001 From: KaterinaWire <57407805+KaterinaWire@users.noreply.github.com> Date: Tue, 7 Jan 2025 16:27:40 +0100 Subject: [PATCH 024/107] Update WireUI/Sources/WireSettingsUI/Resources/en.lproj/Accessibility.strings Co-authored-by: Christoph Aldrian --- .../WireSettingsUI/Resources/en.lproj/Accessibility.strings | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Accessibility.strings b/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Accessibility.strings index 9131729c59c..44b9a4f4a7e 100644 --- a/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Accessibility.strings +++ b/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Accessibility.strings @@ -16,4 +16,4 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -"setBackupPassword.close.label" = "Close setup backup password" +"setBackupPassword.close.label" = "Close setup backup password"; From 57975f8d4c50388cfe1d0a1f778254bb550c92ef Mon Sep 17 00:00:00 2001 From: KaterinaWire <57407805+KaterinaWire@users.noreply.github.com> Date: Tue, 7 Jan 2025 16:27:53 +0100 Subject: [PATCH 025/107] Update WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupHandler.swift Co-authored-by: Christoph Aldrian --- .../WireSettingsUI/Account/Backup/Models/BackupHandler.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupHandler.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupHandler.swift index 37d53f6ee3b..c94f60a817c 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupHandler.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupHandler.swift @@ -18,7 +18,7 @@ import Foundation -public struct BackupHandler { +public struct BackupResultHandler { let onSuccess: (URL, @escaping () -> Void) -> Void let onFailure: (any Error) -> Void From 1ea474494397ef3a685b254c5603c10c8beda8a3 Mon Sep 17 00:00:00 2001 From: KaterinaWire <57407805+KaterinaWire@users.noreply.github.com> Date: Tue, 7 Jan 2025 16:28:27 +0100 Subject: [PATCH 026/107] Update WireUI/Sources/WireSettingsUI/Account/Backup/ViewControllers/BackupActionsHostingController.swift Co-authored-by: Christoph Aldrian --- .../BackupActionsHostingController.swift | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewControllers/BackupActionsHostingController.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewControllers/BackupActionsHostingController.swift index d7ac231510c..fb46aeab1b8 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewControllers/BackupActionsHostingController.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewControllers/BackupActionsHostingController.swift @@ -17,18 +17,8 @@ // import SwiftUI -import UIKit -public final class BackupActionsHostingController: UIHostingController { - private let viewModel: BackupActionsViewModel - - public init(viewModel: BackupActionsViewModel) { - self.viewModel = viewModel - super.init(rootView: BackupActionsView(viewModel: viewModel)) - } - - @available(*, unavailable) - dynamic required init?(coder aDecoder: NSCoder) { - fatalError("init(coder:) has not been implemented") - } +@MainActor +public func BackupActionsHostingController(viewModel: BackupActionsViewModel) -> UIViewController { + UIHostingController(rootView: BackupActionsView(viewModel: viewModel)) } From 22b555d71fc1aa064481e5696f9ddb3a127a0985 Mon Sep 17 00:00:00 2001 From: KaterinaWire <57407805+KaterinaWire@users.noreply.github.com> Date: Tue, 7 Jan 2025 16:29:15 +0100 Subject: [PATCH 027/107] Update WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift Co-authored-by: Christoph Aldrian --- .../WireSettingsUI/Account/Backup/Views/ExportBackupView.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift index 22addca868a..7eb60a7f288 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift @@ -40,7 +40,8 @@ public struct ExportBackupView: View { public var body: some View { SetBackupPasswordView( passwordValidator: passwordValidator, - exportBackup: exportBackup) + exportBackup: exportBackup + ) .background(Color.viewBackground) .scrollContentBackground(.hidden) .navigationTitle( From 6d9f90d1f84ebf4ca082a6454edb5eddb45c2f2a Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Tue, 7 Jan 2025 17:09:06 +0100 Subject: [PATCH 028/107] address comments --- .../Backup/Models/BackupActionsSection.swift | 1 - ...andler.swift => BackupResultHandler.swift} | 0 .../ViewModels/BackupActionsViewModel.swift | 10 ++-- .../Backup/Views/BackupActionsView.swift | 27 +--------- .../Backup/Views/ExportBackupView.swift | 2 +- .../Backup/Views/PasswordFieldView.swift | 6 +-- .../Views/Preview/BackupActionsPreview.swift | 49 +++++++++++++++++++ .../Resources/en.lproj/Localizable.strings | 4 +- ...ingsTableViewControllerSnapshotTests.swift | 6 +++ .../Backup/BackupPasswordValidator.swift | 2 +- ...ettingsCellDescriptorFactory+Account.swift | 5 +- .../SettingsCellDescriptorFactory.swift | 1 + 12 files changed, 73 insertions(+), 40 deletions(-) rename WireUI/Sources/WireSettingsUI/Account/Backup/Models/{BackupHandler.swift => BackupResultHandler.swift} (100%) create mode 100644 WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupActionsSection.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupActionsSection.swift index 702e67ac634..91e5de2d8f0 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupActionsSection.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupActionsSection.swift @@ -16,7 +16,6 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import Foundation import SwiftUI /// The section that will be displayed in the backup actions diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupHandler.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupResultHandler.swift similarity index 100% rename from WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupHandler.swift rename to WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupResultHandler.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift index fc480c5df2a..f4404e5e60d 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift @@ -22,16 +22,16 @@ public final class BackupActionsViewModel: ObservableObject { @Published var sections: [BackupActionsSection] = [] private let backupSource: any BackupSourceProtocol - private let backupHandler: BackupHandler + private let backupResultHandler: BackupResultHandler let passwordValidator: any BackupPasswordValidatorProtocol public init( backupSource: any BackupSourceProtocol, - backupHandler: BackupHandler, + backupResultHandler: BackupResultHandler, passwordValidator: any BackupPasswordValidatorProtocol ) { self.backupSource = backupSource - self.backupHandler = backupHandler + self.backupResultHandler = backupResultHandler self.passwordValidator = passwordValidator self.sections = [ @@ -42,11 +42,11 @@ public final class BackupActionsViewModel: ObservableObject { func backupActiveAccount(password: String) { do { let backupPath = try backupSource.backupActiveAccount(password: password) - backupHandler.onSuccess(backupPath) { [weak self] in + backupResultHandler.onSuccess(backupPath) { [weak self] in self?.backupSource.clearPreviousBackups() } } catch { - backupHandler.onFailure(error) + backupResultHandler.onFailure(error) } } } diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift index 9673c950e00..808d8871e76 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift @@ -71,30 +71,5 @@ public struct BackupActionsView: View { } #Preview { - BackupActionsView(viewModel: BackupActionsViewModel( - backupSource: MockBackupSource(), - backupHandler: BackupHandler( - onSuccess: { _, _ in }, - onFailure: { _ in } - ), - passwordValidator: MockBackupPasswordValidator() - )) -} - -private class MockBackupSource: BackupSourceProtocol { - func backupActiveAccount(password: String) throws -> URL { - URL(fileURLWithPath: "path") - } - - func clearPreviousBackups() {} -} - -class MockBackupPasswordValidator: BackupPasswordValidatorProtocol { - func isValid(password: String) -> Bool { - true - } - - var localizedRulesDescription: String { - "Use at least 8 characters, with one lowercase letter, one capital letter, a number, and a special character." - } + BackupActionsPreview() } diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift index 7eb60a7f288..593dd3d5ee8 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift @@ -94,7 +94,7 @@ private struct SetBackupPasswordView: View { PasswordFieldView( password: $password, - isPasswordValid: passwordValidator.isValid(password: password), + isPasswordValid: passwordValidator.isPasswordValid(password), isPasswordVisible: $isPasswordVisible, passwordRules: Text(passwordValidator.localizedRulesDescription)) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift index 705752d14e2..0307fdc4f37 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift @@ -27,13 +27,13 @@ struct PasswordFieldView: View { var body: some View { VStack(alignment: .leading, spacing: 8) { - Text(L10n.SetBackupPassword.title) + Text(L10n.ExportBackup.SetBackupPassword.title) .font(.subheadline) .foregroundColor(isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color) ZStack { if isPasswordVisible { - TextField(L10n.SetBackupPassword.placeholder, + TextField(L10n.ExportBackup.SetBackupPassword.placeholder, text: $password) .font(.textStyle(.body1)) .textFieldStyle(RoundedBorderTextFieldStyle()) @@ -42,7 +42,7 @@ struct PasswordFieldView: View { .stroke(isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color, lineWidth: 1) ) } else { - SecureField(L10n.SetBackupPassword.placeholder, + SecureField(L10n.ExportBackup.SetBackupPassword.placeholder, text: $password) .textFieldStyle(RoundedBorderTextFieldStyle()) .overlay( diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift new file mode 100644 index 00000000000..92500cfd9b8 --- /dev/null +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift @@ -0,0 +1,49 @@ +// +// Wire +// Copyright (C) 2025 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import SwiftUI + +@ViewBuilder @MainActor +func BackupActionsPreview() -> some View { + BackupActionsView(viewModel: BackupActionsViewModel( + backupSource: MockBackupSource(), + backupResultHandler: BackupResultHandler( + onSuccess: { _, _ in }, + onFailure: { _ in } + ), + passwordValidator: MockBackupPasswordValidator() + )) +} + +private class MockBackupSource: BackupSourceProtocol { + func backupActiveAccount(password: String) throws -> URL { + URL(fileURLWithPath: "path") + } + + func clearPreviousBackups() {} +} + +class MockBackupPasswordValidator: BackupPasswordValidatorProtocol { + func isPasswordValid(_ password: String) -> Bool { + true + } + + var localizedRulesDescription: String { + "Use at least 8 characters, with one lowercase letter, one capital letter, a number, and a special character." + } +} diff --git a/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Localizable.strings b/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Localizable.strings index 8e7df5bfbab..9b4d9c7f48c 100644 --- a/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Localizable.strings +++ b/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Localizable.strings @@ -23,6 +23,6 @@ "exportBackup.title" = "Set password"; "exportBackup.description" = "The backup will be compressed and encrypted with a password. Make sure to store it in a secure place."; "exportBackup.button" = "Back Up Now"; -"setBackupPassword.title" = "Password (OPTIONAL)"; -"setBackupPassword.placeholder" = "Enter password"; +"exportBackup.setBackupPassword.title" = "Password (OPTIONAL)"; +"exportBackup.setBackupPassword.placeholder" = "Enter password"; diff --git a/wire-ios/Wire-iOS Tests/SettingsTableViewControllerSnapshotTests.swift b/wire-ios/Wire-iOS Tests/SettingsTableViewControllerSnapshotTests.swift index d489321b4ac..f4d8c8d8dd7 100644 --- a/wire-ios/Wire-iOS Tests/SettingsTableViewControllerSnapshotTests.swift +++ b/wire-ios/Wire-iOS Tests/SettingsTableViewControllerSnapshotTests.swift @@ -90,6 +90,7 @@ final class SettingsTableViewControllerSnapshotTests: XCTestCase { // MARK: - Snapshot Tests + @MainActor func testForSettingGroup() throws { let group = settingsCellDescriptorFactory.settingsGroup( isPublicDomain: true, @@ -99,6 +100,7 @@ final class SettingsTableViewControllerSnapshotTests: XCTestCase { try verify(group: group) } + @MainActor private func testForAccountGroup( federated: Bool, disabledEditing: Bool = false, @@ -117,18 +119,22 @@ final class SettingsTableViewControllerSnapshotTests: XCTestCase { try verify(group: group, file: file, testName: testName, line: line) } + @MainActor func testForAccountGroup_Federated() throws { try testForAccountGroup(federated: true) } + @MainActor func testForAccountGroup_NotFederated() throws { try testForAccountGroup(federated: false) } + @MainActor func testForAccountGroupWithDisabledEditing_Federated() throws { try testForAccountGroup(federated: true, disabledEditing: true) } + @MainActor func testForAccountGroupWithDisabledEditing_NotFederated() throws { try testForAccountGroup(federated: false, disabledEditing: true) } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupPasswordValidator.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupPasswordValidator.swift index c2306f30500..02bf6281505 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupPasswordValidator.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupPasswordValidator.swift @@ -20,7 +20,7 @@ import WireSettingsUI struct BackupPasswordValidator: BackupPasswordValidatorProtocol { - func isValid(password: String) -> Bool { + func isPasswordValid(_ password: String) -> Bool { guard !password.isEmpty else { return true } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift index 560c7834edc..7563d0bf90a 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift @@ -35,6 +35,7 @@ extension ZMUser { extension SettingsCellDescriptorFactory { + @MainActor func accountGroup( isPublicDomain: Bool, userSession: UserSession, @@ -161,6 +162,7 @@ extension SettingsCellDescriptorFactory { ) } + @MainActor func conversationsSection() -> SettingsSectionDescriptorType { SettingsSectionDescriptor( cellDescriptors: [backUpElement()], @@ -367,6 +369,7 @@ extension SettingsCellDescriptorFactory { SettingsPropertyToggleCellDescriptor(settingsProperty: settingsPropertyFactory.property(.encryptMessagesAtRest)) } + @MainActor func backUpElement() -> any SettingsCellDescriptorType { SettingsExternalScreenCellDescriptor( title: L10n.Localizable.Self.Settings.HistoryBackup.title, @@ -380,7 +383,7 @@ extension SettingsCellDescriptorFactory { if selfUser.hasValidEmail || selfUser.usesCompanyLogin { let viewModel = BackupActionsViewModel( backupSource: BackupSource(), - backupHandler: BackupHandler( + backupResultHandler: BackupResultHandler( onSuccess: presentShareSheet, onFailure: presentAlert ), diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory.swift index 210e55e78c9..5276bae25d0 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory.swift @@ -115,6 +115,7 @@ struct SettingsCellDescriptorFactory { ) } + @MainActor func settingsGroup( isPublicDomain: Bool, userSession: UserSession, From 24c1ed33ff54b5a3952eed59f1a62f6c32aa5438 Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Tue, 7 Jan 2025 17:14:49 +0100 Subject: [PATCH 029/107] fix SwiftFormat issues --- .../Backup/Views/ExportBackupView.swift | 32 +++++++++---------- .../CoreDataStackTests+Backup.swift | 4 +-- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift index 593dd3d5ee8..93319806279 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift @@ -42,24 +42,24 @@ public struct ExportBackupView: View { passwordValidator: passwordValidator, exportBackup: exportBackup ) - .background(Color.viewBackground) - .scrollContentBackground(.hidden) - .navigationTitle( - Text(L10n.ExportBackup.title) - ) - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .topBarTrailing) { - CloseButton( - action: didTapClose, - accessibilityLabel: String( - localized: "setBackupPassword.close.label", - table: "Accessibility", - bundle: .module - ) + .background(Color.viewBackground) + .scrollContentBackground(.hidden) + .navigationTitle( + Text(L10n.ExportBackup.title) + ) + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + CloseButton( + action: didTapClose, + accessibilityLabel: String( + localized: "setBackupPassword.close.label", + table: "Accessibility", + bundle: .module ) - } + ) } + } } private func didTapClose() { diff --git a/wire-ios-data-model/Tests/Source/ManagedObjectContext/CoreDataStackTests+Backup.swift b/wire-ios-data-model/Tests/Source/ManagedObjectContext/CoreDataStackTests+Backup.swift index dbd5d2101db..7eb0da44153 100644 --- a/wire-ios-data-model/Tests/Source/ManagedObjectContext/CoreDataStackTests+Backup.swift +++ b/wire-ios-data-model/Tests/Source/ManagedObjectContext/CoreDataStackTests+Backup.swift @@ -216,7 +216,7 @@ final class CoreDataStackTests_Backup: DatabaseBaseTest { directory.viewContext.saveOrRollback() // when - let _ = try createBackup(accountIdentifier: uuid) + _ = try createBackup(accountIdentifier: uuid) // then let fetchConversations = ZMConversation.sortedFetchRequest() @@ -231,7 +231,7 @@ final class CoreDataStackTests_Backup: DatabaseBaseTest { directory.viewContext.saveOrRollback() // when - let _ = try createBackup(accountIdentifier: uuid) + _ = try createBackup(accountIdentifier: uuid) // then let anotherDirectory = createStorageStackAndWaitForCompletion(userID: uuid) From 1a6418f1ba067b0b786e245e4631a30d799a9ab3 Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Tue, 7 Jan 2025 17:22:32 +0100 Subject: [PATCH 030/107] fix SwiftFormat issues --- .../Backup/Views/PasswordFieldView.swift | 24 +++++++++---------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift index 0307fdc4f37..43aa56362f4 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift @@ -33,21 +33,19 @@ struct PasswordFieldView: View { ZStack { if isPasswordVisible { - TextField(L10n.ExportBackup.SetBackupPassword.placeholder, - text: $password) - .font(.textStyle(.body1)) - .textFieldStyle(RoundedBorderTextFieldStyle()) - .overlay( - RoundedRectangle(cornerRadius: 5) - .stroke(isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color, lineWidth: 1) - ) + TextField( + L10n.ExportBackup.SetBackupPassword.placeholder, + text: $password) + .font(.textStyle(.body1)) + .textFieldStyle(RoundedBorderTextFieldStyle()) + .overlay(RoundedRectangle(cornerRadius: 5) + .stroke(isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color, lineWidth: 1) + ) } else { - SecureField(L10n.ExportBackup.SetBackupPassword.placeholder, - text: $password) + SecureField(L10n.ExportBackup.SetBackupPassword.placeholder, text: $password) .textFieldStyle(RoundedBorderTextFieldStyle()) - .overlay( - RoundedRectangle(cornerRadius: 5) - .stroke(isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color, lineWidth: 1) + .overlay(RoundedRectangle(cornerRadius: 5) + .stroke(isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color, lineWidth: 1) ) } From 814ad5bc00a16c1a717e299a9594b41c811caf81 Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Tue, 7 Jan 2025 17:30:58 +0100 Subject: [PATCH 031/107] fix SwiftLint issues --- .../WireDesign/Buttons/LinkButtonStyle.swift | 2 +- .../Backup/Views/PasswordFieldView.swift | 22 ++++++++++--------- .../Resources/en.lproj/Localizable.strings | 1 + .../Buttons/LinkButtonStyleUITests.swift | 2 +- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/WireUI/Sources/WireDesign/Buttons/LinkButtonStyle.swift b/WireUI/Sources/WireDesign/Buttons/LinkButtonStyle.swift index 41dd83fb96e..a305320355c 100644 --- a/WireUI/Sources/WireDesign/Buttons/LinkButtonStyle.swift +++ b/WireUI/Sources/WireDesign/Buttons/LinkButtonStyle.swift @@ -1,6 +1,6 @@ // // Wire -// Copyright (C) 2024 Wire Swiss GmbH +// Copyright (C) 2025 Wire Swiss GmbH // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift index 43aa56362f4..8f79da660bf 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift @@ -34,18 +34,20 @@ struct PasswordFieldView: View { ZStack { if isPasswordVisible { TextField( - L10n.ExportBackup.SetBackupPassword.placeholder, - text: $password) + L10n.ExportBackup.SetBackupPassword.placeholder, text: $password) .font(.textStyle(.body1)) .textFieldStyle(RoundedBorderTextFieldStyle()) - .overlay(RoundedRectangle(cornerRadius: 5) - .stroke(isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color, lineWidth: 1) + .overlay( + RoundedRectangle(cornerRadius: 5) + .stroke(isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color, lineWidth: 1 + ) ) } else { SecureField(L10n.ExportBackup.SetBackupPassword.placeholder, text: $password) .textFieldStyle(RoundedBorderTextFieldStyle()) - .overlay(RoundedRectangle(cornerRadius: 5) - .stroke(isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color, lineWidth: 1) + .overlay( + RoundedRectangle(cornerRadius: 5) + .stroke(isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color, lineWidth: 1) ) } @@ -77,7 +79,7 @@ struct PasswordFieldView: View { password: .constant(""), isPasswordValid: false, isPasswordVisible: .constant(false), - passwordRules: Text("Use at least 8 characters, with one lowercase letter, one capital letter, a number, and a special character.") + passwordRules: Text(L10n.ExportBackup.SetBackupPassword.rules) ) } @@ -87,7 +89,7 @@ struct PasswordFieldView: View { password: .constant("ValidPassword1!"), isPasswordValid: false, isPasswordVisible: .constant(true), - passwordRules: Text("Use at least 8 characters, with one lowercase letter, one capital letter, a number, and a special character.") + passwordRules: Text(L10n.ExportBackup.SetBackupPassword.rules) ) } @@ -97,7 +99,7 @@ struct PasswordFieldView: View { password: .constant("ValidPassword1!"), isPasswordValid: true, isPasswordVisible: .constant(false), - passwordRules: Text("Use at least 8 characters, with one lowercase letter, one capital letter, a number, and a special character.") + passwordRules: Text(L10n.ExportBackup.SetBackupPassword.rules) ) } @@ -107,6 +109,6 @@ struct PasswordFieldView: View { password: .constant("ValidPassword1!"), isPasswordValid: true, isPasswordVisible: .constant(true), - passwordRules: Text("Use at least 8 characters, with one lowercase letter, one capital letter, a number, and a special character.") + passwordRules: Text(L10n.ExportBackup.SetBackupPassword.rules) ) } diff --git a/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Localizable.strings b/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Localizable.strings index 9b4d9c7f48c..8d2314f6bb1 100644 --- a/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Localizable.strings +++ b/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Localizable.strings @@ -25,4 +25,5 @@ "exportBackup.button" = "Back Up Now"; "exportBackup.setBackupPassword.title" = "Password (OPTIONAL)"; "exportBackup.setBackupPassword.placeholder" = "Enter password"; +"exportBackup.setBackupPassword.rules" = "Use at least 8 characters, with one lowercase letter, one capital letter, a number, and a special character."; diff --git a/WireUI/Tests/WireDesignTests/Buttons/LinkButtonStyleUITests.swift b/WireUI/Tests/WireDesignTests/Buttons/LinkButtonStyleUITests.swift index d5fe7be3f1e..7346a4c7b48 100644 --- a/WireUI/Tests/WireDesignTests/Buttons/LinkButtonStyleUITests.swift +++ b/WireUI/Tests/WireDesignTests/Buttons/LinkButtonStyleUITests.swift @@ -1,6 +1,6 @@ // // Wire -// Copyright (C) 2024 Wire Swiss GmbH +// Copyright (C) 2025 Wire Swiss GmbH // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by From 57d7945d19de84db08e180a9226575a6730e7fe2 Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Tue, 7 Jan 2025 17:35:30 +0100 Subject: [PATCH 032/107] fix SwiftFormat issues --- .../Account/Backup/Views/ExportBackupView.swift | 3 ++- .../Account/Backup/Views/PasswordFieldView.swift | 10 ++++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift index 93319806279..937f96ff88f 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift @@ -96,7 +96,8 @@ private struct SetBackupPasswordView: View { password: $password, isPasswordValid: passwordValidator.isPasswordValid(password), isPasswordVisible: $isPasswordVisible, - passwordRules: Text(passwordValidator.localizedRulesDescription)) + passwordRules: Text(passwordValidator.localizedRulesDescription) + ) Spacer() diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift index 8f79da660bf..eb7e53247e1 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift @@ -39,18 +39,20 @@ struct PasswordFieldView: View { .textFieldStyle(RoundedBorderTextFieldStyle()) .overlay( RoundedRectangle(cornerRadius: 5) - .stroke(isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color, lineWidth: 1 - ) + .stroke( + isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color, + lineWidth: 1) ) } else { SecureField(L10n.ExportBackup.SetBackupPassword.placeholder, text: $password) .textFieldStyle(RoundedBorderTextFieldStyle()) .overlay( RoundedRectangle(cornerRadius: 5) - .stroke(isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color, lineWidth: 1) + .stroke( + isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color, + lineWidth: 1) ) } - HStack { Spacer() Button(action: { From 89299fad475c91b68218aa64f411ef9a177771fe Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Tue, 7 Jan 2025 17:43:57 +0100 Subject: [PATCH 033/107] remove BackupActionsSection --- .../Backup/Models/BackupActionsSection.swift | 61 ------------------- .../ViewModels/BackupActionsViewModel.swift | 6 -- .../Backup/Views/BackupActionsView.swift | 54 +++++++--------- .../Backup/Views/PasswordFieldView.swift | 9 ++- 4 files changed, 29 insertions(+), 101 deletions(-) delete mode 100644 WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupActionsSection.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupActionsSection.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupActionsSection.swift deleted file mode 100644 index 91e5de2d8f0..00000000000 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupActionsSection.swift +++ /dev/null @@ -1,61 +0,0 @@ -// -// Wire -// Copyright (C) 2025 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import SwiftUI - -/// The section that will be displayed in the backup actions -struct BackupActionsSection: Identifiable { - - enum Section { - case backup - case restore - - var title: Text { - switch self { - case .backup: - Text(L10n.Settings.ExportBackup.action) - case .restore: - Text(L10n.Settings.RestoreFromBackup.action) - } - } - - var footer: Text { - switch self { - case .backup: - Text(L10n.Settings.ExportBackup.description) - case .restore: - Text(L10n.Settings.RestoreFromBackup.description) - } - } - } - - /// Unique identifier for the section - let id: UUID - - /// The section type - let type: Section - - init( - id: UUID = UUID(), - type: Section - ) { - self.id = id - self.type = type - } - -} diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift index f4404e5e60d..406d81c1f20 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift @@ -19,8 +19,6 @@ import SwiftUI public final class BackupActionsViewModel: ObservableObject { - @Published var sections: [BackupActionsSection] = [] - private let backupSource: any BackupSourceProtocol private let backupResultHandler: BackupResultHandler let passwordValidator: any BackupPasswordValidatorProtocol @@ -33,10 +31,6 @@ public final class BackupActionsViewModel: ObservableObject { self.backupSource = backupSource self.backupResultHandler = backupResultHandler self.passwordValidator = passwordValidator - - self.sections = [ - BackupActionsSection(type: .backup) - ] } func backupActiveAccount(password: String) { diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift index 808d8871e76..bd124642a02 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift @@ -23,7 +23,6 @@ import WireReusableUIComponents public struct BackupActionsView: View { @ObservedObject private var viewModel: BackupActionsViewModel @State private var isBackupSheetPresented: Bool = false - @State private var isRestoreSheetPresented: Bool = false public init(viewModel: BackupActionsViewModel) { self.viewModel = viewModel @@ -31,37 +30,30 @@ public struct BackupActionsView: View { public var body: some View { List { - ForEach(viewModel.sections) { section in - Section( - footer: section.type.footer - ) { - Button(action: { - switch section.type { - case .backup: - isBackupSheetPresented.toggle() - case .restore: - isRestoreSheetPresented.toggle() - } - }, label: { - HStack { - section.type.title - .font(.textStyle(.body2)) - .foregroundStyle(Color.primaryText) - Spacer() - Image(systemName: "chevron.right").foregroundStyle(Color.primary) - } - }) - .sheet(isPresented: $isBackupSheetPresented) { - NavigationStack { - ExportBackupView( - passwordValidator: viewModel.passwordValidator, - exportBackup: { password in - viewModel.backupActiveAccount(password: password) - } - ) - } - .presentationDetents([.medium, .large]) + Section( + footer: Text(L10n.ExportBackup.description) + ) { + Button(action: { + isBackupSheetPresented.toggle() + }, label: { + HStack { + Text(L10n.ExportBackup.title) + .font(.textStyle(.body2)) + .foregroundStyle(Color.primaryText) + Spacer() + Image(systemName: "chevron.right").foregroundStyle(Color.primary) } + }) + .sheet(isPresented: $isBackupSheetPresented) { + NavigationStack { + ExportBackupView( + passwordValidator: viewModel.passwordValidator, + exportBackup: { password in + viewModel.backupActiveAccount(password: password) + } + ) + } + .presentationDetents([.medium, .large]) } } } diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift index eb7e53247e1..92b29a8f1ff 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift @@ -34,14 +34,16 @@ struct PasswordFieldView: View { ZStack { if isPasswordVisible { TextField( - L10n.ExportBackup.SetBackupPassword.placeholder, text: $password) + L10n.ExportBackup.SetBackupPassword.placeholder, text: $password + ) .font(.textStyle(.body1)) .textFieldStyle(RoundedBorderTextFieldStyle()) .overlay( RoundedRectangle(cornerRadius: 5) .stroke( isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color, - lineWidth: 1) + lineWidth: 1 + ) ) } else { SecureField(L10n.ExportBackup.SetBackupPassword.placeholder, text: $password) @@ -50,7 +52,8 @@ struct PasswordFieldView: View { RoundedRectangle(cornerRadius: 5) .stroke( isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color, - lineWidth: 1) + lineWidth: 1 + ) ) } HStack { From 69032be62060062af10838a350bc39af4a2a8e3e Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Wed, 8 Jan 2025 09:37:26 +0100 Subject: [PATCH 034/107] add restore backup ligic --- .../Models/RestoreBackupResultHandler.swift | 30 +++++ .../Backup/Protocols/RestoreSource.swift | 32 +++++ .../ViewModels/BackupActionsViewModel.swift | 19 ++- .../Backup/Views/BackupActionsView.swift | 57 +++------ .../Account/Backup/Views/BackupPicker.swift | 55 ++++++++ .../Backup/Views/ExportBackupView.swift | 28 +---- .../Backup/Views/PasswordFieldView.swift | 22 ++-- .../Views/Preview/BackupActionsPreview.swift | 8 ++ .../Views/Preview/ExportBackupPreview.swift | 44 +++++++ .../Views/Preview/RestoreBackupPreview.swift | 43 +++++++ .../Backup/Views/RestoreBackupView.swift | 63 +++------- .../Resources/en.lproj/Localizable.strings | 4 +- .../CoreDataStack+Backup.swift | 119 ++++++++++++++++++ .../SessionManager+Backup.swift | 47 +++++++ wire-ios/Wire-iOS.xcodeproj/project.pbxproj | 4 + .../Backup/BackupRestoreController.swift | 4 +- .../Settings/Backup/RestoreSource.swift | 42 +++++++ ...ettingsCellDescriptorFactory+Account.swift | 4 + 18 files changed, 502 insertions(+), 123 deletions(-) create mode 100644 WireUI/Sources/WireSettingsUI/Account/Backup/Models/RestoreBackupResultHandler.swift create mode 100644 WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/RestoreSource.swift create mode 100644 WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupPicker.swift create mode 100644 WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/ExportBackupPreview.swift create mode 100644 WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/RestoreBackupPreview.swift create mode 100644 wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/RestoreSource.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Models/RestoreBackupResultHandler.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Models/RestoreBackupResultHandler.swift new file mode 100644 index 00000000000..4fc77aa3bf1 --- /dev/null +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Models/RestoreBackupResultHandler.swift @@ -0,0 +1,30 @@ +// +// Wire +// Copyright (C) 2025 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +public struct RestoreBackupResultHandler { + let onSuccess: (@escaping () -> Void) -> Void + let onFailure: (any Error) -> Void + + public init( + onSuccess: @escaping (@escaping () -> Void) -> Void, + onFailure: @escaping (any Error) -> Void + ) { + self.onSuccess = onSuccess + self.onFailure = onFailure + } +} diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/RestoreSource.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/RestoreSource.swift new file mode 100644 index 00000000000..8aead1c9ff3 --- /dev/null +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/RestoreSource.swift @@ -0,0 +1,32 @@ +// +// Wire +// Copyright (C) 2025 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import Foundation + +public enum RestoreBackupError: Error { + + case decryptionError + case generic(any Error) + +} + +public protocol RestoreSourceProtocol { + + func restoreFromBackup(at location: URL, password: String) throws + +} diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift index f15335e5a58..35fc15de9fd 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift @@ -20,16 +20,22 @@ import SwiftUI public final class BackupActionsViewModel: ObservableObject { private let backupSource: any BackupSourceProtocol + private let restoreSource: any RestoreSourceProtocol private let backupResultHandler: BackupResultHandler + private let restoreBackupResultHandler: RestoreBackupResultHandler let passwordValidator: any BackupPasswordValidatorProtocol public init( backupSource: any BackupSourceProtocol, + restoreSource: any RestoreSourceProtocol, backupResultHandler: BackupResultHandler, + restoreBackupResultHandler: RestoreBackupResultHandler, passwordValidator: any BackupPasswordValidatorProtocol ) { self.backupSource = backupSource + self.restoreSource = restoreSource self.backupResultHandler = backupResultHandler + self.restoreBackupResultHandler = restoreBackupResultHandler self.passwordValidator = passwordValidator } @@ -44,6 +50,17 @@ public final class BackupActionsViewModel: ObservableObject { } } - func restoreBackup(password: String, from url: URL) { + func restoreBackup(at url: URL, password: String) throws { + do { + try restoreSource.restoreFromBackup(at: url, password: password) + print("all good") + } catch let error as RestoreBackupError { + switch error { + case .decryptionError: + print("decryptionError") + case let .generic(error): + print("generic") + } + } } } diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift index 983ccf3268d..9ae25f7d998 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift @@ -24,6 +24,8 @@ public struct BackupActionsView: View { @ObservedObject private var viewModel: BackupActionsViewModel @State private var isExportBackupSheetPresented: Bool = false @State private var isBackupPickerPresented: Bool = false + @State private var isRestoreBackupSheetPresented: Bool = false + @State private var selectedFileURL: URL? public init(viewModel: BackupActionsViewModel) { self.viewModel = viewModel @@ -38,7 +40,7 @@ public struct BackupActionsView: View { isExportBackupSheetPresented.toggle() }, label: { HStack { - Text(L10n.ExportBackup.title) + Text(L10n.Settings.ExportBackup.action) .font(.textStyle(.body2)) .foregroundStyle(Color.primaryText) Spacer() @@ -65,7 +67,7 @@ public struct BackupActionsView: View { isBackupPickerPresented.toggle() }, label: { HStack { - Text(L10n.RestoreFromBackup.title) + Text(L10n.Settings.RestoreFromBackup.action) .font(.textStyle(.body2)) .foregroundStyle(Color.primaryText) Spacer() @@ -73,12 +75,26 @@ public struct BackupActionsView: View { } }) .fullScreenCover(isPresented: $isBackupPickerPresented) { - DocumentPicker { url in + BackupPicker { url in if let fileURL = url { - print("Selected file URL: \(fileURL)") + selectedFileURL = fileURL + isRestoreBackupSheetPresented = true } } } + .sheet(isPresented: $isRestoreBackupSheetPresented) { + NavigationStack { + RestoreBackupView { password in + if let fileURL = selectedFileURL { + try? viewModel.restoreBackup( + at: fileURL, + password: password + ) + } + } + } + .presentationDetents([.medium, .large]) + } } } .listStyle(.grouped) @@ -86,39 +102,6 @@ public struct BackupActionsView: View { } } -struct DocumentPicker: UIViewControllerRepresentable { - var completion: (URL?) -> Void - - func makeUIViewController(context: Context) -> UIDocumentPickerViewController { - let picker = UIDocumentPickerViewController(forOpeningContentTypes: [.item]) - picker.allowsMultipleSelection = false - picker.delegate = context.coordinator - return picker - } - - func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: Context) {} - - func makeCoordinator() -> Coordinator { - Coordinator(completion: completion) - } - - class Coordinator: NSObject, UIDocumentPickerDelegate { - var completion: (URL?) -> Void - - init(completion: @escaping (URL?) -> Void) { - self.completion = completion - } - - func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { - completion(urls.first) - } - - func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) { - completion(nil) - } - } -} - #Preview { BackupActionsPreview() } diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupPicker.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupPicker.swift new file mode 100644 index 00000000000..be971b811e9 --- /dev/null +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupPicker.swift @@ -0,0 +1,55 @@ +// +// Wire +// Copyright (C) 2025 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import SwiftUI + +struct BackupPicker: UIViewControllerRepresentable { + var completion: (URL?) -> Void + + func makeUIViewController(context: Context) -> UIDocumentPickerViewController { + let picker = UIDocumentPickerViewController(forOpeningContentTypes: [.item]) + picker.allowsMultipleSelection = false + picker.delegate = context.coordinator + return picker + } + + func updateUIViewController(_ uiViewController: UIDocumentPickerViewController, context: Context) {} + + func makeCoordinator() -> Coordinator { + Coordinator(completion: completion) + } + + class Coordinator: NSObject, UIDocumentPickerDelegate { + var completion: (URL?) -> Void + + init(completion: @escaping (URL?) -> Void) { + self.completion = completion + } + + func documentPicker( + _ controller: UIDocumentPickerViewController, + didPickDocumentsAt urls: [URL] + ) { + completion(urls.first) + } + + func documentPickerWasCancelled(_ controller: UIDocumentPickerViewController) { + completion(nil) + } + } +} diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift index 937f96ff88f..b953d994d39 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift @@ -94,8 +94,9 @@ private struct SetBackupPasswordView: View { PasswordFieldView( password: $password, - isPasswordValid: passwordValidator.isPasswordValid(password), isPasswordVisible: $isPasswordVisible, + title: Text(L10n.ExportBackup.SetBackupPassword.title), + isPasswordValid: passwordValidator.isPasswordValid(password), passwordRules: Text(passwordValidator.localizedRulesDescription) ) @@ -124,28 +125,3 @@ private struct SetBackupPasswordView: View { #Preview("Export Backup sheet") { ExportBackupPreview() } - -private struct ExportBackupPreview: View { - @State private var isPresented = true - - var body: some View { - Button( - action: { - isPresented.toggle() - }, - label: { - Text(L10n.ExportBackup.button) - } - ) - .sheet(isPresented: $isPresented) { - NavigationStack { - ExportBackupView( - passwordValidator: MockBackupPasswordValidator(), - exportBackup: { _ in } - ) - } - .presentationDragIndicator(.visible) - .presentationDetents([.medium, .large]) - } - } -} diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift index 92b29a8f1ff..eb9c3e5903b 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift @@ -21,15 +21,17 @@ import WireDesign struct PasswordFieldView: View { @Binding var password: String - let isPasswordValid: Bool @Binding var isPasswordVisible: Bool - let passwordRules: Text + let title: Text + var isPasswordValid: Bool = true + let passwordRules: Text? var body: some View { VStack(alignment: .leading, spacing: 8) { - Text(L10n.ExportBackup.SetBackupPassword.title) + title .font(.subheadline) - .foregroundColor(isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color) + .foregroundColor(isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color + ) ZStack { if isPasswordVisible { @@ -82,8 +84,9 @@ struct PasswordFieldView: View { #Preview("Invalid Password - Hidden") { PasswordFieldView( password: .constant(""), - isPasswordValid: false, isPasswordVisible: .constant(false), + title: Text(L10n.ExportBackup.SetBackupPassword.title), + isPasswordValid: false, passwordRules: Text(L10n.ExportBackup.SetBackupPassword.rules) ) } @@ -92,8 +95,9 @@ struct PasswordFieldView: View { #Preview("Invalid Password - Visible") { PasswordFieldView( password: .constant("ValidPassword1!"), - isPasswordValid: false, isPasswordVisible: .constant(true), + title: Text(L10n.ExportBackup.SetBackupPassword.title), + isPasswordValid: false, passwordRules: Text(L10n.ExportBackup.SetBackupPassword.rules) ) } @@ -102,8 +106,9 @@ struct PasswordFieldView: View { #Preview("Valid Password - Hidden") { PasswordFieldView( password: .constant("ValidPassword1!"), - isPasswordValid: true, isPasswordVisible: .constant(false), + title: Text(L10n.ExportBackup.SetBackupPassword.title), + isPasswordValid: true, passwordRules: Text(L10n.ExportBackup.SetBackupPassword.rules) ) } @@ -112,8 +117,9 @@ struct PasswordFieldView: View { #Preview("Valid Password - Visible") { PasswordFieldView( password: .constant("ValidPassword1!"), - isPasswordValid: true, isPasswordVisible: .constant(true), + title: Text(L10n.ExportBackup.SetBackupPassword.title), + isPasswordValid: true, passwordRules: Text(L10n.ExportBackup.SetBackupPassword.rules) ) } diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift index 92500cfd9b8..06d04d03431 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift @@ -22,10 +22,14 @@ import SwiftUI func BackupActionsPreview() -> some View { BackupActionsView(viewModel: BackupActionsViewModel( backupSource: MockBackupSource(), + restoreSource: MockRestoreSource(), backupResultHandler: BackupResultHandler( onSuccess: { _, _ in }, onFailure: { _ in } ), + restoreBackupResultHandler: RestoreBackupResultHandler( + onSuccess: { _ in }, + onFailure: { _ in }), passwordValidator: MockBackupPasswordValidator() )) } @@ -38,6 +42,10 @@ private class MockBackupSource: BackupSourceProtocol { func clearPreviousBackups() {} } +private class MockRestoreSource: RestoreSourceProtocol { + func restoreFromBackup(at location: URL, password: String) throws {} +} + class MockBackupPasswordValidator: BackupPasswordValidatorProtocol { func isPasswordValid(_ password: String) -> Bool { true diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/ExportBackupPreview.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/ExportBackupPreview.swift new file mode 100644 index 00000000000..e9391f82128 --- /dev/null +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/ExportBackupPreview.swift @@ -0,0 +1,44 @@ +// +// Wire +// Copyright (C) 2025 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import SwiftUI + +struct ExportBackupPreview: View { + @State private var isPresented = true + + var body: some View { + Button( + action: { + isPresented.toggle() + }, + label: { + Text(L10n.ExportBackup.button) + } + ) + .sheet(isPresented: $isPresented) { + NavigationStack { + ExportBackupView( + passwordValidator: MockBackupPasswordValidator(), + exportBackup: { _ in } + ) + } + .presentationDragIndicator(.visible) + .presentationDetents([.medium, .large]) + } + } +} diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/RestoreBackupPreview.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/RestoreBackupPreview.swift new file mode 100644 index 00000000000..71a3733a307 --- /dev/null +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/RestoreBackupPreview.swift @@ -0,0 +1,43 @@ +// +// Wire +// Copyright (C) 2025 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import SwiftUI + +struct RestoreBackupPreview: View { + @State private var isPresented = true + + var body: some View { + Button( + action: { + isPresented.toggle() + }, + label: { + Text(L10n.RestoreFromBackup.button) + } + ) + .sheet(isPresented: $isPresented) { + NavigationStack { + RestoreBackupView( + importBackup: { _ in } + ) + } + .presentationDragIndicator(.visible) + .presentationDetents([.medium, .large]) + } + } +} diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/RestoreBackupView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/RestoreBackupView.swift index 2417e3f9f37..63d3a821703 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/RestoreBackupView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/RestoreBackupView.swift @@ -23,24 +23,20 @@ import WireReusableUIComponents struct RestoreBackupView: View { @Environment(\.dismiss) private var dismiss - private let backupPath: URL - private let importBackup: (String, URL) -> Void + private let importBackup: (String) -> Void public init( - backupPath: URL, - importBackup: @escaping (String, URL) -> Void + importBackup: @escaping (String) -> Void ) { - self.backupPath = backupPath self.importBackup = importBackup } public var body: some View { - PpasswordBackupView( - backupPath: backupPath, importBackup: importBackup) + PpasswordBackupView(importBackup: importBackup) .background(Color.viewBackground) .scrollContentBackground(.hidden) .navigationTitle( - Text("Enter password") + Text(L10n.RestoreFromBackup.title) ) .navigationBarTitleDisplayMode(.inline) .toolbar { @@ -67,36 +63,32 @@ private struct PpasswordBackupView: View { @State private var password: String = "" @State private var isPasswordVisible: Bool = false - private let backupPath: URL - private let importBackup: (String, URL) -> Void + private let importBackup: (String) -> Void - init( - backupPath: URL, - importBackup: @escaping (String, URL) -> Void - ) { - self.backupPath = backupPath + init(importBackup: @escaping (String) -> Void) { self.importBackup = importBackup } var body: some View { VStack(spacing: 20) { - Text(L10n.RestoreFromBackup.title) + Text(L10n.RestoreFromBackup.description) .font(.textStyle(.body1)) .foregroundStyle(Color.primaryText) .multilineTextAlignment(.leading) + .frame(maxWidth: .infinity, alignment: .leading) .padding(.horizontal) -// PasswordFieldView( -// password: $password, -// isPasswordValid: passwordValidator.isValid(password: password), -// isPasswordVisible: $isPasswordVisible, -// passwordRules: Text(passwordValidator.localizedRulesDescription)) - + PasswordFieldView( + password: $password, + isPasswordVisible: $isPasswordVisible, + title: Text(L10n.RestoreFromBackup.EnterPassword.title), + passwordRules: nil + ) Spacer() Button( action: { - importBackup(password, backupPath) + importBackup(password) dismiss() }, label: { @@ -117,28 +109,3 @@ private struct PpasswordBackupView: View { #Preview("Export Backup sheet") { RestoreBackupPreview() } - -private struct RestoreBackupPreview: View { - @State private var isPresented = true - - var body: some View { - Button( - action: { - isPresented.toggle() - }, - label: { - Text(L10n.RestoreFromBackup.button) - } - ) - .sheet(isPresented: $isPresented) { - NavigationStack { - RestoreBackupView( - backupPath: URL(fileURLWithPath: ""), - importBackup: { _, _ in } - ) - } - .presentationDragIndicator(.visible) - .presentationDetents([.medium, .large]) - } - } -} diff --git a/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Localizable.strings b/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Localizable.strings index 1c66835ed4f..d80baaeb0ca 100644 --- a/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Localizable.strings +++ b/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Localizable.strings @@ -26,6 +26,8 @@ "exportBackup.setBackupPassword.title" = "Password (OPTIONAL)"; "exportBackup.setBackupPassword.placeholder" = "Enter password"; "exportBackup.setBackupPassword.rules" = "Use at least 8 characters, with one lowercase letter, one capital letter, a number, and a special character."; -"restoreFromBackup.title" = "This backup is password protected."; +"restoreFromBackup.title" = "Enter password"; +"restoreFromBackup.description" = "This backup is password protected."; "restoreFromBackup.button" = "Continue"; +"restoreFromBackup.enterPassword.title" = "Password"; "restoreFromBackup.enterPassword.error" = "Wrong password. Please verify your input and try again"; diff --git a/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack+Backup.swift b/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack+Backup.swift index 66311cd99d1..14ea9db02ac 100644 --- a/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack+Backup.swift +++ b/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack+Backup.swift @@ -189,6 +189,38 @@ public extension CoreDataStack { ) } + static func importLocalStorage( + accountIdentifier: UUID, + from backupDirectory: URL, + applicationContainer: URL, + dispatchGroup: ZMSDispatchGroup + ) throws { + // Start background activity + guard let activity = BackgroundActivityFactory.shared.startBackgroundActivity(name: "import backup") else { + WireLogger.localStorage + .error("backup: error backing up local store: \(CoreDataStackError.noDatabaseActivity)") + log.debug("error backing up local store: \(CoreDataStackError.noDatabaseActivity)") + throw CoreDataStackError.noDatabaseActivity + } + + do { + try importLocalStorage( + accountIdentifier: accountIdentifier, + from: backupDirectory, + applicationContainer: applicationContainer, + dispatchGroup: dispatchGroup, + messagingMigrator: CoreDataMigrator(isInMemoryStore: false) + ) + } catch { + // Ensure background activity is ended even if an error occurs + BackgroundActivityFactory.shared.endBackgroundActivity(activity) + throw error + } + + // End background activity after successful completion + BackgroundActivityFactory.shared.endBackgroundActivity(activity) + } + internal static func importLocalStorage( accountIdentifier: UUID, from backupDirectory: URL, @@ -279,6 +311,93 @@ public extension CoreDataStack { } } + internal static func importLocalStorage( + accountIdentifier: UUID, + from backupDirectory: URL, + applicationContainer: URL, + dispatchGroup: ZMSDispatchGroup, + messagingMigrator: CoreDataMessagingMigratorProtocol + ) throws { + let accountDirectory = accountDataFolder( + accountIdentifier: accountIdentifier, + applicationContainer: applicationContainer + ) + let accountStoreFile = accountDirectory.appendingPersistentStoreLocation() + let backupStoreFile = backupDirectory + .appendingPathComponent(databaseDirectoryName) + .appendingStoreFile() + let metadataURL = backupDirectory.appendingPathComponent(metadataFilename) + + try workQueue.sync { + do { + // Load metadata + let metadata = try BackupMetadata(url: metadataURL) + let currentModel = CoreDataStack.loadMessagingModel() + + // Ensure the model version is available + guard let backupModel = managedObjectModel(for: metadata.modelVersion) else { + throw BackupImportError.missingModelVersion(metadata.modelVersion) + } + + // Verify metadata compatibility + if let verificationError = metadata.verify( + using: accountIdentifier, + modelVersionProvider: currentModel + ) { + throw BackupImportError.incompatibleBackup(verificationError) + } + + // Set up the coordinator with the backup model + let coordinator = NSPersistentStoreCoordinator(managedObjectModel: backupModel) + + // Create the target directory for the account store + try fileManager.createDirectory( + at: accountStoreFile.deletingLastPathComponent(), + withIntermediateDirectories: true, + attributes: nil + ) + let options = NSPersistentStoreCoordinator.persistentStoreOptions(supportsMigration: false) + + // Prepare the backup store for import + WireLogger.localStorage.debug("backup: import prepare", attributes: .safePublic) + try prepareStoreForBackupImport(coordinator: coordinator, location: backupStoreFile, options: options) + + // Perform the migration + let tp = TimePoint(interval: 60.0, label: "db migration") + WireLogger.localStorage.debug("backup: migrate database \(metadata.modelVersion) to \(currentModel.version)") + try messagingMigrator.migrateStore(at: backupStoreFile, toVersion: .current) + + if !tp.warnIfLongerThanInterval() { + WireLogger.localStorage.info( + "time spent in migration only: \(tp.elapsedTime)", + attributes: .safePublic + ) + } + + // Import the persistent store to the account data directory + WireLogger.localStorage.debug( + "backup: import the persistent store to the account data directory", + attributes: .safePublic + ) + try coordinator.replacePersistentStore( + at: accountStoreFile, + destinationOptions: options, + withPersistentStoreFrom: backupStoreFile, + sourceOptions: options, + ofType: NSSQLiteStoreType + ) + + WireLogger.localStorage.info( + "successfully imported backup with metadata: \(metadata)", + attributes: .safePublic + ) + } catch { + WireLogger.localStorage.error("backup: error importing local store: \(error)", attributes: .safePublic) + throw BackupImportError.failedToCopy(error) + } + } + } + private static func managedObjectModel(for dataModelVersion: String) -> NSManagedObjectModel? { let version = CoreDataMessagingMigrationVersion.allCases.first { $0.dataModelVersion == dataModelVersion diff --git a/wire-ios-sync-engine/Source/SessionManager/SessionManager+Backup.swift b/wire-ios-sync-engine/Source/SessionManager/SessionManager+Backup.swift index 713948895b8..a277c7c44e6 100644 --- a/wire-ios-sync-engine/Source/SessionManager/SessionManager+Backup.swift +++ b/wire-ios-sync-engine/Source/SessionManager/SessionManager+Backup.swift @@ -176,6 +176,53 @@ extension SessionManager { } } + public func restoreFromBackup( + at location: URL, + password: String + ) throws { + guard let userId = activeUserSession?.userId else { + throw BackupError.notAuthenticated + } + + // Verify the imported file has the correct file extension. + guard BackupFileExtensions.allCases.contains(where: { + $0.rawValue == location.pathExtension + }) else { + throw BackupError.invalidFileExtension + } + + let decryptedURL = SessionManager.temporaryURL(for: location) + WireLogger.localStorage.debug("coordinated file access at: \(location.absoluteString)") + + do { + try SessionManager.decrypt( + from: location, + to: decryptedURL, + password: password, + accountId: userId + ) + } catch ChaCha20Poly1305.StreamEncryption.EncryptionError.decryptionFailed { + throw BackupError.decryptionError + } catch ChaCha20Poly1305.StreamEncryption.EncryptionError.keyGenerationFailed { + throw BackupError.keyCreationFailed + } catch { + throw error + } + + let url = SessionManager.unzippedBackupURL(for: location) + + guard decryptedURL.unzip(to: url) else { + throw BackupError.compressionError + } + + try CoreDataStack.importLocalStorage( + accountIdentifier: userId, + from: url, + applicationContainer: sharedContainerURL, + dispatchGroup: dispatchGroup + ) + } + // MARK: - Encryption & Decryption static func encrypt(from input: URL, to output: URL, password: String, accountId: UUID) throws { diff --git a/wire-ios/Wire-iOS.xcodeproj/project.pbxproj b/wire-ios/Wire-iOS.xcodeproj/project.pbxproj index f9447f1794f..288e3930881 100644 --- a/wire-ios/Wire-iOS.xcodeproj/project.pbxproj +++ b/wire-ios/Wire-iOS.xcodeproj/project.pbxproj @@ -102,6 +102,7 @@ 06C412AC238FCAA80018866F /* Int+Const.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06C412AB238FCAA80018866F /* Int+Const.swift */; }; 06C412B0239103910018866F /* ConversationInputBarViewController+DragAndDrop.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06C412AF239103910018866F /* ConversationInputBarViewController+DragAndDrop.swift */; }; 06CDC6F62A2DDBCE00EB518D /* FailedUsersSystemMessageCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06CDC6F52A2DDBCE00EB518D /* FailedUsersSystemMessageCell.swift */; }; + 06CDDA952D2E5F960092BECC /* RestoreSource.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06CDDA942D2E5F920092BECC /* RestoreSource.swift */; }; 06D93B472B56F64F00A0A512 /* E2EIFeatureChange.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06D93B462B56F64F00A0A512 /* E2EIFeatureChange.swift */; }; 06DF42D12757DA56009C8A99 /* GuestLinkInfoCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06DF42D02757DA55009C8A99 /* GuestLinkInfoCell.swift */; }; 06E097982A20BEE000B38C4A /* ZMConversation+IncompleteMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 06E097972A20BEE000B38C4A /* ZMConversation+IncompleteMetadata.swift */; }; @@ -2044,6 +2045,7 @@ 06C412AB238FCAA80018866F /* Int+Const.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Int+Const.swift"; sourceTree = ""; }; 06C412AF239103910018866F /* ConversationInputBarViewController+DragAndDrop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ConversationInputBarViewController+DragAndDrop.swift"; sourceTree = ""; }; 06CDC6F52A2DDBCE00EB518D /* FailedUsersSystemMessageCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FailedUsersSystemMessageCell.swift; sourceTree = ""; }; + 06CDDA942D2E5F920092BECC /* RestoreSource.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RestoreSource.swift; sourceTree = ""; }; 06D93B462B56F64F00A0A512 /* E2EIFeatureChange.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = E2EIFeatureChange.swift; sourceTree = ""; }; 06DF42D02757DA55009C8A99 /* GuestLinkInfoCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GuestLinkInfoCell.swift; sourceTree = ""; }; 06E097972A20BEE000B38C4A /* ZMConversation+IncompleteMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ZMConversation+IncompleteMetadata.swift"; sourceTree = ""; }; @@ -7484,6 +7486,7 @@ children = ( 060F032A2D2C12910016431F /* BackupPasswordValidator.swift */, E666EDD52B73E62800C03E2B /* BackupSource.swift */, + 06CDDA942D2E5F920092BECC /* RestoreSource.swift */, ); path = Backup; sourceTree = ""; @@ -9665,6 +9668,7 @@ A9BBD8D026A5D51D00C768B1 /* LayoutDirection.swift in Sources */, 061EF88F2797F74900584C53 /* SettingsCopyButtonCellDescriptor.swift in Sources */, 874A02561D1D329B00D0D74A /* AudioEffectCell.swift in Sources */, + 06CDDA952D2E5F960092BECC /* RestoreSource.swift in Sources */, E966C50A2CDCD0E200BF1195 /* UpdateConversationFolderUseCase+UpdateConversationFolderUseCaseProtocol .swift in Sources */, 55A42A701FC872600056752F /* CallQualityController+Budget.swift in Sources */, 87E56EDA1E4E10CA0097D489 /* TextSearchViewController.swift in Sources */, diff --git a/wire-ios/Wire-iOS/Sources/Authentication/Backup/BackupRestoreController.swift b/wire-ios/Wire-iOS/Sources/Authentication/Backup/BackupRestoreController.swift index 62066c9f0e0..1dfb5514b75 100644 --- a/wire-ios/Wire-iOS/Sources/Authentication/Backup/BackupRestoreController.swift +++ b/wire-ios/Wire-iOS/Sources/Authentication/Backup/BackupRestoreController.swift @@ -111,8 +111,8 @@ final class BackupRestoreController: NSObject { } Task { @MainActor in activityIndicator.start() } -// - sessionManager.restoreFromBackup(at: url, password: password) { [weak self] result in + + sessionManager.restoreFromBackup(at: url, password: password) { [weak self] result in // guard let self else { BackgroundActivityFactory.shared.endBackgroundActivity(activity) WireLogger.localStorage.error("SessionManager.self is `nil` in performRestore") diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/RestoreSource.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/RestoreSource.swift new file mode 100644 index 00000000000..6d3b0025e86 --- /dev/null +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/RestoreSource.swift @@ -0,0 +1,42 @@ +// +// Wire +// Copyright (C) 2025 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import Foundation +import WireSettingsUI +import class WireSyncEngine.SessionManager + +struct RestoreSource: RestoreSourceProtocol { + + func restoreFromBackup(at location: URL, password: String) throws { + do { + try SessionManager.shared?.restoreFromBackup( + at: location, + password: password) + } catch let error as SessionManager.BackupError { + switch error { + case .decryptionError: + throw RestoreBackupError.decryptionError + default: + throw RestoreBackupError.generic(error) + } + } catch { + throw RestoreBackupError.generic(error) + } + } + +} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift index 7563d0bf90a..afb550a6226 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift @@ -383,10 +383,14 @@ extension SettingsCellDescriptorFactory { if selfUser.hasValidEmail || selfUser.usesCompanyLogin { let viewModel = BackupActionsViewModel( backupSource: BackupSource(), + restoreSource: RestoreSource(), backupResultHandler: BackupResultHandler( onSuccess: presentShareSheet, onFailure: presentAlert ), + restoreBackupResultHandler: RestoreBackupResultHandler( + onSuccess: { _ in }, + onFailure: presentAlert), passwordValidator: BackupPasswordValidator() ) let backupActionsController = BackupActionsHostingController(viewModel: viewModel) From 0c79c3bfc5f71ef1bd5e51f889b3a5485005006e Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Wed, 8 Jan 2025 23:25:53 +0100 Subject: [PATCH 035/107] temp --- .../Models/RestoreBackupResultHandler.swift | 3 + .../Backup/Protocols/RestoreSource.swift | 1 + .../ViewModels/BackupActionsViewModel.swift | 12 +++ .../Backup/Views/BackupActionsView.swift | 13 +-- .../Views/Preview/BackupActionsPreview.swift | 5 ++ .../Backup/Views/RestoreBackupView.swift | 1 + .../Resources/en.lproj/Localizable.strings | 7 ++ .../CoreDataStack+Backup.swift | 1 + .../CoreDataStack+Migration.swift | 1 + .../SessionManager+APIVersionResolver.swift | 2 +- .../SessionManager+Backup.swift | 86 +++++++++++++++---- .../SessionManager/SessionManager.swift | 3 +- .../ZMUserSession+Authentication.swift | 2 +- .../Generated/Strings+Generated.swift | 12 +++ .../Resources/Base.lproj/Localizable.strings | 6 ++ .../Settings/Backup/RestoreSource.swift | 11 ++- ...ettingsCellDescriptorFactory+Account.swift | 29 ++++++- 17 files changed, 167 insertions(+), 28 deletions(-) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Models/RestoreBackupResultHandler.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Models/RestoreBackupResultHandler.swift index 4fc77aa3bf1..46903504fdc 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Models/RestoreBackupResultHandler.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Models/RestoreBackupResultHandler.swift @@ -18,13 +18,16 @@ public struct RestoreBackupResultHandler { let onSuccess: (@escaping () -> Void) -> Void + let onConfirmation: (@escaping () -> Void) -> Void let onFailure: (any Error) -> Void public init( onSuccess: @escaping (@escaping () -> Void) -> Void, + onConfirmation: @escaping (@escaping () -> Void) -> Void, onFailure: @escaping (any Error) -> Void ) { self.onSuccess = onSuccess + self.onConfirmation = onConfirmation self.onFailure = onFailure } } diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/RestoreSource.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/RestoreSource.swift index 8aead1c9ff3..3d70c8fdd3e 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/RestoreSource.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/RestoreSource.swift @@ -28,5 +28,6 @@ public enum RestoreBackupError: Error { public protocol RestoreSourceProtocol { func restoreFromBackup(at location: URL, password: String) throws + func restoreFromBackup(at location: URL, password: String, completion: @escaping (Result) -> Void) } diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift index 35fc15de9fd..110bf2aa7c3 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift @@ -63,4 +63,16 @@ public final class BackupActionsViewModel: ObservableObject { } } } + + func restoreFromBackup(at location: URL, password: String, completion: @escaping (Result) -> Void) { + restoreSource.restoreFromBackup(at: location, password: password) { result in + print("AAAA: \(result)") + } + } + + func confirmBackupRestore(completion: @escaping () -> Void) { + restoreBackupResultHandler.onConfirmation { + completion() + } + } } diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift index 9ae25f7d998..d3a81ea4881 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift @@ -64,7 +64,9 @@ public struct BackupActionsView: View { footer: Text(L10n.Settings.RestoreFromBackup.description) ) { Button(action: { - isBackupPickerPresented.toggle() + viewModel.confirmBackupRestore { + isBackupPickerPresented.toggle() + } }, label: { HStack { Text(L10n.Settings.RestoreFromBackup.action) @@ -86,10 +88,11 @@ public struct BackupActionsView: View { NavigationStack { RestoreBackupView { password in if let fileURL = selectedFileURL { - try? viewModel.restoreBackup( - at: fileURL, - password: password - ) + viewModel.restoreFromBackup(at: fileURL, password: password, completion: {_ in }) +// try? viewModel.restoreBackup( +// at: fileURL, +// password: password +// ) } } } diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift index 06d04d03431..d4157c6eb5e 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift @@ -29,6 +29,7 @@ func BackupActionsPreview() -> some View { ), restoreBackupResultHandler: RestoreBackupResultHandler( onSuccess: { _ in }, + onConfirmation: { _ in }, onFailure: { _ in }), passwordValidator: MockBackupPasswordValidator() )) @@ -43,6 +44,10 @@ private class MockBackupSource: BackupSourceProtocol { } private class MockRestoreSource: RestoreSourceProtocol { + func restoreFromBackup(at location: URL, password: String, completion: @escaping (Result) -> Void) { + + } + func restoreFromBackup(at location: URL, password: String) throws {} } diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/RestoreBackupView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/RestoreBackupView.swift index 63d3a821703..93a36bbac71 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/RestoreBackupView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/RestoreBackupView.swift @@ -24,6 +24,7 @@ import WireReusableUIComponents struct RestoreBackupView: View { @Environment(\.dismiss) private var dismiss private let importBackup: (String) -> Void + //private let importBackup1: (String) -> Void public init( importBackup: @escaping (String) -> Void diff --git a/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Localizable.strings b/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Localizable.strings index d80baaeb0ca..e5afac1b524 100644 --- a/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Localizable.strings +++ b/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Localizable.strings @@ -31,3 +31,10 @@ "restoreFromBackup.button" = "Continue"; "restoreFromBackup.enterPassword.title" = "Password"; "restoreFromBackup.enterPassword.error" = "Wrong password. Please verify your input and try again"; +//"restoreFromBackup.confirmation.title" = "Backup file will overwrite your history"; +//"restoreFromBackup.confirmation.description" = "The backup contents will replace the current conversation history on this device."; +//"restoreFromBackup.confirmation.cancelButton" = "Cancel"; +//"restoreFromBackup.confirmation.overrideButton" = "Override"; +"restoreFromBackup.successAlert.title" = "Success"; +"restoreFromBackup.successAlert.description" = "Your history is restored."; +"restoreFromBackup.successAlert.button" = "OK"; diff --git a/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack+Backup.swift b/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack+Backup.swift index 14ea9db02ac..e5a88a2f1ff 100644 --- a/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack+Backup.swift +++ b/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack+Backup.swift @@ -322,6 +322,7 @@ public extension CoreDataStack { accountIdentifier: accountIdentifier, applicationContainer: applicationContainer ) + let accountStoreFile = accountDirectory.appendingPersistentStoreLocation() let backupStoreFile = backupDirectory .appendingPathComponent(databaseDirectoryName) diff --git a/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack+Migration.swift b/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack+Migration.swift index ffe55d2963a..4a78d37ee85 100644 --- a/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack+Migration.swift +++ b/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack+Migration.swift @@ -107,6 +107,7 @@ extension CoreDataStack { let migrationStoreLocation = databaseDirectory.appendingStoreFile() let options = NSPersistentStoreCoordinator.persistentStoreOptions(supportsMigration: false) + // // Recreate the persistent store inside a new location try coordinator.replacePersistentStore( at: migrationStoreLocation, diff --git a/wire-ios-sync-engine/Source/SessionManager/SessionManager+APIVersionResolver.swift b/wire-ios-sync-engine/Source/SessionManager/SessionManager+APIVersionResolver.swift index c17e402a4f4..97638ffc809 100644 --- a/wire-ios-sync-engine/Source/SessionManager/SessionManager+APIVersionResolver.swift +++ b/wire-ios-sync-engine/Source/SessionManager/SessionManager+APIVersionResolver.swift @@ -60,7 +60,7 @@ extension SessionManager: APIVersionResolverDelegate { } } - private func migrateSessions(_ sessions: [ZMUserSession], to apiVersion: APIVersion) { + private func migrateSessions(_ sessions: [ZMUserSession], to apiVersion: APIVersion) {// delegate?.sessionManagerWillMigrateAccount { [weak self] in guard let self else { return } Task { diff --git a/wire-ios-sync-engine/Source/SessionManager/SessionManager+Backup.swift b/wire-ios-sync-engine/Source/SessionManager/SessionManager+Backup.swift index a277c7c44e6..ece6d310da5 100644 --- a/wire-ios-sync-engine/Source/SessionManager/SessionManager+Backup.swift +++ b/wire-ios-sync-engine/Source/SessionManager/SessionManager+Backup.swift @@ -115,13 +115,19 @@ extension SessionManager { } } - guard - let status = unauthenticatedSession?.authenticationStatus, - let userId = status.authenticatedUserIdentifier - else { +// guard +// let status = unauthenticatedSession?.authenticationStatus, +// let userId = status.authenticatedUserIdentifier +// else { +// return completion(.failure(BackupError.notAuthenticated)) +// } + + guard let account = activeUserSession?.account, + let userId = activeUserSession?.userId else { return completion(.failure(BackupError.notAuthenticated)) } + // Verify the imported file has the correct file extension. guard BackupFileExtensions.allCases.contains(where: { $0.rawValue == location.pathExtension @@ -165,13 +171,37 @@ extension SessionManager { return complete(.failure(BackupError.compressionError)) } - CoreDataStack.importLocalStorage( - accountIdentifier: userId, - from: url, - applicationContainer: sharedContainerURL, - dispatchGroup: dispatchGroup - ) { result in - completion(result.map { _ in }) + // CoreDataStack.importLocalStorage( + // accountIdentifier: userId, + // from: url, + // applicationContainer: sharedContainerURL, + // dispatchGroup: dispatchGroup + // ) { result in + // completion(result.map { _ in }) + // } + self.delegate?.sessionManagerWillMigrateAccount { [weak self] in + guard let self else { + return complete(.failure(NSError( + userSessionErrorCode: .unknownError, + userInfo: ["reason": "SessionManager.self is `nil` during session migration"] + ))) + } + + CoreDataStack.importLocalStorage( + accountIdentifier: userId, + from: url, + applicationContainer: self.sharedContainerURL, + dispatchGroup: self.dispatchGroup + ) { result in + switch result { + case .success: + self.activateSession(for: account) { sessionResult in + complete(.success(())) + } + case .failure(let error): + complete(.failure(error)) + } + } } } } @@ -180,7 +210,8 @@ extension SessionManager { at location: URL, password: String ) throws { - guard let userId = activeUserSession?.userId else { + guard let account = activeUserSession?.account, + let userId = activeUserSession?.userId else { throw BackupError.notAuthenticated } @@ -214,13 +245,32 @@ extension SessionManager { guard decryptedURL.unzip(to: url) else { throw BackupError.compressionError } + print("Account111: \(account)") + self.delegate?.sessionManagerWillMigrateAccount { [weak self] in + try? CoreDataStack.importLocalStorage( + accountIdentifier: userId, + from: url, + applicationContainer: self!.sharedContainerURL, + dispatchGroup: self!.dispatchGroup + ) + print("Account222: \(account)") + self?.activateSession(for: account) { session in + } + } - try CoreDataStack.importLocalStorage( - accountIdentifier: userId, - from: url, - applicationContainer: sharedContainerURL, - dispatchGroup: dispatchGroup - ) +// self.delegate?.sessionManagerWillMigrateAccount(userSessionCanBeTornDown: { +// do { +// try CoreDataStack.importLocalStorage( +// accountIdentifier: userId, +// from: url, +// applicationContainer: self.sharedContainerURL, +// dispatchGroup: self.dispatchGroup +// ) +// self.activateSession(for: account) { _ in } +// } catch { +// +// } +// }) } // MARK: - Encryption & Decryption diff --git a/wire-ios-sync-engine/Source/SessionManager/SessionManager.swift b/wire-ios-sync-engine/Source/SessionManager/SessionManager.swift index f131dbfb24a..ae3511118be 100644 --- a/wire-ios-sync-engine/Source/SessionManager/SessionManager.swift +++ b/wire-ios-sync-engine/Source/SessionManager/SessionManager.swift @@ -929,8 +929,9 @@ public final class SessionManager: NSObject, SessionManagerType { activateSession(for: account, completion: completion) } - fileprivate func activateSession(for account: Account, completion: @escaping (ZMUserSession) -> Void) { + func activateSession(for account: Account, completion: @escaping (ZMUserSession) -> Void) { withSession(for: account, notifyAboutMigration: true) { session in + print("session111: \(session)") self.activeUserSession = session WireLogger.sessionManager .debug("Activated ZMUserSession for account - \(account.userIdentifier.safeForLoggingDescription)") diff --git a/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+Authentication.swift b/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+Authentication.swift index d4aa229c787..53fb68746e8 100644 --- a/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+Authentication.swift +++ b/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+Authentication.swift @@ -47,7 +47,7 @@ extension ZMUserSession { let needsToRegisterMLSClient = ZMClientRegistrationStatus.needsToRegisterMLSClient(in: managedObjectContext) let waitingToRegisterMLSClient = needsToRegisterMLSClient && !hasCompletedInitialSync - return isAuthenticated && !needsToRegisterClient && !waitingToRegisterMLSClient + return true//isAuthenticated && !needsToRegisterClient && !waitingToRegisterMLSClient } /// `True` if the session has a valid authentication cookie diff --git a/wire-ios/Wire-iOS/Generated/Strings+Generated.swift b/wire-ios/Wire-iOS/Generated/Strings+Generated.swift index 2f1e2556866..5d7830a63fe 100644 --- a/wire-ios/Wire-iOS/Generated/Strings+Generated.swift +++ b/wire-ios/Wire-iOS/Generated/Strings+Generated.swift @@ -5130,6 +5130,18 @@ internal enum L10n { } } } + internal enum RestoreBackup { + internal enum Confirmation { + /// Cancel + internal static let cancelButton = L10n.tr("Localizable", "restore_backup.confirmation.cancel_button", fallback: "Cancel") + /// The backup contents will replace the current conversation history on this device. + internal static let description = L10n.tr("Localizable", "restore_backup.confirmation.description", fallback: "The backup contents will replace the current conversation history on this device.") + /// Override + internal static let overrideButton = L10n.tr("Localizable", "restore_backup.confirmation.override_button", fallback: "Override") + /// Backup file will overwrite your history + internal static let title = L10n.tr("Localizable", "restore_backup.confirmation.title", fallback: "Backup file will overwrite your history") + } + } internal enum RevokedCertificate { internal enum Alert { /// Continue Using This Device diff --git a/wire-ios/Wire-iOS/Resources/Base.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/Base.lproj/Localizable.strings index 644440bfc29..79e054f8e5b 100644 --- a/wire-ios/Wire-iOS/Resources/Base.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/Base.lproj/Localizable.strings @@ -1518,6 +1518,12 @@ "registration.no_history.restore_backup_failed.wrong_version.title" = "Incompatible backup"; "registration.no_history.restore_backup_failed.wrong_version.message" = "This backup was created by a newer or outdated version of Wire and cannot be restored here."; +// Restore backup +"restore_backup.confirmation.title" = "Backup file will overwrite your history"; +"restore_backup.confirmation.description" = "The backup contents will replace the current conversation history on this device."; +"restore_backup.confirmation.cancel_button" = "Cancel"; +"restore_backup.confirmation.override_button" = "Override"; + // SSO – Company Login "login.sso.alert.title" = "Enterprise Login"; "login.sso.alert.message.sso_and_email" = "Please enter your email or SSO code. If your email matches an enterprise installation of Wire, this app will connect to that server."; diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/RestoreSource.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/RestoreSource.swift index 6d3b0025e86..5414380322c 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/RestoreSource.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/RestoreSource.swift @@ -38,5 +38,14 @@ struct RestoreSource: RestoreSourceProtocol { throw RestoreBackupError.generic(error) } } - + + func restoreFromBackup(at location: URL, password: String, completion: @escaping (Result) -> Void) { + SessionManager.shared?.restoreFromBackup( + at: location, + password: password, + completion: { result in + print(result) + }) + } + } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift index afb550a6226..4c0fbaf1a53 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift @@ -390,7 +390,9 @@ extension SettingsCellDescriptorFactory { ), restoreBackupResultHandler: RestoreBackupResultHandler( onSuccess: { _ in }, - onFailure: presentAlert), + onConfirmation: presentConfirmationAlert, + onFailure: presentAlert + ), passwordValidator: BackupPasswordValidator() ) let backupActionsController = BackupActionsHostingController(viewModel: viewModel) @@ -498,4 +500,29 @@ extension SettingsCellDescriptorFactory { controller.present(activityController, animated: true) } + private func presentConfirmationAlert(completion: @escaping Completion) { + guard let controller = UIApplication.shared.topmostViewController(onlyFullScreen: false) else { + return + } + + let alert = UIAlertController( + title: L10n.Localizable.RestoreBackup.Confirmation.title, + message: L10n.Localizable.RestoreBackup.Confirmation.description, + preferredStyle: .alert + ) + + alert.addAction(UIAlertAction( + title: L10n.Localizable.RestoreBackup.Confirmation.cancelButton, + style: .cancel + )) + alert.addAction(UIAlertAction( + title: L10n.Localizable.RestoreBackup.Confirmation.overrideButton, + style: .default, handler: { _ in + completion() + })) + + controller.present(alert, animated: true) + } + + } From e900fd894c4e2a52b6c98fc915a900bb18f1b32b Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Thu, 9 Jan 2025 01:19:43 +0100 Subject: [PATCH 036/107] add snapshots --- .../Backup/Views/BackupActionsView.swift | 2 +- .../Backup/Views/PasswordFieldView.swift | 4 +- .../BackupActionsViewSnapshotTests.swift | 52 +++++++++++++++++++ .../SnapshotTestReferenceImageDirectory.swift | 23 ++++++++ 4 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 WireUI/Tests/WireSettingsUITests/Account/Backup/BackupActionsViewSnapshotTests.swift create mode 100644 WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/SnapshotTestReferenceImageDirectory.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift index bd124642a02..024885e57e0 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift @@ -37,7 +37,7 @@ public struct BackupActionsView: View { isBackupSheetPresented.toggle() }, label: { HStack { - Text(L10n.ExportBackup.title) + Text(L10n.Settings.ExportBackup.action) .font(.textStyle(.body2)) .foregroundStyle(Color.primaryText) Spacer() diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift index 92b29a8f1ff..535c73455e7 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift @@ -42,7 +42,7 @@ struct PasswordFieldView: View { RoundedRectangle(cornerRadius: 5) .stroke( isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color, - lineWidth: 1 + lineWidth: password.isEmpty ? 0 : 1 ) ) } else { @@ -52,7 +52,7 @@ struct PasswordFieldView: View { RoundedRectangle(cornerRadius: 5) .stroke( isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color, - lineWidth: 1 + lineWidth: password.isEmpty ? 0 : 11 ) ) } diff --git a/WireUI/Tests/WireSettingsUITests/Account/Backup/BackupActionsViewSnapshotTests.swift b/WireUI/Tests/WireSettingsUITests/Account/Backup/BackupActionsViewSnapshotTests.swift new file mode 100644 index 00000000000..a9e056b038c --- /dev/null +++ b/WireUI/Tests/WireSettingsUITests/Account/Backup/BackupActionsViewSnapshotTests.swift @@ -0,0 +1,52 @@ +// +// Wire +// Copyright (C) 2025 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import SwiftUI +import WireTestingPackage +import XCTest + +@testable import WireSettingsUI + +final class BackupActionsViewSnapshotTests: XCTestCase { + + private var snapshotHelper: SnapshotHelper! + + override func setUp() { + snapshotHelper = .init() + .withSnapshotDirectory(SnapshotTestReferenceImageDirectory) + } + + override func tearDown() { + snapshotHelper = nil + } + + @MainActor + func testBackupActions() { + let screenBounds = UIScreen.main.bounds + let sut = BackupActionsPreview() + .frame(width: screenBounds.width, height: screenBounds.height) + + snapshotHelper + .withUserInterfaceStyle(.light) + .verify(matching: sut, named: "light") + snapshotHelper + .withUserInterfaceStyle(.dark) + .verify(matching: sut, named: "dark") + } + +} diff --git a/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/SnapshotTestReferenceImageDirectory.swift b/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/SnapshotTestReferenceImageDirectory.swift new file mode 100644 index 00000000000..6b1997849e8 --- /dev/null +++ b/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/SnapshotTestReferenceImageDirectory.swift @@ -0,0 +1,23 @@ +// +// Wire +// Copyright (C) 2025 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import Foundation + +public let SnapshotTestReferenceImageDirectory = URL(fileURLWithPath: #filePath) + .deletingLastPathComponent() + .path From bb90d8a36ab8d451a910770558b8b0552836ab09 Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Thu, 9 Jan 2025 01:24:49 +0100 Subject: [PATCH 037/107] add snapshots --- .../BackupActionsViewSnapshotTests/testBackupActions.dark.png | 3 +++ .../BackupActionsViewSnapshotTests/testBackupActions.light.png | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.dark.png create mode 100644 WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.light.png diff --git a/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.dark.png b/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.dark.png new file mode 100644 index 00000000000..a6531e1a866 --- /dev/null +++ b/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:648a5994eee46beedd378c6d089b70008806ad8b215ae482edaf26cca12c9ba4 +size 92098 diff --git a/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.light.png b/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.light.png new file mode 100644 index 00000000000..7997101e34e --- /dev/null +++ b/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.light.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b7b78d0a13f807a3b05cbc0411bac8c201a5ffa5e112d0f61f6f44e451026ec0 +size 92317 From 28e07e909ceebecc1ec48d27e55cb0a94f148573 Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Thu, 9 Jan 2025 08:31:51 +0100 Subject: [PATCH 038/107] add snapshots and clean up --- .../Models/RestoreBackupResultHandler.swift | 4 +- .../ViewModels/BackupActionsViewModel.swift | 23 +++++--- .../Backup/Views/BackupActionsView.swift | 5 +- .../Views/Preview/BackupActionsPreview.swift | 8 +-- .../Views/Preview/RestoreBackupPreview.swift | 2 +- .../Backup/Views/RestoreBackupView.swift | 1 - .../Resources/en.lproj/Localizable.strings | 7 --- .../ExportBackupViewSnapshotTests.swift | 55 +++++++++++++++++++ .../RestoreBackupViewSnapshotTests.swift | 54 ++++++++++++++++++ .../testBackupActions.dark.png | 3 - .../testBackupActions.light.png | 3 - .../ZMUserSession+Authentication.swift | 2 +- .../Generated/Strings+Generated.swift | 8 +++ .../Resources/Base.lproj/Localizable.strings | 2 + .../Settings/Backup/RestoreSource.swift | 5 +- ...ettingsCellDescriptorFactory+Account.swift | 19 ++++++- 16 files changed, 166 insertions(+), 35 deletions(-) create mode 100644 WireUI/Tests/WireSettingsUITests/Account/Backup/ExportBackupViewSnapshotTests.swift create mode 100644 WireUI/Tests/WireSettingsUITests/Account/Backup/RestoreBackupViewSnapshotTests.swift delete mode 100644 WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.dark.png delete mode 100644 WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.light.png diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Models/RestoreBackupResultHandler.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Models/RestoreBackupResultHandler.swift index 46903504fdc..1b0ff2af590 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Models/RestoreBackupResultHandler.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Models/RestoreBackupResultHandler.swift @@ -17,12 +17,12 @@ // public struct RestoreBackupResultHandler { - let onSuccess: (@escaping () -> Void) -> Void + let onSuccess: () -> Void let onConfirmation: (@escaping () -> Void) -> Void let onFailure: (any Error) -> Void public init( - onSuccess: @escaping (@escaping () -> Void) -> Void, + onSuccess: @escaping () -> Void, onConfirmation: @escaping (@escaping () -> Void) -> Void, onFailure: @escaping (any Error) -> Void ) { diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift index 110bf2aa7c3..fd4d37342e1 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift @@ -53,22 +53,31 @@ public final class BackupActionsViewModel: ObservableObject { func restoreBackup(at url: URL, password: String) throws { do { try restoreSource.restoreFromBackup(at: url, password: password) - print("all good") + restoreBackupResultHandler.onSuccess() } catch let error as RestoreBackupError { switch error { case .decryptionError: - print("decryptionError") + return case let .generic(error): - print("generic") + restoreBackupResultHandler.onFailure(error) } } } - func restoreFromBackup(at location: URL, password: String, completion: @escaping (Result) -> Void) { - restoreSource.restoreFromBackup(at: location, password: password) { result in - print("AAAA: \(result)") + func restoreFromBackup( + at location: URL, + password: String, + completion: @escaping (Result) -> Void) { + restoreSource.restoreFromBackup(at: location, password: password) { result in + switch result { + case .success: + self.restoreBackupResultHandler.onSuccess() + case let .failure(error): + self.restoreBackupResultHandler.onFailure(error) + } + completion(result) + } } - } func confirmBackupRestore(completion: @escaping () -> Void) { restoreBackupResultHandler.onConfirmation { diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift index d3a81ea4881..88d7c3fea57 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift @@ -88,7 +88,10 @@ public struct BackupActionsView: View { NavigationStack { RestoreBackupView { password in if let fileURL = selectedFileURL { - viewModel.restoreFromBackup(at: fileURL, password: password, completion: {_ in }) + viewModel.restoreFromBackup( + at: fileURL, + password: password, + completion: { _ in }) // try? viewModel.restoreBackup( // at: fileURL, // password: password diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift index d4157c6eb5e..fe4c3a7d5c3 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift @@ -28,7 +28,7 @@ func BackupActionsPreview() -> some View { onFailure: { _ in } ), restoreBackupResultHandler: RestoreBackupResultHandler( - onSuccess: { _ in }, + onSuccess: {}, onConfirmation: { _ in }, onFailure: { _ in }), passwordValidator: MockBackupPasswordValidator() @@ -44,10 +44,8 @@ private class MockBackupSource: BackupSourceProtocol { } private class MockRestoreSource: RestoreSourceProtocol { - func restoreFromBackup(at location: URL, password: String, completion: @escaping (Result) -> Void) { - - } - + func restoreFromBackup(at location: URL, password: String, completion: @escaping (Result) -> Void) { } + func restoreFromBackup(at location: URL, password: String) throws {} } diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/RestoreBackupPreview.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/RestoreBackupPreview.swift index 71a3733a307..72e39c3aba2 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/RestoreBackupPreview.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/RestoreBackupPreview.swift @@ -33,7 +33,7 @@ struct RestoreBackupPreview: View { .sheet(isPresented: $isPresented) { NavigationStack { RestoreBackupView( - importBackup: { _ in } + importBackup: { _ in } ) } .presentationDragIndicator(.visible) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/RestoreBackupView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/RestoreBackupView.swift index 93a36bbac71..63d3a821703 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/RestoreBackupView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/RestoreBackupView.swift @@ -24,7 +24,6 @@ import WireReusableUIComponents struct RestoreBackupView: View { @Environment(\.dismiss) private var dismiss private let importBackup: (String) -> Void - //private let importBackup1: (String) -> Void public init( importBackup: @escaping (String) -> Void diff --git a/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Localizable.strings b/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Localizable.strings index e5afac1b524..d80baaeb0ca 100644 --- a/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Localizable.strings +++ b/WireUI/Sources/WireSettingsUI/Resources/en.lproj/Localizable.strings @@ -31,10 +31,3 @@ "restoreFromBackup.button" = "Continue"; "restoreFromBackup.enterPassword.title" = "Password"; "restoreFromBackup.enterPassword.error" = "Wrong password. Please verify your input and try again"; -//"restoreFromBackup.confirmation.title" = "Backup file will overwrite your history"; -//"restoreFromBackup.confirmation.description" = "The backup contents will replace the current conversation history on this device."; -//"restoreFromBackup.confirmation.cancelButton" = "Cancel"; -//"restoreFromBackup.confirmation.overrideButton" = "Override"; -"restoreFromBackup.successAlert.title" = "Success"; -"restoreFromBackup.successAlert.description" = "Your history is restored."; -"restoreFromBackup.successAlert.button" = "OK"; diff --git a/WireUI/Tests/WireSettingsUITests/Account/Backup/ExportBackupViewSnapshotTests.swift b/WireUI/Tests/WireSettingsUITests/Account/Backup/ExportBackupViewSnapshotTests.swift new file mode 100644 index 00000000000..f17178dba6a --- /dev/null +++ b/WireUI/Tests/WireSettingsUITests/Account/Backup/ExportBackupViewSnapshotTests.swift @@ -0,0 +1,55 @@ +// +// Wire +// Copyright (C) 2025 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import SwiftUI +import WireTestingPackage +import XCTest + +@testable import WireSettingsUI + +final class ExportBackupViewSnapshotTests: XCTestCase { + + private var snapshotHelper: SnapshotHelper! + + override func setUp() { + snapshotHelper = .init() + .withSnapshotDirectory(SnapshotTestReferenceImageDirectory) + } + + override func tearDown() { + snapshotHelper = nil + } + + @MainActor + func testBackupActions() { + let screenBounds = UIScreen.main.bounds + let sut = ExportBackupView( + passwordValidator: MockBackupPasswordValidator(), + exportBackup: { _ in } + ) + .frame(width: screenBounds.width, height: screenBounds.height) + + snapshotHelper + .withUserInterfaceStyle(.light) + .verify(matching: sut, named: "light") + snapshotHelper + .withUserInterfaceStyle(.dark) + .verify(matching: sut, named: "dark") + } + +} diff --git a/WireUI/Tests/WireSettingsUITests/Account/Backup/RestoreBackupViewSnapshotTests.swift b/WireUI/Tests/WireSettingsUITests/Account/Backup/RestoreBackupViewSnapshotTests.swift new file mode 100644 index 00000000000..bdf345aa2d3 --- /dev/null +++ b/WireUI/Tests/WireSettingsUITests/Account/Backup/RestoreBackupViewSnapshotTests.swift @@ -0,0 +1,54 @@ +// +// Wire +// Copyright (C) 2025 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import SwiftUI +import WireTestingPackage +import XCTest + +@testable import WireSettingsUI + +final class RestoreBackupViewSnapshotTests: XCTestCase { + + private var snapshotHelper: SnapshotHelper! + + override func setUp() { + snapshotHelper = .init() + .withSnapshotDirectory(SnapshotTestReferenceImageDirectory) + } + + override func tearDown() { + snapshotHelper = nil + } + + @MainActor + func testRestoreBackupSheet() { + let screenBounds = UIScreen.main.bounds + let sut = RestoreBackupView( + importBackup: { _ in } + ) + .frame(width: screenBounds.width, height: screenBounds.height) + + snapshotHelper + .withUserInterfaceStyle(.light) + .verify(matching: sut, named: "light") + snapshotHelper + .withUserInterfaceStyle(.dark) + .verify(matching: sut, named: "dark") + } + +} diff --git a/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.dark.png b/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.dark.png deleted file mode 100644 index a6531e1a866..00000000000 --- a/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.dark.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:648a5994eee46beedd378c6d089b70008806ad8b215ae482edaf26cca12c9ba4 -size 92098 diff --git a/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.light.png b/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.light.png deleted file mode 100644 index 7997101e34e..00000000000 --- a/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.light.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b7b78d0a13f807a3b05cbc0411bac8c201a5ffa5e112d0f61f6f44e451026ec0 -size 92317 diff --git a/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+Authentication.swift b/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+Authentication.swift index 53fb68746e8..5fc486118d8 100644 --- a/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+Authentication.swift +++ b/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+Authentication.swift @@ -47,7 +47,7 @@ extension ZMUserSession { let needsToRegisterMLSClient = ZMClientRegistrationStatus.needsToRegisterMLSClient(in: managedObjectContext) let waitingToRegisterMLSClient = needsToRegisterMLSClient && !hasCompletedInitialSync - return true//isAuthenticated && !needsToRegisterClient && !waitingToRegisterMLSClient + return isAuthenticated && !needsToRegisterClient && !waitingToRegisterMLSClient// } /// `True` if the session has a valid authentication cookie diff --git a/wire-ios/Wire-iOS/Generated/Strings+Generated.swift b/wire-ios/Wire-iOS/Generated/Strings+Generated.swift index 5d7830a63fe..bdf056417dc 100644 --- a/wire-ios/Wire-iOS/Generated/Strings+Generated.swift +++ b/wire-ios/Wire-iOS/Generated/Strings+Generated.swift @@ -5130,6 +5130,14 @@ internal enum L10n { } } } + internal enum RestoreFromBackup { + internal enum SuccessAlert { + /// Your history is restored. + internal static let description = L10n.tr("Localizable", "restoreFromBackup.successAlert.description", fallback: "Your history is restored.") + /// Success + internal static let title = L10n.tr("Localizable", "restoreFromBackup.successAlert.title", fallback: "Success") + } + } internal enum RestoreBackup { internal enum Confirmation { /// Cancel diff --git a/wire-ios/Wire-iOS/Resources/Base.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/Base.lproj/Localizable.strings index 79e054f8e5b..275c2d79f8a 100644 --- a/wire-ios/Wire-iOS/Resources/Base.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/Base.lproj/Localizable.strings @@ -1523,6 +1523,8 @@ "restore_backup.confirmation.description" = "The backup contents will replace the current conversation history on this device."; "restore_backup.confirmation.cancel_button" = "Cancel"; "restore_backup.confirmation.override_button" = "Override"; +"restoreFromBackup.successAlert.title" = "Success"; +"restoreFromBackup.successAlert.description" = "Your history is restored."; // SSO – Company Login "login.sso.alert.title" = "Enterprise Login"; diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/RestoreSource.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/RestoreSource.swift index 5414380322c..b71688cd820 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/RestoreSource.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/RestoreSource.swift @@ -43,9 +43,8 @@ struct RestoreSource: RestoreSourceProtocol { SessionManager.shared?.restoreFromBackup( at: location, password: password, - completion: { result in - print(result) - }) + completion: completion + ) } } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift index 4c0fbaf1a53..567f508e181 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift @@ -389,7 +389,7 @@ extension SettingsCellDescriptorFactory { onFailure: presentAlert ), restoreBackupResultHandler: RestoreBackupResultHandler( - onSuccess: { _ in }, + onSuccess: presentOnSuccessAlert, onConfirmation: presentConfirmationAlert, onFailure: presentAlert ), @@ -524,5 +524,22 @@ extension SettingsCellDescriptorFactory { controller.present(alert, animated: true) } + private func presentOnSuccessAlert() { + guard let controller = UIApplication.shared.topmostViewController(onlyFullScreen: false) else { + return + } + + let alert = UIAlertController( + title: L10n.Localizable.RestoreFromBackup.SuccessAlert.title, + message: L10n.Localizable.RestoreFromBackup.SuccessAlert.description, + preferredStyle: .alert + ) + alert.addAction(UIAlertAction( + title: L10n.Localizable.General.ok, + style: .cancel + )) + + controller.present(alert, animated: true) + } } From 8cacb059d24ff07288b0d9e994962909ada94a84 Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Thu, 9 Jan 2025 08:33:04 +0100 Subject: [PATCH 039/107] add snapshots --- .../BackupActionsViewSnapshotTests/testBackupActions.dark.png | 3 +++ .../BackupActionsViewSnapshotTests/testBackupActions.light.png | 3 +++ .../ExportBackupViewSnapshotTests/testBackupActions.dark.png | 3 +++ .../ExportBackupViewSnapshotTests/testBackupActions.light.png | 3 +++ .../testRestoreBackupSheet.dark.png | 3 +++ .../testRestoreBackupSheet.light.png | 3 +++ 6 files changed, 18 insertions(+) create mode 100644 WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.dark.png create mode 100644 WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.light.png create mode 100644 WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/ExportBackupViewSnapshotTests/testBackupActions.dark.png create mode 100644 WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/ExportBackupViewSnapshotTests/testBackupActions.light.png create mode 100644 WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/RestoreBackupViewSnapshotTests/testRestoreBackupSheet.dark.png create mode 100644 WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/RestoreBackupViewSnapshotTests/testRestoreBackupSheet.light.png diff --git a/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.dark.png b/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.dark.png new file mode 100644 index 00000000000..9fac45db4c2 --- /dev/null +++ b/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:003cb2ab1d92e9b8533c622c39e35c2fbdc964fd9eb5c79e0a5d42e86337ef50 +size 153275 diff --git a/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.light.png b/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.light.png new file mode 100644 index 00000000000..3ff9ff07055 --- /dev/null +++ b/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.light.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:bd3b47a1f34c42f3f74516d011d0177cf64ce9f029b6060e205ab97df315a4ec +size 153164 diff --git a/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/ExportBackupViewSnapshotTests/testBackupActions.dark.png b/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/ExportBackupViewSnapshotTests/testBackupActions.dark.png new file mode 100644 index 00000000000..c5e5119e8da --- /dev/null +++ b/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/ExportBackupViewSnapshotTests/testBackupActions.dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8d2700a43a4645477f6d12b2281bdeba3c0a4cfa4375ab10a0ba1868b418de63 +size 137951 diff --git a/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/ExportBackupViewSnapshotTests/testBackupActions.light.png b/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/ExportBackupViewSnapshotTests/testBackupActions.light.png new file mode 100644 index 00000000000..9e3c91f822d --- /dev/null +++ b/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/ExportBackupViewSnapshotTests/testBackupActions.light.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d16c74b833c6bf2cb2a163e284a3fe7c4a0287fc11f1c9e5b8222a326175ee7d +size 137678 diff --git a/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/RestoreBackupViewSnapshotTests/testRestoreBackupSheet.dark.png b/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/RestoreBackupViewSnapshotTests/testRestoreBackupSheet.dark.png new file mode 100644 index 00000000000..b606000ded9 --- /dev/null +++ b/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/RestoreBackupViewSnapshotTests/testRestoreBackupSheet.dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ac61fa377f264bcdc8f724eb5d1443bfe7a45cc7a78c0e554a0feac6d822dfb6 +size 98758 diff --git a/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/RestoreBackupViewSnapshotTests/testRestoreBackupSheet.light.png b/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/RestoreBackupViewSnapshotTests/testRestoreBackupSheet.light.png new file mode 100644 index 00000000000..5226af2521e --- /dev/null +++ b/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/RestoreBackupViewSnapshotTests/testRestoreBackupSheet.light.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:28581781238ed1e5a9543ef1ee64d8dc6af769c339df91b98f73474797bfbe74 +size 98341 From 318ed9b4a6a39853e3549b29e1862f423fc68ca1 Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Thu, 9 Jan 2025 08:51:45 +0100 Subject: [PATCH 040/107] fix typo --- .../WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift index 535c73455e7..29e53de4806 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift @@ -52,7 +52,7 @@ struct PasswordFieldView: View { RoundedRectangle(cornerRadius: 5) .stroke( isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color, - lineWidth: password.isEmpty ? 0 : 11 + lineWidth: password.isEmpty ? 0 : 1 ) ) } From c8ccf2ecb4d081ac138320ece4d3575347256f91 Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Thu, 9 Jan 2025 12:44:41 +0100 Subject: [PATCH 041/107] temp restore backup --- .../Backup/Models/BackupResultHandler.swift | 4 +- .../Models/RestoreBackupResultHandler.swift | 4 +- .../ViewModels/BackupActionsViewModel.swift | 12 ++--- .../Backup/Views/BackupActionsView.swift | 4 -- .../Views/Preview/BackupActionsPreview.swift | 4 +- .../CoreDataStackTests+Backup.swift | 2 +- .../SessionManager+Backup.swift | 48 +++++++++---------- .../SessionManager/SessionManager.swift | 1 - .../ZMClientRegistrationStatus.swift | 4 +- .../Generated/Strings+Generated.swift | 24 ++++++---- .../Resources/Base.lproj/Localizable.strings | 6 ++- ...ettingsCellDescriptorFactory+Account.swift | 22 ++++++--- 12 files changed, 74 insertions(+), 61 deletions(-) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupResultHandler.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupResultHandler.swift index c94f60a817c..ca0c420704b 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupResultHandler.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupResultHandler.swift @@ -20,11 +20,11 @@ import Foundation public struct BackupResultHandler { let onSuccess: (URL, @escaping () -> Void) -> Void - let onFailure: (any Error) -> Void + let onFailure: () -> Void public init( onSuccess: @escaping (URL, @escaping () -> Void) -> Void, - onFailure: @escaping (any Error) -> Void + onFailure: @escaping () -> Void ) { self.onSuccess = onSuccess self.onFailure = onFailure diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Models/RestoreBackupResultHandler.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Models/RestoreBackupResultHandler.swift index 1b0ff2af590..bd8158e28a6 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Models/RestoreBackupResultHandler.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Models/RestoreBackupResultHandler.swift @@ -19,12 +19,12 @@ public struct RestoreBackupResultHandler { let onSuccess: () -> Void let onConfirmation: (@escaping () -> Void) -> Void - let onFailure: (any Error) -> Void + let onFailure: () -> Void public init( onSuccess: @escaping () -> Void, onConfirmation: @escaping (@escaping () -> Void) -> Void, - onFailure: @escaping (any Error) -> Void + onFailure: @escaping () -> Void ) { self.onSuccess = onSuccess self.onConfirmation = onConfirmation diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift index fd4d37342e1..b3cf3f17fc1 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift @@ -46,20 +46,20 @@ public final class BackupActionsViewModel: ObservableObject { self?.backupSource.clearPreviousBackups() } } catch { - backupResultHandler.onFailure(error) + backupResultHandler.onFailure() } } func restoreBackup(at url: URL, password: String) throws { do { try restoreSource.restoreFromBackup(at: url, password: password) - restoreBackupResultHandler.onSuccess() + //restoreBackupResultHandler.onSuccess() } catch let error as RestoreBackupError { switch error { case .decryptionError: return - case let .generic(error): - restoreBackupResultHandler.onFailure(error) + case .generic: + restoreBackupResultHandler.onFailure() } } } @@ -72,8 +72,8 @@ public final class BackupActionsViewModel: ObservableObject { switch result { case .success: self.restoreBackupResultHandler.onSuccess() - case let .failure(error): - self.restoreBackupResultHandler.onFailure(error) + case .failure: + self.restoreBackupResultHandler.onFailure() } completion(result) } diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift index 88d7c3fea57..88e4ef90edb 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift @@ -92,10 +92,6 @@ public struct BackupActionsView: View { at: fileURL, password: password, completion: { _ in }) -// try? viewModel.restoreBackup( -// at: fileURL, -// password: password -// ) } } } diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift index fe4c3a7d5c3..d4ff869a678 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift @@ -25,12 +25,12 @@ func BackupActionsPreview() -> some View { restoreSource: MockRestoreSource(), backupResultHandler: BackupResultHandler( onSuccess: { _, _ in }, - onFailure: { _ in } + onFailure: {} ), restoreBackupResultHandler: RestoreBackupResultHandler( onSuccess: {}, onConfirmation: { _ in }, - onFailure: { _ in }), + onFailure: {}), passwordValidator: MockBackupPasswordValidator() )) } diff --git a/wire-ios-data-model/Tests/Source/ManagedObjectContext/CoreDataStackTests+Backup.swift b/wire-ios-data-model/Tests/Source/ManagedObjectContext/CoreDataStackTests+Backup.swift index 7eb0da44153..56fea50b0aa 100644 --- a/wire-ios-data-model/Tests/Source/ManagedObjectContext/CoreDataStackTests+Backup.swift +++ b/wire-ios-data-model/Tests/Source/ManagedObjectContext/CoreDataStackTests+Backup.swift @@ -260,7 +260,7 @@ final class CoreDataStackTests_Backup: DatabaseBaseTest { XCTAssertEqual(try directory.viewContext.count(for: fetchConversations), 1) } - func testThatMetadataIsDeletedWhenImportingBackup() throws { + func testThatMetadataIsDeletedWhenImportingBackup() throws {// // given let uuid = UUID() let directory = createStorageStackAndWaitForCompletion(userID: uuid) diff --git a/wire-ios-sync-engine/Source/SessionManager/SessionManager+Backup.swift b/wire-ios-sync-engine/Source/SessionManager/SessionManager+Backup.swift index ece6d310da5..d1bc7d43f3e 100644 --- a/wire-ios-sync-engine/Source/SessionManager/SessionManager+Backup.swift +++ b/wire-ios-sync-engine/Source/SessionManager/SessionManager+Backup.swift @@ -123,11 +123,12 @@ extension SessionManager { // } guard let account = activeUserSession?.account, - let userId = activeUserSession?.userId else { + let userId = activeUserSession?.userId, + let clientId = activeUserSession?.selfUserClient?.remoteIdentifier + else { return completion(.failure(BackupError.notAuthenticated)) } - // Verify the imported file has the correct file extension. guard BackupFileExtensions.allCases.contains(where: { $0.rawValue == location.pathExtension @@ -179,27 +180,28 @@ extension SessionManager { // ) { result in // completion(result.map { _ in }) // } - self.delegate?.sessionManagerWillMigrateAccount { [weak self] in - guard let self else { - return complete(.failure(NSError( - userSessionErrorCode: .unknownError, - userInfo: ["reason": "SessionManager.self is `nil` during session migration"] - ))) - } - - CoreDataStack.importLocalStorage( - accountIdentifier: userId, - from: url, - applicationContainer: self.sharedContainerURL, - dispatchGroup: self.dispatchGroup - ) { result in - switch result { - case .success: - self.activateSession(for: account) { sessionResult in - complete(.success(())) + let sharedContainerURL = sharedContainerURL + let dispatchGroup = dispatchGroup + delegate?.sessionManagerWillMigrateAccount { [weak self] in + self?.tearDownBackgroundSession(for: account.userIdentifier) { + self?.activeUserSession = nil + CoreDataStack.importLocalStorage( + accountIdentifier: userId, + from: url, + applicationContainer: sharedContainerURL, + dispatchGroup: dispatchGroup + ) { result in + + switch result { + case .success: + self?.activateSession(for: account) { session in + session.managedObjectContext.setPersistentStoreMetadata(clientId, key: ZMPersistedClientIdKey) + session.managedObjectContext.saveOrRollback() + } + case .failure(let error): + WireLogger.apiMigration.error("failed to migrate account: \(error)") + //complete(.failure(error)) } - case .failure(let error): - complete(.failure(error)) } } } @@ -245,7 +247,6 @@ extension SessionManager { guard decryptedURL.unzip(to: url) else { throw BackupError.compressionError } - print("Account111: \(account)") self.delegate?.sessionManagerWillMigrateAccount { [weak self] in try? CoreDataStack.importLocalStorage( accountIdentifier: userId, @@ -253,7 +254,6 @@ extension SessionManager { applicationContainer: self!.sharedContainerURL, dispatchGroup: self!.dispatchGroup ) - print("Account222: \(account)") self?.activateSession(for: account) { session in } } diff --git a/wire-ios-sync-engine/Source/SessionManager/SessionManager.swift b/wire-ios-sync-engine/Source/SessionManager/SessionManager.swift index d3af99e84dd..3fac1cf4112 100644 --- a/wire-ios-sync-engine/Source/SessionManager/SessionManager.swift +++ b/wire-ios-sync-engine/Source/SessionManager/SessionManager.swift @@ -931,7 +931,6 @@ public final class SessionManager: NSObject, SessionManagerType { func activateSession(for account: Account, completion: @escaping (ZMUserSession) -> Void) { withSession(for: account, notifyAboutMigration: true) { session in - print("session111: \(session)") self.activeUserSession = session WireLogger.sessionManager .debug("Activated ZMUserSession for account - \(account.userIdentifier.safeForLoggingDescription)") diff --git a/wire-ios-sync-engine/Source/UserSession/ZMClientRegistrationStatus.swift b/wire-ios-sync-engine/Source/UserSession/ZMClientRegistrationStatus.swift index 4e55434d8fe..65c719f8803 100644 --- a/wire-ios-sync-engine/Source/UserSession/ZMClientRegistrationStatus.swift +++ b/wire-ios-sync-engine/Source/UserSession/ZMClientRegistrationStatus.swift @@ -267,7 +267,7 @@ public class ZMClientRegistrationStatus: NSObject, ClientRegistrationDelegate { Self.needsToRegisterMLSClient(in: managedObjectContext) } - @objc(needsToRegisterClientInContext:) + @objc(needsToRegisterClientInContext:)// public static func needsToRegisterClient(in context: NSManagedObjectContext) -> Bool { // replace with selfUser.client.remoteIdentifier == nil if let clientID = context.persistentStoreMetadata(forKey: ZMPersistedClientIdKey) as? String { @@ -481,7 +481,7 @@ public class ZMClientRegistrationStatus: NSObject, ClientRegistrationDelegate { } @objc(didRegisterProteusClient:) - public func didRegisterProteusClient(_ client: UserClient) { + public func didRegisterProteusClient(_ client: UserClient) {// WireLogger.authentication.info("Did register proteus client") managedObjectContext.setPersistentStoreMetadata(client.remoteIdentifier, key: ZMPersistedClientIdKey) diff --git a/wire-ios/Wire-iOS/Generated/Strings+Generated.swift b/wire-ios/Wire-iOS/Generated/Strings+Generated.swift index bdf056417dc..cb07eabb95a 100644 --- a/wire-ios/Wire-iOS/Generated/Strings+Generated.swift +++ b/wire-ios/Wire-iOS/Generated/Strings+Generated.swift @@ -3285,6 +3285,12 @@ internal enum L10n { internal static let verificationCodeTooMany = L10n.tr("Localizable", "error.user.verification_code_too_many", fallback: "We already sent you a verification code. Tap Resend after 10 minutes to get a new one.") } } + internal enum ExportBackup { + internal enum Failed { + /// The backup could not be created. Please try again or contact Wire support. + internal static let message = L10n.tr("Localizable", "export_backup.failed.message", fallback: "The backup could not be created. Please try again or contact Wire support.") + } + } internal enum FailedToGetCertificate { internal enum Alert { /// Please try again, or reach out to your team admin. @@ -5130,14 +5136,6 @@ internal enum L10n { } } } - internal enum RestoreFromBackup { - internal enum SuccessAlert { - /// Your history is restored. - internal static let description = L10n.tr("Localizable", "restoreFromBackup.successAlert.description", fallback: "Your history is restored.") - /// Success - internal static let title = L10n.tr("Localizable", "restoreFromBackup.successAlert.title", fallback: "Success") - } - } internal enum RestoreBackup { internal enum Confirmation { /// Cancel @@ -5149,6 +5147,16 @@ internal enum L10n { /// Backup file will overwrite your history internal static let title = L10n.tr("Localizable", "restore_backup.confirmation.title", fallback: "Backup file will overwrite your history") } + internal enum Failed { + /// The backup could not be restored. Please try again or contact Wire support. + internal static let message = L10n.tr("Localizable", "restore_backup.failed.message", fallback: "The backup could not be restored. Please try again or contact Wire support.") + } + internal enum SuccessAlert { + /// Your history is restored. + internal static let description = L10n.tr("Localizable", "restore_backup.successAlert.description", fallback: "Your history is restored.") + /// Success + internal static let title = L10n.tr("Localizable", "restore_backup.successAlert.title", fallback: "Success") + } } internal enum RevokedCertificate { internal enum Alert { diff --git a/wire-ios/Wire-iOS/Resources/Base.lproj/Localizable.strings b/wire-ios/Wire-iOS/Resources/Base.lproj/Localizable.strings index 275c2d79f8a..1239943a76b 100644 --- a/wire-ios/Wire-iOS/Resources/Base.lproj/Localizable.strings +++ b/wire-ios/Wire-iOS/Resources/Base.lproj/Localizable.strings @@ -1523,8 +1523,10 @@ "restore_backup.confirmation.description" = "The backup contents will replace the current conversation history on this device."; "restore_backup.confirmation.cancel_button" = "Cancel"; "restore_backup.confirmation.override_button" = "Override"; -"restoreFromBackup.successAlert.title" = "Success"; -"restoreFromBackup.successAlert.description" = "Your history is restored."; +"restore_backup.successAlert.title" = "Success"; +"restore_backup.successAlert.description" = "Your history is restored."; +"restore_backup.failed.message" = "The backup could not be restored. Please try again or contact Wire support."; +"export_backup.failed.message" = "The backup could not be created. Please try again or contact Wire support."; // SSO – Company Login "login.sso.alert.title" = "Enterprise Login"; diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift index 567f508e181..7c7698d496a 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift @@ -386,12 +386,12 @@ extension SettingsCellDescriptorFactory { restoreSource: RestoreSource(), backupResultHandler: BackupResultHandler( onSuccess: presentShareSheet, - onFailure: presentAlert + onFailure: presentExportBackupErrorAlert ), restoreBackupResultHandler: RestoreBackupResultHandler( onSuccess: presentOnSuccessAlert, onConfirmation: presentConfirmationAlert, - onFailure: presentAlert + onFailure: presentRestoreBackupErrorAlert ), passwordValidator: BackupPasswordValidator() ) @@ -470,14 +470,14 @@ extension SettingsCellDescriptorFactory { extension SettingsCellDescriptorFactory { - private func presentAlert(for error: Error) { + private func presentErrorAlert(errorMessage: String) { guard let controller = UIApplication.shared.topmostViewController(onlyFullScreen: false) else { return } let alert = UIAlertController( - title: L10n.Localizable.Self.Settings.HistoryBackup.Error.title, - message: error.localizedDescription, + title: L10n.Localizable.General.failure, + message: errorMessage, preferredStyle: .alert ) alert.addAction(UIAlertAction( @@ -488,6 +488,14 @@ extension SettingsCellDescriptorFactory { controller.present(alert, animated: true) } + private func presentExportBackupErrorAlert() { + presentErrorAlert(errorMessage: L10n.Localizable.ExportBackup.Failed.message) + } + + private func presentRestoreBackupErrorAlert() { + presentErrorAlert(errorMessage: L10n.Localizable.RestoreBackup.Failed.message) + } + private func presentShareSheet(with url: URL, completion: @escaping Completion) { guard let controller = UIApplication.shared.topmostViewController(onlyFullScreen: false) else { return @@ -530,8 +538,8 @@ extension SettingsCellDescriptorFactory { } let alert = UIAlertController( - title: L10n.Localizable.RestoreFromBackup.SuccessAlert.title, - message: L10n.Localizable.RestoreFromBackup.SuccessAlert.description, + title: L10n.Localizable.RestoreBackup.SuccessAlert.title, + message: L10n.Localizable.RestoreBackup.SuccessAlert.description, preferredStyle: .alert ) alert.addAction(UIAlertAction( From 9590683f71c7fe4c676a8dcd72628f6a99729175 Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Fri, 10 Jan 2025 09:49:50 +0100 Subject: [PATCH 042/107] chore: enable SwiftGen for Accessibility.strings - WPB-15211 (#2372) --- WireUI/Sources/WireSettingsUI/.swiftgen.yml | 1 + .../Backup/Views/BackupActionsView.swift | 4 +- .../Backup/Views/ExportBackupView.swift | 8 ++-- .../Backup/Views/PasswordFieldView.swift | 14 +++---- WireUI/Sources/WireSidebarUI/.swiftgen.yml | 1 + .../Views/SidebarAccountInfoView.swift | 4 +- .../WireSidebarUI/Views/SidebarView.swift | 42 +++++++++---------- 7 files changed, 36 insertions(+), 38 deletions(-) diff --git a/WireUI/Sources/WireSettingsUI/.swiftgen.yml b/WireUI/Sources/WireSettingsUI/.swiftgen.yml index 668e9ff4faf..1c04a15290b 100644 --- a/WireUI/Sources/WireSettingsUI/.swiftgen.yml +++ b/WireUI/Sources/WireSettingsUI/.swiftgen.yml @@ -7,6 +7,7 @@ output_dir: ${GENERATED}/ strings: inputs: + - Resources/en.lproj/Accessibility.strings - Resources/en.lproj/Localizable.strings filter: outputs: diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift index 024885e57e0..ed386bb387d 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift @@ -31,13 +31,13 @@ public struct BackupActionsView: View { public var body: some View { List { Section( - footer: Text(L10n.ExportBackup.description) + footer: Text(L10n.Localizable.ExportBackup.description) ) { Button(action: { isBackupSheetPresented.toggle() }, label: { HStack { - Text(L10n.Settings.ExportBackup.action) + Text(L10n.Localizable.Settings.ExportBackup.action) .font(.textStyle(.body2)) .foregroundStyle(Color.primaryText) Spacer() diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift index 937f96ff88f..925cb54e6d6 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift @@ -45,7 +45,7 @@ public struct ExportBackupView: View { .background(Color.viewBackground) .scrollContentBackground(.hidden) .navigationTitle( - Text(L10n.ExportBackup.title) + Text(L10n.Localizable.ExportBackup.title) ) .navigationBarTitleDisplayMode(.inline) .toolbar { @@ -86,7 +86,7 @@ private struct SetBackupPasswordView: View { var body: some View { VStack(spacing: 20) { - Text(L10n.ExportBackup.description) + Text(L10n.Localizable.ExportBackup.description) .font(.textStyle(.body1)) .foregroundStyle(Color.primaryText) .multilineTextAlignment(.leading) @@ -107,7 +107,7 @@ private struct SetBackupPasswordView: View { dismiss() }, label: { - Text(L10n.ExportBackup.button) + Text(L10n.Localizable.ExportBackup.button) } ) .wireButtonStyle(.primary) @@ -134,7 +134,7 @@ private struct ExportBackupPreview: View { isPresented.toggle() }, label: { - Text(L10n.ExportBackup.button) + Text(L10n.Localizable.ExportBackup.button) } ) .sheet(isPresented: $isPresented) { diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift index 29e53de4806..3054dd4ead2 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift @@ -27,14 +27,14 @@ struct PasswordFieldView: View { var body: some View { VStack(alignment: .leading, spacing: 8) { - Text(L10n.ExportBackup.SetBackupPassword.title) + Text(L10n.Localizable.ExportBackup.SetBackupPassword.title) .font(.subheadline) .foregroundColor(isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color) ZStack { if isPasswordVisible { TextField( - L10n.ExportBackup.SetBackupPassword.placeholder, text: $password + L10n.Localizable.ExportBackup.SetBackupPassword.placeholder, text: $password ) .font(.textStyle(.body1)) .textFieldStyle(RoundedBorderTextFieldStyle()) @@ -46,7 +46,7 @@ struct PasswordFieldView: View { ) ) } else { - SecureField(L10n.ExportBackup.SetBackupPassword.placeholder, text: $password) + SecureField(L10n.Localizable.ExportBackup.SetBackupPassword.placeholder, text: $password) .textFieldStyle(RoundedBorderTextFieldStyle()) .overlay( RoundedRectangle(cornerRadius: 5) @@ -84,7 +84,7 @@ struct PasswordFieldView: View { password: .constant(""), isPasswordValid: false, isPasswordVisible: .constant(false), - passwordRules: Text(L10n.ExportBackup.SetBackupPassword.rules) + passwordRules: Text(L10n.Localizable.ExportBackup.SetBackupPassword.rules) ) } @@ -94,7 +94,7 @@ struct PasswordFieldView: View { password: .constant("ValidPassword1!"), isPasswordValid: false, isPasswordVisible: .constant(true), - passwordRules: Text(L10n.ExportBackup.SetBackupPassword.rules) + passwordRules: Text(L10n.Localizable.ExportBackup.SetBackupPassword.rules) ) } @@ -104,7 +104,7 @@ struct PasswordFieldView: View { password: .constant("ValidPassword1!"), isPasswordValid: true, isPasswordVisible: .constant(false), - passwordRules: Text(L10n.ExportBackup.SetBackupPassword.rules) + passwordRules: Text(L10n.Localizable.ExportBackup.SetBackupPassword.rules) ) } @@ -114,6 +114,6 @@ struct PasswordFieldView: View { password: .constant("ValidPassword1!"), isPasswordValid: true, isPasswordVisible: .constant(true), - passwordRules: Text(L10n.ExportBackup.SetBackupPassword.rules) + passwordRules: Text(L10n.Localizable.ExportBackup.SetBackupPassword.rules) ) } diff --git a/WireUI/Sources/WireSidebarUI/.swiftgen.yml b/WireUI/Sources/WireSidebarUI/.swiftgen.yml index 668e9ff4faf..1c04a15290b 100644 --- a/WireUI/Sources/WireSidebarUI/.swiftgen.yml +++ b/WireUI/Sources/WireSidebarUI/.swiftgen.yml @@ -7,6 +7,7 @@ output_dir: ${GENERATED}/ strings: inputs: + - Resources/en.lproj/Accessibility.strings - Resources/en.lproj/Localizable.strings filter: outputs: diff --git a/WireUI/Sources/WireSidebarUI/Views/SidebarAccountInfoView.swift b/WireUI/Sources/WireSidebarUI/Views/SidebarAccountInfoView.swift index da8165fbd8c..1d849475988 100644 --- a/WireUI/Sources/WireSidebarUI/Views/SidebarAccountInfoView.swift +++ b/WireUI/Sources/WireSidebarUI/Views/SidebarAccountInfoView.swift @@ -71,7 +71,7 @@ struct SidebarAccountInfoView: @ViewBuilder private var scrollableMenuItems: some View { VStack(alignment: .leading, spacing: 0) { - menuItemHeader(L10n.Sidebar.ConversationFilter.title, addTopPadding: false) + menuItemHeader(L10n.Localizable.Sidebar.ConversationFilter.title, addTopPadding: false) let conversationFilters: [SidebarSelectableMenuItem] = [ .all, .favorites, @@ -131,7 +131,7 @@ public struct SidebarView: selectableMenuItem(conversationFilter) } - menuItemHeader(L10n.Sidebar.Contacts.title) + menuItemHeader(L10n.Localizable.Sidebar.Contacts.title) nonselectableMenuItem(.connect) } .padding(.horizontal, 16) @@ -160,15 +160,15 @@ public struct SidebarView: let action: () -> Void switch menuItem { case .connect: - text = Text(L10n.Sidebar.Contacts.Connect.title) + text = Text(L10n.Localizable.Sidebar.Contacts.Connect.title) accessibilityLabel = Text("sidebar.contacts.connect.title", bundle: .module) icon = "person.badge.plus" isLink = false action = connectAction case .support: - text = Text(L10n.Sidebar.Support.title) - accessibilityLabel = Text("sidebar.support.description", tableName: "Accessibility", bundle: .module) + text = Text(L10n.Localizable.Sidebar.Support.title) + accessibilityLabel = Text(L10n.Accessibility.Sidebar.Support.description) icon = "questionmark.circle" isLink = true action = supportAction @@ -204,43 +204,39 @@ public struct SidebarView: let accessibilityLabel: Text switch menuItem { case .all: - text = Text(L10n.Sidebar.ConversationFilter.All.title) + text = Text(L10n.Localizable.Sidebar.ConversationFilter.All.title) icon = "text.bubble" - accessibilityLabel = Text(L10n.Sidebar.ConversationFilter.All.title) + accessibilityLabel = Text(L10n.Localizable.Sidebar.ConversationFilter.All.title) case .favorites: - text = Text(L10n.Sidebar.ConversationFilter.Favorites.title) + text = Text(L10n.Localizable.Sidebar.ConversationFilter.Favorites.title) icon = "star" - accessibilityLabel = Text(L10n.Sidebar.ConversationFilter.Favorites.title) + accessibilityLabel = Text(L10n.Localizable.Sidebar.ConversationFilter.Favorites.title) case .groups: - text = Text(L10n.Sidebar.ConversationFilter.Groups.title) + text = Text(L10n.Localizable.Sidebar.ConversationFilter.Groups.title) icon = "person.3" - accessibilityLabel = Text(L10n.Sidebar.ConversationFilter.Groups.title) + accessibilityLabel = Text(L10n.Localizable.Sidebar.ConversationFilter.Groups.title) case .oneOnOne: - text = Text(L10n.Sidebar.ConversationFilter.OneOnOneConversations.title) + text = Text(L10n.Localizable.Sidebar.ConversationFilter.OneOnOneConversations.title) icon = "person" - accessibilityLabel = Text( - "sidebar.conversation_filter.oneOnOneConversations.description", - tableName: "Accessibility", - bundle: .module - ) + accessibilityLabel = Text(L10n.Accessibility.Sidebar.ConversationFilter.OneOnOneConversations.description) case .folders: - text = Text(L10n.Sidebar.ConversationFilter.Folders.title) + text = Text(L10n.Localizable.Sidebar.ConversationFilter.Folders.title) icon = "folder" - accessibilityLabel = Text(L10n.Sidebar.ConversationFilter.Folders.title) + accessibilityLabel = Text(L10n.Localizable.Sidebar.ConversationFilter.Folders.title) case .archive: - text = Text(L10n.Sidebar.ConversationFilter.Archived.title) + text = Text(L10n.Localizable.Sidebar.ConversationFilter.Archived.title) icon = "archivebox" - accessibilityLabel = Text(L10n.Sidebar.ConversationFilter.Archived.title) + accessibilityLabel = Text(L10n.Localizable.Sidebar.ConversationFilter.Archived.title) case .settings: - text = Text(L10n.Sidebar.Settings.title) + text = Text(L10n.Localizable.Sidebar.Settings.title) icon = "gearshape" - accessibilityLabel = Text("sidebar.settings.description", tableName: "Accessibility", bundle: .module) + accessibilityLabel = Text(L10n.Accessibility.Sidebar.Settings.description) } return SidebarMenuItemView( From a6fc9f6569fc8526173898f740a5c1216342943f Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Fri, 10 Jan 2025 16:11:30 +0100 Subject: [PATCH 043/107] feat: support large fonts --- .../Backup/Views/BackupActionsView.swift | 8 +- .../Backup/Views/ExportBackupView.swift | 101 ++++++++---------- .../Backup/Views/PasswordFieldView.swift | 43 ++++---- .../Views/Preview/ExportBackupPreview.swift | 49 +++++++++ 4 files changed, 122 insertions(+), 79 deletions(-) create mode 100644 WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/ExportBackupPreview.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift index ed386bb387d..eadc820af80 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift @@ -38,7 +38,7 @@ public struct BackupActionsView: View { }, label: { HStack { Text(L10n.Localizable.Settings.ExportBackup.action) - .font(.textStyle(.body2)) + .wireTextStyle(.body2) .foregroundStyle(Color.primaryText) Spacer() Image(systemName: "chevron.right").foregroundStyle(Color.primary) @@ -53,12 +53,14 @@ public struct BackupActionsView: View { } ) } - .presentationDetents([.medium, .large]) + .presentationDetents([.medium]) } } + .listRowBackground(Color(ColorTheme.Backgrounds.surface)) } .listStyle(.grouped) - .listRowBackground(Color(ColorTheme.Backgrounds.background)) + .background(Color(ColorTheme.Backgrounds.background)) + .scrollContentBackground(.hidden) } } diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift index 925cb54e6d6..32a5f97adb9 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift @@ -52,11 +52,7 @@ public struct ExportBackupView: View { ToolbarItem(placement: .topBarTrailing) { CloseButton( action: didTapClose, - accessibilityLabel: String( - localized: "setBackupPassword.close.label", - table: "Accessibility", - bundle: .module - ) + accessibilityLabel: L10n.Accessibility.SetBackupPassword.Close.label ) } } @@ -72,6 +68,7 @@ private struct SetBackupPasswordView: View { @Environment(\.dismiss) var dismiss @State private var password: String = "" @State private var isPasswordVisible: Bool = false + @State private var contentFits: Bool = true private let passwordValidator: any BackupPasswordValidatorProtocol private let exportBackup: (String) -> Void @@ -85,37 +82,50 @@ private struct SetBackupPasswordView: View { } var body: some View { - VStack(spacing: 20) { - Text(L10n.Localizable.ExportBackup.description) - .font(.textStyle(.body1)) - .foregroundStyle(Color.primaryText) - .multilineTextAlignment(.leading) - .padding(.horizontal) - - PasswordFieldView( - password: $password, - isPasswordValid: passwordValidator.isPasswordValid(password), - isPasswordVisible: $isPasswordVisible, - passwordRules: Text(passwordValidator.localizedRulesDescription) - ) - - Spacer() - - Button( - action: { - exportBackup(password) - dismiss() - }, - label: { - Text(L10n.Localizable.ExportBackup.button) + GeometryReader { geometry in + VStack { + ScrollView { + VStack(spacing: 20) { + Text(L10n.Localizable.ExportBackup.description) + .wireTextStyle(.body1) + .foregroundStyle(Color.primaryText) + .multilineTextAlignment(.leading) + .padding(.horizontal) + + PasswordFieldView( + password: $password, + isPasswordValid: passwordValidator.isPasswordValid(password), + isPasswordVisible: $isPasswordVisible, + passwordRules: Text(passwordValidator.localizedRulesDescription) + ) + } + .background( + GeometryReader { contentGeometry in + Color.clear.onAppear { + contentFits = contentGeometry.size.height <= geometry.size.height + } + }) + .frame(maxWidth: .infinity) } - ) - .wireButtonStyle(.primary) - .padding() + .scrollDisabled(contentFits) + + Spacer() + + Button( + action: { + exportBackup(password) + dismiss() + }, + label: { + Text(L10n.Localizable.ExportBackup.button) + } + ) + .disabled(!passwordValidator.isPasswordValid(password)) + .wireButtonStyle(.primary) + .padding() + } } - .frame(maxHeight: .infinity) } - } // MARK: - Previews @@ -124,28 +134,3 @@ private struct SetBackupPasswordView: View { #Preview("Export Backup sheet") { ExportBackupPreview() } - -private struct ExportBackupPreview: View { - @State private var isPresented = true - - var body: some View { - Button( - action: { - isPresented.toggle() - }, - label: { - Text(L10n.Localizable.ExportBackup.button) - } - ) - .sheet(isPresented: $isPresented) { - NavigationStack { - ExportBackupView( - passwordValidator: MockBackupPasswordValidator(), - exportBackup: { _ in } - ) - } - .presentationDragIndicator(.visible) - .presentationDetents([.medium, .large]) - } - } -} diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift index 3054dd4ead2..4c6cdfa4470 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift @@ -29,32 +29,19 @@ struct PasswordFieldView: View { VStack(alignment: .leading, spacing: 8) { Text(L10n.Localizable.ExportBackup.SetBackupPassword.title) .font(.subheadline) - .foregroundColor(isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color) + .foregroundColor(calculatedColor) ZStack { if isPasswordVisible { TextField( - L10n.Localizable.ExportBackup.SetBackupPassword.placeholder, text: $password + L10n.Localizable.ExportBackup.SetBackupPassword.placeholder, + text: $password ) - .font(.textStyle(.body1)) + .wireTextStyle(.body1) .textFieldStyle(RoundedBorderTextFieldStyle()) - .overlay( - RoundedRectangle(cornerRadius: 5) - .stroke( - isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color, - lineWidth: password.isEmpty ? 0 : 1 - ) - ) } else { SecureField(L10n.Localizable.ExportBackup.SetBackupPassword.placeholder, text: $password) .textFieldStyle(RoundedBorderTextFieldStyle()) - .overlay( - RoundedRectangle(cornerRadius: 5) - .stroke( - isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color, - lineWidth: password.isEmpty ? 0 : 1 - ) - ) } HStack { Spacer() @@ -67,13 +54,33 @@ struct PasswordFieldView: View { .padding(.trailing, 10) } } + .overlay( + RoundedRectangle(cornerRadius: 5) + .stroke( + calculatedColor, + lineWidth: password.isEmpty ? 0 : 1 + ) + ) passwordRules .font(.caption) - .foregroundColor(isPasswordValid ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.error.color) + .foregroundColor(calculatedColor) } .padding(.horizontal) } + + // MARK - Helper + + private var calculatedColor: Color { + switch (password.isEmpty, isPasswordValid) { + case (_, false): + ColorTheme.Base.error.color + case (true, _): + ColorTheme.Base.secondaryText.color + case (false, true): + ColorTheme.Base.primary.color + } + } } // MARK: - Previews diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/ExportBackupPreview.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/ExportBackupPreview.swift new file mode 100644 index 00000000000..c20d74302eb --- /dev/null +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/ExportBackupPreview.swift @@ -0,0 +1,49 @@ +// +// Wire +// Copyright (C) 2025 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import SwiftUI + +@ViewBuilder @MainActor +func ExportBackupPreview() -> some View { + ExportBackup_Preview() +} + +private struct ExportBackup_Preview: View { + @State private var isPresented = true + + var body: some View { + Button( + action: { + isPresented.toggle() + }, + label: { + Text(L10n.Localizable.ExportBackup.button) + } + ) + .sheet(isPresented: $isPresented) { + NavigationStack { + ExportBackupView( + passwordValidator: MockBackupPasswordValidator(), + exportBackup: { _ in } + ) + } + .presentationDragIndicator(.visible) + .presentationDetents([.medium]) + } + } +} From ada73598b07a77df462e3bc865de31abbb98a157 Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Fri, 10 Jan 2025 16:25:33 +0100 Subject: [PATCH 044/107] fix: SwiftLint and SwiftFormat issues --- .../WireSettingsUI/Account/Backup/Views/ExportBackupView.swift | 3 ++- .../Account/Backup/Views/PasswordFieldView.swift | 2 +- .../Account/Backup/Views/Preview/ExportBackupPreview.swift | 2 +- 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift index 32a5f97adb9..9aa8d2c0802 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift @@ -104,7 +104,8 @@ private struct SetBackupPasswordView: View { Color.clear.onAppear { contentFits = contentGeometry.size.height <= geometry.size.height } - }) + } + ) .frame(maxWidth: .infinity) } .scrollDisabled(contentFits) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift index 4c6cdfa4470..718bbf99705 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift @@ -69,7 +69,7 @@ struct PasswordFieldView: View { .padding(.horizontal) } - // MARK - Helper + // MARK: - Helper private var calculatedColor: Color { switch (password.isEmpty, isPasswordValid) { diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/ExportBackupPreview.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/ExportBackupPreview.swift index c20d74302eb..cb9bad0c160 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/ExportBackupPreview.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/ExportBackupPreview.swift @@ -25,7 +25,7 @@ func ExportBackupPreview() -> some View { private struct ExportBackup_Preview: View { @State private var isPresented = true - + var body: some View { Button( action: { From e1871ace879a0387c824328d8a1a53d67568c1bd Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Fri, 10 Jan 2025 18:45:10 +0100 Subject: [PATCH 045/107] fix tests --- .../Account/Backup/Views/BackupActionsView.swift | 2 +- .../BackupActionsViewSnapshotTests/testBackupActions.dark.png | 3 --- .../BackupActionsViewSnapshotTests/testBackupActions.light.png | 3 --- 3 files changed, 1 insertion(+), 7 deletions(-) delete mode 100644 WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.dark.png delete mode 100644 WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.light.png diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift index eadc820af80..b4d4e19405f 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift @@ -31,7 +31,7 @@ public struct BackupActionsView: View { public var body: some View { List { Section( - footer: Text(L10n.Localizable.ExportBackup.description) + footer: Text(L10n.Localizable.Settings.ExportBackup.description) ) { Button(action: { isBackupSheetPresented.toggle() diff --git a/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.dark.png b/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.dark.png deleted file mode 100644 index a6531e1a866..00000000000 --- a/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.dark.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:648a5994eee46beedd378c6d089b70008806ad8b215ae482edaf26cca12c9ba4 -size 92098 diff --git a/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.light.png b/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.light.png deleted file mode 100644 index 7997101e34e..00000000000 --- a/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.light.png +++ /dev/null @@ -1,3 +0,0 @@ -version https://git-lfs.github.com/spec/v1 -oid sha256:b7b78d0a13f807a3b05cbc0411bac8c201a5ffa5e112d0f61f6f44e451026ec0 -size 92317 From a6d3a54d18d8db0d2892e5aa852117f597b5fb6b Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Fri, 10 Jan 2025 18:46:52 +0100 Subject: [PATCH 046/107] add snapshots --- .../BackupActionsViewSnapshotTests/testBackupActions.dark.png | 3 +++ .../BackupActionsViewSnapshotTests/testBackupActions.light.png | 3 +++ 2 files changed, 6 insertions(+) create mode 100644 WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.dark.png create mode 100644 WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.light.png diff --git a/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.dark.png b/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.dark.png new file mode 100644 index 00000000000..abd882875b2 --- /dev/null +++ b/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1a2a79d1da2ee5c1e9b068c3bb0afea7c4630e768b3ecc60545c82c54fe27fc9 +size 111485 diff --git a/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.light.png b/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.light.png new file mode 100644 index 00000000000..701533acf17 --- /dev/null +++ b/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.light.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:8116e7198ae2646fc6283f76e350222afcbbadc258cd2c8f9b6b19a3b82ca729 +size 112335 From 5bb1e1d53f5e25eba45b908c23cae91a1f0f5222 Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Fri, 10 Jan 2025 18:57:34 +0100 Subject: [PATCH 047/107] resolve conflicts --- .../Views/Preview/RestoreBackupPreview.swift | 2 +- .../Backup/Views/RestoreBackupView.swift | 17 ++++++----------- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/RestoreBackupPreview.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/RestoreBackupPreview.swift index 72e39c3aba2..f36e2a72208 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/RestoreBackupPreview.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/RestoreBackupPreview.swift @@ -27,7 +27,7 @@ struct RestoreBackupPreview: View { isPresented.toggle() }, label: { - Text(L10n.RestoreFromBackup.button) + Text(L10n.Localizable.RestoreFromBackup.button) } ) .sheet(isPresented: $isPresented) { diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/RestoreBackupView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/RestoreBackupView.swift index 63d3a821703..127a4df02cd 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/RestoreBackupView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/RestoreBackupView.swift @@ -32,22 +32,18 @@ struct RestoreBackupView: View { } public var body: some View { - PpasswordBackupView(importBackup: importBackup) + PasswordBackupView(importBackup: importBackup) .background(Color.viewBackground) .scrollContentBackground(.hidden) .navigationTitle( - Text(L10n.RestoreFromBackup.title) + Text(L10n.Localizable.RestoreFromBackup.title) ) .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .topBarTrailing) { CloseButton( action: didTapClose, - accessibilityLabel: String( - localized: "restoreBackup.close.label", - table: "Accessibility", - bundle: .module - ) + accessibilityLabel: L10n.Accessibility.RestoreBackup.Close.label ) } } @@ -58,7 +54,7 @@ struct RestoreBackupView: View { } } -private struct PpasswordBackupView: View { +private struct PasswordBackupView: View { @Environment(\.dismiss) var dismiss @State private var password: String = "" @State private var isPasswordVisible: Bool = false @@ -71,7 +67,7 @@ private struct PpasswordBackupView: View { var body: some View { VStack(spacing: 20) { - Text(L10n.RestoreFromBackup.description) + Text(L10n.Localizable.RestoreFromBackup.description) .font(.textStyle(.body1)) .foregroundStyle(Color.primaryText) .multilineTextAlignment(.leading) @@ -81,7 +77,6 @@ private struct PpasswordBackupView: View { PasswordFieldView( password: $password, isPasswordVisible: $isPasswordVisible, - title: Text(L10n.RestoreFromBackup.EnterPassword.title), passwordRules: nil ) Spacer() @@ -92,7 +87,7 @@ private struct PpasswordBackupView: View { dismiss() }, label: { - Text(L10n.RestoreFromBackup.button) + Text(L10n.Localizable.RestoreFromBackup.button) } ) .wireButtonStyle(.primary) From c8a4314cfad8594f3ca8850d4d10b0d3779a1a73 Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Sat, 11 Jan 2025 00:21:01 +0100 Subject: [PATCH 048/107] clean up --- .../Backup/Protocols/RestoreSource.swift | 6 +- .../ViewModels/BackupActionsViewModel.swift | 33 ++--- .../Backup/Views/BackupActionsView.swift | 3 +- .../Views/Preview/BackupActionsPreview.swift | 8 +- .../Views/Preview/RestoreBackupPreview.swift | 2 +- .../SessionManager+APIVersionResolver.swift | 2 +- .../SessionManager+Backup.swift | 113 ++---------------- .../Settings/Backup/RestoreSource.swift | 23 +--- 8 files changed, 36 insertions(+), 154 deletions(-) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/RestoreSource.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/RestoreSource.swift index 3d70c8fdd3e..83d671cb900 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/RestoreSource.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/RestoreSource.swift @@ -27,7 +27,9 @@ public enum RestoreBackupError: Error { public protocol RestoreSourceProtocol { - func restoreFromBackup(at location: URL, password: String) throws - func restoreFromBackup(at location: URL, password: String, completion: @escaping (Result) -> Void) + func restoreFromBackup( + at location: URL, + password: String, + completion: @escaping (Result) -> Void) } diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift index b3cf3f17fc1..581940b985e 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift @@ -50,34 +50,21 @@ public final class BackupActionsViewModel: ObservableObject { } } - func restoreBackup(at url: URL, password: String) throws { - do { - try restoreSource.restoreFromBackup(at: url, password: password) - //restoreBackupResultHandler.onSuccess() - } catch let error as RestoreBackupError { - switch error { - case .decryptionError: - return - case .generic: - restoreBackupResultHandler.onFailure() - } - } - } - func restoreFromBackup( at location: URL, password: String, - completion: @escaping (Result) -> Void) { - restoreSource.restoreFromBackup(at: location, password: password) { result in - switch result { - case .success: - self.restoreBackupResultHandler.onSuccess() - case .failure: - self.restoreBackupResultHandler.onFailure() - } - completion(result) + completion: @escaping (Result) -> Void + ) { + restoreSource.restoreFromBackup(at: location, password: password) { result in + completion(result) + switch result { + case .success: + self.restoreBackupResultHandler.onSuccess() + case .failure: + self.restoreBackupResultHandler.onFailure() } } + } func confirmBackupRestore(completion: @escaping () -> Void) { restoreBackupResultHandler.onConfirmation { diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift index 175073e80c1..97b8b41f75b 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift @@ -59,6 +59,7 @@ public struct BackupActionsView: View { .presentationDetents([.medium]) } } + .listRowBackground(Color(ColorTheme.Backgrounds.surface)) Section( footer: Text(L10n.Localizable.Settings.RestoreFromBackup.description) @@ -95,7 +96,7 @@ public struct BackupActionsView: View { } } } - .presentationDetents([.medium, .large]) + .presentationDetents([.medium]) } } .listRowBackground(Color(ColorTheme.Backgrounds.surface)) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift index d4ff869a678..aa179fa7a1a 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift @@ -44,9 +44,11 @@ private class MockBackupSource: BackupSourceProtocol { } private class MockRestoreSource: RestoreSourceProtocol { - func restoreFromBackup(at location: URL, password: String, completion: @escaping (Result) -> Void) { } - - func restoreFromBackup(at location: URL, password: String) throws {} + func restoreFromBackup( + at location: URL, + password: String, + completion: @escaping (Result) -> Void + ) {} } class MockBackupPasswordValidator: BackupPasswordValidatorProtocol { diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/RestoreBackupPreview.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/RestoreBackupPreview.swift index f36e2a72208..bb8a094462d 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/RestoreBackupPreview.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/RestoreBackupPreview.swift @@ -37,7 +37,7 @@ struct RestoreBackupPreview: View { ) } .presentationDragIndicator(.visible) - .presentationDetents([.medium, .large]) + .presentationDetents([.medium]) } } } diff --git a/wire-ios-sync-engine/Source/SessionManager/SessionManager+APIVersionResolver.swift b/wire-ios-sync-engine/Source/SessionManager/SessionManager+APIVersionResolver.swift index 97638ffc809..c17e402a4f4 100644 --- a/wire-ios-sync-engine/Source/SessionManager/SessionManager+APIVersionResolver.swift +++ b/wire-ios-sync-engine/Source/SessionManager/SessionManager+APIVersionResolver.swift @@ -60,7 +60,7 @@ extension SessionManager: APIVersionResolverDelegate { } } - private func migrateSessions(_ sessions: [ZMUserSession], to apiVersion: APIVersion) {// + private func migrateSessions(_ sessions: [ZMUserSession], to apiVersion: APIVersion) { delegate?.sessionManagerWillMigrateAccount { [weak self] in guard let self else { return } Task { diff --git a/wire-ios-sync-engine/Source/SessionManager/SessionManager+Backup.swift b/wire-ios-sync-engine/Source/SessionManager/SessionManager+Backup.swift index d1bc7d43f3e..713948895b8 100644 --- a/wire-ios-sync-engine/Source/SessionManager/SessionManager+Backup.swift +++ b/wire-ios-sync-engine/Source/SessionManager/SessionManager+Backup.swift @@ -115,16 +115,9 @@ extension SessionManager { } } -// guard -// let status = unauthenticatedSession?.authenticationStatus, -// let userId = status.authenticatedUserIdentifier -// else { -// return completion(.failure(BackupError.notAuthenticated)) -// } - - guard let account = activeUserSession?.account, - let userId = activeUserSession?.userId, - let clientId = activeUserSession?.selfUserClient?.remoteIdentifier + guard + let status = unauthenticatedSession?.authenticationStatus, + let userId = status.authenticatedUserIdentifier else { return completion(.failure(BackupError.notAuthenticated)) } @@ -172,105 +165,15 @@ extension SessionManager { return complete(.failure(BackupError.compressionError)) } - // CoreDataStack.importLocalStorage( - // accountIdentifier: userId, - // from: url, - // applicationContainer: sharedContainerURL, - // dispatchGroup: dispatchGroup - // ) { result in - // completion(result.map { _ in }) - // } - let sharedContainerURL = sharedContainerURL - let dispatchGroup = dispatchGroup - delegate?.sessionManagerWillMigrateAccount { [weak self] in - self?.tearDownBackgroundSession(for: account.userIdentifier) { - self?.activeUserSession = nil - CoreDataStack.importLocalStorage( - accountIdentifier: userId, - from: url, - applicationContainer: sharedContainerURL, - dispatchGroup: dispatchGroup - ) { result in - - switch result { - case .success: - self?.activateSession(for: account) { session in - session.managedObjectContext.setPersistentStoreMetadata(clientId, key: ZMPersistedClientIdKey) - session.managedObjectContext.saveOrRollback() - } - case .failure(let error): - WireLogger.apiMigration.error("failed to migrate account: \(error)") - //complete(.failure(error)) - } - } - } - } - } - } - - public func restoreFromBackup( - at location: URL, - password: String - ) throws { - guard let account = activeUserSession?.account, - let userId = activeUserSession?.userId else { - throw BackupError.notAuthenticated - } - - // Verify the imported file has the correct file extension. - guard BackupFileExtensions.allCases.contains(where: { - $0.rawValue == location.pathExtension - }) else { - throw BackupError.invalidFileExtension - } - - let decryptedURL = SessionManager.temporaryURL(for: location) - WireLogger.localStorage.debug("coordinated file access at: \(location.absoluteString)") - - do { - try SessionManager.decrypt( - from: location, - to: decryptedURL, - password: password, - accountId: userId - ) - } catch ChaCha20Poly1305.StreamEncryption.EncryptionError.decryptionFailed { - throw BackupError.decryptionError - } catch ChaCha20Poly1305.StreamEncryption.EncryptionError.keyGenerationFailed { - throw BackupError.keyCreationFailed - } catch { - throw error - } - - let url = SessionManager.unzippedBackupURL(for: location) - - guard decryptedURL.unzip(to: url) else { - throw BackupError.compressionError - } - self.delegate?.sessionManagerWillMigrateAccount { [weak self] in - try? CoreDataStack.importLocalStorage( + CoreDataStack.importLocalStorage( accountIdentifier: userId, from: url, - applicationContainer: self!.sharedContainerURL, - dispatchGroup: self!.dispatchGroup - ) - self?.activateSession(for: account) { session in + applicationContainer: sharedContainerURL, + dispatchGroup: dispatchGroup + ) { result in + completion(result.map { _ in }) } } - -// self.delegate?.sessionManagerWillMigrateAccount(userSessionCanBeTornDown: { -// do { -// try CoreDataStack.importLocalStorage( -// accountIdentifier: userId, -// from: url, -// applicationContainer: self.sharedContainerURL, -// dispatchGroup: self.dispatchGroup -// ) -// self.activateSession(for: account) { _ in } -// } catch { -// -// } -// }) } // MARK: - Encryption & Decryption diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/RestoreSource.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/RestoreSource.swift index b71688cd820..7e7445aaabe 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/RestoreSource.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/RestoreSource.swift @@ -22,24 +22,11 @@ import class WireSyncEngine.SessionManager struct RestoreSource: RestoreSourceProtocol { - func restoreFromBackup(at location: URL, password: String) throws { - do { - try SessionManager.shared?.restoreFromBackup( - at: location, - password: password) - } catch let error as SessionManager.BackupError { - switch error { - case .decryptionError: - throw RestoreBackupError.decryptionError - default: - throw RestoreBackupError.generic(error) - } - } catch { - throw RestoreBackupError.generic(error) - } - } - - func restoreFromBackup(at location: URL, password: String, completion: @escaping (Result) -> Void) { + func restoreFromBackup( + at location: URL, + password: String, + completion: @escaping (Result) -> Void + ) { SessionManager.shared?.restoreFromBackup( at: location, password: password, From 5f0b70eb9e1d53ccca28eb03a87342dc3d899a3f Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Sat, 11 Jan 2025 00:29:17 +0100 Subject: [PATCH 049/107] clean up --- .../Backup/Protocols/RestoreSource.swift | 3 +- .../Backup/Views/BackupActionsView.swift | 3 +- .../ExportBackupViewSnapshotTests.swift | 3 +- .../RestoreBackupViewSnapshotTests.swift | 3 +- .../CoreDataStack+Backup.swift | 120 ------------------ .../CoreDataStack+Migration.swift | 1 - .../CoreDataStackTests+Backup.swift | 2 +- .../ZMClientRegistrationStatus.swift | 4 +- .../ZMUserSession+Authentication.swift | 2 +- .../Backup/BackupRestoreController.swift | 2 +- ...ettingsCellDescriptorFactory+Account.swift | 3 +- 11 files changed, 13 insertions(+), 133 deletions(-) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/RestoreSource.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/RestoreSource.swift index 83d671cb900..e7420eae35c 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/RestoreSource.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/RestoreSource.swift @@ -30,6 +30,7 @@ public protocol RestoreSourceProtocol { func restoreFromBackup( at location: URL, password: String, - completion: @escaping (Result) -> Void) + completion: @escaping (Result) -> Void + ) } diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift index 97b8b41f75b..6c8216266cf 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift @@ -92,7 +92,8 @@ public struct BackupActionsView: View { viewModel.restoreFromBackup( at: fileURL, password: password, - completion: { _ in }) + completion: { _ in } + ) } } } diff --git a/WireUI/Tests/WireSettingsUITests/Account/Backup/ExportBackupViewSnapshotTests.swift b/WireUI/Tests/WireSettingsUITests/Account/Backup/ExportBackupViewSnapshotTests.swift index f17178dba6a..172240dbaa4 100644 --- a/WireUI/Tests/WireSettingsUITests/Account/Backup/ExportBackupViewSnapshotTests.swift +++ b/WireUI/Tests/WireSettingsUITests/Account/Backup/ExportBackupViewSnapshotTests.swift @@ -41,8 +41,7 @@ final class ExportBackupViewSnapshotTests: XCTestCase { let sut = ExportBackupView( passwordValidator: MockBackupPasswordValidator(), exportBackup: { _ in } - ) - .frame(width: screenBounds.width, height: screenBounds.height) + ).frame(width: screenBounds.width, height: screenBounds.height) snapshotHelper .withUserInterfaceStyle(.light) diff --git a/WireUI/Tests/WireSettingsUITests/Account/Backup/RestoreBackupViewSnapshotTests.swift b/WireUI/Tests/WireSettingsUITests/Account/Backup/RestoreBackupViewSnapshotTests.swift index bdf345aa2d3..564d036325d 100644 --- a/WireUI/Tests/WireSettingsUITests/Account/Backup/RestoreBackupViewSnapshotTests.swift +++ b/WireUI/Tests/WireSettingsUITests/Account/Backup/RestoreBackupViewSnapshotTests.swift @@ -40,8 +40,7 @@ final class RestoreBackupViewSnapshotTests: XCTestCase { let screenBounds = UIScreen.main.bounds let sut = RestoreBackupView( importBackup: { _ in } - ) - .frame(width: screenBounds.width, height: screenBounds.height) + ).frame(width: screenBounds.width, height: screenBounds.height) snapshotHelper .withUserInterfaceStyle(.light) diff --git a/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack+Backup.swift b/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack+Backup.swift index e5a88a2f1ff..66311cd99d1 100644 --- a/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack+Backup.swift +++ b/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack+Backup.swift @@ -189,38 +189,6 @@ public extension CoreDataStack { ) } - static func importLocalStorage( - accountIdentifier: UUID, - from backupDirectory: URL, - applicationContainer: URL, - dispatchGroup: ZMSDispatchGroup - ) throws { - // Start background activity - guard let activity = BackgroundActivityFactory.shared.startBackgroundActivity(name: "import backup") else { - WireLogger.localStorage - .error("backup: error backing up local store: \(CoreDataStackError.noDatabaseActivity)") - log.debug("error backing up local store: \(CoreDataStackError.noDatabaseActivity)") - throw CoreDataStackError.noDatabaseActivity - } - - do { - try importLocalStorage( - accountIdentifier: accountIdentifier, - from: backupDirectory, - applicationContainer: applicationContainer, - dispatchGroup: dispatchGroup, - messagingMigrator: CoreDataMigrator(isInMemoryStore: false) - ) - } catch { - // Ensure background activity is ended even if an error occurs - BackgroundActivityFactory.shared.endBackgroundActivity(activity) - throw error - } - - // End background activity after successful completion - BackgroundActivityFactory.shared.endBackgroundActivity(activity) - } - internal static func importLocalStorage( accountIdentifier: UUID, from backupDirectory: URL, @@ -311,94 +279,6 @@ public extension CoreDataStack { } } - internal static func importLocalStorage( - accountIdentifier: UUID, - from backupDirectory: URL, - applicationContainer: URL, - dispatchGroup: ZMSDispatchGroup, - messagingMigrator: CoreDataMessagingMigratorProtocol - ) throws { - let accountDirectory = accountDataFolder( - accountIdentifier: accountIdentifier, - applicationContainer: applicationContainer - ) - - let accountStoreFile = accountDirectory.appendingPersistentStoreLocation() - let backupStoreFile = backupDirectory - .appendingPathComponent(databaseDirectoryName) - .appendingStoreFile() - let metadataURL = backupDirectory.appendingPathComponent(metadataFilename) - - try workQueue.sync { - do { - // Load metadata - let metadata = try BackupMetadata(url: metadataURL) - let currentModel = CoreDataStack.loadMessagingModel() - - // Ensure the model version is available - guard let backupModel = managedObjectModel(for: metadata.modelVersion) else { - throw BackupImportError.missingModelVersion(metadata.modelVersion) - } - - // Verify metadata compatibility - if let verificationError = metadata.verify( - using: accountIdentifier, - modelVersionProvider: currentModel - ) { - throw BackupImportError.incompatibleBackup(verificationError) - } - - // Set up the coordinator with the backup model - let coordinator = NSPersistentStoreCoordinator(managedObjectModel: backupModel) - - // Create the target directory for the account store - try fileManager.createDirectory( - at: accountStoreFile.deletingLastPathComponent(), - withIntermediateDirectories: true, - attributes: nil - ) - let options = NSPersistentStoreCoordinator.persistentStoreOptions(supportsMigration: false) - - // Prepare the backup store for import - WireLogger.localStorage.debug("backup: import prepare", attributes: .safePublic) - try prepareStoreForBackupImport(coordinator: coordinator, location: backupStoreFile, options: options) - - // Perform the migration - let tp = TimePoint(interval: 60.0, label: "db migration") - WireLogger.localStorage.debug("backup: migrate database \(metadata.modelVersion) to \(currentModel.version)") - try messagingMigrator.migrateStore(at: backupStoreFile, toVersion: .current) - - if !tp.warnIfLongerThanInterval() { - WireLogger.localStorage.info( - "time spent in migration only: \(tp.elapsedTime)", - attributes: .safePublic - ) - } - - // Import the persistent store to the account data directory - WireLogger.localStorage.debug( - "backup: import the persistent store to the account data directory", - attributes: .safePublic - ) - try coordinator.replacePersistentStore( - at: accountStoreFile, - destinationOptions: options, - withPersistentStoreFrom: backupStoreFile, - sourceOptions: options, - ofType: NSSQLiteStoreType - ) - - WireLogger.localStorage.info( - "successfully imported backup with metadata: \(metadata)", - attributes: .safePublic - ) - } catch { - WireLogger.localStorage.error("backup: error importing local store: \(error)", attributes: .safePublic) - throw BackupImportError.failedToCopy(error) - } - } - } - private static func managedObjectModel(for dataModelVersion: String) -> NSManagedObjectModel? { let version = CoreDataMessagingMigrationVersion.allCases.first { $0.dataModelVersion == dataModelVersion diff --git a/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack+Migration.swift b/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack+Migration.swift index 4a78d37ee85..ffe55d2963a 100644 --- a/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack+Migration.swift +++ b/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack+Migration.swift @@ -107,7 +107,6 @@ extension CoreDataStack { let migrationStoreLocation = databaseDirectory.appendingStoreFile() let options = NSPersistentStoreCoordinator.persistentStoreOptions(supportsMigration: false) - // // Recreate the persistent store inside a new location try coordinator.replacePersistentStore( at: migrationStoreLocation, diff --git a/wire-ios-data-model/Tests/Source/ManagedObjectContext/CoreDataStackTests+Backup.swift b/wire-ios-data-model/Tests/Source/ManagedObjectContext/CoreDataStackTests+Backup.swift index 56fea50b0aa..7eb0da44153 100644 --- a/wire-ios-data-model/Tests/Source/ManagedObjectContext/CoreDataStackTests+Backup.swift +++ b/wire-ios-data-model/Tests/Source/ManagedObjectContext/CoreDataStackTests+Backup.swift @@ -260,7 +260,7 @@ final class CoreDataStackTests_Backup: DatabaseBaseTest { XCTAssertEqual(try directory.viewContext.count(for: fetchConversations), 1) } - func testThatMetadataIsDeletedWhenImportingBackup() throws {// + func testThatMetadataIsDeletedWhenImportingBackup() throws { // given let uuid = UUID() let directory = createStorageStackAndWaitForCompletion(userID: uuid) diff --git a/wire-ios-sync-engine/Source/UserSession/ZMClientRegistrationStatus.swift b/wire-ios-sync-engine/Source/UserSession/ZMClientRegistrationStatus.swift index 65c719f8803..4e55434d8fe 100644 --- a/wire-ios-sync-engine/Source/UserSession/ZMClientRegistrationStatus.swift +++ b/wire-ios-sync-engine/Source/UserSession/ZMClientRegistrationStatus.swift @@ -267,7 +267,7 @@ public class ZMClientRegistrationStatus: NSObject, ClientRegistrationDelegate { Self.needsToRegisterMLSClient(in: managedObjectContext) } - @objc(needsToRegisterClientInContext:)// + @objc(needsToRegisterClientInContext:) public static func needsToRegisterClient(in context: NSManagedObjectContext) -> Bool { // replace with selfUser.client.remoteIdentifier == nil if let clientID = context.persistentStoreMetadata(forKey: ZMPersistedClientIdKey) as? String { @@ -481,7 +481,7 @@ public class ZMClientRegistrationStatus: NSObject, ClientRegistrationDelegate { } @objc(didRegisterProteusClient:) - public func didRegisterProteusClient(_ client: UserClient) {// + public func didRegisterProteusClient(_ client: UserClient) { WireLogger.authentication.info("Did register proteus client") managedObjectContext.setPersistentStoreMetadata(client.remoteIdentifier, key: ZMPersistedClientIdKey) diff --git a/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+Authentication.swift b/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+Authentication.swift index 5fc486118d8..d4aa229c787 100644 --- a/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+Authentication.swift +++ b/wire-ios-sync-engine/Source/UserSession/ZMUserSession/ZMUserSession+Authentication.swift @@ -47,7 +47,7 @@ extension ZMUserSession { let needsToRegisterMLSClient = ZMClientRegistrationStatus.needsToRegisterMLSClient(in: managedObjectContext) let waitingToRegisterMLSClient = needsToRegisterMLSClient && !hasCompletedInitialSync - return isAuthenticated && !needsToRegisterClient && !waitingToRegisterMLSClient// + return isAuthenticated && !needsToRegisterClient && !waitingToRegisterMLSClient } /// `True` if the session has a valid authentication cookie diff --git a/wire-ios/Wire-iOS/Sources/Authentication/Backup/BackupRestoreController.swift b/wire-ios/Wire-iOS/Sources/Authentication/Backup/BackupRestoreController.swift index daabffb3874..676bf091c6b 100644 --- a/wire-ios/Wire-iOS/Sources/Authentication/Backup/BackupRestoreController.swift +++ b/wire-ios/Wire-iOS/Sources/Authentication/Backup/BackupRestoreController.swift @@ -116,7 +116,7 @@ final class BackupRestoreController: NSObject { Task { @MainActor in activityIndicator.start() } - sessionManager.restoreFromBackup(at: url, password: password) { [weak self] result in // + sessionManager.restoreFromBackup(at: url, password: password) { [weak self] result in guard let self else { BackgroundActivityFactory.shared.endBackgroundActivity(activity) WireLogger.localStorage.error("SessionManager.self is `nil` in performRestore") diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift index 7c7698d496a..27c3c4fbb45 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift @@ -527,7 +527,8 @@ extension SettingsCellDescriptorFactory { title: L10n.Localizable.RestoreBackup.Confirmation.overrideButton, style: .default, handler: { _ in completion() - })) + }) + ) controller.present(alert, animated: true) } From e276615a5656f844bde3787a79dcfa55d305f962 Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Sat, 11 Jan 2025 01:06:48 +0100 Subject: [PATCH 050/107] fix SwiftFormat issues --- .../Views/Preview/BackupActionsPreview.swift | 3 +- .../Backup/Views/RestoreBackupView.swift | 114 ++++++++++++++---- ...ettingsCellDescriptorFactory+Account.swift | 4 +- 3 files changed, 92 insertions(+), 29 deletions(-) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift index aa179fa7a1a..c1c09359455 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift @@ -30,7 +30,8 @@ func BackupActionsPreview() -> some View { restoreBackupResultHandler: RestoreBackupResultHandler( onSuccess: {}, onConfirmation: { _ in }, - onFailure: {}), + onFailure: {} + ), passwordValidator: MockBackupPasswordValidator() )) } diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/RestoreBackupView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/RestoreBackupView.swift index 127a4df02cd..4009224ba8a 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/RestoreBackupView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/RestoreBackupView.swift @@ -58,6 +58,7 @@ private struct PasswordBackupView: View { @Environment(\.dismiss) var dismiss @State private var password: String = "" @State private var isPasswordVisible: Bool = false + @State private var contentFits: Bool = true private let importBackup: (String) -> Void @@ -66,36 +67,97 @@ private struct PasswordBackupView: View { } var body: some View { - VStack(spacing: 20) { - Text(L10n.Localizable.RestoreFromBackup.description) - .font(.textStyle(.body1)) - .foregroundStyle(Color.primaryText) - .multilineTextAlignment(.leading) - .frame(maxWidth: .infinity, alignment: .leading) - .padding(.horizontal) - - PasswordFieldView( - password: $password, - isPasswordVisible: $isPasswordVisible, - passwordRules: nil - ) - Spacer() - - Button( - action: { - importBackup(password) - dismiss() - }, - label: { - Text(L10n.Localizable.RestoreFromBackup.button) + GeometryReader { geometry in + VStack { + ScrollView { + VStack(spacing: 20) { + Text(L10n.Localizable.RestoreFromBackup.description) + .wireTextStyle(.body1) + .foregroundStyle(Color.primaryText) + .multilineTextAlignment(.leading) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.horizontal) + + EnterPasswordFieldView( + password: $password, + isPasswordVisible: $isPasswordVisible + ) + } + .background( + GeometryReader { contentGeometry in + Color.clear.onAppear { + contentFits = contentGeometry.size.height <= geometry.size.height + } + } + ) + .frame(maxWidth: .infinity) } - ) - .wireButtonStyle(.primary) - .padding() + .scrollDisabled(contentFits) + + Spacer() + + Button( + action: { + importBackup(password) + dismiss() + }, + label: { + Text(L10n.Localizable.RestoreFromBackup.button) + } + ) + .wireButtonStyle(.primary) + .padding() + } } - .frame(maxHeight: .infinity) } +} + +private struct EnterPasswordFieldView: View { + @Binding var password: String + @Binding var isPasswordVisible: Bool + + var body: some View { + VStack(alignment: .leading, spacing: 8) { + Text(L10n.Localizable.RestoreFromBackup.EnterPassword.title) + .font(.subheadline) + .foregroundColor(password.isEmpty ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.primary.color) + ZStack { + if isPasswordVisible { + TextField( + L10n.Localizable.ExportBackup.SetBackupPassword.placeholder, + text: $password + ) + .wireTextStyle(.body1) + .textFieldStyle(RoundedBorderTextFieldStyle()) + } else { + SecureField(L10n.Localizable.ExportBackup.SetBackupPassword.placeholder, + text: $password + ) + .textFieldStyle(RoundedBorderTextFieldStyle()) + } + HStack { + Spacer() + Button(action: { + isPasswordVisible.toggle() + }, label: { + Image(systemName: isPasswordVisible ? "eye" : "eye.slash") + .foregroundColor(.gray) + }) + .padding(.trailing, 10) + } + } + .overlay( + RoundedRectangle(cornerRadius: 5) + .stroke( + password.isEmpty ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.primary.color, + lineWidth: password.isEmpty ? 0 : 1 + ) + ) + + } + .padding(.horizontal) + } } // MARK: - Previews diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift index 27c3c4fbb45..f464b36ff81 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift @@ -521,8 +521,8 @@ extension SettingsCellDescriptorFactory { alert.addAction(UIAlertAction( title: L10n.Localizable.RestoreBackup.Confirmation.cancelButton, - style: .cancel - )) + style: .cancel) + ) alert.addAction(UIAlertAction( title: L10n.Localizable.RestoreBackup.Confirmation.overrideButton, style: .default, handler: { _ in From e09f5af79f690d4498330c45bc9f38b2f2e5fb79 Mon Sep 17 00:00:00 2001 From: KaterinaWire Date: Sat, 11 Jan 2025 01:12:14 +0100 Subject: [PATCH 051/107] fix SwiftFormat issues --- .../Account/Backup/Views/RestoreBackupView.swift | 5 +++-- .../SettingsCellDescriptorFactory+Account.swift | 5 ++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/RestoreBackupView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/RestoreBackupView.swift index 4009224ba8a..f87e64681e4 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/RestoreBackupView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/RestoreBackupView.swift @@ -131,8 +131,9 @@ private struct EnterPasswordFieldView: View { .wireTextStyle(.body1) .textFieldStyle(RoundedBorderTextFieldStyle()) } else { - SecureField(L10n.Localizable.ExportBackup.SetBackupPassword.placeholder, - text: $password + SecureField( + L10n.Localizable.ExportBackup.SetBackupPassword.placeholder, + text: $password ) .textFieldStyle(RoundedBorderTextFieldStyle()) } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift index f464b36ff81..98e43ae829c 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift @@ -525,9 +525,8 @@ extension SettingsCellDescriptorFactory { ) alert.addAction(UIAlertAction( title: L10n.Localizable.RestoreBackup.Confirmation.overrideButton, - style: .default, handler: { _ in - completion() - }) + style: .default, + handler: { _ in completion() }) ) controller.present(alert, animated: true) From 89c6a3ca72ae71adf2baf720be10194d36dada53 Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Mon, 13 Jan 2025 14:45:53 +0100 Subject: [PATCH 052/107] attempt to not switch to a different text field --- .../Backup/Views/PasswordFieldView.swift | 47 ++++++++++++++----- 1 file changed, 36 insertions(+), 11 deletions(-) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift index 718bbf99705..63292b98e35 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift @@ -18,6 +18,7 @@ import SwiftUI import WireDesign +import WireFoundation struct PasswordFieldView: View { @Binding var password: String @@ -32,17 +33,22 @@ struct PasswordFieldView: View { .foregroundColor(calculatedColor) ZStack { - if isPasswordVisible { - TextField( - L10n.Localizable.ExportBackup.SetBackupPassword.placeholder, - text: $password - ) - .wireTextStyle(.body1) + SecureField(L10n.Localizable.ExportBackup.SetBackupPassword.placeholder, text: $password) .textFieldStyle(RoundedBorderTextFieldStyle()) - } else { - SecureField(L10n.Localizable.ExportBackup.SetBackupPassword.placeholder, text: $password) - .textFieldStyle(RoundedBorderTextFieldStyle()) - } + .foregroundStyle(isPasswordVisible ? .clear : ColorTheme.Base.primary.color) + .overlay { + if isPasswordVisible { + HStack { + Text(password) + .disabled(true) + .background(Color.red) + .wireTextStyle(.body1) + // .multilineTextAlignment(.leading) + Spacer() + } + .padding(8) + } + } HStack { Spacer() Button(action: { @@ -92,7 +98,7 @@ struct PasswordFieldView: View { isPasswordValid: false, isPasswordVisible: .constant(false), passwordRules: Text(L10n.Localizable.ExportBackup.SetBackupPassword.rules) - ) + ).environment(\.wireTextStyleMapping, PreviewTextStyleMapping()) } @available(iOS 17, *) @@ -124,3 +130,22 @@ struct PasswordFieldView: View { passwordRules: Text(L10n.Localizable.ExportBackup.SetBackupPassword.rules) ) } + +private func PreviewTextStyleMapping() -> WireTextStyleMapping { + .init { _ in + fatalError("not implemented for preview yet") + } fontMapping: { textStyle in + switch textStyle { +// case .h2: +// .title3.bold() +// case .h3: +// .headline + case .body1: + .body +// case .subline1: +// .caption + default: + fatalError("not implemented for preview yet") + } + } +} From a8a16b71e438a08d6e8878483ed30aa86866efce Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Mon, 13 Jan 2025 14:50:22 +0100 Subject: [PATCH 053/107] revert some changes --- .../Backup/Views/PasswordFieldView.swift | 25 ++++++++----------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift index 63292b98e35..dd8060f42ea 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift @@ -33,22 +33,17 @@ struct PasswordFieldView: View { .foregroundColor(calculatedColor) ZStack { - SecureField(L10n.Localizable.ExportBackup.SetBackupPassword.placeholder, text: $password) + if isPasswordVisible { + TextField( + L10n.Localizable.ExportBackup.SetBackupPassword.placeholder, + text: $password + ) + .wireTextStyle(.body1) .textFieldStyle(RoundedBorderTextFieldStyle()) - .foregroundStyle(isPasswordVisible ? .clear : ColorTheme.Base.primary.color) - .overlay { - if isPasswordVisible { - HStack { - Text(password) - .disabled(true) - .background(Color.red) - .wireTextStyle(.body1) - // .multilineTextAlignment(.leading) - Spacer() - } - .padding(8) - } - } + } else { + SecureField(L10n.Localizable.ExportBackup.SetBackupPassword.placeholder, text: $password) + .textFieldStyle(RoundedBorderTextFieldStyle()) + } HStack { Spacer() Button(action: { From 215f01b8777e08f03c74d4e826c8b955216deffc Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Mon, 13 Jan 2025 17:05:42 +0100 Subject: [PATCH 054/107] attempt to present error --- .../ViewModels/BackupActionsViewModel.swift | 26 +++++++++++++++++ .../Backup/Views/BackupActionsView.swift | 29 ++++++++++--------- .../Backup/Views/ExportBackupView.swift | 17 +++++------ .../Backup/Views/PasswordFieldView.swift | 6 ---- 4 files changed, 48 insertions(+), 30 deletions(-) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift index 406d81c1f20..a3cf9bd9e92 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift @@ -19,10 +19,14 @@ import SwiftUI public final class BackupActionsViewModel: ObservableObject { + private let backupSource: any BackupSourceProtocol private let backupResultHandler: BackupResultHandler let passwordValidator: any BackupPasswordValidatorProtocol + @Published private(set) var progress: Double? + @Published var presentBackupFailedAlert = false + public init( backupSource: any BackupSourceProtocol, backupResultHandler: BackupResultHandler, @@ -35,12 +39,34 @@ public final class BackupActionsViewModel: ObservableObject { func backupActiveAccount(password: String) { do { + progress = 0.5 + throw DummyError.some + let backupPath = try backupSource.backupActiveAccount(password: password) backupResultHandler.onSuccess(backupPath) { [weak self] in self?.backupSource.clearPreviousBackups() + self?.progress = 1 } } catch { + progress = nil + DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3)) { + self.presentBackupFailedAlert = true + } backupResultHandler.onFailure(error) } } + + enum BackupStatus { + case inProgress(Double) + case failed(any Error) + + var error: (any Error)? { + if case .failed(let error) = self { return error } + return nil + } + } +} + +enum DummyError: Error { + case some } diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift index b4d4e19405f..37173db413e 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift @@ -20,30 +20,26 @@ import SwiftUI import WireDesign import WireReusableUIComponents -public struct BackupActionsView: View { - @ObservedObject private var viewModel: BackupActionsViewModel - @State private var isBackupSheetPresented: Bool = false +struct BackupActionsView: View { - public init(viewModel: BackupActionsViewModel) { - self.viewModel = viewModel - } + @ObservedObject private(set) var viewModel: BackupActionsViewModel + @State private var isBackupSheetPresented = false - public var body: some View { + var body: some View { List { - Section( - footer: Text(L10n.Localizable.Settings.ExportBackup.description) - ) { - Button(action: { + Section(footer: Text(L10n.Localizable.Settings.ExportBackup.description)) { + Button { isBackupSheetPresented.toggle() - }, label: { + } label: { HStack { Text(L10n.Localizable.Settings.ExportBackup.action) .wireTextStyle(.body2) .foregroundStyle(Color.primaryText) Spacer() - Image(systemName: "chevron.right").foregroundStyle(Color.primary) + Image(systemName: "chevron.right") + .foregroundStyle(Color.primary) } - }) + } .sheet(isPresented: $isBackupSheetPresented) { NavigationStack { ExportBackupView( @@ -61,6 +57,11 @@ public struct BackupActionsView: View { .listStyle(.grouped) .background(Color(ColorTheme.Backgrounds.background)) .scrollContentBackground(.hidden) + .alert("abcd", isPresented: $viewModel.presentBackupFailedAlert) { + Button("OK") { + viewModel.presentBackupFailedAlert = false + } + } } } diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift index 9aa8d2c0802..05416e4f6ba 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift @@ -65,7 +65,7 @@ public struct ExportBackupView: View { } private struct SetBackupPasswordView: View { - @Environment(\.dismiss) var dismiss + @Environment(\.dismiss) private var dismiss @State private var password: String = "" @State private var isPasswordVisible: Bool = false @State private var contentFits: Bool = true @@ -112,15 +112,12 @@ private struct SetBackupPasswordView: View { Spacer() - Button( - action: { - exportBackup(password) - dismiss() - }, - label: { - Text(L10n.Localizable.ExportBackup.button) - } - ) + Button { + dismiss() + exportBackup(password) + } label: { + Text(L10n.Localizable.ExportBackup.button) + } .disabled(!passwordValidator.isPasswordValid(password)) .wireButtonStyle(.primary) .padding() diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift index dd8060f42ea..105b7d6292b 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift @@ -131,14 +131,8 @@ private func PreviewTextStyleMapping() -> WireTextStyleMapping { fatalError("not implemented for preview yet") } fontMapping: { textStyle in switch textStyle { -// case .h2: -// .title3.bold() -// case .h3: -// .headline case .body1: .body -// case .subline1: -// .caption default: fatalError("not implemented for preview yet") } From 67c5ea0d4994d7e04aa1a853450f68d4e475be58 Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Mon, 13 Jan 2025 17:57:38 +0100 Subject: [PATCH 055/107] some UI fixes --- .../ViewModels/BackupActionsViewModel.swift | 10 -------- .../Backup/Views/BackupActionsView.swift | 24 ++++++++----------- .../Views/Preview/BackupActionsPreview.swift | 15 ++++++++++++ 3 files changed, 25 insertions(+), 24 deletions(-) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift index a3cf9bd9e92..7407baa930f 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift @@ -55,16 +55,6 @@ public final class BackupActionsViewModel: ObservableObject { backupResultHandler.onFailure(error) } } - - enum BackupStatus { - case inProgress(Double) - case failed(any Error) - - var error: (any Error)? { - if case .failed(let error) = self { return error } - return nil - } - } } enum DummyError: Error { diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift index 37173db413e..cbe96154d12 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift @@ -35,24 +35,20 @@ struct BackupActionsView: View { Text(L10n.Localizable.Settings.ExportBackup.action) .wireTextStyle(.body2) .foregroundStyle(Color.primaryText) - Spacer() - Image(systemName: "chevron.right") - .foregroundStyle(Color.primary) } } - .sheet(isPresented: $isBackupSheetPresented) { - NavigationStack { - ExportBackupView( - passwordValidator: viewModel.passwordValidator, - exportBackup: { password in - viewModel.backupActiveAccount(password: password) - } - ) - } - .presentationDetents([.medium]) + } + .sheet(isPresented: $isBackupSheetPresented) { + NavigationStack { + ExportBackupView( + passwordValidator: viewModel.passwordValidator, + exportBackup: { password in + viewModel.backupActiveAccount(password: password) + } + ) } + .presentationDetents([.medium]) } - .listRowBackground(Color(ColorTheme.Backgrounds.surface)) } .listStyle(.grouped) .background(Color(ColorTheme.Backgrounds.background)) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift index 92500cfd9b8..afb8b6ab386 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift @@ -16,6 +16,7 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // +import WireFoundation import SwiftUI @ViewBuilder @MainActor @@ -28,6 +29,7 @@ func BackupActionsPreview() -> some View { ), passwordValidator: MockBackupPasswordValidator() )) + .environment(\.wireTextStyleMapping, PreviewTextStyleMapping()) } private class MockBackupSource: BackupSourceProtocol { @@ -47,3 +49,16 @@ class MockBackupPasswordValidator: BackupPasswordValidatorProtocol { "Use at least 8 characters, with one lowercase letter, one capital letter, a number, and a special character." } } + +private func PreviewTextStyleMapping() -> WireTextStyleMapping { + .init { _ in + fatalError("not implemented for preview yet") + } fontMapping: { textStyle in + switch textStyle { + case .body2: + .callout.weight(.semibold) + default: + fatalError("not implemented for preview yet") + } + } +} From 20671e0d22ed3b574c713e986ff2e7599c41e95e Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Mon, 13 Jan 2025 18:25:54 +0100 Subject: [PATCH 056/107] renamings --- .../Models/BackupResultHandler.swift | 0 .../Protocols/BackupPasswordValidator.swift | 0 .../Protocols/BackupSource.swift | 0 .../BackupRestoreViewController.swift} | 11 +++++++++-- .../ViewModels/BackupRestoreViewModel.swift} | 2 +- .../Views/BackupRestoreView.swift} | 4 ++-- .../Views/ExportBackupView.swift | 0 .../Views/PasswordFieldView.swift | 0 .../Views/Preview/BackupActionsPreview.swift | 2 +- .../Views/Preview/ExportBackupPreview.swift | 0 .../SettingsCellDescriptorFactory+Account.swift | 4 ++-- 11 files changed, 15 insertions(+), 8 deletions(-) rename WireUI/Sources/WireSettingsUI/Account/{Backup => BackupRestore}/Models/BackupResultHandler.swift (100%) rename WireUI/Sources/WireSettingsUI/Account/{Backup => BackupRestore}/Protocols/BackupPasswordValidator.swift (100%) rename WireUI/Sources/WireSettingsUI/Account/{Backup => BackupRestore}/Protocols/BackupSource.swift (100%) rename WireUI/Sources/WireSettingsUI/Account/{Backup/ViewControllers/BackupActionsHostingController.swift => BackupRestore/ViewControllers/BackupRestoreViewController.swift} (71%) rename WireUI/Sources/WireSettingsUI/Account/{Backup/ViewModels/BackupActionsViewModel.swift => BackupRestore/ViewModels/BackupRestoreViewModel.swift} (97%) rename WireUI/Sources/WireSettingsUI/Account/{Backup/Views/BackupActionsView.swift => BackupRestore/Views/BackupRestoreView.swift} (95%) rename WireUI/Sources/WireSettingsUI/Account/{Backup => BackupRestore}/Views/ExportBackupView.swift (100%) rename WireUI/Sources/WireSettingsUI/Account/{Backup => BackupRestore}/Views/PasswordFieldView.swift (100%) rename WireUI/Sources/WireSettingsUI/Account/{Backup => BackupRestore}/Views/Preview/BackupActionsPreview.swift (97%) rename WireUI/Sources/WireSettingsUI/Account/{Backup => BackupRestore}/Views/Preview/ExportBackupPreview.swift (100%) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupResultHandler.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Models/BackupResultHandler.swift similarity index 100% rename from WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupResultHandler.swift rename to WireUI/Sources/WireSettingsUI/Account/BackupRestore/Models/BackupResultHandler.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/BackupPasswordValidator.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/BackupPasswordValidator.swift similarity index 100% rename from WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/BackupPasswordValidator.swift rename to WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/BackupPasswordValidator.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/BackupSource.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/BackupSource.swift similarity index 100% rename from WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/BackupSource.swift rename to WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/BackupSource.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewControllers/BackupActionsHostingController.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift similarity index 71% rename from WireUI/Sources/WireSettingsUI/Account/Backup/ViewControllers/BackupActionsHostingController.swift rename to WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift index fb46aeab1b8..3b3b32c373e 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewControllers/BackupActionsHostingController.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift @@ -19,6 +19,13 @@ import SwiftUI @MainActor -public func BackupActionsHostingController(viewModel: BackupActionsViewModel) -> UIViewController { - UIHostingController(rootView: BackupActionsView(viewModel: viewModel)) +public func BackupRestoreViewController(viewModel: BackupRestoreViewModel) -> UIViewController { + UIHostingController(rootView: BackupRestoreView(viewModel: viewModel)) +} + +@MainActor +public func BackupRestoreViewController( +// viewModel: BackupRestoreViewModel +) -> UIViewController { + fatalError() } diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewModels/BackupRestoreViewModel.swift similarity index 97% rename from WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift rename to WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewModels/BackupRestoreViewModel.swift index 7407baa930f..ce75cc3349a 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewModels/BackupRestoreViewModel.swift @@ -18,7 +18,7 @@ import SwiftUI -public final class BackupActionsViewModel: ObservableObject { +public final class BackupRestoreViewModel: ObservableObject { private let backupSource: any BackupSourceProtocol private let backupResultHandler: BackupResultHandler diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift similarity index 95% rename from WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift rename to WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift index cbe96154d12..91b458a4d24 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift @@ -20,9 +20,9 @@ import SwiftUI import WireDesign import WireReusableUIComponents -struct BackupActionsView: View { +struct BackupRestoreView: View { - @ObservedObject private(set) var viewModel: BackupActionsViewModel + @ObservedObject private(set) var viewModel: BackupRestoreViewModel @State private var isBackupSheetPresented = false var body: some View { diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/ExportBackupView.swift similarity index 100% rename from WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift rename to WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/ExportBackupView.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/PasswordFieldView.swift similarity index 100% rename from WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift rename to WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/PasswordFieldView.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/BackupActionsPreview.swift similarity index 97% rename from WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift rename to WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/BackupActionsPreview.swift index afb8b6ab386..944a2814ad4 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/BackupActionsPreview.swift @@ -21,7 +21,7 @@ import SwiftUI @ViewBuilder @MainActor func BackupActionsPreview() -> some View { - BackupActionsView(viewModel: BackupActionsViewModel( + BackupRestoreView(viewModel: BackupRestoreViewModel( backupSource: MockBackupSource(), backupResultHandler: BackupResultHandler( onSuccess: { _, _ in }, diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/ExportBackupPreview.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/ExportBackupPreview.swift similarity index 100% rename from WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/ExportBackupPreview.swift rename to WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/ExportBackupPreview.swift diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift index 7563d0bf90a..f85974fe10f 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift @@ -381,7 +381,7 @@ extension SettingsCellDescriptorFactory { return .none } if selfUser.hasValidEmail || selfUser.usesCompanyLogin { - let viewModel = BackupActionsViewModel( + let viewModel = BackupRestoreViewModel( backupSource: BackupSource(), backupResultHandler: BackupResultHandler( onSuccess: presentShareSheet, @@ -389,7 +389,7 @@ extension SettingsCellDescriptorFactory { ), passwordValidator: BackupPasswordValidator() ) - let backupActionsController = BackupActionsHostingController(viewModel: viewModel) + let backupActionsController = BackupRestoreViewController(viewModel: viewModel) backupActionsController.setupNavigationBarTitle(L10n.Localizable.Self.Settings.HistoryBackup.title) return backupActionsController } else { From e372462d933426a5c38d0fb5c93dc13ca4da14a7 Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Mon, 13 Jan 2025 18:31:19 +0100 Subject: [PATCH 057/107] add todo --- .../CellDescriptors/SettingsCellDescriptorFactory+Account.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift index f85974fe10f..9df6f567964 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift @@ -462,6 +462,7 @@ extension SettingsCellDescriptorFactory { // MARK: - Backup action handler +// TODO: remove, present alert and file picker with BackupRestoreViewController extension SettingsCellDescriptorFactory { private func presentAlert(for error: Error) { From bfc722c0b891509d022c8e64169216df0911059c Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Tue, 14 Jan 2025 09:27:38 +0100 Subject: [PATCH 058/107] add subview to alertcontroller --- .../BackupRestoreViewController.swift | 62 ++++++++++++++++--- .../Views/BackupRestoreView.swift | 5 -- 2 files changed, 53 insertions(+), 14 deletions(-) diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift index 3b3b32c373e..63f50616b14 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift @@ -18,14 +18,58 @@ import SwiftUI -@MainActor -public func BackupRestoreViewController(viewModel: BackupRestoreViewModel) -> UIViewController { - UIHostingController(rootView: BackupRestoreView(viewModel: viewModel)) -} +public final class BackupRestoreViewController: UIViewController { + + private let viewModel: BackupRestoreViewModel + + public init(viewModel: BackupRestoreViewModel) { + self.viewModel = viewModel + super.init(nibName: nil, bundle: nil) + } + + @available(*, unavailable) + required init?(coder: NSCoder) { + fatalError("init(coder:) is not supported") + } + + public override func viewDidLoad() { + super.viewDidLoad() + setupView() + + DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(4)) { + let alertController = UIAlertController(title: "title", message: "message\n\n", preferredStyle: .alert) + + let activityIndicator = UIActivityIndicatorView(style: .medium) + activityIndicator.translatesAutoresizingMaskIntoConstraints = false + activityIndicator.startAnimating() + + alertController.view.addSubview(activityIndicator) + + NSLayoutConstraint.activate([ + activityIndicator.centerXAnchor.constraint(equalTo: alertController.view.centerXAnchor), + activityIndicator.bottomAnchor.constraint(equalTo: alertController.view.bottomAnchor, constant: -40) + ]) + + let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { _ in + print("Cancel button tapped!") + // Add any additional cleanup or logic here + } + alertController.addAction(cancelAction) + + self.present(alertController, animated: true) + } + } -@MainActor -public func BackupRestoreViewController( -// viewModel: BackupRestoreViewModel -) -> UIViewController { - fatalError() + private func setupView() { + let hostingController = UIHostingController(rootView: BackupRestoreView(viewModel: viewModel)) + addChild(hostingController) + hostingController.view.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(hostingController.view) + NSLayoutConstraint.activate([ + hostingController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), + hostingController.view.topAnchor.constraint(equalTo: view.topAnchor), + view.trailingAnchor.constraint(equalTo: hostingController.view.trailingAnchor), + view.bottomAnchor.constraint(equalTo: hostingController.view.bottomAnchor), + ]) + } } diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift index 91b458a4d24..531366616cf 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift @@ -53,11 +53,6 @@ struct BackupRestoreView: View { .listStyle(.grouped) .background(Color(ColorTheme.Backgrounds.background)) .scrollContentBackground(.hidden) - .alert("abcd", isPresented: $viewModel.presentBackupFailedAlert) { - Button("OK") { - viewModel.presentBackupFailedAlert = false - } - } } } From 1e134afdb1c0c6aecf79abaf0a1453a970e7d522 Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Tue, 14 Jan 2025 09:44:17 +0100 Subject: [PATCH 059/107] Trigger CI From a52f9f3b84de44caa097ae45ebc22c67947d9d72 Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Tue, 14 Jan 2025 10:14:51 +0100 Subject: [PATCH 060/107] fix snapshot reference images --- .../PlaceholderTests.swift | 26 ------------------- .../testBackupActions.dark.png | 4 +-- .../testBackupActions.light.png | 4 +-- 3 files changed, 4 insertions(+), 30 deletions(-) delete mode 100644 WireUI/Tests/WireSettingsUITests/PlaceholderTests.swift diff --git a/WireUI/Tests/WireSettingsUITests/PlaceholderTests.swift b/WireUI/Tests/WireSettingsUITests/PlaceholderTests.swift deleted file mode 100644 index cd66deaba62..00000000000 --- a/WireUI/Tests/WireSettingsUITests/PlaceholderTests.swift +++ /dev/null @@ -1,26 +0,0 @@ -// -// Wire -// Copyright (C) 2025 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import XCTest - -@testable import WireSettingsUI - -final class PlaceholderTests: XCTestCase { - - func testNothing() {} -} diff --git a/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.dark.png b/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.dark.png index 7c16df4ace2..8ba9c91f2e8 100644 --- a/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.dark.png +++ b/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:753e7576a9a0ef5ac3499cefb50532df55ecdb01045812514ec312ff5e92cbfa -size 284 +oid sha256:282b59b7cd1f034ded6b87978c7821930efd730f7dc1d22f95179d8494a72e3c +size 153482 diff --git a/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.light.png b/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.light.png index 4e5456a8005..db62a92da52 100644 --- a/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.light.png +++ b/WireUI/Tests/WireSettingsUITests/Resources/ReferenceImages/BackupActionsViewSnapshotTests/testBackupActions.light.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a1112beeea436526c6697f6423fee3659e288d2a47f725256a50a587421aa18b -size 284 +oid sha256:4a9090546b55a1955989c7f7353bfe5fa92607d0f18029bf29a7e530e0b8375f +size 152702 From 9fef825a18ded48ff12c7edb97b037f74b80cf23 Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Tue, 14 Jan 2025 10:28:24 +0100 Subject: [PATCH 061/107] format code --- ...SettingsCellDescriptorFactory+Account.swift | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift index 98e43ae829c..9d54f580b5b 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift @@ -519,14 +519,18 @@ extension SettingsCellDescriptorFactory { preferredStyle: .alert ) - alert.addAction(UIAlertAction( - title: L10n.Localizable.RestoreBackup.Confirmation.cancelButton, - style: .cancel) + alert.addAction( + UIAlertAction( + title: L10n.Localizable.RestoreBackup.Confirmation.cancelButton, + style: .cancel + ) ) - alert.addAction(UIAlertAction( - title: L10n.Localizable.RestoreBackup.Confirmation.overrideButton, - style: .default, - handler: { _ in completion() }) + alert.addAction( + UIAlertAction( + title: L10n.Localizable.RestoreBackup.Confirmation.overrideButton, + style: .default, + handler: { _ in completion() } + ) ) controller.present(alert, animated: true) From d0db7b1a83dea405d1edc409f361270e05a54028 Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Tue, 14 Jan 2025 10:37:15 +0100 Subject: [PATCH 062/107] revert renaming directory --- .../{BackupRestore => Backup}/Models/BackupResultHandler.swift | 0 .../Protocols/BackupPasswordValidator.swift | 0 .../{BackupRestore => Backup}/Protocols/BackupSource.swift | 0 .../ViewControllers/BackupRestoreViewController.swift | 0 .../ViewModels/BackupActionsViewModel.swift} | 0 .../Views/BackupActionsView.swift} | 0 .../{BackupRestore => Backup}/Views/ExportBackupView.swift | 0 .../{BackupRestore => Backup}/Views/PasswordFieldView.swift | 0 .../Views/Preview/BackupActionsPreview.swift | 0 .../Views/Preview/ExportBackupPreview.swift | 0 10 files changed, 0 insertions(+), 0 deletions(-) rename WireUI/Sources/WireSettingsUI/Account/{BackupRestore => Backup}/Models/BackupResultHandler.swift (100%) rename WireUI/Sources/WireSettingsUI/Account/{BackupRestore => Backup}/Protocols/BackupPasswordValidator.swift (100%) rename WireUI/Sources/WireSettingsUI/Account/{BackupRestore => Backup}/Protocols/BackupSource.swift (100%) rename WireUI/Sources/WireSettingsUI/Account/{BackupRestore => Backup}/ViewControllers/BackupRestoreViewController.swift (100%) rename WireUI/Sources/WireSettingsUI/Account/{BackupRestore/ViewModels/BackupRestoreViewModel.swift => Backup/ViewModels/BackupActionsViewModel.swift} (100%) rename WireUI/Sources/WireSettingsUI/Account/{BackupRestore/Views/BackupRestoreView.swift => Backup/Views/BackupActionsView.swift} (100%) rename WireUI/Sources/WireSettingsUI/Account/{BackupRestore => Backup}/Views/ExportBackupView.swift (100%) rename WireUI/Sources/WireSettingsUI/Account/{BackupRestore => Backup}/Views/PasswordFieldView.swift (100%) rename WireUI/Sources/WireSettingsUI/Account/{BackupRestore => Backup}/Views/Preview/BackupActionsPreview.swift (100%) rename WireUI/Sources/WireSettingsUI/Account/{BackupRestore => Backup}/Views/Preview/ExportBackupPreview.swift (100%) diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Models/BackupResultHandler.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupResultHandler.swift similarity index 100% rename from WireUI/Sources/WireSettingsUI/Account/BackupRestore/Models/BackupResultHandler.swift rename to WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupResultHandler.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/BackupPasswordValidator.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/BackupPasswordValidator.swift similarity index 100% rename from WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/BackupPasswordValidator.swift rename to WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/BackupPasswordValidator.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/BackupSource.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/BackupSource.swift similarity index 100% rename from WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/BackupSource.swift rename to WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/BackupSource.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewControllers/BackupRestoreViewController.swift similarity index 100% rename from WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift rename to WireUI/Sources/WireSettingsUI/Account/Backup/ViewControllers/BackupRestoreViewController.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewModels/BackupRestoreViewModel.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift similarity index 100% rename from WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewModels/BackupRestoreViewModel.swift rename to WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift similarity index 100% rename from WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift rename to WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/ExportBackupView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift similarity index 100% rename from WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/ExportBackupView.swift rename to WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/PasswordFieldView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift similarity index 100% rename from WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/PasswordFieldView.swift rename to WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/BackupActionsPreview.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift similarity index 100% rename from WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/BackupActionsPreview.swift rename to WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/ExportBackupPreview.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/ExportBackupPreview.swift similarity index 100% rename from WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/ExportBackupPreview.swift rename to WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/ExportBackupPreview.swift From 1dd52836a2c48dabb799549d39bdfb0be1a52d8b Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Tue, 14 Jan 2025 10:43:31 +0100 Subject: [PATCH 063/107] revert some changes --- .../ViewModels/BackupActionsViewModel.swift | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift index ce75cc3349a..7777e04e35b 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift @@ -24,9 +24,6 @@ public final class BackupRestoreViewModel: ObservableObject { private let backupResultHandler: BackupResultHandler let passwordValidator: any BackupPasswordValidatorProtocol - @Published private(set) var progress: Double? - @Published var presentBackupFailedAlert = false - public init( backupSource: any BackupSourceProtocol, backupResultHandler: BackupResultHandler, @@ -39,24 +36,12 @@ public final class BackupRestoreViewModel: ObservableObject { func backupActiveAccount(password: String) { do { - progress = 0.5 - throw DummyError.some - let backupPath = try backupSource.backupActiveAccount(password: password) backupResultHandler.onSuccess(backupPath) { [weak self] in self?.backupSource.clearPreviousBackups() - self?.progress = 1 } } catch { - progress = nil - DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3)) { - self.presentBackupFailedAlert = true - } backupResultHandler.onFailure(error) } } } - -enum DummyError: Error { - case some -} From cf57f18a74ec3ace73abf1d7637052aecad89108 Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Tue, 14 Jan 2025 11:03:28 +0100 Subject: [PATCH 064/107] fix build errors --- .../Backup/ViewControllers/BackupRestoreViewController.swift | 2 +- ...kupActionsViewModel.swift => BackupRestoreViewModel.swift} | 0 .../Account/Backup/Views/BackupActionsView.swift | 4 ++-- .../Account/Backup/Views/Preview/BackupActionsPreview.swift | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/{BackupActionsViewModel.swift => BackupRestoreViewModel.swift} (100%) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewControllers/BackupRestoreViewController.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewControllers/BackupRestoreViewController.swift index 63f50616b14..541acba50aa 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewControllers/BackupRestoreViewController.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewControllers/BackupRestoreViewController.swift @@ -61,7 +61,7 @@ public final class BackupRestoreViewController: UIViewController { } private func setupView() { - let hostingController = UIHostingController(rootView: BackupRestoreView(viewModel: viewModel)) + let hostingController = UIHostingController(rootView: BackupActionsView(viewModel: viewModel)) addChild(hostingController) hostingController.view.translatesAutoresizingMaskIntoConstraints = false view.addSubview(hostingController.view) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupRestoreViewModel.swift similarity index 100% rename from WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupActionsViewModel.swift rename to WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupRestoreViewModel.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift index 6c8216266cf..a890e5c1f6a 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift @@ -21,13 +21,13 @@ import WireDesign import WireReusableUIComponents public struct BackupActionsView: View { - @ObservedObject private var viewModel: BackupActionsViewModel + @ObservedObject private var viewModel: BackupRestoreViewModel @State private var isExportBackupSheetPresented: Bool = false @State private var isBackupPickerPresented: Bool = false @State private var isRestoreBackupSheetPresented: Bool = false @State private var selectedFileURL: URL? - public init(viewModel: BackupActionsViewModel) { + public init(viewModel: BackupRestoreViewModel) { self.viewModel = viewModel } diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift index 02398af7c0e..cfd563ca914 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift @@ -21,7 +21,7 @@ import SwiftUI @ViewBuilder @MainActor func BackupActionsPreview() -> some View { - BackupRestoreView(viewModel: BackupRestoreViewModel( + BackupActionsView(viewModel: BackupRestoreViewModel( backupSource: MockBackupSource(), restoreSource: MockRestoreSource(), backupResultHandler: BackupResultHandler( From ceb14b0958f4466350962d5d8a91316f397d5467 Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Tue, 14 Jan 2025 11:31:40 +0100 Subject: [PATCH 065/107] prepare for creating a custom alert controller --- .../BlockingActivityIndicator.swift | 2 +- .../ProgressIndicatingAlertController.swift | 52 +++++++++++++++++++ 2 files changed, 53 insertions(+), 1 deletion(-) create mode 100644 WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertController.swift diff --git a/WireUI/Sources/WireReusableUIComponents/BlockingActivityIndicator/BlockingActivityIndicator.swift b/WireUI/Sources/WireReusableUIComponents/BlockingActivityIndicator/BlockingActivityIndicator.swift index bf859550547..3216bfabf9c 100644 --- a/WireUI/Sources/WireReusableUIComponents/BlockingActivityIndicator/BlockingActivityIndicator.swift +++ b/WireUI/Sources/WireReusableUIComponents/BlockingActivityIndicator/BlockingActivityIndicator.swift @@ -138,7 +138,7 @@ private extension UIView { } } -private var stateKey = 0 +@MainActor private var stateKey = 0 // MARK: - Previews diff --git a/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertController.swift b/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertController.swift new file mode 100644 index 00000000000..3c95d16ae71 --- /dev/null +++ b/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertController.swift @@ -0,0 +1,52 @@ +// +// Wire +// Copyright (C) 2025 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import UIKit + +public final class ProgressIndicatingAlertController: UIViewController {} + +@available(iOS 17, *) +#Preview { + { + let vc = ProgressIndicatingAlertController() + vc.navigationItem.title = "test" + + let label = UILabel() + label.text = "Hello, World!" + label.translatesAutoresizingMaskIntoConstraints = false + label.font = UIFont.preferredFont(forTextStyle: .body) + label.adjustsFontForContentSizeCategory = true + vc.view.addSubview(label) + label.centerXAnchor.constraint(equalTo: vc.view.centerXAnchor).isActive = true + label.topAnchor.constraint(equalToSystemSpacingBelow: vc.view.safeAreaLayoutGuide.topAnchor, multiplier: 3).isActive = true + + let alertController = UIAlertController(title: "title", message: "message", preferredStyle: .alert) + + let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { _ in + print("Cancel button tapped!") + // Add any additional cleanup or logic here + } + alertController.addAction(cancelAction) + + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(50)) { + vc.present(alertController, animated: false) + } + + return UINavigationController(rootViewController: vc) + }() +} From ac434b71ea1578ae10b7a149fb31700cec544a3c Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Tue, 14 Jan 2025 11:42:40 +0100 Subject: [PATCH 066/107] custom alert controller --- .../ProgressIndicatingAlertController.swift | 138 +++++++++++++++++- 1 file changed, 135 insertions(+), 3 deletions(-) diff --git a/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertController.swift b/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertController.swift index 3c95d16ae71..e49cf56425a 100644 --- a/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertController.swift +++ b/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertController.swift @@ -18,12 +18,144 @@ import UIKit -public final class ProgressIndicatingAlertController: UIViewController {} +public final class ProgressIndicatingAlertController: UIViewController { + + private let containerView = UIView() + private let titleLabel = UILabel() + private let messageLabel = UILabel() + private let stackView = UIStackView() + + var alertTitle: String? + var alertMessage: String? + var actions: [UIAlertAction] = [] + + // Initializer + init(title: String?, message: String?) { + self.alertTitle = title + self.alertMessage = message + super.init(nibName: nil, bundle: nil) + self.modalPresentationStyle = .overFullScreen + self.modalTransitionStyle = .crossDissolve + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func viewDidLoad() { + super.viewDidLoad() + + setupView() + setupContent() + } + + private func setupView() { + view.backgroundColor = UIColor.black.withAlphaComponent(0.3) + + // Container View + containerView.backgroundColor = .white + containerView.layer.cornerRadius = 12 + containerView.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(containerView) + + NSLayoutConstraint.activate([ + containerView.centerXAnchor.constraint(equalTo: view.centerXAnchor), + containerView.centerYAnchor.constraint(equalTo: view.centerYAnchor), + containerView.widthAnchor.constraint(equalToConstant: 270) + ]) + + // Stack View + stackView.axis = .vertical + stackView.spacing = 8 + stackView.translatesAutoresizingMaskIntoConstraints = false + containerView.addSubview(stackView) + + NSLayoutConstraint.activate([ + stackView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 16), + stackView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16), + stackView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -16), + stackView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -16) + ]) + } + + private func setupContent() { + // Title + if let title = alertTitle { + titleLabel.text = title + titleLabel.font = UIFont.boldSystemFont(ofSize: 17) + titleLabel.textAlignment = .center + titleLabel.numberOfLines = 0 + stackView.addArrangedSubview(titleLabel) + } + + // Message + if let message = alertMessage { + messageLabel.text = message + messageLabel.font = UIFont.systemFont(ofSize: 13) + messageLabel.textAlignment = .center + messageLabel.numberOfLines = 0 + stackView.addArrangedSubview(messageLabel) + } + + // Actions (Buttons) + for action in actions { + let button = UIButton(type: .system) + button.setTitle(action.title, for: .normal) + button.titleLabel?.font = UIFont.systemFont(ofSize: 17) + button.setTitleColor(action.style == .destructive ? .red : .systemBlue, for: .normal) + button.addTarget(self, action: #selector(handleAction(_:)), for: .touchUpInside) + button.tag = actions.firstIndex(of: action) ?? 0 + stackView.addArrangedSubview(button) + } + } + + @objc private func handleAction(_ sender: UIButton) { + let action = actions[sender.tag] +// action.handler?(action) + dismiss(animated: true, completion: nil) + } + + // Add Action Method + func addAction(_ action: UIAlertAction) { + actions.append(action) + } +} + +@available(iOS 17, *) +#Preview("ProgressIndicatingAlertController") { + { + let vc = UIViewController() + vc.navigationItem.title = "test" + + let label = UILabel() + label.text = "Hello, World!" + label.translatesAutoresizingMaskIntoConstraints = false + label.font = UIFont.preferredFont(forTextStyle: .body) + label.adjustsFontForContentSizeCategory = true + vc.view.addSubview(label) + label.centerXAnchor.constraint(equalTo: vc.view.centerXAnchor).isActive = true + label.topAnchor.constraint(equalToSystemSpacingBelow: vc.view.safeAreaLayoutGuide.topAnchor, multiplier: 3).isActive = true + + let alertController = ProgressIndicatingAlertController(title: "title", message: "message") + + let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { _ in + print("Cancel button tapped!") + // Add any additional cleanup or logic here + } + alertController.addAction(cancelAction) + + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(50)) { + vc.present(alertController, animated: false) + } + + return UINavigationController(rootViewController: vc) + }() +} @available(iOS 17, *) -#Preview { +#Preview("UIAlertController") { { - let vc = ProgressIndicatingAlertController() + let vc = UIViewController() vc.navigationItem.title = "test" let label = UILabel() From a19196982aa5a4c15607f435ba1076107a6d2047 Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Tue, 14 Jan 2025 11:47:59 +0100 Subject: [PATCH 067/107] custom action --- .../ProgressIndicatingAlertController.swift | 90 +++++++++++++++---- 1 file changed, 72 insertions(+), 18 deletions(-) diff --git a/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertController.swift b/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertController.swift index e49cf56425a..40703948c4f 100644 --- a/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertController.swift +++ b/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertController.swift @@ -20,6 +20,12 @@ import UIKit public final class ProgressIndicatingAlertController: UIViewController { + struct Action { + let title: String + let style: UIAlertAction.Style + let handler: (() -> Void)? + } + private let containerView = UIView() private let titleLabel = UILabel() private let messageLabel = UILabel() @@ -27,13 +33,15 @@ public final class ProgressIndicatingAlertController: UIViewController { var alertTitle: String? var alertMessage: String? - var actions: [UIAlertAction] = [] + var actions: [Action] = [] // Initializer init(title: String?, message: String?) { self.alertTitle = title self.alertMessage = message super.init(nibName: nil, bundle: nil) + + // Presentation style to mimic an alert self.modalPresentationStyle = .overFullScreen self.modalTransitionStyle = .crossDissolve } @@ -45,15 +53,21 @@ public final class ProgressIndicatingAlertController: UIViewController { public override func viewDidLoad() { super.viewDidLoad() - setupView() + setupBackground() + setupContainerView() + setupStackView() setupContent() } - private func setupView() { + // MARK: - Setup Methods + + private func setupBackground() { + // Dim the background to mimic an alert view.backgroundColor = UIColor.black.withAlphaComponent(0.3) + } - // Container View - containerView.backgroundColor = .white + private func setupContainerView() { + containerView.backgroundColor = .systemBackground containerView.layer.cornerRadius = 12 containerView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(containerView) @@ -63,10 +77,11 @@ public final class ProgressIndicatingAlertController: UIViewController { containerView.centerYAnchor.constraint(equalTo: view.centerYAnchor), containerView.widthAnchor.constraint(equalToConstant: 270) ]) + } - // Stack View + private func setupStackView() { stackView.axis = .vertical - stackView.spacing = 8 + stackView.spacing = 16 stackView.translatesAutoresizingMaskIntoConstraints = false containerView.addSubview(stackView) @@ -80,20 +95,24 @@ public final class ProgressIndicatingAlertController: UIViewController { private func setupContent() { // Title - if let title = alertTitle { - titleLabel.text = title - titleLabel.font = UIFont.boldSystemFont(ofSize: 17) + if let titleText = alertTitle { + titleLabel.text = titleText + titleLabel.font = UIFont.preferredFont(forTextStyle: .headline) + titleLabel.adjustsFontForContentSizeCategory = true titleLabel.textAlignment = .center titleLabel.numberOfLines = 0 + stackView.addArrangedSubview(titleLabel) } // Message - if let message = alertMessage { - messageLabel.text = message - messageLabel.font = UIFont.systemFont(ofSize: 13) + if let messageText = alertMessage { + messageLabel.text = messageText + messageLabel.font = UIFont.preferredFont(forTextStyle: .body) + messageLabel.adjustsFontForContentSizeCategory = true messageLabel.textAlignment = .center messageLabel.numberOfLines = 0 + stackView.addArrangedSubview(messageLabel) } @@ -101,21 +120,56 @@ public final class ProgressIndicatingAlertController: UIViewController { for action in actions { let button = UIButton(type: .system) button.setTitle(action.title, for: .normal) - button.titleLabel?.font = UIFont.systemFont(ofSize: 17) - button.setTitleColor(action.style == .destructive ? .red : .systemBlue, for: .normal) - button.addTarget(self, action: #selector(handleAction(_:)), for: .touchUpInside) + + // Use a preferred font style for buttons, e.g. .body or .callout + button.titleLabel?.font = UIFont.preferredFont(forTextStyle: .body) + button.titleLabel?.adjustsFontForContentSizeCategory = true + + switch action.style { + case .destructive: + button.setTitleColor(.systemRed, for: .normal) + default: + button.setTitleColor(.systemBlue, for: .normal) + } + + // Tag each button so we know which action to trigger button.tag = actions.firstIndex(of: action) ?? 0 + button.addTarget(self, action: #selector(handleAction(_:)), for: .touchUpInside) + + // Add a separator for clarity if you like + // (Or you can just add the button if you don't need separators) + let separator = UIView() + separator.backgroundColor = .separator + separator.translatesAutoresizingMaskIntoConstraints = false + + // If you want a thin line above each button (optional): + if stackView.arrangedSubviews.last != nil { + let separatorContainer = UIView() + separatorContainer.addSubview(separator) + NSLayoutConstraint.activate([ + separator.leadingAnchor.constraint(equalTo: separatorContainer.leadingAnchor), + separator.trailingAnchor.constraint(equalTo: separatorContainer.trailingAnchor), + separator.topAnchor.constraint(equalTo: separatorContainer.topAnchor), + separator.heightAnchor.constraint(equalToConstant: 0.5), + separatorContainer.heightAnchor.constraint(equalToConstant: 0.5) + ]) + stackView.addArrangedSubview(separatorContainer) + } + stackView.addArrangedSubview(button) } } + // MARK: - Action Handling + @objc private func handleAction(_ sender: UIButton) { let action = actions[sender.tag] -// action.handler?(action) + action?(action) dismiss(animated: true, completion: nil) } - // Add Action Method + // MARK: - Public Methods + func addAction(_ action: UIAlertAction) { actions.append(action) } From fbc218135a62f4d65d6cfd5ff51f7e4dd58eed3e Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Tue, 14 Jan 2025 11:56:09 +0100 Subject: [PATCH 068/107] prepare for adding a progress indicator --- .../ProgressIndicatingAlertController.swift | 151 ++++++++++-------- 1 file changed, 88 insertions(+), 63 deletions(-) diff --git a/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertController.swift b/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertController.swift index 40703948c4f..8047f589c81 100644 --- a/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertController.swift +++ b/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertController.swift @@ -18,51 +18,77 @@ import UIKit +/// A custom view controller that displays an alert-like interface. public final class ProgressIndicatingAlertController: UIViewController { - struct Action { + /// A custom action model that mimics UIAlertAction. + struct CustomAlertAction { + /// The button title shown in the alert. let title: String + + /// The style for the button (default, cancel, or destructive). let style: UIAlertAction.Style - let handler: (() -> Void)? + + /// The closure to execute when the button is tapped. + let handler: ((CustomAlertAction) -> Void)? } + // MARK: - UI Elements + private let containerView = UIView() private let titleLabel = UILabel() private let messageLabel = UILabel() + + /// A stack view to layout the title, message, and buttons vertically. private let stackView = UIStackView() - var alertTitle: String? - var alertMessage: String? - var actions: [Action] = [] + // MARK: - Properties + + /// The alert's title and message text. + private let alertTitle: String? + private let alertMessage: String? + + /// An array of custom actions that will be turned into buttons. + private var actions: [CustomAlertAction] = [] + + // MARK: - Initializer - // Initializer init(title: String?, message: String?) { self.alertTitle = title self.alertMessage = message super.init(nibName: nil, bundle: nil) - // Presentation style to mimic an alert - self.modalPresentationStyle = .overFullScreen - self.modalTransitionStyle = .crossDissolve + // Presentation style to mimic a system alert (centered, dim background). + modalPresentationStyle = .overFullScreen + modalTransitionStyle = .crossDissolve } required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } + // MARK: - Lifecycle + public override func viewDidLoad() { super.viewDidLoad() - setupBackground() + setupBackgroundDimming() setupContainerView() setupStackView() - setupContent() + setupContent() // Populate with title, message, and buttons. } - // MARK: - Setup Methods + // MARK: - Public Methods + + /// Add a custom action to the alert. + func addAction(_ action: CustomAlertAction) { + actions.append(action) + } - private func setupBackground() { - // Dim the background to mimic an alert + // MARK: - Private Setup Methods + + private func setupBackgroundDimming() { + // Dim the background to mimic a system alert’s overlay. view.backgroundColor = UIColor.black.withAlphaComponent(0.3) } @@ -70,8 +96,10 @@ public final class ProgressIndicatingAlertController: UIViewController { containerView.backgroundColor = .systemBackground containerView.layer.cornerRadius = 12 containerView.translatesAutoresizingMaskIntoConstraints = false + view.addSubview(containerView) + // Center the container and set its width (similar to UIAlertController). NSLayoutConstraint.activate([ containerView.centerXAnchor.constraint(equalTo: view.centerXAnchor), containerView.centerYAnchor.constraint(equalTo: view.centerYAnchor), @@ -82,9 +110,13 @@ public final class ProgressIndicatingAlertController: UIViewController { private func setupStackView() { stackView.axis = .vertical stackView.spacing = 16 + stackView.alignment = .fill + stackView.distribution = .equalSpacing stackView.translatesAutoresizingMaskIntoConstraints = false + containerView.addSubview(stackView) + // Pin stackView edges to containerView with some padding. NSLayoutConstraint.activate([ stackView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 16), stackView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16), @@ -94,84 +126,77 @@ public final class ProgressIndicatingAlertController: UIViewController { } private func setupContent() { - // Title - if let titleText = alertTitle { - titleLabel.text = titleText - titleLabel.font = UIFont.preferredFont(forTextStyle: .headline) - titleLabel.adjustsFontForContentSizeCategory = true + // 1. Title + if let alertTitle = alertTitle { + titleLabel.text = alertTitle +// titleLabel.font = UIFont.boldSystemFont(ofSize: 17) + titleLabel.textColor = .label titleLabel.textAlignment = .center titleLabel.numberOfLines = 0 - + titleLabel.font = UIFont.preferredFont(forTextStyle: .headline) + titleLabel.adjustsFontForContentSizeCategory = true stackView.addArrangedSubview(titleLabel) } - // Message - if let messageText = alertMessage { - messageLabel.text = messageText - messageLabel.font = UIFont.preferredFont(forTextStyle: .body) - messageLabel.adjustsFontForContentSizeCategory = true + // 2. Message + if let alertMessage = alertMessage { + messageLabel.text = alertMessage + messageLabel.font = UIFont.systemFont(ofSize: 13) + messageLabel.textColor = .secondaryLabel messageLabel.textAlignment = .center messageLabel.numberOfLines = 0 - stackView.addArrangedSubview(messageLabel) } - // Actions (Buttons) - for action in actions { + // 3. Buttons (one for each CustomAlertAction) + for (index, action) in actions.enumerated() { + // Add a thin separator above each button (optional, for clarity). + if stackView.arrangedSubviews.count > 0 { + let separator = UIView() + separator.backgroundColor = .separator + separator.translatesAutoresizingMaskIntoConstraints = false + separator.heightAnchor.constraint(equalToConstant: 0.5).isActive = true + stackView.addArrangedSubview(separator) + } + + // Create a button for the action. let button = UIButton(type: .system) button.setTitle(action.title, for: .normal) - +// button.titleLabel?.font = UIFont.systemFont(ofSize: 17) // Use a preferred font style for buttons, e.g. .body or .callout - button.titleLabel?.font = UIFont.preferredFont(forTextStyle: .body) - button.titleLabel?.adjustsFontForContentSizeCategory = true + button.titleLabel?.font = UIFont.preferredFont(forTextStyle: .body) + button.titleLabel?.adjustsFontForContentSizeCategory = true + // Match the color style of system alerts. switch action.style { case .destructive: button.setTitleColor(.systemRed, for: .normal) + case .cancel: + button.setTitleColor(.systemBlue, for: .normal) + button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 17) // TODO: dynamic type default: button.setTitleColor(.systemBlue, for: .normal) } - // Tag each button so we know which action to trigger - button.tag = actions.firstIndex(of: action) ?? 0 - button.addTarget(self, action: #selector(handleAction(_:)), for: .touchUpInside) - - // Add a separator for clarity if you like - // (Or you can just add the button if you don't need separators) - let separator = UIView() - separator.backgroundColor = .separator - separator.translatesAutoresizingMaskIntoConstraints = false - - // If you want a thin line above each button (optional): - if stackView.arrangedSubviews.last != nil { - let separatorContainer = UIView() - separatorContainer.addSubview(separator) - NSLayoutConstraint.activate([ - separator.leadingAnchor.constraint(equalTo: separatorContainer.leadingAnchor), - separator.trailingAnchor.constraint(equalTo: separatorContainer.trailingAnchor), - separator.topAnchor.constraint(equalTo: separatorContainer.topAnchor), - separator.heightAnchor.constraint(equalToConstant: 0.5), - separatorContainer.heightAnchor.constraint(equalToConstant: 0.5) - ]) - stackView.addArrangedSubview(separatorContainer) - } + // Tag to identify which action was tapped. + button.tag = index + button.addTarget(self, action: #selector(handleButtonTap(_:)), for: .touchUpInside) + // Add the button to the stack. stackView.addArrangedSubview(button) } } // MARK: - Action Handling - @objc private func handleAction(_ sender: UIButton) { - let action = actions[sender.tag] - action?(action) - dismiss(animated: true, completion: nil) - } + @objc private func handleButtonTap(_ sender: UIButton) { + let tappedAction = actions[sender.tag] - // MARK: - Public Methods + // Call the custom handler closure. + tappedAction.handler?(tappedAction) - func addAction(_ action: UIAlertAction) { - actions.append(action) + // Dismiss the custom alert. + dismiss(animated: true, completion: nil) } } @@ -192,7 +217,7 @@ public final class ProgressIndicatingAlertController: UIViewController { let alertController = ProgressIndicatingAlertController(title: "title", message: "message") - let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { _ in + let cancelAction = ProgressIndicatingAlertController.CustomAlertAction(title: "Cancel", style: .cancel) { _ in print("Cancel button tapped!") // Add any additional cleanup or logic here } From 874c3b17a4854ca5e51765d10f99fa3c34e2774d Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Tue, 14 Jan 2025 12:52:59 +0100 Subject: [PATCH 069/107] custom layout --- .../ProgressIndicatingAlertController.swift | 168 ++++++++---------- 1 file changed, 77 insertions(+), 91 deletions(-) diff --git a/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertController.swift b/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertController.swift index 8047f589c81..cab2afbd84a 100644 --- a/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertController.swift +++ b/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertController.swift @@ -16,55 +16,44 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // +import WireDesign import UIKit -/// A custom view controller that displays an alert-like interface. +/// A custom view controller which displays an alert-like interface with a progress bar and a cancel button. public final class ProgressIndicatingAlertController: UIViewController { - /// A custom action model that mimics UIAlertAction. - struct CustomAlertAction { - /// The button title shown in the alert. - let title: String - - /// The style for the button (default, cancel, or destructive). - let style: UIAlertAction.Style + // MARK: - Properties - /// The closure to execute when the button is tapped. - let handler: ((CustomAlertAction) -> Void)? + public var progress = Float() { + didSet { progressView.progress = progress } } - // MARK: - UI Elements + private let message: String + private let cancelHandler: () -> Void + + // MARK: - Subviews private let containerView = UIView() private let titleLabel = UILabel() private let messageLabel = UILabel() - - /// A stack view to layout the title, message, and buttons vertically. - private let stackView = UIStackView() - - // MARK: - Properties - - /// The alert's title and message text. - private let alertTitle: String? - private let alertMessage: String? - - /// An array of custom actions that will be turned into buttons. - private var actions: [CustomAlertAction] = [] + private let progressLabel = UILabel() + private let progressView = UIProgressView() // MARK: - Initializer - init(title: String?, message: String?) { - self.alertTitle = title - self.alertMessage = message + init(title: String, message: String, cancelHandler: @escaping () -> Void) { + self.message = message + self.cancelHandler = cancelHandler super.init(nibName: nil, bundle: nil) + self.title = title - // Presentation style to mimic a system alert (centered, dim background). modalPresentationStyle = .overFullScreen modalTransitionStyle = .crossDissolve } + @available(*, unavailable) required init?(coder: NSCoder) { - fatalError("init(coder:) has not been implemented") + fatalError("init(coder:) is not supported") } // MARK: - Lifecycle @@ -74,22 +63,13 @@ public final class ProgressIndicatingAlertController: UIViewController { setupBackgroundDimming() setupContainerView() - setupStackView() - setupContent() // Populate with title, message, and buttons. - } - - // MARK: - Public Methods - - /// Add a custom action to the alert. - func addAction(_ action: CustomAlertAction) { - actions.append(action) + setupContent() } // MARK: - Private Setup Methods private func setupBackgroundDimming() { - // Dim the background to mimic a system alert’s overlay. - view.backgroundColor = UIColor.black.withAlphaComponent(0.3) + view.backgroundColor = UIColor.black.withAlphaComponent(0.2) // TODO: test dark mode } private func setupContainerView() { @@ -99,7 +79,6 @@ public final class ProgressIndicatingAlertController: UIViewController { view.addSubview(containerView) - // Center the container and set its width (similar to UIAlertController). NSLayoutConstraint.activate([ containerView.centerXAnchor.constraint(equalTo: view.centerXAnchor), containerView.centerYAnchor.constraint(equalTo: view.centerYAnchor), @@ -107,48 +86,39 @@ public final class ProgressIndicatingAlertController: UIViewController { ]) } - private func setupStackView() { - stackView.axis = .vertical - stackView.spacing = 16 - stackView.alignment = .fill - stackView.distribution = .equalSpacing - stackView.translatesAutoresizingMaskIntoConstraints = false - - containerView.addSubview(stackView) - - // Pin stackView edges to containerView with some padding. - NSLayoutConstraint.activate([ - stackView.topAnchor.constraint(equalTo: containerView.topAnchor, constant: 16), - stackView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor, constant: 16), - stackView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor, constant: -16), - stackView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor, constant: -16) - ]) - } - private func setupContent() { - // 1. Title - if let alertTitle = alertTitle { - titleLabel.text = alertTitle -// titleLabel.font = UIFont.boldSystemFont(ofSize: 17) - titleLabel.textColor = .label - titleLabel.textAlignment = .center - titleLabel.numberOfLines = 0 - titleLabel.font = UIFont.preferredFont(forTextStyle: .headline) - titleLabel.adjustsFontForContentSizeCategory = true - stackView.addArrangedSubview(titleLabel) - } - - // 2. Message - if let alertMessage = alertMessage { - messageLabel.text = alertMessage - messageLabel.font = UIFont.systemFont(ofSize: 13) - messageLabel.textColor = .secondaryLabel - messageLabel.textAlignment = .center - messageLabel.numberOfLines = 0 - stackView.addArrangedSubview(messageLabel) - } + titleLabel.text = title + titleLabel.textColor = .label + titleLabel.textAlignment = .center + titleLabel.numberOfLines = 0 + titleLabel.font = .preferredFont(forTextStyle: .headline) // TODO: wiretextstyle + titleLabel.adjustsFontForContentSizeCategory = true + titleLabel.translatesAutoresizingMaskIntoConstraints = false + containerView.addSubview(titleLabel) + + messageLabel.text = message + messageLabel.font = .preferredFont(forTextStyle: .footnote) // TODO: wiretextstyle + messageLabel.textColor = BaseColorPalette.Grays.gray70 + messageLabel.textAlignment = .center + messageLabel.numberOfLines = 0 + messageLabel.translatesAutoresizingMaskIntoConstraints = false + containerView.addSubview(messageLabel) + + progressLabel.text = "25%" + progressLabel.font = .preferredFont(forTextStyle: .caption2) // TODO: wiretextstyle + progressLabel.textColor = BaseColorPalette.Grays.gray70 + progressLabel.textAlignment = .center + progressLabel.numberOfLines = 1 + progressLabel.translatesAutoresizingMaskIntoConstraints = false + containerView.addSubview(progressLabel) + + progressView.translatesAutoresizingMaskIntoConstraints = false + containerView.addSubview(progressView) + + // TODO: button // 3. Buttons (one for each CustomAlertAction) + /* for (index, action) in actions.enumerated() { // Add a thin separator above each button (optional, for clarity). if stackView.arrangedSubviews.count > 0 { @@ -185,18 +155,37 @@ public final class ProgressIndicatingAlertController: UIViewController { // Add the button to the stack. stackView.addArrangedSubview(button) } + */ + + NSLayoutConstraint.activate([ + titleLabel.leadingAnchor.constraint(equalToSystemSpacingAfter: containerView.leadingAnchor, multiplier: 2), + titleLabel.topAnchor.constraint(equalToSystemSpacingBelow: containerView.topAnchor, multiplier: 2.5), + containerView.trailingAnchor.constraint(equalToSystemSpacingAfter: titleLabel.trailingAnchor, multiplier: 2), + + messageLabel.leadingAnchor.constraint(equalTo: titleLabel.leadingAnchor), + messageLabel.topAnchor.constraint(equalToSystemSpacingBelow: titleLabel.bottomAnchor, multiplier: 1), + titleLabel.trailingAnchor.constraint(equalTo: messageLabel.trailingAnchor), + + progressLabel.centerXAnchor.constraint(equalTo: containerView.centerXAnchor), + progressLabel.topAnchor.constraint(equalToSystemSpacingBelow: messageLabel.bottomAnchor, multiplier: 1), + + progressView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor), + progressView.topAnchor.constraint(equalToSystemSpacingBelow: progressLabel.bottomAnchor, multiplier: 1), + containerView.trailingAnchor.constraint(equalTo: progressView.trailingAnchor), + containerView.bottomAnchor.constraint(equalToSystemSpacingBelow: progressView.bottomAnchor, multiplier: 3), + ]) } // MARK: - Action Handling @objc private func handleButtonTap(_ sender: UIButton) { - let tappedAction = actions[sender.tag] - - // Call the custom handler closure. - tappedAction.handler?(tappedAction) - - // Dismiss the custom alert. - dismiss(animated: true, completion: nil) +// let tappedAction = actions[sender.tag] +// +// // Call the custom handler closure. +// tappedAction.handler?(tappedAction) +// +// // Dismiss the custom alert. +// dismiss(animated: true, completion: nil) } } @@ -215,13 +204,10 @@ public final class ProgressIndicatingAlertController: UIViewController { label.centerXAnchor.constraint(equalTo: vc.view.centerXAnchor).isActive = true label.topAnchor.constraint(equalToSystemSpacingBelow: vc.view.safeAreaLayoutGuide.topAnchor, multiplier: 3).isActive = true - let alertController = ProgressIndicatingAlertController(title: "title", message: "message") - - let cancelAction = ProgressIndicatingAlertController.CustomAlertAction(title: "Cancel", style: .cancel) { _ in + let alertController = ProgressIndicatingAlertController(title: "title", message: "message") { print("Cancel button tapped!") - // Add any additional cleanup or logic here } - alertController.addAction(cancelAction) + alertController.progress = 0.25 DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(50)) { vc.present(alertController, animated: false) From 142c338aee182b86440d340d77f8ab1cc7270edf Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Tue, 14 Jan 2025 13:53:17 +0100 Subject: [PATCH 070/107] finish implementation --- .../ProgressIndicatingAlertController.swift | 78 ++++++------------- 1 file changed, 23 insertions(+), 55 deletions(-) diff --git a/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertController.swift b/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertController.swift index cab2afbd84a..4015096220c 100644 --- a/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertController.swift +++ b/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertController.swift @@ -25,7 +25,10 @@ public final class ProgressIndicatingAlertController: UIViewController { // MARK: - Properties public var progress = Float() { - didSet { progressView.progress = progress } + didSet { + progressLabel.text = "\(Int(progress * 100))%" + progressView.progress = progress + } } private let message: String @@ -38,6 +41,7 @@ public final class ProgressIndicatingAlertController: UIViewController { private let messageLabel = UILabel() private let progressLabel = UILabel() private let progressView = UIProgressView() + private let cancelButton = UIButton(type: .system) // MARK: - Initializer @@ -91,71 +95,36 @@ public final class ProgressIndicatingAlertController: UIViewController { titleLabel.textColor = .label titleLabel.textAlignment = .center titleLabel.numberOfLines = 0 - titleLabel.font = .preferredFont(forTextStyle: .headline) // TODO: wiretextstyle + titleLabel.font = .preferredFont(forTextStyle: .headline) titleLabel.adjustsFontForContentSizeCategory = true titleLabel.translatesAutoresizingMaskIntoConstraints = false containerView.addSubview(titleLabel) messageLabel.text = message - messageLabel.font = .preferredFont(forTextStyle: .footnote) // TODO: wiretextstyle + messageLabel.font = .preferredFont(forTextStyle: .caption1) messageLabel.textColor = BaseColorPalette.Grays.gray70 messageLabel.textAlignment = .center messageLabel.numberOfLines = 0 messageLabel.translatesAutoresizingMaskIntoConstraints = false containerView.addSubview(messageLabel) - progressLabel.text = "25%" - progressLabel.font = .preferredFont(forTextStyle: .caption2) // TODO: wiretextstyle + progressLabel.text = "\(Int(progress * 100))%" + progressLabel.font = .preferredFont(forTextStyle: .caption1) progressLabel.textColor = BaseColorPalette.Grays.gray70 progressLabel.textAlignment = .center progressLabel.numberOfLines = 1 progressLabel.translatesAutoresizingMaskIntoConstraints = false containerView.addSubview(progressLabel) + progressView.progress = progress progressView.translatesAutoresizingMaskIntoConstraints = false containerView.addSubview(progressView) - // TODO: button - - // 3. Buttons (one for each CustomAlertAction) - /* - for (index, action) in actions.enumerated() { - // Add a thin separator above each button (optional, for clarity). - if stackView.arrangedSubviews.count > 0 { - let separator = UIView() - separator.backgroundColor = .separator - separator.translatesAutoresizingMaskIntoConstraints = false - separator.heightAnchor.constraint(equalToConstant: 0.5).isActive = true - stackView.addArrangedSubview(separator) - } - - // Create a button for the action. - let button = UIButton(type: .system) - button.setTitle(action.title, for: .normal) -// button.titleLabel?.font = UIFont.systemFont(ofSize: 17) - // Use a preferred font style for buttons, e.g. .body or .callout - button.titleLabel?.font = UIFont.preferredFont(forTextStyle: .body) - button.titleLabel?.adjustsFontForContentSizeCategory = true - - // Match the color style of system alerts. - switch action.style { - case .destructive: - button.setTitleColor(.systemRed, for: .normal) - case .cancel: - button.setTitleColor(.systemBlue, for: .normal) - button.titleLabel?.font = UIFont.boldSystemFont(ofSize: 17) // TODO: dynamic type - default: - button.setTitleColor(.systemBlue, for: .normal) - } - - // Tag to identify which action was tapped. - button.tag = index - button.addTarget(self, action: #selector(handleButtonTap(_:)), for: .touchUpInside) - - // Add the button to the stack. - stackView.addArrangedSubview(button) - } - */ + cancelButton.setTitle("cancel", for: .normal) // TODO: localization + cancelButton.titleLabel?.font = .preferredFont(forTextStyle: .body) + cancelButton.addTarget(self, action: #selector(handleCancel(_:)), for: .primaryActionTriggered) + cancelButton.translatesAutoresizingMaskIntoConstraints = false + containerView.addSubview(cancelButton) NSLayoutConstraint.activate([ titleLabel.leadingAnchor.constraint(equalToSystemSpacingAfter: containerView.leadingAnchor, multiplier: 2), @@ -172,20 +141,19 @@ public final class ProgressIndicatingAlertController: UIViewController { progressView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor), progressView.topAnchor.constraint(equalToSystemSpacingBelow: progressLabel.bottomAnchor, multiplier: 1), containerView.trailingAnchor.constraint(equalTo: progressView.trailingAnchor), - containerView.bottomAnchor.constraint(equalToSystemSpacingBelow: progressView.bottomAnchor, multiplier: 3), + + cancelButton.leadingAnchor.constraint(equalTo: titleLabel.leadingAnchor), + cancelButton.topAnchor.constraint(equalToSystemSpacingBelow: progressView.bottomAnchor, multiplier: 1), + titleLabel.trailingAnchor.constraint(equalTo: cancelButton.trailingAnchor), + containerView.bottomAnchor.constraint(equalToSystemSpacingBelow: cancelButton.bottomAnchor, multiplier: 1), ]) } // MARK: - Action Handling - @objc private func handleButtonTap(_ sender: UIButton) { -// let tappedAction = actions[sender.tag] -// -// // Call the custom handler closure. -// tappedAction.handler?(tappedAction) -// -// // Dismiss the custom alert. -// dismiss(animated: true, completion: nil) + @objc private func handleCancel(_ sender: UIButton) { + cancelHandler() + presentingViewController?.dismiss(animated: true) } } From df99c3aa80ce2aee87870410e3b819038423ec96 Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Tue, 14 Jan 2025 14:50:25 +0100 Subject: [PATCH 071/107] create snapshot tests --- .../ProgressIndicatingAlertController.swift | 51 +++++--------- ...ressIndicatingAlertControllerPreview.swift | 47 +++++++++++++ ...ogressIndicatingAlertControllerTests.swift | 66 +++++++++++++++++++ 3 files changed, 131 insertions(+), 33 deletions(-) create mode 100644 WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertControllerPreview.swift create mode 100644 WireUI/Tests/WireReusableUIComponentsTests/ProgressIndicatingAlertController/ProgressIndicatingAlertControllerTests.swift diff --git a/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertController.swift b/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertController.swift index 4015096220c..15b8e9ce8a7 100644 --- a/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertController.swift +++ b/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertController.swift @@ -32,7 +32,7 @@ public final class ProgressIndicatingAlertController: UIViewController { } private let message: String - private let cancelHandler: () -> Void + private let cancelAction: Action // MARK: - Subviews @@ -45,9 +45,13 @@ public final class ProgressIndicatingAlertController: UIViewController { // MARK: - Initializer - init(title: String, message: String, cancelHandler: @escaping () -> Void) { + init( + title: String, + message: String, + cancelAction: Action + ) { self.message = message - self.cancelHandler = cancelHandler + self.cancelAction = cancelAction super.init(nibName: nil, bundle: nil) self.title = title @@ -120,7 +124,7 @@ public final class ProgressIndicatingAlertController: UIViewController { progressView.translatesAutoresizingMaskIntoConstraints = false containerView.addSubview(progressView) - cancelButton.setTitle("cancel", for: .normal) // TODO: localization + cancelButton.setTitle("Cancel", for: .normal) // TODO: localization cancelButton.titleLabel?.font = .preferredFont(forTextStyle: .body) cancelButton.addTarget(self, action: #selector(handleCancel(_:)), for: .primaryActionTriggered) cancelButton.translatesAutoresizingMaskIntoConstraints = false @@ -152,37 +156,21 @@ public final class ProgressIndicatingAlertController: UIViewController { // MARK: - Action Handling @objc private func handleCancel(_ sender: UIButton) { - cancelHandler() + cancelAction.handler() presentingViewController?.dismiss(animated: true) } + + // MARK: - Nested Types + + struct Action { + var title: String + var handler: () -> Void + } } @available(iOS 17, *) #Preview("ProgressIndicatingAlertController") { - { - let vc = UIViewController() - vc.navigationItem.title = "test" - - let label = UILabel() - label.text = "Hello, World!" - label.translatesAutoresizingMaskIntoConstraints = false - label.font = UIFont.preferredFont(forTextStyle: .body) - label.adjustsFontForContentSizeCategory = true - vc.view.addSubview(label) - label.centerXAnchor.constraint(equalTo: vc.view.centerXAnchor).isActive = true - label.topAnchor.constraint(equalToSystemSpacingBelow: vc.view.safeAreaLayoutGuide.topAnchor, multiplier: 3).isActive = true - - let alertController = ProgressIndicatingAlertController(title: "title", message: "message") { - print("Cancel button tapped!") - } - alertController.progress = 0.25 - - DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(50)) { - vc.present(alertController, animated: false) - } - - return UINavigationController(rootViewController: vc) - }() + ProgressIndicatingAlertControllerPreview() } @available(iOS 17, *) @@ -202,10 +190,7 @@ public final class ProgressIndicatingAlertController: UIViewController { let alertController = UIAlertController(title: "title", message: "message", preferredStyle: .alert) - let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { _ in - print("Cancel button tapped!") - // Add any additional cleanup or logic here - } + let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { _ in } alertController.addAction(cancelAction) DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(50)) { diff --git a/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertControllerPreview.swift b/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertControllerPreview.swift new file mode 100644 index 00000000000..10c5965f06c --- /dev/null +++ b/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertControllerPreview.swift @@ -0,0 +1,47 @@ +// +// Wire +// Copyright (C) 2025 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import UIKit + +@MainActor +func ProgressIndicatingAlertControllerPreview() -> UIViewController { + let vc = UIViewController() + vc.navigationItem.title = "ProgressIndicatingAlertController" + + let label = UILabel() + label.text = "Hello, World!" + label.translatesAutoresizingMaskIntoConstraints = false + label.font = UIFont.preferredFont(forTextStyle: .body) + label.adjustsFontForContentSizeCategory = true + vc.view.addSubview(label) + label.centerXAnchor.constraint(equalTo: vc.view.centerXAnchor).isActive = true + label.topAnchor.constraint(equalToSystemSpacingBelow: vc.view.safeAreaLayoutGuide.topAnchor, multiplier: 3).isActive = true + + let alertController = ProgressIndicatingAlertController( + title: "Creating Backup", + message: "Saving conversation history...", + cancelAction: .init(title: "Cancel", handler: {}) + ) + alertController.progress = 0.25 + + DispatchQueue.main.asyncAfter(deadline: .now() + .milliseconds(50)) { + vc.present(alertController, animated: false) + } + + return UINavigationController(rootViewController: vc) +} diff --git a/WireUI/Tests/WireReusableUIComponentsTests/ProgressIndicatingAlertController/ProgressIndicatingAlertControllerTests.swift b/WireUI/Tests/WireReusableUIComponentsTests/ProgressIndicatingAlertController/ProgressIndicatingAlertControllerTests.swift new file mode 100644 index 00000000000..482394c1439 --- /dev/null +++ b/WireUI/Tests/WireReusableUIComponentsTests/ProgressIndicatingAlertController/ProgressIndicatingAlertControllerTests.swift @@ -0,0 +1,66 @@ +// +// Wire +// Copyright (C) 2025 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import SwiftUI +import WireTestingPackage +import XCTest + +@testable import WireReusableUIComponents + +final class ProgressIndicatingAlertControllerTests: XCTestCase { + + private var snapshotHelper: SnapshotHelper! + + override func setUp() { + snapshotHelper = .init() + .withSnapshotDirectory(SnapshotTestReferenceImageDirectory) + } + + override func tearDown() { + snapshotHelper = nil + } + + @MainActor + func testColorSchemeVariants() { + let screenBounds = UIScreen.main.bounds + let sut = ProgressIndicatingAlertControllerPreview() + .frame(width: screenBounds.width, height: screenBounds.height) + + snapshotHelper + .withUserInterfaceStyle(.light) + .verify(matching: sut, named: "light") + snapshotHelper + .withUserInterfaceStyle(.dark) + .verify(matching: sut, named: "dark") + } + + @MainActor + func testDynamicTypeVariants() { + let screenBounds = UIScreen.main.bounds + let sut = ProgressIndicatingAlertControllerPreview() + .frame(width: screenBounds.width * 2 / 3, height: screenBounds.height * 2 / 3) + + for dynamicTypeSize in DynamicTypeSize.allCases { + snapshotHelper + .verify( + matching: sut.dynamicTypeSize(dynamicTypeSize), + named: "\(dynamicTypeSize)" + ) + } + } +} From d42821ea6ae2e54b42feed624b795f4c1c883ea7 Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Tue, 14 Jan 2025 15:11:59 +0100 Subject: [PATCH 072/107] attempt to create snapshot images --- ...ogressIndicatingAlertControllerTests.swift | 53 +++++++++++-------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/WireUI/Tests/WireReusableUIComponentsTests/ProgressIndicatingAlertController/ProgressIndicatingAlertControllerTests.swift b/WireUI/Tests/WireReusableUIComponentsTests/ProgressIndicatingAlertController/ProgressIndicatingAlertControllerTests.swift index 482394c1439..557b9e0fb06 100644 --- a/WireUI/Tests/WireReusableUIComponentsTests/ProgressIndicatingAlertController/ProgressIndicatingAlertControllerTests.swift +++ b/WireUI/Tests/WireReusableUIComponentsTests/ProgressIndicatingAlertController/ProgressIndicatingAlertControllerTests.swift @@ -22,45 +22,56 @@ import XCTest @testable import WireReusableUIComponents +@MainActor final class ProgressIndicatingAlertControllerTests: XCTestCase { + private var sut: UIViewController! + private var window: UIWindow! private var snapshotHelper: SnapshotHelper! - override func setUp() { + override func setUp() async throws { + window = .init(frame: UIScreen.main.bounds) + window.backgroundColor = .systemBackground + window.makeKeyAndVisible() + snapshotHelper = .init() .withSnapshotDirectory(SnapshotTestReferenceImageDirectory) } - override func tearDown() { + override func tearDown() async throws { + window.isHidden = true + window = nil snapshotHelper = nil } - @MainActor - func testColorSchemeVariants() { - let screenBounds = UIScreen.main.bounds - let sut = ProgressIndicatingAlertControllerPreview() - .frame(width: screenBounds.width, height: screenBounds.height) - - snapshotHelper - .withUserInterfaceStyle(.light) - .verify(matching: sut, named: "light") + @available(iOS 17, *) @MainActor + func testUIFontDarkUserInterfaceStyle() async { + sut = ProgressIndicatingAlertControllerPreview() + try? await Task.sleep(for: .milliseconds(200)) snapshotHelper .withUserInterfaceStyle(.dark) - .verify(matching: sut, named: "dark") + .verify(matching: sut) } - @MainActor - func testDynamicTypeVariants() { - let screenBounds = UIScreen.main.bounds - let sut = ProgressIndicatingAlertControllerPreview() - .frame(width: screenBounds.width * 2 / 3, height: screenBounds.height * 2 / 3) - - for dynamicTypeSize in DynamicTypeSize.allCases { + @available(iOS 17, *) @MainActor + func testUIFontContentSizeCategories() async { + sut = ProgressIndicatingAlertControllerPreview() + window.rootViewController = sut + try? await Task.sleep(for: .milliseconds(200)) + for contentSizeCategory in UIContentSizeCategory.allCases { + sut.traitOverrides.preferredContentSizeCategory = contentSizeCategory snapshotHelper .verify( - matching: sut.dynamicTypeSize(dynamicTypeSize), - named: "\(dynamicTypeSize)" + matching: renderedImage(), + named: "\(contentSizeCategory)" ) } } + + private func renderedImage() -> UIImage { + let renderer = UIGraphicsImageRenderer(size: sut.view.bounds.size) + return renderer.image { _ in + sut.view.drawHierarchy(in: sut.view.bounds, afterScreenUpdates: true) + } + } } From 1e39fd39ee1490ea7ee3a95c3f4917f98ea269e4 Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Tue, 14 Jan 2025 15:18:19 +0100 Subject: [PATCH 073/107] fix style --- .../ProgressIndicatingAlertController.swift | 1 + .../ProgressIndicatingAlertControllerTests.swift | 2 ++ 2 files changed, 3 insertions(+) diff --git a/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertController.swift b/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertController.swift index 15b8e9ce8a7..6aad354e74d 100644 --- a/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertController.swift +++ b/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertController.swift @@ -120,6 +120,7 @@ public final class ProgressIndicatingAlertController: UIViewController { progressLabel.translatesAutoresizingMaskIntoConstraints = false containerView.addSubview(progressLabel) + progressView.progressViewStyle = .bar progressView.progress = progress progressView.translatesAutoresizingMaskIntoConstraints = false containerView.addSubview(progressView) diff --git a/WireUI/Tests/WireReusableUIComponentsTests/ProgressIndicatingAlertController/ProgressIndicatingAlertControllerTests.swift b/WireUI/Tests/WireReusableUIComponentsTests/ProgressIndicatingAlertController/ProgressIndicatingAlertControllerTests.swift index 557b9e0fb06..003b5308023 100644 --- a/WireUI/Tests/WireReusableUIComponentsTests/ProgressIndicatingAlertController/ProgressIndicatingAlertControllerTests.swift +++ b/WireUI/Tests/WireReusableUIComponentsTests/ProgressIndicatingAlertController/ProgressIndicatingAlertControllerTests.swift @@ -66,6 +66,8 @@ final class ProgressIndicatingAlertControllerTests: XCTestCase { named: "\(contentSizeCategory)" ) } + + XCTFail("doesn't work") } private func renderedImage() -> UIImage { From 9ad99341059bc381c5da071e62594637df82ca19 Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Tue, 14 Jan 2025 15:28:04 +0100 Subject: [PATCH 074/107] cleanup & refactoring --- .../Protocols/BackupPasswordValidator.swift | 1 + .../BackupRestoreViewController.swift | 24 +-------------- .../Backup/Views/BackupActionsView.swift | 30 +++++++------------ 3 files changed, 13 insertions(+), 42 deletions(-) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/BackupPasswordValidator.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/BackupPasswordValidator.swift index 311fb67560a..f6f7b6fb532 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/BackupPasswordValidator.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/BackupPasswordValidator.swift @@ -16,6 +16,7 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // +/// Determines if a given password is valid for encrypting a backup. public protocol BackupPasswordValidatorProtocol { func isPasswordValid(_ password: String) -> Bool diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewControllers/BackupRestoreViewController.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewControllers/BackupRestoreViewController.swift index 541acba50aa..b3c8b33f7c0 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewControllers/BackupRestoreViewController.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewControllers/BackupRestoreViewController.swift @@ -35,29 +35,6 @@ public final class BackupRestoreViewController: UIViewController { public override func viewDidLoad() { super.viewDidLoad() setupView() - - DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(4)) { - let alertController = UIAlertController(title: "title", message: "message\n\n", preferredStyle: .alert) - - let activityIndicator = UIActivityIndicatorView(style: .medium) - activityIndicator.translatesAutoresizingMaskIntoConstraints = false - activityIndicator.startAnimating() - - alertController.view.addSubview(activityIndicator) - - NSLayoutConstraint.activate([ - activityIndicator.centerXAnchor.constraint(equalTo: alertController.view.centerXAnchor), - activityIndicator.bottomAnchor.constraint(equalTo: alertController.view.bottomAnchor, constant: -40) - ]) - - let cancelAction = UIAlertAction(title: "Cancel", style: .cancel) { _ in - print("Cancel button tapped!") - // Add any additional cleanup or logic here - } - alertController.addAction(cancelAction) - - self.present(alertController, animated: true) - } } private func setupView() { @@ -71,5 +48,6 @@ public final class BackupRestoreViewController: UIViewController { view.trailingAnchor.constraint(equalTo: hostingController.view.trailingAnchor), view.bottomAnchor.constraint(equalTo: hostingController.view.bottomAnchor), ]) + hostingController.didMove(toParent: self) } } diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift index a890e5c1f6a..637af465cc1 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift @@ -20,25 +20,20 @@ import SwiftUI import WireDesign import WireReusableUIComponents -public struct BackupActionsView: View { +struct BackupActionsView: View { + @ObservedObject private var viewModel: BackupRestoreViewModel @State private var isExportBackupSheetPresented: Bool = false @State private var isBackupPickerPresented: Bool = false @State private var isRestoreBackupSheetPresented: Bool = false @State private var selectedFileURL: URL? - public init(viewModel: BackupRestoreViewModel) { - self.viewModel = viewModel - } - - public var body: some View { + var body: some View { List { - Section( - footer: Text(L10n.Localizable.Settings.ExportBackup.description) - ) { - Button(action: { + Section(footer: Text(L10n.Localizable.Settings.ExportBackup.description)) { + Button { isExportBackupSheetPresented.toggle() - }, label: { + } label: { HStack { Text(L10n.Localizable.Settings.ExportBackup.action) .wireTextStyle(.body2) @@ -46,7 +41,7 @@ public struct BackupActionsView: View { Spacer() Image(systemName: "chevron.right").foregroundStyle(Color.primary) } - }) + } .sheet(isPresented: $isExportBackupSheetPresented) { NavigationStack { ExportBackupView( @@ -61,14 +56,12 @@ public struct BackupActionsView: View { } .listRowBackground(Color(ColorTheme.Backgrounds.surface)) - Section( - footer: Text(L10n.Localizable.Settings.RestoreFromBackup.description) - ) { - Button(action: { + Section(footer: Text(L10n.Localizable.Settings.RestoreFromBackup.description)) { + Button { viewModel.confirmBackupRestore { isBackupPickerPresented.toggle() } - }, label: { + } label: { HStack { Text(L10n.Localizable.Settings.RestoreFromBackup.action) .font(.textStyle(.body2)) @@ -76,7 +69,7 @@ public struct BackupActionsView: View { Spacer() Image(systemName: "chevron.right").foregroundStyle(Color.primary) } - }) + } .fullScreenCover(isPresented: $isBackupPickerPresented) { BackupPicker { url in if let fileURL = url { @@ -103,7 +96,6 @@ public struct BackupActionsView: View { .listRowBackground(Color(ColorTheme.Backgrounds.surface)) } .listStyle(.grouped) - .background(Color(ColorTheme.Backgrounds.background)) .scrollContentBackground(.hidden) } } From 727bc20dd21b4bc042b825e6c83368942e0ed3fb Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Tue, 14 Jan 2025 15:33:49 +0100 Subject: [PATCH 075/107] renamings and fixes --- ... => BackupPasswordValidatorProtocol.swift} | 0 .../BackupRestoreViewController.swift | 2 +- ...ionsView.swift => BackupRestoreView.swift} | 30 +++++++------------ ...w.swift => BackupRestoreViewPreview.swift} | 4 +-- ...t => BackupRestoreViewSnapshotTests.swift} | 2 +- 5 files changed, 15 insertions(+), 23 deletions(-) rename WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/{BackupPasswordValidator.swift => BackupPasswordValidatorProtocol.swift} (100%) rename WireUI/Sources/WireSettingsUI/Account/Backup/Views/{BackupActionsView.swift => BackupRestoreView.swift} (76%) rename WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/{BackupActionsPreview.swift => BackupRestoreViewPreview.swift} (95%) rename WireUI/Tests/WireSettingsUITests/Account/Backup/{BackupActionsViewSnapshotTests.swift => BackupRestoreViewSnapshotTests.swift} (97%) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/BackupPasswordValidator.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/BackupPasswordValidatorProtocol.swift similarity index 100% rename from WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/BackupPasswordValidator.swift rename to WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/BackupPasswordValidatorProtocol.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewControllers/BackupRestoreViewController.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewControllers/BackupRestoreViewController.swift index b3c8b33f7c0..90d55ecb229 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewControllers/BackupRestoreViewController.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/ViewControllers/BackupRestoreViewController.swift @@ -38,7 +38,7 @@ public final class BackupRestoreViewController: UIViewController { } private func setupView() { - let hostingController = UIHostingController(rootView: BackupActionsView(viewModel: viewModel)) + let hostingController = UIHostingController(rootView: BackupRestoreView(viewModel: viewModel)) addChild(hostingController) hostingController.view.translatesAutoresizingMaskIntoConstraints = false view.addSubview(hostingController.view) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupRestoreView.swift similarity index 76% rename from WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift rename to WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupRestoreView.swift index 637af465cc1..a9d102e558e 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupActionsView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupRestoreView.swift @@ -20,9 +20,10 @@ import SwiftUI import WireDesign import WireReusableUIComponents -struct BackupActionsView: View { +struct BackupRestoreView: View { + + @ObservedObject private(set) var viewModel: BackupRestoreViewModel - @ObservedObject private var viewModel: BackupRestoreViewModel @State private var isExportBackupSheetPresented: Bool = false @State private var isBackupPickerPresented: Bool = false @State private var isRestoreBackupSheetPresented: Bool = false @@ -34,13 +35,9 @@ struct BackupActionsView: View { Button { isExportBackupSheetPresented.toggle() } label: { - HStack { - Text(L10n.Localizable.Settings.ExportBackup.action) - .wireTextStyle(.body2) - .foregroundStyle(Color.primaryText) - Spacer() - Image(systemName: "chevron.right").foregroundStyle(Color.primary) - } + Text(L10n.Localizable.Settings.ExportBackup.action) + .wireTextStyle(.body2) + .foregroundStyle(Color.primaryText) } .sheet(isPresented: $isExportBackupSheetPresented) { NavigationStack { @@ -54,7 +51,6 @@ struct BackupActionsView: View { .presentationDetents([.medium]) } } - .listRowBackground(Color(ColorTheme.Backgrounds.surface)) Section(footer: Text(L10n.Localizable.Settings.RestoreFromBackup.description)) { Button { @@ -62,13 +58,9 @@ struct BackupActionsView: View { isBackupPickerPresented.toggle() } } label: { - HStack { - Text(L10n.Localizable.Settings.RestoreFromBackup.action) - .font(.textStyle(.body2)) - .foregroundStyle(Color.primaryText) - Spacer() - Image(systemName: "chevron.right").foregroundStyle(Color.primary) - } + Text(L10n.Localizable.Settings.RestoreFromBackup.action) + .font(.textStyle(.body2)) + .foregroundStyle(Color.primaryText) } .fullScreenCover(isPresented: $isBackupPickerPresented) { BackupPicker { url in @@ -93,13 +85,13 @@ struct BackupActionsView: View { .presentationDetents([.medium]) } } - .listRowBackground(Color(ColorTheme.Backgrounds.surface)) } .listStyle(.grouped) + .background(Color(ColorTheme.Backgrounds.background)) .scrollContentBackground(.hidden) } } #Preview { - BackupActionsPreview() + BackupRestoreViewPreview() } diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupRestoreViewPreview.swift similarity index 95% rename from WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift rename to WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupRestoreViewPreview.swift index cfd563ca914..4d016fbcd1f 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupActionsPreview.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupRestoreViewPreview.swift @@ -20,8 +20,8 @@ import WireFoundation import SwiftUI @ViewBuilder @MainActor -func BackupActionsPreview() -> some View { - BackupActionsView(viewModel: BackupRestoreViewModel( +func BackupRestoreViewPreview() -> some View { + BackupRestoreView(viewModel: BackupRestoreViewModel( backupSource: MockBackupSource(), restoreSource: MockRestoreSource(), backupResultHandler: BackupResultHandler( diff --git a/WireUI/Tests/WireSettingsUITests/Account/Backup/BackupActionsViewSnapshotTests.swift b/WireUI/Tests/WireSettingsUITests/Account/Backup/BackupRestoreViewSnapshotTests.swift similarity index 97% rename from WireUI/Tests/WireSettingsUITests/Account/Backup/BackupActionsViewSnapshotTests.swift rename to WireUI/Tests/WireSettingsUITests/Account/Backup/BackupRestoreViewSnapshotTests.swift index a9e056b038c..c15b7b79d12 100644 --- a/WireUI/Tests/WireSettingsUITests/Account/Backup/BackupActionsViewSnapshotTests.swift +++ b/WireUI/Tests/WireSettingsUITests/Account/Backup/BackupRestoreViewSnapshotTests.swift @@ -38,7 +38,7 @@ final class BackupActionsViewSnapshotTests: XCTestCase { @MainActor func testBackupActions() { let screenBounds = UIScreen.main.bounds - let sut = BackupActionsPreview() + let sut = BackupRestoreViewPreview() .frame(width: screenBounds.width, height: screenBounds.height) snapshotHelper From 3b34b821cd0703a18cbac703ffad7c45a4a91d51 Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Tue, 14 Jan 2025 15:40:06 +0100 Subject: [PATCH 076/107] add TODO --- .../CellDescriptors/SettingsCellDescriptorFactory+Account.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift index c309a44cb06..885fcd67710 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift @@ -528,7 +528,7 @@ extension SettingsCellDescriptorFactory { ) alert.addAction( UIAlertAction( - title: L10n.Localizable.RestoreBackup.Confirmation.overrideButton, + title: L10n.Localizable.RestoreBackup.Confirmation.overrideButton, // TODO: overwrite? style: .default, handler: { _ in completion() } ) From dc4a05edbda60bd3345c259417a29a3b3dc5dfcc Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Tue, 14 Jan 2025 17:08:04 +0100 Subject: [PATCH 077/107] fix text style injection --- .../WireSettingsUI/Account/Backup/Views/BackupRestoreView.swift | 2 ++ .../Account/Backup/Views/Preview/BackupRestoreViewPreview.swift | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupRestoreView.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupRestoreView.swift index a9d102e558e..f835de39a31 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupRestoreView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupRestoreView.swift @@ -18,6 +18,7 @@ import SwiftUI import WireDesign +import WireFoundation import WireReusableUIComponents struct BackupRestoreView: View { @@ -89,6 +90,7 @@ struct BackupRestoreView: View { .listStyle(.grouped) .background(Color(ColorTheme.Backgrounds.background)) .scrollContentBackground(.hidden) + .environment(\.wireTextStyleMapping, WireTextStyleMapping()) } } diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupRestoreViewPreview.swift b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupRestoreViewPreview.swift index 4d016fbcd1f..36c5d4d5715 100644 --- a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupRestoreViewPreview.swift +++ b/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupRestoreViewPreview.swift @@ -35,7 +35,6 @@ func BackupRestoreViewPreview() -> some View { ), passwordValidator: MockBackupPasswordValidator() )) - .environment(\.wireTextStyleMapping, PreviewTextStyleMapping()) } private class MockBackupSource: BackupSourceProtocol { From 5fb161f7c435d689d8adc90a628fc23d0c238a9e Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Tue, 14 Jan 2025 17:08:19 +0100 Subject: [PATCH 078/107] rename directory --- .../{Backup => BackupRestore}/Models/BackupResultHandler.swift | 0 .../Models/RestoreBackupResultHandler.swift | 0 .../Protocols/BackupPasswordValidatorProtocol.swift | 0 .../{Backup => BackupRestore}/Protocols/BackupSource.swift | 0 .../{Backup => BackupRestore}/Protocols/RestoreSource.swift | 0 .../ViewControllers/BackupRestoreViewController.swift | 0 .../ViewModels/BackupRestoreViewModel.swift | 0 .../Account/{Backup => BackupRestore}/Views/BackupPicker.swift | 0 .../{Backup => BackupRestore}/Views/BackupRestoreView.swift | 0 .../{Backup => BackupRestore}/Views/ExportBackupView.swift | 0 .../{Backup => BackupRestore}/Views/PasswordFieldView.swift | 0 .../Views/Preview/BackupRestoreViewPreview.swift | 0 .../Views/Preview/ExportBackupPreview.swift | 0 .../Views/Preview/RestoreBackupPreview.swift | 0 .../{Backup => BackupRestore}/Views/RestoreBackupView.swift | 0 15 files changed, 0 insertions(+), 0 deletions(-) rename WireUI/Sources/WireSettingsUI/Account/{Backup => BackupRestore}/Models/BackupResultHandler.swift (100%) rename WireUI/Sources/WireSettingsUI/Account/{Backup => BackupRestore}/Models/RestoreBackupResultHandler.swift (100%) rename WireUI/Sources/WireSettingsUI/Account/{Backup => BackupRestore}/Protocols/BackupPasswordValidatorProtocol.swift (100%) rename WireUI/Sources/WireSettingsUI/Account/{Backup => BackupRestore}/Protocols/BackupSource.swift (100%) rename WireUI/Sources/WireSettingsUI/Account/{Backup => BackupRestore}/Protocols/RestoreSource.swift (100%) rename WireUI/Sources/WireSettingsUI/Account/{Backup => BackupRestore}/ViewControllers/BackupRestoreViewController.swift (100%) rename WireUI/Sources/WireSettingsUI/Account/{Backup => BackupRestore}/ViewModels/BackupRestoreViewModel.swift (100%) rename WireUI/Sources/WireSettingsUI/Account/{Backup => BackupRestore}/Views/BackupPicker.swift (100%) rename WireUI/Sources/WireSettingsUI/Account/{Backup => BackupRestore}/Views/BackupRestoreView.swift (100%) rename WireUI/Sources/WireSettingsUI/Account/{Backup => BackupRestore}/Views/ExportBackupView.swift (100%) rename WireUI/Sources/WireSettingsUI/Account/{Backup => BackupRestore}/Views/PasswordFieldView.swift (100%) rename WireUI/Sources/WireSettingsUI/Account/{Backup => BackupRestore}/Views/Preview/BackupRestoreViewPreview.swift (100%) rename WireUI/Sources/WireSettingsUI/Account/{Backup => BackupRestore}/Views/Preview/ExportBackupPreview.swift (100%) rename WireUI/Sources/WireSettingsUI/Account/{Backup => BackupRestore}/Views/Preview/RestoreBackupPreview.swift (100%) rename WireUI/Sources/WireSettingsUI/Account/{Backup => BackupRestore}/Views/RestoreBackupView.swift (100%) diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupResultHandler.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Models/BackupResultHandler.swift similarity index 100% rename from WireUI/Sources/WireSettingsUI/Account/Backup/Models/BackupResultHandler.swift rename to WireUI/Sources/WireSettingsUI/Account/BackupRestore/Models/BackupResultHandler.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Models/RestoreBackupResultHandler.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Models/RestoreBackupResultHandler.swift similarity index 100% rename from WireUI/Sources/WireSettingsUI/Account/Backup/Models/RestoreBackupResultHandler.swift rename to WireUI/Sources/WireSettingsUI/Account/BackupRestore/Models/RestoreBackupResultHandler.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/BackupPasswordValidatorProtocol.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/BackupPasswordValidatorProtocol.swift similarity index 100% rename from WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/BackupPasswordValidatorProtocol.swift rename to WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/BackupPasswordValidatorProtocol.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/BackupSource.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/BackupSource.swift similarity index 100% rename from WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/BackupSource.swift rename to WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/BackupSource.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/RestoreSource.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/RestoreSource.swift similarity index 100% rename from WireUI/Sources/WireSettingsUI/Account/Backup/Protocols/RestoreSource.swift rename to WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/RestoreSource.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewControllers/BackupRestoreViewController.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift similarity index 100% rename from WireUI/Sources/WireSettingsUI/Account/Backup/ViewControllers/BackupRestoreViewController.swift rename to WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupRestoreViewModel.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewModels/BackupRestoreViewModel.swift similarity index 100% rename from WireUI/Sources/WireSettingsUI/Account/Backup/ViewModels/BackupRestoreViewModel.swift rename to WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewModels/BackupRestoreViewModel.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupPicker.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupPicker.swift similarity index 100% rename from WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupPicker.swift rename to WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupPicker.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupRestoreView.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift similarity index 100% rename from WireUI/Sources/WireSettingsUI/Account/Backup/Views/BackupRestoreView.swift rename to WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/ExportBackupView.swift similarity index 100% rename from WireUI/Sources/WireSettingsUI/Account/Backup/Views/ExportBackupView.swift rename to WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/ExportBackupView.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/PasswordFieldView.swift similarity index 100% rename from WireUI/Sources/WireSettingsUI/Account/Backup/Views/PasswordFieldView.swift rename to WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/PasswordFieldView.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupRestoreViewPreview.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/BackupRestoreViewPreview.swift similarity index 100% rename from WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/BackupRestoreViewPreview.swift rename to WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/BackupRestoreViewPreview.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/ExportBackupPreview.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/ExportBackupPreview.swift similarity index 100% rename from WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/ExportBackupPreview.swift rename to WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/ExportBackupPreview.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/RestoreBackupPreview.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/RestoreBackupPreview.swift similarity index 100% rename from WireUI/Sources/WireSettingsUI/Account/Backup/Views/Preview/RestoreBackupPreview.swift rename to WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/RestoreBackupPreview.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/Backup/Views/RestoreBackupView.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/RestoreBackupView.swift similarity index 100% rename from WireUI/Sources/WireSettingsUI/Account/Backup/Views/RestoreBackupView.swift rename to WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/RestoreBackupView.swift From e3f0a234dc57e9a07a7baaccd0d63ddeeaff8f63 Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Tue, 14 Jan 2025 17:55:54 +0100 Subject: [PATCH 079/107] improve modularization --- .../BackupRestore/BackupRestoreBuilder.swift | 32 +++++++++++++++++ .../BackupRestoreViewController.swift | 13 ++++++- .../ViewModels/BackupRestoreViewModel.swift | 8 ++++- .../BackupRestore/Views/BackupPicker.swift | 2 +- .../Views/BackupRestoreView.swift | 15 ++------ .../Views/ExportBackupView.swift | 35 ++++++++++--------- .../Views/PasswordFieldView.swift | 7 ++++ .../Preview/BackupRestoreViewPreview.swift | 29 ++++++++------- ...ettingsCellDescriptorFactory+Account.swift | 9 ++--- 9 files changed, 102 insertions(+), 48 deletions(-) create mode 100644 WireUI/Sources/WireSettingsUI/Account/BackupRestore/BackupRestoreBuilder.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/BackupRestoreBuilder.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/BackupRestoreBuilder.swift new file mode 100644 index 00000000000..392df064a04 --- /dev/null +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/BackupRestoreBuilder.swift @@ -0,0 +1,32 @@ +// +// Wire +// Copyright (C) 2025 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import UIKit + +public struct BackupRestoreBuilder { + +// public var + + public init() {} + + @MainActor + public func build() -> UIViewController { + let viewModel = BackupRestoreViewModel() + return BackupRestoreViewController(viewModel: viewModel) + } +} diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift index 90d55ecb229..520dd70f975 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift @@ -38,7 +38,18 @@ public final class BackupRestoreViewController: UIViewController { } private func setupView() { - let hostingController = UIHostingController(rootView: BackupRestoreView(viewModel: viewModel)) + let backupRestoreView = BackupRestoreView( + viewModel: viewModel, + exportBackupSheetContent: { + ExportBackupView( + passwordValidator: self.viewModel.passwordValidator, + exportBackup: { password in + self.viewModel.backupActiveAccount(password: password) + } + ) + } + ) + let hostingController = UIHostingController(rootView: backupRestoreView) addChild(hostingController) hostingController.view.translatesAutoresizingMaskIntoConstraints = false view.addSubview(hostingController.view) diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewModels/BackupRestoreViewModel.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewModels/BackupRestoreViewModel.swift index fb3e4b26613..f76b1065f9e 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewModels/BackupRestoreViewModel.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewModels/BackupRestoreViewModel.swift @@ -24,7 +24,13 @@ public final class BackupRestoreViewModel: ObservableObject { private let restoreSource: any RestoreSourceProtocol private let backupResultHandler: BackupResultHandler private let restoreBackupResultHandler: RestoreBackupResultHandler - let passwordValidator: any BackupPasswordValidatorProtocol + let passwordValidator: any BackupPasswordValidatorProtocol // TODO: private + + public init( + // + ) { + fatalError() + } public init( backupSource: any BackupSourceProtocol, diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupPicker.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupPicker.swift index be971b811e9..123bd195743 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupPicker.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupPicker.swift @@ -18,7 +18,7 @@ import SwiftUI -struct BackupPicker: UIViewControllerRepresentable { +struct BackupPicker: UIViewControllerRepresentable { // TODO: present from view controller instead var completion: (URL?) -> Void func makeUIViewController(context: Context) -> UIDocumentPickerViewController { diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift index f835de39a31..0a385711d0d 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift @@ -21,9 +21,10 @@ import WireDesign import WireFoundation import WireReusableUIComponents -struct BackupRestoreView: View { +struct BackupRestoreView: View { @ObservedObject private(set) var viewModel: BackupRestoreViewModel + @ViewBuilder private(set) var exportBackupSheetContent: () -> ExportBackupSheet @State private var isExportBackupSheetPresented: Bool = false @State private var isBackupPickerPresented: Bool = false @@ -40,17 +41,7 @@ struct BackupRestoreView: View { .wireTextStyle(.body2) .foregroundStyle(Color.primaryText) } - .sheet(isPresented: $isExportBackupSheetPresented) { - NavigationStack { - ExportBackupView( - passwordValidator: viewModel.passwordValidator, - exportBackup: { password in - viewModel.backupActiveAccount(password: password) - } - ) - } - .presentationDetents([.medium]) - } + .sheet(isPresented: $isExportBackupSheetPresented, content: exportBackupSheetContent) } Section(footer: Text(L10n.Localizable.Settings.RestoreFromBackup.description)) { diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/ExportBackupView.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/ExportBackupView.swift index b54a35954b3..a8843e73cc6 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/ExportBackupView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/ExportBackupView.swift @@ -38,24 +38,27 @@ public struct ExportBackupView: View { } public var body: some View { - SetBackupPasswordView( - passwordValidator: passwordValidator, - exportBackup: exportBackup - ) - .background(Color.viewBackground) - .scrollContentBackground(.hidden) - .navigationTitle( - Text(L10n.Localizable.ExportBackup.title) - ) - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .topBarTrailing) { - CloseButton( - action: didTapClose, - accessibilityLabel: L10n.Accessibility.SetBackupPassword.Close.label - ) + NavigationStack { + SetBackupPasswordView( + passwordValidator: passwordValidator, + exportBackup: exportBackup + ) + .background(Color.viewBackground) + .scrollContentBackground(.hidden) + .navigationTitle( + Text(L10n.Localizable.ExportBackup.title) + ) + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + CloseButton( + action: didTapClose, + accessibilityLabel: L10n.Accessibility.SetBackupPassword.Close.label + ) + } } } + .presentationDetents([.medium]) } private func didTapClose() { diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/PasswordFieldView.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/PasswordFieldView.swift index 962517b4e93..1e09fed6c64 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/PasswordFieldView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/PasswordFieldView.swift @@ -26,6 +26,8 @@ struct PasswordFieldView: View { var isPasswordValid: Bool = true let passwordRules: Text? + @FocusState private var isPasswordFieldFocused: Bool + var body: some View { VStack(alignment: .leading, spacing: 8) { Text(L10n.Localizable.ExportBackup.SetBackupPassword.title) @@ -40,9 +42,11 @@ struct PasswordFieldView: View { ) .wireTextStyle(.body1) .textFieldStyle(RoundedBorderTextFieldStyle()) + .focused($isPasswordFieldFocused) } else { SecureField(L10n.Localizable.ExportBackup.SetBackupPassword.placeholder, text: $password) .textFieldStyle(RoundedBorderTextFieldStyle()) + .focused($isPasswordFieldFocused) } HStack { Spacer() @@ -68,6 +72,9 @@ struct PasswordFieldView: View { .foregroundColor(calculatedColor) } .padding(.horizontal) + .onAppear { + isPasswordFieldFocused = true + } } // MARK: - Helper diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/BackupRestoreViewPreview.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/BackupRestoreViewPreview.swift index 36c5d4d5715..6502d260ec6 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/BackupRestoreViewPreview.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/BackupRestoreViewPreview.swift @@ -21,20 +21,23 @@ import SwiftUI @ViewBuilder @MainActor func BackupRestoreViewPreview() -> some View { - BackupRestoreView(viewModel: BackupRestoreViewModel( - backupSource: MockBackupSource(), - restoreSource: MockRestoreSource(), - backupResultHandler: BackupResultHandler( - onSuccess: { _, _ in }, - onFailure: {} + BackupRestoreView( + viewModel: BackupRestoreViewModel( + backupSource: MockBackupSource(), + restoreSource: MockRestoreSource(), + backupResultHandler: BackupResultHandler( + onSuccess: { _, _ in }, + onFailure: {} + ), + restoreBackupResultHandler: RestoreBackupResultHandler( + onSuccess: {}, + onConfirmation: { _ in }, + onFailure: {} + ), + passwordValidator: MockBackupPasswordValidator() ), - restoreBackupResultHandler: RestoreBackupResultHandler( - onSuccess: {}, - onConfirmation: { _ in }, - onFailure: {} - ), - passwordValidator: MockBackupPasswordValidator() - )) + exportBackupSheetContent: { EmptyView() } + ) } private class MockBackupSource: BackupSourceProtocol { diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift index 885fcd67710..c105da9d6b1 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift @@ -381,7 +381,8 @@ extension SettingsCellDescriptorFactory { return .none } if selfUser.hasValidEmail || selfUser.usesCompanyLogin { - let viewModel = BackupRestoreViewModel( +// let backupRestoreController = BackupRestoreBuilder().build() + let viewModel = BackupRestoreViewModel( // TODO: delete, use builder backupSource: BackupSource(), restoreSource: RestoreSource(), backupResultHandler: BackupResultHandler( @@ -395,9 +396,9 @@ extension SettingsCellDescriptorFactory { ), passwordValidator: BackupPasswordValidator() ) - let backupActionsController = BackupRestoreViewController(viewModel: viewModel) - backupActionsController.setupNavigationBarTitle(L10n.Localizable.Self.Settings.HistoryBackup.title) - return backupActionsController + let backupRestoreController = BackupRestoreViewController(viewModel: viewModel) + backupRestoreController.setupNavigationBarTitle(L10n.Localizable.Self.Settings.HistoryBackup.title) + return backupRestoreController } else { let alert = UIAlertController( title: L10n.Localizable.Self.Settings.HistoryBackup.SetEmail.title, From 9d2422230604ff07e5b217f068807e209c259024 Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Wed, 15 Jan 2025 09:16:13 +0100 Subject: [PATCH 080/107] separate directory for export backup --- .../ExportBackupPreview.swift | 5 ++-- .../ExportBackupView.swift | 17 +++++--------- .../ExportBackup/ExportBackupViewModel.swift | 23 +++++++++++++++++++ .../BackupRestoreViewController.swift | 5 ++-- .../Views/PasswordFieldView.swift | 6 ++--- 5 files changed, 38 insertions(+), 18 deletions(-) rename WireUI/Sources/WireSettingsUI/Account/BackupRestore/{Views/Preview => ExportBackup}/ExportBackupPreview.swift (94%) rename WireUI/Sources/WireSettingsUI/Account/BackupRestore/{Views => ExportBackup}/ExportBackupView.swift (90%) create mode 100644 WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupViewModel.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/ExportBackupPreview.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupPreview.swift similarity index 94% rename from WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/ExportBackupPreview.swift rename to WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupPreview.swift index cb9bad0c160..3a32f4b5339 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/ExportBackupPreview.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupPreview.swift @@ -38,8 +38,9 @@ private struct ExportBackup_Preview: View { .sheet(isPresented: $isPresented) { NavigationStack { ExportBackupView( - passwordValidator: MockBackupPasswordValidator(), - exportBackup: { _ in } + viewModel: .init(), + exportBackup: { _ in }, + passwordValidator: MockBackupPasswordValidator() ) } .presentationDragIndicator(.visible) diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/ExportBackupView.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupView.swift similarity index 90% rename from WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/ExportBackupView.swift rename to WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupView.swift index a8843e73cc6..49190c9c508 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/ExportBackupView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupView.swift @@ -23,21 +23,16 @@ import WireReusableUIComponents /// A view that allows to export the backup. -public struct ExportBackupView: View { +struct ExportBackupView: View { @Environment(\.dismiss) private var dismiss - private let exportBackup: (String) -> Void - private let passwordValidator: any BackupPasswordValidatorProtocol - public init( - passwordValidator: any BackupPasswordValidatorProtocol, - exportBackup: @escaping (String) -> Void - ) { - self.exportBackup = exportBackup - self.passwordValidator = passwordValidator - } + @ObservedObject private(set) var viewModel: ExportBackupViewModel - public var body: some View { + let exportBackup: (String) -> Void + let passwordValidator: any BackupPasswordValidatorProtocol + + var body: some View { NavigationStack { SetBackupPasswordView( passwordValidator: passwordValidator, diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupViewModel.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupViewModel.swift new file mode 100644 index 00000000000..7b185875c6b --- /dev/null +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupViewModel.swift @@ -0,0 +1,23 @@ +// +// Wire +// Copyright (C) 2025 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import Foundation + +final class ExportBackupViewModel: ObservableObject { + +} diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift index 520dd70f975..452487c427e 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift @@ -42,10 +42,11 @@ public final class BackupRestoreViewController: UIViewController { viewModel: viewModel, exportBackupSheetContent: { ExportBackupView( - passwordValidator: self.viewModel.passwordValidator, + viewModel: .init(), // TODO: fix exportBackup: { password in self.viewModel.backupActiveAccount(password: password) - } + }, + passwordValidator: self.viewModel.passwordValidator ) } ) diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/PasswordFieldView.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/PasswordFieldView.swift index 1e09fed6c64..2d36aaaba22 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/PasswordFieldView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/PasswordFieldView.swift @@ -50,12 +50,12 @@ struct PasswordFieldView: View { } HStack { Spacer() - Button(action: { + Button { isPasswordVisible.toggle() - }, label: { + } label: { Image(systemName: isPasswordVisible ? "eye" : "eye.slash") .foregroundColor(.gray) - }) + } .padding(.trailing, 10) } } From aba3c2adacbf706d55404cc36b62c718717b27ac Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Wed, 15 Jan 2025 09:26:49 +0100 Subject: [PATCH 081/107] refactoring --- .../Buttons/CloseButton.swift | 2 +- .../ExportBackup/ExportBackupView.swift | 75 +++++++------------ .../ExportBackup/ExportBackupViewModel.swift | 3 + 3 files changed, 31 insertions(+), 49 deletions(-) diff --git a/WireUI/Sources/WireReusableUIComponents/Buttons/CloseButton.swift b/WireUI/Sources/WireReusableUIComponents/Buttons/CloseButton.swift index 9bfd47fde46..f1b2f9c00e4 100644 --- a/WireUI/Sources/WireReusableUIComponents/Buttons/CloseButton.swift +++ b/WireUI/Sources/WireReusableUIComponents/Buttons/CloseButton.swift @@ -22,7 +22,7 @@ import WireDesign public struct CloseButton: View { private let action: () -> Void - private let accessibilityLabel: String + private let accessibilityLabel: String // TODO: remove public var body: some View { Button(action: action) { diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupView.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupView.swift index 49190c9c508..fcafec6699d 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupView.swift @@ -28,58 +28,32 @@ struct ExportBackupView: View { @Environment(\.dismiss) private var dismiss @ObservedObject private(set) var viewModel: ExportBackupViewModel + @State private var isScrollDisabled: Bool = true let exportBackup: (String) -> Void let passwordValidator: any BackupPasswordValidatorProtocol var body: some View { NavigationStack { - SetBackupPasswordView( - passwordValidator: passwordValidator, - exportBackup: exportBackup - ) - .background(Color.viewBackground) - .scrollContentBackground(.hidden) - .navigationTitle( - Text(L10n.Localizable.ExportBackup.title) - ) - .navigationBarTitleDisplayMode(.inline) - .toolbar { - ToolbarItem(placement: .topBarTrailing) { - CloseButton( - action: didTapClose, - accessibilityLabel: L10n.Accessibility.SetBackupPassword.Close.label - ) + setBackupPasswordView + .background(Color.viewBackground) + .scrollContentBackground(.hidden) + .navigationTitle(Text(L10n.Localizable.ExportBackup.title)) + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .topBarTrailing) { + CloseButton( + action: didTapClose, + accessibilityLabel: L10n.Accessibility.SetBackupPassword.Close.label + ) + } } - } } .presentationDetents([.medium]) } - private func didTapClose() { - dismiss() - } - -} - -private struct SetBackupPasswordView: View { - @Environment(\.dismiss) private var dismiss - @State private var password: String = "" - @State private var isPasswordVisible: Bool = false - @State private var contentFits: Bool = true - - private let passwordValidator: any BackupPasswordValidatorProtocol - private let exportBackup: (String) -> Void - - init( - passwordValidator: any BackupPasswordValidatorProtocol, - exportBackup: @escaping (String) -> Void - ) { - self.passwordValidator = passwordValidator - self.exportBackup = exportBackup - } - - var body: some View { + @ViewBuilder + private var setBackupPasswordView: some View { GeometryReader { geometry in VStack { ScrollView { @@ -91,37 +65,42 @@ private struct SetBackupPasswordView: View { .padding(.horizontal) PasswordFieldView( - password: $password, - isPasswordVisible: $isPasswordVisible, - isPasswordValid: passwordValidator.isPasswordValid(password), + password: $viewModel.password, + isPasswordVisible: $viewModel.isPasswordVisible, + isPasswordValid: passwordValidator.isPasswordValid(viewModel.password), passwordRules: Text(passwordValidator.localizedRulesDescription) ) } .background( GeometryReader { contentGeometry in Color.clear.onAppear { - contentFits = contentGeometry.size.height <= geometry.size.height + isScrollDisabled = contentGeometry.size.height <= geometry.size.height } } ) .frame(maxWidth: .infinity) } - .scrollDisabled(contentFits) + .scrollDisabled(isScrollDisabled) Spacer() Button { dismiss() - exportBackup(password) + exportBackup(viewModel.password) } label: { Text(L10n.Localizable.ExportBackup.button) } - .disabled(!passwordValidator.isPasswordValid(password)) + .disabled(!passwordValidator.isPasswordValid(viewModel.password)) .wireButtonStyle(.primary) .padding() } } } + + private func didTapClose() { + dismiss() + } + } // MARK: - Previews diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupViewModel.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupViewModel.swift index 7b185875c6b..46410ca3a6d 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupViewModel.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupViewModel.swift @@ -20,4 +20,7 @@ import Foundation final class ExportBackupViewModel: ObservableObject { + @Published var password = "" + @Published var isPasswordVisible = false + } From 204c369c2e2d6d7c2e0b33fef7029fae7fd8a0c2 Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Wed, 15 Jan 2025 09:31:26 +0100 Subject: [PATCH 082/107] generic BackupPasswordValidator --- .../ExportBackup/ExportBackupPreview.swift | 2 +- .../ExportBackup/ExportBackupView.swift | 4 ++-- .../ExportBackup/ExportBackupViewModel.swift | 17 +++++++++++++++-- .../BackupRestoreViewController.swift | 2 +- 4 files changed, 19 insertions(+), 6 deletions(-) diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupPreview.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupPreview.swift index 3a32f4b5339..31af931527c 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupPreview.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupPreview.swift @@ -38,7 +38,7 @@ private struct ExportBackup_Preview: View { .sheet(isPresented: $isPresented) { NavigationStack { ExportBackupView( - viewModel: .init(), + viewModel: .init(passwordValidator: MockBackupPasswordValidator()), exportBackup: { _ in }, passwordValidator: MockBackupPasswordValidator() ) diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupView.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupView.swift index fcafec6699d..ed9d4a9e110 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupView.swift @@ -23,11 +23,11 @@ import WireReusableUIComponents /// A view that allows to export the backup. -struct ExportBackupView: View { +struct ExportBackupView: View { @Environment(\.dismiss) private var dismiss - @ObservedObject private(set) var viewModel: ExportBackupViewModel + @ObservedObject private(set) var viewModel: ExportBackupViewModel @State private var isScrollDisabled: Bool = true let exportBackup: (String) -> Void diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupViewModel.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupViewModel.swift index 46410ca3a6d..314b442aa0b 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupViewModel.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupViewModel.swift @@ -18,9 +18,22 @@ import Foundation -final class ExportBackupViewModel: ObservableObject { +final class ExportBackupViewModel: ObservableObject { + + @Published var password = "" { + didSet { validatePassword() } + } - @Published var password = "" @Published var isPasswordVisible = false + @Published private(set) var isPasswordValid = false + + private let passwordValidator: BackupPasswordValidator + + init(passwordValidator: BackupPasswordValidator) { + self.passwordValidator = passwordValidator + } + private func validatePassword() { + fatalError("TODO") + } } diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift index 452487c427e..6a97b4ea0a5 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift @@ -42,7 +42,7 @@ public final class BackupRestoreViewController: UIViewController { viewModel: viewModel, exportBackupSheetContent: { ExportBackupView( - viewModel: .init(), // TODO: fix + viewModel: .init(passwordValidator: viewModel.passwordValidator), exportBackup: { password in self.viewModel.backupActiveAccount(password: password) }, From 8ba955534ab46d29931454aad7ea1b833d45a707 Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Wed, 15 Jan 2025 09:34:37 +0100 Subject: [PATCH 083/107] move password validation into view model --- .../ExportBackup/ExportBackupPreview.swift | 3 +-- .../BackupRestore/ExportBackup/ExportBackupView.swift | 11 +++++------ .../ExportBackup/ExportBackupViewModel.swift | 10 ++++++---- .../ViewControllers/BackupRestoreViewController.swift | 5 ++--- 4 files changed, 14 insertions(+), 15 deletions(-) diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupPreview.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupPreview.swift index 31af931527c..f78bcd1eac3 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupPreview.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupPreview.swift @@ -39,8 +39,7 @@ private struct ExportBackup_Preview: View { NavigationStack { ExportBackupView( viewModel: .init(passwordValidator: MockBackupPasswordValidator()), - exportBackup: { _ in }, - passwordValidator: MockBackupPasswordValidator() + exportBackup: { _ in } ) } .presentationDragIndicator(.visible) diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupView.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupView.swift index ed9d4a9e110..9708bd94e70 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupView.swift @@ -23,15 +23,14 @@ import WireReusableUIComponents /// A view that allows to export the backup. -struct ExportBackupView: View { +struct ExportBackupView: View { @Environment(\.dismiss) private var dismiss - @ObservedObject private(set) var viewModel: ExportBackupViewModel + @ObservedObject private(set) var viewModel: ExportBackupViewModel @State private var isScrollDisabled: Bool = true let exportBackup: (String) -> Void - let passwordValidator: any BackupPasswordValidatorProtocol var body: some View { NavigationStack { @@ -67,8 +66,8 @@ struct ExportBackupView: ObservableObject { +final class ExportBackupViewModel: ObservableObject { @Published var password = "" { didSet { validatePassword() } @@ -27,13 +27,15 @@ final class ExportBackupViewModel Date: Wed, 15 Jan 2025 10:03:57 +0100 Subject: [PATCH 084/107] fix build errors --- .../BackupRestore/BackupRestoreBuilder.swift | 12 ++++++++---- .../ExportBackup/ExportBackupViewModel.swift | 2 +- .../BackupRestoreViewController.swift | 10 ++++++++-- .../ViewModels/BackupRestoreViewModel.swift | 5 +---- .../Preview/BackupRestoreViewPreview.swift | 4 ++-- ...SettingsCellDescriptorFactory+Account.swift | 18 +++++++++++++----- 6 files changed, 33 insertions(+), 18 deletions(-) diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/BackupRestoreBuilder.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/BackupRestoreBuilder.swift index 392df064a04..55d6ab083b5 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/BackupRestoreBuilder.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/BackupRestoreBuilder.swift @@ -20,13 +20,17 @@ import UIKit public struct BackupRestoreBuilder { -// public var + public var backupPasswordValidator: any BackupPasswordValidatorProtocol - public init() {} + public init(backupPasswordValidator: any BackupPasswordValidatorProtocol) { + self.backupPasswordValidator = backupPasswordValidator + } @MainActor public func build() -> UIViewController { - let viewModel = BackupRestoreViewModel() - return BackupRestoreViewController(viewModel: viewModel) + BackupRestoreViewController( + viewModel: BackupRestoreViewModel(), + backupPasswordValidator: backupPasswordValidator + ) } } diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupViewModel.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupViewModel.swift index 4a0471232f6..aa5d96b87c2 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupViewModel.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupViewModel.swift @@ -25,7 +25,7 @@ final class ExportBackupViewModel: ObservableObject { } @Published var isPasswordVisible = false - @Published private(set) var isPasswordValid = false + @Published private(set) var isPasswordValid = true var localizedPasswordRules: String { passwordValidator.localizedRulesDescription } diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift index d59f4d8908b..734771a39f6 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift @@ -21,9 +21,14 @@ import SwiftUI public final class BackupRestoreViewController: UIViewController { private let viewModel: BackupRestoreViewModel + private let backupPasswordValidator: any BackupPasswordValidatorProtocol - public init(viewModel: BackupRestoreViewModel) { + public init( + viewModel: BackupRestoreViewModel, + backupPasswordValidator: any BackupPasswordValidatorProtocol + ) { self.viewModel = viewModel + self.backupPasswordValidator = backupPasswordValidator super.init(nibName: nil, bundle: nil) } @@ -38,11 +43,12 @@ public final class BackupRestoreViewController: UIViewController { } private func setupView() { + let backupPasswordValidator = backupPasswordValidator let backupRestoreView = BackupRestoreView( viewModel: viewModel, exportBackupSheetContent: { ExportBackupView( - viewModel: .init(passwordValidator: self.viewModel.passwordValidator), + viewModel: .init(passwordValidator: backupPasswordValidator), exportBackup: { password in self.viewModel.backupActiveAccount(password: password) } diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewModels/BackupRestoreViewModel.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewModels/BackupRestoreViewModel.swift index f76b1065f9e..a0e639fd956 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewModels/BackupRestoreViewModel.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewModels/BackupRestoreViewModel.swift @@ -24,7 +24,6 @@ public final class BackupRestoreViewModel: ObservableObject { private let restoreSource: any RestoreSourceProtocol private let backupResultHandler: BackupResultHandler private let restoreBackupResultHandler: RestoreBackupResultHandler - let passwordValidator: any BackupPasswordValidatorProtocol // TODO: private public init( // @@ -36,14 +35,12 @@ public final class BackupRestoreViewModel: ObservableObject { backupSource: any BackupSourceProtocol, restoreSource: any RestoreSourceProtocol, backupResultHandler: BackupResultHandler, - restoreBackupResultHandler: RestoreBackupResultHandler, - passwordValidator: any BackupPasswordValidatorProtocol + restoreBackupResultHandler: RestoreBackupResultHandler ) { self.backupSource = backupSource self.restoreSource = restoreSource self.backupResultHandler = backupResultHandler self.restoreBackupResultHandler = restoreBackupResultHandler - self.passwordValidator = passwordValidator } func backupActiveAccount(password: String) { diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/BackupRestoreViewPreview.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/BackupRestoreViewPreview.swift index 6502d260ec6..a56d0f4c519 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/BackupRestoreViewPreview.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/BackupRestoreViewPreview.swift @@ -33,8 +33,8 @@ func BackupRestoreViewPreview() -> some View { onSuccess: {}, onConfirmation: { _ in }, onFailure: {} - ), - passwordValidator: MockBackupPasswordValidator() + ) +// passwordValidator: MockBackupPasswordValidator() ), exportBackupSheetContent: { EmptyView() } ) diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift index c105da9d6b1..dbc9a3c886e 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift @@ -369,6 +369,12 @@ extension SettingsCellDescriptorFactory { SettingsPropertyToggleCellDescriptor(settingsProperty: settingsPropertyFactory.property(.encryptMessagesAtRest)) } + private var backupRestoreBuilder: BackupRestoreBuilder { + .init( + backupPasswordValidator: BackupPasswordValidator() + ) + } + @MainActor func backUpElement() -> any SettingsCellDescriptorType { SettingsExternalScreenCellDescriptor( @@ -381,8 +387,8 @@ extension SettingsCellDescriptorFactory { return .none } if selfUser.hasValidEmail || selfUser.usesCompanyLogin { -// let backupRestoreController = BackupRestoreBuilder().build() - let viewModel = BackupRestoreViewModel( // TODO: delete, use builder +// let backupRestoreController = backupRestoreBuilder.build() + let viewModel = BackupRestoreViewModel( // TODO: delete, use `backupRestoreBuilder` backupSource: BackupSource(), restoreSource: RestoreSource(), backupResultHandler: BackupResultHandler( @@ -393,10 +399,12 @@ extension SettingsCellDescriptorFactory { onSuccess: presentOnSuccessAlert, onConfirmation: presentConfirmationAlert, onFailure: presentRestoreBackupErrorAlert - ), - passwordValidator: BackupPasswordValidator() + ) + ) + let backupRestoreController = BackupRestoreViewController( + viewModel: viewModel, + backupPasswordValidator: BackupPasswordValidator() ) - let backupRestoreController = BackupRestoreViewController(viewModel: viewModel) backupRestoreController.setupNavigationBarTitle(L10n.Localizable.Self.Settings.HistoryBackup.title) return backupRestoreController } else { From 2935b16d4c9aaf651fdf838ae82a55b2144c0c21 Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Wed, 15 Jan 2025 10:19:30 +0100 Subject: [PATCH 085/107] move close button icon into reusable components --- .../Resources/Images.xcassets/Contents.json | 0 .../Resources/Images.xcassets/Icons/Close.imageset/Contents.json | 0 .../Resources/Images.xcassets/Icons/Close.imageset/Icon.svg | 0 .../Resources/Images.xcassets/Icons/Contents.json | 0 4 files changed, 0 insertions(+), 0 deletions(-) rename WireUI/Sources/{WireDesign => WireReusableUIComponents}/Resources/Images.xcassets/Contents.json (100%) rename WireUI/Sources/{WireDesign => WireReusableUIComponents}/Resources/Images.xcassets/Icons/Close.imageset/Contents.json (100%) rename WireUI/Sources/{WireDesign => WireReusableUIComponents}/Resources/Images.xcassets/Icons/Close.imageset/Icon.svg (100%) rename WireUI/Sources/{WireDesign => WireReusableUIComponents}/Resources/Images.xcassets/Icons/Contents.json (100%) diff --git a/WireUI/Sources/WireDesign/Resources/Images.xcassets/Contents.json b/WireUI/Sources/WireReusableUIComponents/Resources/Images.xcassets/Contents.json similarity index 100% rename from WireUI/Sources/WireDesign/Resources/Images.xcassets/Contents.json rename to WireUI/Sources/WireReusableUIComponents/Resources/Images.xcassets/Contents.json diff --git a/WireUI/Sources/WireDesign/Resources/Images.xcassets/Icons/Close.imageset/Contents.json b/WireUI/Sources/WireReusableUIComponents/Resources/Images.xcassets/Icons/Close.imageset/Contents.json similarity index 100% rename from WireUI/Sources/WireDesign/Resources/Images.xcassets/Icons/Close.imageset/Contents.json rename to WireUI/Sources/WireReusableUIComponents/Resources/Images.xcassets/Icons/Close.imageset/Contents.json diff --git a/WireUI/Sources/WireDesign/Resources/Images.xcassets/Icons/Close.imageset/Icon.svg b/WireUI/Sources/WireReusableUIComponents/Resources/Images.xcassets/Icons/Close.imageset/Icon.svg similarity index 100% rename from WireUI/Sources/WireDesign/Resources/Images.xcassets/Icons/Close.imageset/Icon.svg rename to WireUI/Sources/WireReusableUIComponents/Resources/Images.xcassets/Icons/Close.imageset/Icon.svg diff --git a/WireUI/Sources/WireDesign/Resources/Images.xcassets/Icons/Contents.json b/WireUI/Sources/WireReusableUIComponents/Resources/Images.xcassets/Icons/Contents.json similarity index 100% rename from WireUI/Sources/WireDesign/Resources/Images.xcassets/Icons/Contents.json rename to WireUI/Sources/WireReusableUIComponents/Resources/Images.xcassets/Icons/Contents.json From 07a037db41fc82aaa2b2f661357e7bf29becf7e4 Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Wed, 15 Jan 2025 10:23:15 +0100 Subject: [PATCH 086/107] fix build errors --- .../Icons/Image+ImageResource.swift | 23 ------------------- .../Icons/UImage+ImageResource.swift | 23 ------------------- .../Buttons/CloseButton.swift | 2 +- .../UIKit/UIBarButtonItem+CloseButton.swift | 2 +- 4 files changed, 2 insertions(+), 48 deletions(-) delete mode 100644 WireUI/Sources/WireDesign/Icons/Image+ImageResource.swift delete mode 100644 WireUI/Sources/WireDesign/Icons/UImage+ImageResource.swift diff --git a/WireUI/Sources/WireDesign/Icons/Image+ImageResource.swift b/WireUI/Sources/WireDesign/Icons/Image+ImageResource.swift deleted file mode 100644 index 64f1f3de891..00000000000 --- a/WireUI/Sources/WireDesign/Icons/Image+ImageResource.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// Wire -// Copyright (C) 2025 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import SwiftUI - -public extension Image { - static let close = Image(.close) -} diff --git a/WireUI/Sources/WireDesign/Icons/UImage+ImageResource.swift b/WireUI/Sources/WireDesign/Icons/UImage+ImageResource.swift deleted file mode 100644 index 162b91a6107..00000000000 --- a/WireUI/Sources/WireDesign/Icons/UImage+ImageResource.swift +++ /dev/null @@ -1,23 +0,0 @@ -// -// Wire -// Copyright (C) 2025 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import UIKit - -public extension UIImage { - static let close = UIImage(resource: .close) -} diff --git a/WireUI/Sources/WireReusableUIComponents/Buttons/CloseButton.swift b/WireUI/Sources/WireReusableUIComponents/Buttons/CloseButton.swift index 9bfd47fde46..3f76e4f030f 100644 --- a/WireUI/Sources/WireReusableUIComponents/Buttons/CloseButton.swift +++ b/WireUI/Sources/WireReusableUIComponents/Buttons/CloseButton.swift @@ -26,7 +26,7 @@ public struct CloseButton: View { public var body: some View { Button(action: action) { - Image.close + Image(.close) } .buttonStyle(.plain) .foregroundColor(Color(uiColor: SemanticColors.Icon.foregroundDefaultBlack)) diff --git a/WireUI/Sources/WireReusableUIComponents/SDKExtensions/UIKit/UIBarButtonItem+CloseButton.swift b/WireUI/Sources/WireReusableUIComponents/SDKExtensions/UIKit/UIBarButtonItem+CloseButton.swift index 2b1f403e0cd..8429d6e3bc7 100644 --- a/WireUI/Sources/WireReusableUIComponents/SDKExtensions/UIKit/UIBarButtonItem+CloseButton.swift +++ b/WireUI/Sources/WireReusableUIComponents/SDKExtensions/UIKit/UIBarButtonItem+CloseButton.swift @@ -31,7 +31,7 @@ public extension UIBarButtonItem { /// /// - Returns: A UIBarButtonItem configured as a close button. static func closeButton(action: UIAction, accessibilityLabel: String) -> UIBarButtonItem { - let closeImage = UIImage.close + let closeImage = UIImage(resource: .close) let closeItem = UIBarButtonItem(title: accessibilityLabel, primaryAction: action) From 9f52d748be2f67c481371574df1807ed6b9c1cfd Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Wed, 15 Jan 2025 10:28:30 +0100 Subject: [PATCH 087/107] use modifier for accessibilityLabel --- WireUI/Sources/WireFolderPickerUI/FolderPicker.swift | 10 +++------- .../Views/FolderPicker/FolderPicker.swift | 10 +++------- .../WireReusableUIComponents/Buttons/CloseButton.swift | 7 ++----- 3 files changed, 8 insertions(+), 19 deletions(-) diff --git a/WireUI/Sources/WireFolderPickerUI/FolderPicker.swift b/WireUI/Sources/WireFolderPickerUI/FolderPicker.swift index b020997adea..6b33501b292 100644 --- a/WireUI/Sources/WireFolderPickerUI/FolderPicker.swift +++ b/WireUI/Sources/WireFolderPickerUI/FolderPicker.swift @@ -63,14 +63,10 @@ public struct FolderPicker: View { .toolbar { if showCloseButton { ToolbarItem(placement: .topBarTrailing) { - CloseButton( - action: didTapClose, - accessibilityLabel: String( - localized: "folderPicker.close.label", - table: "Accessibility", - bundle: .module + CloseButton(action: didTapClose) + .accessibilityLabel( + Text("folderPicker.close.label", tableName: "Accessibility", bundle: .module) ) - ) } } } diff --git a/WireUI/Sources/WireMoveToFolderUI/Views/FolderPicker/FolderPicker.swift b/WireUI/Sources/WireMoveToFolderUI/Views/FolderPicker/FolderPicker.swift index 64a825a3c3b..0fd1b7c0b3d 100644 --- a/WireUI/Sources/WireMoveToFolderUI/Views/FolderPicker/FolderPicker.swift +++ b/WireUI/Sources/WireMoveToFolderUI/Views/FolderPicker/FolderPicker.swift @@ -50,14 +50,10 @@ public struct FolderPicker: View { .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .navigationBarLeading) { - CloseButton( - action: didTapClose, - accessibilityLabel: String( - localized: "folderPicker.close.label", - table: "Accessibility", - bundle: .module + CloseButton(action: didTapClose) + .accessibilityLabel( + Text("folderPicker.close.label", tableName: "Accessibility", bundle: .module) ) - ) } ToolbarItem(placement: .navigationBarTrailing) { NavigationLink { diff --git a/WireUI/Sources/WireReusableUIComponents/Buttons/CloseButton.swift b/WireUI/Sources/WireReusableUIComponents/Buttons/CloseButton.swift index 3f76e4f030f..d4ddbcfac05 100644 --- a/WireUI/Sources/WireReusableUIComponents/Buttons/CloseButton.swift +++ b/WireUI/Sources/WireReusableUIComponents/Buttons/CloseButton.swift @@ -22,7 +22,6 @@ import WireDesign public struct CloseButton: View { private let action: () -> Void - private let accessibilityLabel: String public var body: some View { Button(action: action) { @@ -30,13 +29,11 @@ public struct CloseButton: View { } .buttonStyle(.plain) .foregroundColor(Color(uiColor: SemanticColors.Icon.foregroundDefaultBlack)) - .accessibilityLabel(Text(accessibilityLabel)) .accessibilityIdentifier("close") } - public init(action: @escaping @MainActor () -> Void, accessibilityLabel: String) { + public init(action: @escaping @MainActor () -> Void) { self.action = action - self.accessibilityLabel = accessibilityLabel } } @@ -46,7 +43,7 @@ public struct CloseButton: View { Text("Hello, World!") .toolbar { ToolbarItem(placement: .topBarTrailing) { - CloseButton(action: { print("Close") }, accessibilityLabel: "Close") + CloseButton { print("Close") } } } } From 61b64f0f1158afd3fd8003a919686eefff4aae73 Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Wed, 15 Jan 2025 10:39:08 +0100 Subject: [PATCH 088/107] update packages --- .../xcshareddata/swiftpm/Package.resolved | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wire-ios-mono.xcworkspace/xcshareddata/swiftpm/Package.resolved b/wire-ios-mono.xcworkspace/xcshareddata/swiftpm/Package.resolved index 3ee62e58e0b..6d68ffbea4c 100644 --- a/wire-ios-mono.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/wire-ios-mono.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -6,8 +6,8 @@ "repositoryURL": "https://github.com/openid/AppAuth-iOS.git", "state": { "branch": null, - "revision": "c89ed571ae140f8eb1142735e6e23d7bb8c34cb2", - "version": "1.7.5" + "revision": "2781038865a80e2c425a1da12cc1327bcd56501f", + "version": "1.7.6" } }, { @@ -141,8 +141,8 @@ "repositoryURL": "https://github.com/apple/swift-log", "state": { "branch": null, - "revision": "9cb486020ebf03bfa5b5df985387a14a98744537", - "version": "1.6.1" + "revision": "96a2f8a0fa41e9e09af4585e2724c4e825410b91", + "version": "1.6.2" } }, { From de88a3c027ba3e77ffaa14ce02bf97e6bf846273 Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Wed, 15 Jan 2025 10:48:48 +0100 Subject: [PATCH 089/107] use close button in Wire-iOS --- .../E2EIdentityCertificateDetailsView.swift | 17 ++++++----------- 1 file changed, 6 insertions(+), 11 deletions(-) diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/DeviceView/Views/E2EIdentityCertificateDetailsView.swift b/wire-ios/Wire-iOS/Sources/UserInterface/DeviceView/Views/E2EIdentityCertificateDetailsView.swift index d9369b5147d..2952ca59f27 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/DeviceView/Views/E2EIdentityCertificateDetailsView.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/DeviceView/Views/E2EIdentityCertificateDetailsView.swift @@ -19,6 +19,7 @@ import SwiftUI import WireCommonComponents import WireDesign +import WireReusableUIComponents struct E2EIdentityCertificateDetailsView: View { @Environment(\.dismiss) private var dismiss @@ -44,18 +45,12 @@ struct E2EIdentityCertificateDetailsView: View { .overlay { HStack { Spacer() - Button( - action: { - dismiss() - didDismiss?() - }, - label: { - Image - .close - .foregroundColor(Color(uiColor: SemanticColors.Icon.foregroundDefaultBlack)) - } - ) + CloseButton { + dismiss() + didDismiss?() + } .accessibilityIdentifier("CloseButton") + .accessibilityLabel(Text(L10n.Localizable.General.close)) .padding(.all, ViewConstants.Padding.standard) } } From 5c3409306a626779c35df3806ab08ecea568542d Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Wed, 15 Jan 2025 11:05:41 +0100 Subject: [PATCH 090/107] combine views --- .../Views/RestoreBackupView.swift | 42 +++++++------------ 1 file changed, 15 insertions(+), 27 deletions(-) diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/RestoreBackupView.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/RestoreBackupView.swift index 508635fab94..21ffb7b2ac7 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/RestoreBackupView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/RestoreBackupView.swift @@ -22,17 +22,24 @@ import WireFoundation import WireReusableUIComponents struct RestoreBackupView: View { + @Environment(\.dismiss) private var dismiss + + // TODO: move to view model? + @State private var password: String = "" + @State private var isPasswordVisible: Bool = false + @State private var contentFits: Bool = true + private let importBackup: (String) -> Void - public init( + init( importBackup: @escaping (String) -> Void ) { self.importBackup = importBackup } - public var body: some View { - PasswordBackupView(importBackup: importBackup) + var body: some View { + passwordBackupView .background(Color.viewBackground) .scrollContentBackground(.hidden) .navigationTitle( @@ -50,21 +57,9 @@ struct RestoreBackupView: View { private func didTapClose() { dismiss() } -} -private struct PasswordBackupView: View { - @Environment(\.dismiss) var dismiss - @State private var password: String = "" - @State private var isPasswordVisible: Bool = false - @State private var contentFits: Bool = true - - private let importBackup: (String) -> Void - - init(importBackup: @escaping (String) -> Void) { - self.importBackup = importBackup - } - - var body: some View { + @ViewBuilder + private var passwordBackupView: some View { GeometryReader { geometry in VStack { ScrollView { @@ -76,10 +71,7 @@ private struct PasswordBackupView: View { .frame(maxWidth: .infinity, alignment: .leading) .padding(.horizontal) - EnterPasswordFieldView( - password: $password, - isPasswordVisible: $isPasswordVisible - ) + enterPasswordFieldView } .background( GeometryReader { contentGeometry in @@ -108,13 +100,9 @@ private struct PasswordBackupView: View { } } } -} -private struct EnterPasswordFieldView: View { - @Binding var password: String - @Binding var isPasswordVisible: Bool - - var body: some View { + @ViewBuilder + private var enterPasswordFieldView: some View { VStack(alignment: .leading, spacing: 8) { Text(L10n.Localizable.RestoreFromBackup.EnterPassword.title) .font(.subheadline) From 8a002bdfab7d32a02722c68a2b8594330599ec4f Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Wed, 15 Jan 2025 11:07:52 +0100 Subject: [PATCH 091/107] format code --- .../ProgressIndicatingAlertController.swift | 15 ++++++++++----- ...ProgressIndicatingAlertControllerPreview.swift | 3 ++- .../ExportBackup/ExportBackupView.swift | 3 +-- .../BackupRestoreViewController.swift | 2 +- .../Views/Preview/BackupRestoreViewPreview.swift | 2 +- .../BackupRestore/Views/RestoreBackupView.swift | 9 +-------- 6 files changed, 16 insertions(+), 18 deletions(-) diff --git a/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertController.swift b/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertController.swift index 6aad354e74d..980b3219eb5 100644 --- a/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertController.swift +++ b/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertController.swift @@ -16,8 +16,8 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import WireDesign import UIKit +import WireDesign /// A custom view controller which displays an alert-like interface with a progress bar and a cancel button. public final class ProgressIndicatingAlertController: UIViewController { @@ -134,7 +134,10 @@ public final class ProgressIndicatingAlertController: UIViewController { NSLayoutConstraint.activate([ titleLabel.leadingAnchor.constraint(equalToSystemSpacingAfter: containerView.leadingAnchor, multiplier: 2), titleLabel.topAnchor.constraint(equalToSystemSpacingBelow: containerView.topAnchor, multiplier: 2.5), - containerView.trailingAnchor.constraint(equalToSystemSpacingAfter: titleLabel.trailingAnchor, multiplier: 2), + containerView.trailingAnchor.constraint( + equalToSystemSpacingAfter: titleLabel.trailingAnchor, + multiplier: 2 + ), messageLabel.leadingAnchor.constraint(equalTo: titleLabel.leadingAnchor), messageLabel.topAnchor.constraint(equalToSystemSpacingBelow: titleLabel.bottomAnchor, multiplier: 1), @@ -150,13 +153,14 @@ public final class ProgressIndicatingAlertController: UIViewController { cancelButton.leadingAnchor.constraint(equalTo: titleLabel.leadingAnchor), cancelButton.topAnchor.constraint(equalToSystemSpacingBelow: progressView.bottomAnchor, multiplier: 1), titleLabel.trailingAnchor.constraint(equalTo: cancelButton.trailingAnchor), - containerView.bottomAnchor.constraint(equalToSystemSpacingBelow: cancelButton.bottomAnchor, multiplier: 1), + containerView.bottomAnchor.constraint(equalToSystemSpacingBelow: cancelButton.bottomAnchor, multiplier: 1) ]) } // MARK: - Action Handling - @objc private func handleCancel(_ sender: UIButton) { + @objc + private func handleCancel(_ sender: UIButton) { cancelAction.handler() presentingViewController?.dismiss(animated: true) } @@ -187,7 +191,8 @@ public final class ProgressIndicatingAlertController: UIViewController { label.adjustsFontForContentSizeCategory = true vc.view.addSubview(label) label.centerXAnchor.constraint(equalTo: vc.view.centerXAnchor).isActive = true - label.topAnchor.constraint(equalToSystemSpacingBelow: vc.view.safeAreaLayoutGuide.topAnchor, multiplier: 3).isActive = true + label.topAnchor.constraint(equalToSystemSpacingBelow: vc.view.safeAreaLayoutGuide.topAnchor, multiplier: 3) + .isActive = true let alertController = UIAlertController(title: "title", message: "message", preferredStyle: .alert) diff --git a/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertControllerPreview.swift b/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertControllerPreview.swift index 10c5965f06c..d5fddaa7227 100644 --- a/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertControllerPreview.swift +++ b/WireUI/Sources/WireReusableUIComponents/ProgressIndicatingAlertController/ProgressIndicatingAlertControllerPreview.swift @@ -30,7 +30,8 @@ func ProgressIndicatingAlertControllerPreview() -> UIViewController { label.adjustsFontForContentSizeCategory = true vc.view.addSubview(label) label.centerXAnchor.constraint(equalTo: vc.view.centerXAnchor).isActive = true - label.topAnchor.constraint(equalToSystemSpacingBelow: vc.view.safeAreaLayoutGuide.topAnchor, multiplier: 3).isActive = true + label.topAnchor.constraint(equalToSystemSpacingBelow: vc.view.safeAreaLayoutGuide.topAnchor, multiplier: 3) + .isActive = true let alertController = ProgressIndicatingAlertController( title: "Creating Backup", diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupView.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupView.swift index b07879c70b2..0d2e3aaaea0 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupView.swift @@ -49,8 +49,7 @@ struct ExportBackupView: View { .presentationDetents([.medium]) } - @ViewBuilder - private var setBackupPasswordView: some View { + @ViewBuilder private var setBackupPasswordView: some View { GeometryReader { geometry in VStack { ScrollView { diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift index 734771a39f6..bf154c1b5fa 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift @@ -63,7 +63,7 @@ public final class BackupRestoreViewController: UIViewController { hostingController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), hostingController.view.topAnchor.constraint(equalTo: view.topAnchor), view.trailingAnchor.constraint(equalTo: hostingController.view.trailingAnchor), - view.bottomAnchor.constraint(equalTo: hostingController.view.bottomAnchor), + view.bottomAnchor.constraint(equalTo: hostingController.view.bottomAnchor) ]) hostingController.didMove(toParent: self) } diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/BackupRestoreViewPreview.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/BackupRestoreViewPreview.swift index a56d0f4c519..9158bb28386 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/BackupRestoreViewPreview.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/BackupRestoreViewPreview.swift @@ -16,8 +16,8 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import WireFoundation import SwiftUI +import WireFoundation @ViewBuilder @MainActor func BackupRestoreViewPreview() -> some View { diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/RestoreBackupView.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/RestoreBackupView.swift index 21ffb7b2ac7..f632d7d891d 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/RestoreBackupView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/RestoreBackupView.swift @@ -32,12 +32,6 @@ struct RestoreBackupView: View { private let importBackup: (String) -> Void - init( - importBackup: @escaping (String) -> Void - ) { - self.importBackup = importBackup - } - var body: some View { passwordBackupView .background(Color.viewBackground) @@ -58,8 +52,7 @@ struct RestoreBackupView: View { dismiss() } - @ViewBuilder - private var passwordBackupView: some View { + @ViewBuilder private var passwordBackupView: some View { GeometryReader { geometry in VStack { ScrollView { From 8a8c87f90421b698783b4befb856c1b842de528a Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Wed, 15 Jan 2025 11:10:48 +0100 Subject: [PATCH 092/107] add TODO --- .../Account/BackupRestore/Views/RestoreBackupView.swift | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/RestoreBackupView.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/RestoreBackupView.swift index f632d7d891d..b3546c0a100 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/RestoreBackupView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/RestoreBackupView.swift @@ -21,16 +21,16 @@ import WireDesign import WireFoundation import WireReusableUIComponents -struct RestoreBackupView: View { +struct RestoreBackupView: View { // TODO: RestoreBackupView is too similar to BackupRestoreView, name it ImportBackupView @Environment(\.dismiss) private var dismiss // TODO: move to view model? @State private var password: String = "" @State private var isPasswordVisible: Bool = false - @State private var contentFits: Bool = true + @State private var contentFits = true - private let importBackup: (String) -> Void + let importBackup: (String) -> Void var body: some View { passwordBackupView @@ -52,7 +52,8 @@ struct RestoreBackupView: View { dismiss() } - @ViewBuilder private var passwordBackupView: some View { + @ViewBuilder + private var passwordBackupView: some View { GeometryReader { geometry in VStack { ScrollView { From a487e2ae1ec5cab9e9bea2b0ba98712a2ada3e68 Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Wed, 15 Jan 2025 11:15:23 +0100 Subject: [PATCH 093/107] rename and move files --- .../ImportBackupView.swift} | 6 +++--- .../ImportBackupViewPreview.swift} | 4 ++-- .../Account/BackupRestore/Views/BackupRestoreView.swift | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) rename WireUI/Sources/WireSettingsUI/Account/BackupRestore/{Views/RestoreBackupView.swift => ImportBackup/ImportBackupView.swift} (96%) rename WireUI/Sources/WireSettingsUI/Account/BackupRestore/{Views/Preview/RestoreBackupPreview.swift => ImportBackup/ImportBackupViewPreview.swift} (94%) diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/RestoreBackupView.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ImportBackup/ImportBackupView.swift similarity index 96% rename from WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/RestoreBackupView.swift rename to WireUI/Sources/WireSettingsUI/Account/BackupRestore/ImportBackup/ImportBackupView.swift index b3546c0a100..bdad6bb28b4 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/RestoreBackupView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ImportBackup/ImportBackupView.swift @@ -21,7 +21,7 @@ import WireDesign import WireFoundation import WireReusableUIComponents -struct RestoreBackupView: View { // TODO: RestoreBackupView is too similar to BackupRestoreView, name it ImportBackupView +struct ImportBackupView: View { @Environment(\.dismiss) private var dismiss @@ -144,6 +144,6 @@ struct RestoreBackupView: View { // TODO: RestoreBackupView is too similar to Ba // MARK: - Previews @available(iOS 17.0, *) -#Preview("Export Backup sheet") { - RestoreBackupPreview() +#Preview("Import Backup sheet") { + ImportBackupViewPreview() } diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/RestoreBackupPreview.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ImportBackup/ImportBackupViewPreview.swift similarity index 94% rename from WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/RestoreBackupPreview.swift rename to WireUI/Sources/WireSettingsUI/Account/BackupRestore/ImportBackup/ImportBackupViewPreview.swift index bb8a094462d..7b6fc424f4b 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/RestoreBackupPreview.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ImportBackup/ImportBackupViewPreview.swift @@ -18,7 +18,7 @@ import SwiftUI -struct RestoreBackupPreview: View { +struct ImportBackupViewPreview: View { @State private var isPresented = true var body: some View { @@ -32,7 +32,7 @@ struct RestoreBackupPreview: View { ) .sheet(isPresented: $isPresented) { NavigationStack { - RestoreBackupView( + ImportBackupView( importBackup: { _ in } ) } diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift index 0a385711d0d..cd4c4b71eb8 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift @@ -64,7 +64,7 @@ struct BackupRestoreView: View { } .sheet(isPresented: $isRestoreBackupSheetPresented) { NavigationStack { - RestoreBackupView { password in + ImportBackupView { password in // TODO: importBackupSheetContent if let fileURL = selectedFileURL { viewModel.restoreFromBackup( at: fileURL, From 882b01f38a2f334cc3f8bfe02e8bd8bb440439fd Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Wed, 15 Jan 2025 11:26:01 +0100 Subject: [PATCH 094/107] create ImportBackupViewModel --- .../ImportBackup/ImportBackupView.swift | 26 +++++++++---------- .../ImportBackup/ImportBackupViewModel.swift | 26 +++++++++++++++++++ .../ImportBackupViewPreview.swift | 1 + .../Views/BackupRestoreView.swift | 2 +- 4 files changed, 41 insertions(+), 14 deletions(-) create mode 100644 WireUI/Sources/WireSettingsUI/Account/BackupRestore/ImportBackup/ImportBackupViewModel.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ImportBackup/ImportBackupView.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ImportBackup/ImportBackupView.swift index bdad6bb28b4..d811847e22e 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ImportBackup/ImportBackupView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ImportBackup/ImportBackupView.swift @@ -25,13 +25,13 @@ struct ImportBackupView: View { @Environment(\.dismiss) private var dismiss - // TODO: move to view model? - @State private var password: String = "" - @State private var isPasswordVisible: Bool = false - @State private var contentFits = true + @ObservedObject private(set) var viewModel: ImportBackupViewModel + // TODO: move to view model? let importBackup: (String) -> Void + @State private var contentFits = true + var body: some View { passwordBackupView .background(Color.viewBackground) @@ -82,7 +82,7 @@ struct ImportBackupView: View { Button( action: { - importBackup(password) + importBackup(viewModel.password) dismiss() }, label: { @@ -100,29 +100,29 @@ struct ImportBackupView: View { VStack(alignment: .leading, spacing: 8) { Text(L10n.Localizable.RestoreFromBackup.EnterPassword.title) .font(.subheadline) - .foregroundColor(password.isEmpty ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.primary.color) + .foregroundColor(viewModel.password.isEmpty ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.primary.color) ZStack { - if isPasswordVisible { + if viewModel.isPasswordVisible { TextField( L10n.Localizable.ExportBackup.SetBackupPassword.placeholder, - text: $password + text: $viewModel.password ) .wireTextStyle(.body1) .textFieldStyle(RoundedBorderTextFieldStyle()) } else { SecureField( L10n.Localizable.ExportBackup.SetBackupPassword.placeholder, - text: $password + text: $viewModel.password ) .textFieldStyle(RoundedBorderTextFieldStyle()) } HStack { Spacer() Button(action: { - isPasswordVisible.toggle() + viewModel.isPasswordVisible.toggle() }, label: { - Image(systemName: isPasswordVisible ? "eye" : "eye.slash") + Image(systemName: viewModel.isPasswordVisible ? "eye" : "eye.slash") .foregroundColor(.gray) }) .padding(.trailing, 10) @@ -131,8 +131,8 @@ struct ImportBackupView: View { .overlay( RoundedRectangle(cornerRadius: 5) .stroke( - password.isEmpty ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.primary.color, - lineWidth: password.isEmpty ? 0 : 1 + viewModel.password.isEmpty ? ColorTheme.Base.secondaryText.color : ColorTheme.Base.primary.color, + lineWidth: viewModel.password.isEmpty ? 0 : 1 ) ) diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ImportBackup/ImportBackupViewModel.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ImportBackup/ImportBackupViewModel.swift new file mode 100644 index 00000000000..9eadd321e47 --- /dev/null +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ImportBackup/ImportBackupViewModel.swift @@ -0,0 +1,26 @@ +// +// Wire +// Copyright (C) 2025 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import Foundation + +final class ImportBackupViewModel: ObservableObject { + + @Published var password = "" + @Published var isPasswordVisible = false + +} diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ImportBackup/ImportBackupViewPreview.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ImportBackup/ImportBackupViewPreview.swift index 7b6fc424f4b..00e4d83bb3f 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ImportBackup/ImportBackupViewPreview.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ImportBackup/ImportBackupViewPreview.swift @@ -33,6 +33,7 @@ struct ImportBackupViewPreview: View { .sheet(isPresented: $isPresented) { NavigationStack { ImportBackupView( + viewModel: .init(), importBackup: { _ in } ) } diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift index cd4c4b71eb8..49423601d31 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift @@ -64,7 +64,7 @@ struct BackupRestoreView: View { } .sheet(isPresented: $isRestoreBackupSheetPresented) { NavigationStack { - ImportBackupView { password in // TODO: importBackupSheetContent + ImportBackupView(viewModel: .init()) { password in // TODO: importBackupSheetContent if let fileURL = selectedFileURL { viewModel.restoreFromBackup( at: fileURL, From b4773fc7b0208741b9afa5bb50aea2a4ec754d07 Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Wed, 15 Jan 2025 12:00:01 +0100 Subject: [PATCH 095/107] prepare for injecting ImportBackupView into BackupRestoreView --- .../ImportBackup/ImportBackupViewModel.swift | 6 +++++ .../ImportBackupViewPreview.swift | 2 +- .../BackupRestoreViewController.swift | 14 +++++++++++ .../Views/BackupRestoreView.swift | 23 +++++-------------- .../Preview/BackupRestoreViewPreview.swift | 3 ++- 5 files changed, 29 insertions(+), 19 deletions(-) diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ImportBackup/ImportBackupViewModel.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ImportBackup/ImportBackupViewModel.swift index 9eadd321e47..5e834265abb 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ImportBackup/ImportBackupViewModel.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ImportBackup/ImportBackupViewModel.swift @@ -23,4 +23,10 @@ final class ImportBackupViewModel: ObservableObject { @Published var password = "" @Published var isPasswordVisible = false + private let importBackupAction: () -> Void // TODO: use case protocol + // TODO: file picker + + init(importBackupAction: @escaping () -> Void) { + self.importBackupAction = importBackupAction + } } diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ImportBackup/ImportBackupViewPreview.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ImportBackup/ImportBackupViewPreview.swift index 00e4d83bb3f..a43cede31cb 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ImportBackup/ImportBackupViewPreview.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ImportBackup/ImportBackupViewPreview.swift @@ -33,7 +33,7 @@ struct ImportBackupViewPreview: View { .sheet(isPresented: $isPresented) { NavigationStack { ImportBackupView( - viewModel: .init(), + viewModel: .init(importBackupAction: {}), importBackup: { _ in } ) } diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift index bf154c1b5fa..dceca3c4c41 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift @@ -53,6 +53,20 @@ public final class BackupRestoreViewController: UIViewController { self.viewModel.backupActiveAccount(password: password) } ) + }, + importBackupSheetContent: { + NavigationStack { + ImportBackupView(viewModel: .init(importBackupAction: { fatalError() })) { password in + if let fileURL = /*selectedFileURL*/ URL(string: "TODO: selectedFileURL") { + self.viewModel.restoreFromBackup( + at: fileURL, + password: password, + completion: { _ in } + ) + } + } + } + .presentationDetents([.medium]) } ) let hostingController = UIHostingController(rootView: backupRestoreView) diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift index 49423601d31..e117cb17c73 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift @@ -21,14 +21,16 @@ import WireDesign import WireFoundation import WireReusableUIComponents -struct BackupRestoreView: View { +struct BackupRestoreView: View { @ObservedObject private(set) var viewModel: BackupRestoreViewModel + @ViewBuilder private(set) var exportBackupSheetContent: () -> ExportBackupSheet + @ViewBuilder private(set) var importBackupSheetContent: () -> ImportBackupSheet @State private var isExportBackupSheetPresented: Bool = false + @State private var isImportBackupSheetPresented: Bool = false @State private var isBackupPickerPresented: Bool = false - @State private var isRestoreBackupSheetPresented: Bool = false @State private var selectedFileURL: URL? var body: some View { @@ -58,24 +60,11 @@ struct BackupRestoreView: View { BackupPicker { url in if let fileURL = url { selectedFileURL = fileURL - isRestoreBackupSheetPresented = true - } - } - } - .sheet(isPresented: $isRestoreBackupSheetPresented) { - NavigationStack { - ImportBackupView(viewModel: .init()) { password in // TODO: importBackupSheetContent - if let fileURL = selectedFileURL { - viewModel.restoreFromBackup( - at: fileURL, - password: password, - completion: { _ in } - ) - } + isImportBackupSheetPresented = true } } - .presentationDetents([.medium]) } + .sheet(isPresented: $isImportBackupSheetPresented, content: importBackupSheetContent) } } .listStyle(.grouped) diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/BackupRestoreViewPreview.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/BackupRestoreViewPreview.swift index 9158bb28386..4fe3a306853 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/BackupRestoreViewPreview.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/BackupRestoreViewPreview.swift @@ -36,7 +36,8 @@ func BackupRestoreViewPreview() -> some View { ) // passwordValidator: MockBackupPasswordValidator() ), - exportBackupSheetContent: { EmptyView() } + exportBackupSheetContent: { EmptyView() }, + importBackupSheetContent: { EmptyView() } ) } From a79d02c4455ace311efa857d6ca173bd7b9422c5 Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Wed, 15 Jan 2025 12:25:49 +0100 Subject: [PATCH 096/107] prepare more refactoring --- .../ExportBackup/ExportBackupPreview.swift | 19 +++++++++++-- .../ExportBackup/ExportBackupView.swift | 7 ++--- .../ExportBackup/ExportBackupViewModel.swift | 27 +++++++++++++++++- .../BackupRestoreAlertPresenterProtocol.swift | 25 +++++++++++++++++ .../BackupRestoreViewController.swift | 28 ++++++++++++++++--- 5 files changed, 95 insertions(+), 11 deletions(-) create mode 100644 WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/BackupRestoreAlertPresenterProtocol.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupPreview.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupPreview.swift index f78bcd1eac3..68607a33ca2 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupPreview.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupPreview.swift @@ -38,8 +38,11 @@ private struct ExportBackup_Preview: View { .sheet(isPresented: $isPresented) { NavigationStack { ExportBackupView( - viewModel: .init(passwordValidator: MockBackupPasswordValidator()), - exportBackup: { _ in } + viewModel: .init( + passwordValidator: MockBackupPasswordValidator(), + alertPresenter: PreviewAlertPresenter(), + exportBackupUseCase: PreviewUseCase() + ) ) } .presentationDragIndicator(.visible) @@ -47,3 +50,15 @@ private struct ExportBackup_Preview: View { } } } + +private struct PreviewAlertPresenter: BackupRestoreAlertPresenterProtocol { + func todo() async -> Bool { + fatalError("not implemented") + } +} + +private struct PreviewUseCase: ExportBackupUseCaseProtocol { + func invoke(url: URL, password: String) async { + fatalError("not implemented") + } +} diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupView.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupView.swift index 0d2e3aaaea0..4ce9e5b583b 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupView.swift @@ -30,8 +30,6 @@ struct ExportBackupView: View { @ObservedObject private(set) var viewModel: ExportBackupViewModel @State private var isScrollDisabled: Bool = true - let exportBackup: (String) -> Void - var body: some View { NavigationStack { setBackupPasswordView @@ -49,7 +47,8 @@ struct ExportBackupView: View { .presentationDetents([.medium]) } - @ViewBuilder private var setBackupPasswordView: some View { + @ViewBuilder + private var setBackupPasswordView: some View { GeometryReader { geometry in VStack { ScrollView { @@ -82,7 +81,7 @@ struct ExportBackupView: View { Button { dismiss() - exportBackup(viewModel.password) + viewModel.triggerExport() } label: { Text(L10n.Localizable.ExportBackup.button) } diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupViewModel.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupViewModel.swift index aa5d96b87c2..dabc6c82f09 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupViewModel.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupViewModel.swift @@ -30,12 +30,37 @@ final class ExportBackupViewModel: ObservableObject { var localizedPasswordRules: String { passwordValidator.localizedRulesDescription } private let passwordValidator: any BackupPasswordValidatorProtocol + private let alertPresenter: any BackupRestoreAlertPresenterProtocol + private let exportBackupUseCase: any ExportBackupUseCaseProtocol - init(passwordValidator: any BackupPasswordValidatorProtocol) { + init( + passwordValidator: any BackupPasswordValidatorProtocol, + alertPresenter: any BackupRestoreAlertPresenterProtocol, + exportBackupUseCase: any ExportBackupUseCaseProtocol + ) { self.passwordValidator = passwordValidator + self.alertPresenter = alertPresenter + self.exportBackupUseCase = exportBackupUseCase } private func validatePassword() { isPasswordValid = passwordValidator.isPasswordValid(password) } + + func triggerExport() { + let password = password + let exportBackupUseCase = exportBackupUseCase + Task { + do { + let url: URL! = .init(string: "https://example.org") + try await exportBackupUseCase.invoke(url: url, password: password) + } catch { + fatalError("TODO: use alertPresenter") + } + } + } +} + +public protocol ExportBackupUseCaseProtocol: Sendable { // TODO: move away from here + func invoke(url: URL, password: String) async } diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/BackupRestoreAlertPresenterProtocol.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/BackupRestoreAlertPresenterProtocol.swift new file mode 100644 index 00000000000..93c4b5ae9d1 --- /dev/null +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/BackupRestoreAlertPresenterProtocol.swift @@ -0,0 +1,25 @@ +// +// Wire +// Copyright (C) 2025 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +public protocol BackupRestoreAlertPresenterProtocol { + + /// <#Description#> + /// - Returns: <#description#> + @MainActor + func todo() async -> Bool +} diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift index dceca3c4c41..935ba27579b 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift @@ -48,10 +48,13 @@ public final class BackupRestoreViewController: UIViewController { viewModel: viewModel, exportBackupSheetContent: { ExportBackupView( - viewModel: .init(passwordValidator: backupPasswordValidator), - exportBackup: { password in - self.viewModel.backupActiveAccount(password: password) - } + viewModel: .init( + passwordValidator: backupPasswordValidator, + alertPresenter: DummyAlertPresenter(), // TODO: fix + exportBackupUseCase: DummyUseCase { password in // TODO: fix + self.viewModel.backupActiveAccount(password: password) + } + ) ) }, importBackupSheetContent: { @@ -82,3 +85,20 @@ public final class BackupRestoreViewController: UIViewController { hostingController.didMove(toParent: self) } } + +// TODO: remove + +private struct DummyAlertPresenter: BackupRestoreAlertPresenterProtocol { + func todo() async -> Bool { + fatalError("not implemented") + } +} + +private struct DummyUseCase: ExportBackupUseCaseProtocol { + + let action: (String) -> Void + + func invoke(url: URL, password: String) async { + action(password) + } +} From 5df3ff613656fb1a875f3b8e4021438b9ad319d7 Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Wed, 15 Jan 2025 12:54:27 +0100 Subject: [PATCH 097/107] prepare fixing crash on iPad --- .../BackupRestore/BackupRestoreBuilder.swift | 15 +++++-- .../ExportBackup/ExportBackupViewModel.swift | 4 -- ...ource.swift => BackupSourceProtocol.swift} | 0 .../ExportBackupUseCaseProtocol.swift | 23 +++++++++++ ...urce.swift => RestoreSourceProtocol.swift} | 0 .../BackupRestoreViewController.swift | 13 +++++-- .../ViewModels/BackupRestoreViewModel.swift | 14 ++++++- .../Settings/Backup/BackupSource.swift | 9 ++++- ...ettingsCellDescriptorFactory+Account.swift | 39 ++++++++++--------- 9 files changed, 84 insertions(+), 33 deletions(-) rename WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/{BackupSource.swift => BackupSourceProtocol.swift} (100%) create mode 100644 WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/ExportBackupUseCaseProtocol.swift rename WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/{RestoreSource.swift => RestoreSourceProtocol.swift} (100%) diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/BackupRestoreBuilder.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/BackupRestoreBuilder.swift index 55d6ab083b5..c4ba098bff3 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/BackupRestoreBuilder.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/BackupRestoreBuilder.swift @@ -21,16 +21,23 @@ import UIKit public struct BackupRestoreBuilder { public var backupPasswordValidator: any BackupPasswordValidatorProtocol + public var exportBackupUseCase: any ExportBackupUseCaseProtocol - public init(backupPasswordValidator: any BackupPasswordValidatorProtocol) { + public init( + backupPasswordValidator: any BackupPasswordValidatorProtocol, + exportBackupUseCase: any ExportBackupUseCaseProtocol + ) { self.backupPasswordValidator = backupPasswordValidator + self.exportBackupUseCase = exportBackupUseCase } @MainActor public func build() -> UIViewController { - BackupRestoreViewController( - viewModel: BackupRestoreViewModel(), - backupPasswordValidator: backupPasswordValidator + let viewModel = BackupRestoreViewModel() + return BackupRestoreViewController( + viewModel: viewModel, + backupPasswordValidator: backupPasswordValidator, + exportBackupUseCase: exportBackupUseCase ) } } diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupViewModel.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupViewModel.swift index dabc6c82f09..53c8a095232 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupViewModel.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupViewModel.swift @@ -60,7 +60,3 @@ final class ExportBackupViewModel: ObservableObject { } } } - -public protocol ExportBackupUseCaseProtocol: Sendable { // TODO: move away from here - func invoke(url: URL, password: String) async -} diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/BackupSource.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/BackupSourceProtocol.swift similarity index 100% rename from WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/BackupSource.swift rename to WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/BackupSourceProtocol.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/ExportBackupUseCaseProtocol.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/ExportBackupUseCaseProtocol.swift new file mode 100644 index 00000000000..2fa7cfd7c83 --- /dev/null +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/ExportBackupUseCaseProtocol.swift @@ -0,0 +1,23 @@ +// +// Wire +// Copyright (C) 2025 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import Foundation + +public protocol ExportBackupUseCaseProtocol: Sendable { + func invoke(url: URL, password: String) async throws +} diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/RestoreSource.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/RestoreSourceProtocol.swift similarity index 100% rename from WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/RestoreSource.swift rename to WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/RestoreSourceProtocol.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift index 935ba27579b..73e7eb59f3b 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift @@ -22,13 +22,16 @@ public final class BackupRestoreViewController: UIViewController { private let viewModel: BackupRestoreViewModel private let backupPasswordValidator: any BackupPasswordValidatorProtocol + private let exportBackupUseCase: any ExportBackupUseCaseProtocol public init( viewModel: BackupRestoreViewModel, - backupPasswordValidator: any BackupPasswordValidatorProtocol + backupPasswordValidator: any BackupPasswordValidatorProtocol, + exportBackupUseCase: any ExportBackupUseCaseProtocol ) { self.viewModel = viewModel self.backupPasswordValidator = backupPasswordValidator + self.exportBackupUseCase = exportBackupUseCase super.init(nibName: nil, bundle: nil) } @@ -44,6 +47,7 @@ public final class BackupRestoreViewController: UIViewController { private func setupView() { let backupPasswordValidator = backupPasswordValidator + let exportBackupUseCase = exportBackupUseCase let backupRestoreView = BackupRestoreView( viewModel: viewModel, exportBackupSheetContent: { @@ -51,9 +55,10 @@ public final class BackupRestoreViewController: UIViewController { viewModel: .init( passwordValidator: backupPasswordValidator, alertPresenter: DummyAlertPresenter(), // TODO: fix - exportBackupUseCase: DummyUseCase { password in // TODO: fix - self.viewModel.backupActiveAccount(password: password) - } + exportBackupUseCase: exportBackupUseCase +// DummyUseCase { password in // TODO: fix +// self.viewModel.backupActiveAccount(password: password) +// } ) ) }, diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewModels/BackupRestoreViewModel.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewModels/BackupRestoreViewModel.swift index a0e639fd956..b3b2a3abce1 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewModels/BackupRestoreViewModel.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewModels/BackupRestoreViewModel.swift @@ -28,7 +28,19 @@ public final class BackupRestoreViewModel: ObservableObject { public init( // ) { - fatalError() + // TODO: fix + backupSource = DummyBackupSource() + restoreSource = DummyRestoreSource() + backupResultHandler = .init(onSuccess: { _, _ in fatalError() }, onFailure: {}) + restoreBackupResultHandler = .init(onSuccess: {}, onConfirmation: { _ in }, onFailure: {}) + + struct DummyBackupSource: BackupSourceProtocol { + func backupActiveAccount(password: String) throws -> URL { fatalError() } + func clearPreviousBackups() { fatalError() } + } + struct DummyRestoreSource: RestoreSourceProtocol { + func restoreFromBackup(at: URL, password: String, completion: @escaping (Result) -> Void) { fatalError() } + } } public init( diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupSource.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupSource.swift index 04485359937..49184b5bf47 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupSource.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupSource.swift @@ -20,12 +20,14 @@ import Foundation import WireSettingsUI import class WireSyncEngine.SessionManager -struct BackupSource: BackupSourceProtocol { +struct BackupSource: BackupSourceProtocol, ExportBackupUseCaseProtocol { + // TODO: remove enum BackupSourceError: Error { case missingSessionManager } + // TODO: remove func backupActiveAccount(password: String) throws -> URL { guard let sessionManager = SessionManager.shared else { throw BackupSourceError.missingSessionManager @@ -33,8 +35,13 @@ struct BackupSource: BackupSourceProtocol { return try sessionManager.backupActiveAccount(password: password) } + // TODO: remove func clearPreviousBackups() { SessionManager.shared?.clearPreviousBackups() } + func invoke(url: URL, password: String) async throws { + let url = try SessionManager.shared?.backupActiveAccount(password: password) + } + } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift index dbc9a3c886e..9da973984a8 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift @@ -371,7 +371,8 @@ extension SettingsCellDescriptorFactory { private var backupRestoreBuilder: BackupRestoreBuilder { .init( - backupPasswordValidator: BackupPasswordValidator() + backupPasswordValidator: BackupPasswordValidator(), + exportBackupUseCase: BackupSource() ) } @@ -387,24 +388,24 @@ extension SettingsCellDescriptorFactory { return .none } if selfUser.hasValidEmail || selfUser.usesCompanyLogin { -// let backupRestoreController = backupRestoreBuilder.build() - let viewModel = BackupRestoreViewModel( // TODO: delete, use `backupRestoreBuilder` - backupSource: BackupSource(), - restoreSource: RestoreSource(), - backupResultHandler: BackupResultHandler( - onSuccess: presentShareSheet, - onFailure: presentExportBackupErrorAlert - ), - restoreBackupResultHandler: RestoreBackupResultHandler( - onSuccess: presentOnSuccessAlert, - onConfirmation: presentConfirmationAlert, - onFailure: presentRestoreBackupErrorAlert - ) - ) - let backupRestoreController = BackupRestoreViewController( - viewModel: viewModel, - backupPasswordValidator: BackupPasswordValidator() - ) + let backupRestoreController = backupRestoreBuilder.build() +// let viewModel = BackupRestoreViewModel( // TODO: delete, use `backupRestoreBuilder` +// backupSource: BackupSource(), +// restoreSource: RestoreSource(), +// backupResultHandler: BackupResultHandler( +// onSuccess: presentShareSheet, +// onFailure: presentExportBackupErrorAlert +// ), +// restoreBackupResultHandler: RestoreBackupResultHandler( +// onSuccess: presentOnSuccessAlert, +// onConfirmation: presentConfirmationAlert, +// onFailure: presentRestoreBackupErrorAlert +// ) +// ) +// let backupRestoreController = BackupRestoreViewController( +// viewModel: viewModel, +// backupPasswordValidator: BackupPasswordValidator() +// ) backupRestoreController.setupNavigationBarTitle(L10n.Localizable.Self.Settings.HistoryBackup.title) return backupRestoreController } else { From 4abb76a011163b0b16696fb314debcd094cabb0f Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Wed, 15 Jan 2025 13:01:08 +0100 Subject: [PATCH 098/107] add TODO --- .../Source/SessionManager/SessionManager+Backup.swift | 2 +- .../Sources/UserInterface/Settings/Backup/BackupSource.swift | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/wire-ios-sync-engine/Source/SessionManager/SessionManager+Backup.swift b/wire-ios-sync-engine/Source/SessionManager/SessionManager+Backup.swift index 713948895b8..3dcad357441 100644 --- a/wire-ios-sync-engine/Source/SessionManager/SessionManager+Backup.swift +++ b/wire-ios-sync-engine/Source/SessionManager/SessionManager+Backup.swift @@ -79,7 +79,7 @@ extension SessionManager { dispatchGroup: ZMSDispatchGroup, handle: String ) throws -> URL { - try workerQueue.sync { + try workerQueue.sync { // TODO: async switch result { case let .success(info): do { diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupSource.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupSource.swift index 49184b5bf47..27cdabe8caf 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupSource.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupSource.swift @@ -40,6 +40,7 @@ struct BackupSource: BackupSourceProtocol, ExportBackupUseCaseProtocol { SessionManager.shared?.clearPreviousBackups() } + @MainActor func invoke(url: URL, password: String) async throws { let url = try SessionManager.shared?.backupActiveAccount(password: password) } From 555ba124ade4a0d4497ca3889c62b07add18470b Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Wed, 15 Jan 2025 13:11:13 +0100 Subject: [PATCH 099/107] revert a few changes of the epic branch --- .../CoreDataStack+Backup.swift | 32 +++++---- .../CoreDataStackTests+Backup.swift | 63 +++++++++++------- .../SessionManager+Backup.swift | 66 ++++++++++--------- .../SessionManager/SessionManager.swift | 2 +- .../Settings/Backup/BackupSource.swift | 7 +- 5 files changed, 101 insertions(+), 69 deletions(-) diff --git a/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack+Backup.swift b/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack+Backup.swift index 66311cd99d1..92d661c8475 100644 --- a/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack+Backup.swift +++ b/wire-ios-data-model/Source/ManagedObjectContext/CoreDataStack+Backup.swift @@ -81,35 +81,41 @@ public extension CoreDataStack { } /// Will make a copy of account storage and place in a unique directory + /// /// - Parameters: /// - accountIdentifier: identifier of account being backed up - /// - clientIdentifier: client ID /// - applicationContainer: shared application container /// - dispatchGroup: group for testing /// - databaseKey: EAR database key - /// - Returns: contains the folder where all data was written to + /// - completion: called on main thread when done. Result will contain the folder where all data was written to. static func backupLocalStorage( accountIdentifier: UUID, clientIdentifier: String, applicationContainer: URL, dispatchGroup: ZMSDispatchGroup, - databaseKey: VolatileData? = nil - ) throws -> BackupInfo { + databaseKey: VolatileData? = nil, + completion: @escaping (Result) -> Void + ) { + func fail(_ error: BackupError) { + log.debug("error backing up local store: \(error)") + DispatchQueue.main.async(group: dispatchGroup) { + completion(.failure(error)) + } + } + let accountDirectory = Self.accountDataFolder( accountIdentifier: accountIdentifier, applicationContainer: applicationContainer ) let storeFile = accountDirectory.appendingPersistentStoreLocation() - guard fileManager.fileExists(atPath: accountDirectory.path) else { - throw BackupError.failedToRead - } + guard fileManager.fileExists(atPath: accountDirectory.path) else { return fail(.failedToRead) } let backupDirectory = backupsDirectory.appendingPathComponent(UUID().uuidString) let databaseDirectory = backupDirectory.appendingPathComponent(databaseDirectoryName) let metadataURL = backupDirectory.appendingPathComponent(metadataFilename) - return try workQueue.sync { + workQueue.async(group: dispatchGroup) { do { let model = CoreDataStack.loadMessagingModel() let coordinator = NSPersistentStoreCoordinator(managedObjectModel: model) @@ -147,9 +153,11 @@ public extension CoreDataStack { try metadata.write(to: metadataURL) log.info("successfully created backup at: \(backupDirectory.path), metadata: \(metadata)") - return BackupInfo(url: backupDirectory, metadata: metadata) + DispatchQueue.main.async(group: dispatchGroup) { + completion(.success(.init(url: backupDirectory, metadata: metadata))) + } } catch { - throw BackupError.failedToWrite(error) + fail(.failedToWrite(error)) } } } @@ -311,9 +319,7 @@ public extension CoreDataStack { try context.performGroupedAndWait { if context.encryptMessagesAtRest { - guard let databaseKey else { - throw BackupError.missingEAREncryptionKey - } + guard let databaseKey else { throw BackupError.missingEAREncryptionKey } try context.migrateAwayFromEncryptionAtRest(databaseKey: databaseKey) context.encryptMessagesAtRest = false _ = context.makeMetadataPersistent() diff --git a/wire-ios-data-model/Tests/Source/ManagedObjectContext/CoreDataStackTests+Backup.swift b/wire-ios-data-model/Tests/Source/ManagedObjectContext/CoreDataStackTests+Backup.swift index 7eb0da44153..296bfea4d39 100644 --- a/wire-ios-data-model/Tests/Source/ManagedObjectContext/CoreDataStackTests+Backup.swift +++ b/wire-ios-data-model/Tests/Source/ManagedObjectContext/CoreDataStackTests+Backup.swift @@ -52,14 +52,21 @@ final class CoreDataStackTests_Backup: DatabaseBaseTest { databaseKey: VolatileData? = nil, file: StaticString = #filePath, line: UInt = #line - ) throws -> CoreDataStack.BackupInfo { - try CoreDataStack.backupLocalStorage( + ) -> Result { + var result: Result? + + CoreDataStack.backupLocalStorage( accountIdentifier: accountIdentifier, clientIdentifier: name, applicationContainer: DatabaseBaseTest.applicationContainer, dispatchGroup: dispatchGroup, databaseKey: databaseKey - ) + ) { + result = $0.map(\.url) + } + XCTAssert(waitForAllGroupsToBeEmpty(withTimeout: 1), file: file, line: line) + + return result ?? .failure(CoreDataStackTests.timedOut) } func importBackup( @@ -98,8 +105,8 @@ final class CoreDataStackTests_Backup: DatabaseBaseTest { _ = ZMConversation.insertGroupConversation(moc: directory.viewContext, participants: [ZMUser]()) directory.viewContext.saveOrRollback() - let backup = try createBackup(accountIdentifier: accountIdentifier) - return backup.url + let backup = createBackup(accountIdentifier: accountIdentifier) + return try backup.get() } // MARK: - Export @@ -108,8 +115,11 @@ final class CoreDataStackTests_Backup: DatabaseBaseTest { // given _ = createStorageStackAndWaitForCompletion(userID: UUID()) - // when / then - XCTAssertThrowsError(try createBackup(accountIdentifier: UUID())) { error in + // when + let result = createBackup(accountIdentifier: UUID()) + + // then + XCTAssertThrowsError(try result.get()) { error in switch error as? CoreDataStack.BackupError { case .failedToRead: break default: XCTFail("unexpected error type") @@ -123,10 +133,10 @@ final class CoreDataStackTests_Backup: DatabaseBaseTest { _ = createStorageStackAndWaitForCompletion(userID: uuid) // when - let result = try createBackup(accountIdentifier: uuid) + let result = createBackup(accountIdentifier: uuid) // then - let url = result.url + let url = try result.get() let fm = FileManager.default XCTAssertTrue(fm.fileExists(atPath: url.path)) @@ -146,12 +156,14 @@ final class CoreDataStackTests_Backup: DatabaseBaseTest { // create empty file where backup needs to be saved to try Data().write(to: CoreDataStack.backupsDirectory) - // when / then - XCTAssertThrowsError(try createBackup(accountIdentifier: uuid)) { error in - switch error as? CoreDataStack.BackupError { - case .failedToWrite: break - default: XCTFail("unexpected error type") - } + // when + let result = createBackup(accountIdentifier: uuid) + + guard case let .failure(error) = result else { return XCTFail() } + + switch error as? CoreDataStack.BackupError { + case .failedToWrite?: break + default: XCTFail("unexpected error type") } } @@ -165,11 +177,11 @@ final class CoreDataStackTests_Backup: DatabaseBaseTest { directory.viewContext.saveOrRollback() // when - let result = try createBackup(accountIdentifier: uuid, databaseKey: directory.viewContext.databaseKey) + let result = createBackup(accountIdentifier: uuid, databaseKey: directory.viewContext.databaseKey) directory.viewContext.saveOrRollback() // then - let backup = result.url + let backup = try result.get() let model = CoreDataStack.loadMessagingModel() let coordinator = NSPersistentStoreCoordinator(managedObjectModel: model) let storeFile = backup.appendingPathComponent("data").appendingStoreFile() @@ -188,8 +200,11 @@ final class CoreDataStackTests_Backup: DatabaseBaseTest { directory.viewContext.databaseKey = nil directory.viewContext.saveOrRollback() - // when then - XCTAssertThrowsError(try createBackup(accountIdentifier: uuid, databaseKey: nil)) { error in + // when + let result = createBackup(accountIdentifier: uuid, databaseKey: nil) + + // then + XCTAssertThrowsError(try result.get()) { error in switch error as? CoreDataStack.BackupError { case let .failedToWrite(failureError): @@ -208,7 +223,7 @@ final class CoreDataStackTests_Backup: DatabaseBaseTest { } } - func testThatItPreservesOriginalDataAfterBackup() throws { + func testThatItPreservesOriginalDataAfterBackup() { // given let uuid = UUID() let directory = createStorageStackAndWaitForCompletion(userID: uuid) @@ -216,9 +231,10 @@ final class CoreDataStackTests_Backup: DatabaseBaseTest { directory.viewContext.saveOrRollback() // when - _ = try createBackup(accountIdentifier: uuid) + let result = createBackup(accountIdentifier: uuid) // then + guard case .success = result else { return XCTFail() } let fetchConversations = ZMConversation.sortedFetchRequest() XCTAssertEqual(try directory.viewContext.count(for: fetchConversations), 1) } @@ -231,9 +247,10 @@ final class CoreDataStackTests_Backup: DatabaseBaseTest { directory.viewContext.saveOrRollback() // when - _ = try createBackup(accountIdentifier: uuid) + let result = createBackup(accountIdentifier: uuid) // then + guard case .success = result else { return XCTFail() } let anotherDirectory = createStorageStackAndWaitForCompletion(userID: uuid) let fetchConversations = ZMConversation.sortedFetchRequest() XCTAssertEqual(try anotherDirectory.viewContext.count(for: fetchConversations), 1) @@ -275,7 +292,7 @@ final class CoreDataStackTests_Backup: DatabaseBaseTest { ) directory.viewContext.forceSaveOrRollback() - let backup = try createBackup(accountIdentifier: uuid).url + let backup = try createBackup(accountIdentifier: uuid).get() // Delete account clearStorageFolder() diff --git a/wire-ios-sync-engine/Source/SessionManager/SessionManager+Backup.swift b/wire-ios-sync-engine/Source/SessionManager/SessionManager+Backup.swift index 3dcad357441..db437698709 100644 --- a/wire-ios-sync-engine/Source/SessionManager/SessionManager+Backup.swift +++ b/wire-ios-sync-engine/Source/SessionManager/SessionManager+Backup.swift @@ -40,36 +40,40 @@ extension SessionManager { case unknown } - public func backupActiveAccount(password: String) throws -> URL { + public func backupActiveAccount(password: String, completion: @escaping (Result) -> Void) { guard let userId = accountManager.selectedAccount?.userIdentifier, let clientId = activeUserSession?.selfUserClient?.remoteIdentifier, let handle = activeUserSession.flatMap(ZMUser.selfUser)?.handle, let activeUserSession else { - throw BackupError.noActiveAccount + return completion(.failure(BackupError.noActiveAccount)) } - do { - let backupInfo = try CoreDataStack.backupLocalStorage( - accountIdentifier: userId, - clientIdentifier: clientId, - applicationContainer: sharedContainerURL, - dispatchGroup: dispatchGroup, - databaseKey: activeUserSession.managedObjectContext.databaseKey - ) - - return try SessionManager.handle( - result: .success(backupInfo), - password: password, - accountId: userId, - dispatchGroup: dispatchGroup, - handle: handle - ) - } catch { - activeUserSession.analyticsEventTracker?.trackEvent(.Backup.exportFailed) - throw error - } + CoreDataStack.backupLocalStorage( + accountIdentifier: userId, + clientIdentifier: clientId, + applicationContainer: sharedContainerURL, + dispatchGroup: dispatchGroup, + databaseKey: activeUserSession.managedObjectContext.databaseKey, + completion: { [dispatchGroup] result in + switch result { + case .success: + break + case .failure: + activeUserSession.analyticsEventTracker?.trackEvent(.Backup.exportFailed) + } + + SessionManager.handle( + result: result, + password: password, + accountId: userId, + dispatchGroup: dispatchGroup, + completion: completion, + handle: handle + ) + } + ) } private static func handle( @@ -77,11 +81,11 @@ extension SessionManager { password: String, accountId: UUID, dispatchGroup: ZMSDispatchGroup, + completion: @escaping (Result) -> Void, handle: String - ) throws -> URL { - try workerQueue.sync { // TODO: async - switch result { - case let .success(info): + ) { + workerQueue.async(group: dispatchGroup) { + let encrypted = result.flatMap { info in do { // 1. Compress the backup let compressed = try compress(backup: info) @@ -89,12 +93,14 @@ extension SessionManager { // 2. Encrypt the backup let url = targetBackupURL(for: info, handle: handle) try encrypt(from: compressed, to: url, password: password, accountId: accountId) - return url + return .success(url) } catch { - throw error + return .failure(error) } - case let .failure(error): - throw error + } + + DispatchQueue.main.async(group: dispatchGroup) { + completion(encrypted) } } } diff --git a/wire-ios-sync-engine/Source/SessionManager/SessionManager.swift b/wire-ios-sync-engine/Source/SessionManager/SessionManager.swift index 3fac1cf4112..a3ddb093bba 100644 --- a/wire-ios-sync-engine/Source/SessionManager/SessionManager.swift +++ b/wire-ios-sync-engine/Source/SessionManager/SessionManager.swift @@ -929,7 +929,7 @@ public final class SessionManager: NSObject, SessionManagerType { activateSession(for: account, completion: completion) } - func activateSession(for account: Account, completion: @escaping (ZMUserSession) -> Void) { + fileprivate func activateSession(for account: Account, completion: @escaping (ZMUserSession) -> Void) { withSession(for: account, notifyAboutMigration: true) { session in self.activeUserSession = session WireLogger.sessionManager diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupSource.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupSource.swift index 27cdabe8caf..5e69f2deefe 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupSource.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupSource.swift @@ -32,7 +32,8 @@ struct BackupSource: BackupSourceProtocol, ExportBackupUseCaseProtocol { guard let sessionManager = SessionManager.shared else { throw BackupSourceError.missingSessionManager } - return try sessionManager.backupActiveAccount(password: password) +// return try sessionManager.backupActiveAccount(password: password) + fatalError() } // TODO: remove @@ -42,7 +43,9 @@ struct BackupSource: BackupSourceProtocol, ExportBackupUseCaseProtocol { @MainActor func invoke(url: URL, password: String) async throws { - let url = try SessionManager.shared?.backupActiveAccount(password: password) + SessionManager.shared?.backupActiveAccount(password: password) { result in + print(result) // TODO: result contains a temporary url, use the provided url argument to move the file + } } } From 8b781514fe4e59203d234d62e1604d5436cb83fc Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Wed, 15 Jan 2025 14:43:25 +0100 Subject: [PATCH 100/107] present activity view controller on iPhone --- ...xportBackupActivityPresenterProtocol.swift | 25 ++++++++++++ .../ExportBackup/ExportBackupPreview.swift | 7 +++- .../ExportBackup/ExportBackupViewModel.swift | 8 +++- .../ExportBackupUseCaseProtocol.swift | 10 +++-- .../BackupRestoreViewController.swift | 40 +++++++++++++++---- .../Settings/Backup/BackupSource.swift | 23 +++++++++-- ...ettingsCellDescriptorFactory+Account.swift | 2 +- 7 files changed, 95 insertions(+), 20 deletions(-) create mode 100644 WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupActivityPresenterProtocol.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupActivityPresenterProtocol.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupActivityPresenterProtocol.swift new file mode 100644 index 00000000000..496d49b296f --- /dev/null +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupActivityPresenterProtocol.swift @@ -0,0 +1,25 @@ +// +// Wire +// Copyright (C) 2025 Wire Swiss GmbH +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see http://www.gnu.org/licenses/. +// + +import Foundation + +/// A conforming type must allow the user to export/share the file at the provided `backup` URL. +/// The async method must only complete when the backup file can safely be deleted. +public protocol ExportBackupActivityPresenterProtocol { + func present(backup: URL) async +} diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupPreview.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupPreview.swift index 68607a33ca2..7db1ae12592 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupPreview.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupPreview.swift @@ -40,6 +40,7 @@ private struct ExportBackup_Preview: View { ExportBackupView( viewModel: .init( passwordValidator: MockBackupPasswordValidator(), + activityPresenter: PreviewActivityPresenter(), alertPresenter: PreviewAlertPresenter(), exportBackupUseCase: PreviewUseCase() ) @@ -58,7 +59,11 @@ private struct PreviewAlertPresenter: BackupRestoreAlertPresenterProtocol { } private struct PreviewUseCase: ExportBackupUseCaseProtocol { - func invoke(url: URL, password: String) async { + func invoke(password: String, activityPresenter: some ExportBackupActivityPresenterProtocol) async throws { fatalError("not implemented") } } + +private struct PreviewActivityPresenter: ExportBackupActivityPresenterProtocol { + func present(backup: URL) async {} +} diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupViewModel.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupViewModel.swift index 53c8a095232..8046ded0f6a 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupViewModel.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupViewModel.swift @@ -18,6 +18,7 @@ import Foundation +@MainActor final class ExportBackupViewModel: ObservableObject { @Published var password = "" { @@ -30,15 +31,18 @@ final class ExportBackupViewModel: ObservableObject { var localizedPasswordRules: String { passwordValidator.localizedRulesDescription } private let passwordValidator: any BackupPasswordValidatorProtocol + private let activityPresenter: any ExportBackupActivityPresenterProtocol private let alertPresenter: any BackupRestoreAlertPresenterProtocol private let exportBackupUseCase: any ExportBackupUseCaseProtocol init( passwordValidator: any BackupPasswordValidatorProtocol, + activityPresenter: any ExportBackupActivityPresenterProtocol, alertPresenter: any BackupRestoreAlertPresenterProtocol, exportBackupUseCase: any ExportBackupUseCaseProtocol ) { self.passwordValidator = passwordValidator + self.activityPresenter = activityPresenter self.alertPresenter = alertPresenter self.exportBackupUseCase = exportBackupUseCase } @@ -50,10 +54,10 @@ final class ExportBackupViewModel: ObservableObject { func triggerExport() { let password = password let exportBackupUseCase = exportBackupUseCase + let activityPresenter = activityPresenter Task { do { - let url: URL! = .init(string: "https://example.org") - try await exportBackupUseCase.invoke(url: url, password: password) + try await exportBackupUseCase.invoke(password: password, activityPresenter: activityPresenter) } catch { fatalError("TODO: use alertPresenter") } diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/ExportBackupUseCaseProtocol.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/ExportBackupUseCaseProtocol.swift index 2fa7cfd7c83..53d95bbccdc 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/ExportBackupUseCaseProtocol.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/ExportBackupUseCaseProtocol.swift @@ -16,8 +16,10 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // -import Foundation - -public protocol ExportBackupUseCaseProtocol: Sendable { - func invoke(url: URL, password: String) async throws +/// A use case to export the current app state using a provided `password`. +public protocol ExportBackupUseCaseProtocol { + func invoke( + password: String, + activityPresenter: some ExportBackupActivityPresenterProtocol + ) async throws } diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift index 73e7eb59f3b..197222da366 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift @@ -54,6 +54,7 @@ public final class BackupRestoreViewController: UIViewController { ExportBackupView( viewModel: .init( passwordValidator: backupPasswordValidator, + activityPresenter: self, alertPresenter: DummyAlertPresenter(), // TODO: fix exportBackupUseCase: exportBackupUseCase // DummyUseCase { password in // TODO: fix @@ -77,20 +78,43 @@ public final class BackupRestoreViewController: UIViewController { .presentationDetents([.medium]) } ) + let hostingController = UIHostingController(rootView: backupRestoreView) addChild(hostingController) + hostingController.view.translatesAutoresizingMaskIntoConstraints = false view.addSubview(hostingController.view) + NSLayoutConstraint.activate([ hostingController.view.leadingAnchor.constraint(equalTo: view.leadingAnchor), hostingController.view.topAnchor.constraint(equalTo: view.topAnchor), view.trailingAnchor.constraint(equalTo: hostingController.view.trailingAnchor), view.bottomAnchor.constraint(equalTo: hostingController.view.bottomAnchor) ]) + hostingController.didMove(toParent: self) } } +// MARK: - BackupRestoreViewController + ExportBackupActivityPresenterProtocol + +extension BackupRestoreViewController: ExportBackupActivityPresenterProtocol { + + public func present(backup: URL) async { + await withCheckedContinuation { continuation in + let activityViewController = UIActivityViewController(activityItems: [backup], applicationActivities: .none) + activityViewController.completionWithItemsHandler = { _, _, _, _ in + // + continuation.resume() + } + if let popoverPresentationController = activityViewController.popoverPresentationController { + fatalError("TODO") + } + present(activityViewController, animated: true) + } + } +} + // TODO: remove private struct DummyAlertPresenter: BackupRestoreAlertPresenterProtocol { @@ -99,11 +123,11 @@ private struct DummyAlertPresenter: BackupRestoreAlertPresenterProtocol { } } -private struct DummyUseCase: ExportBackupUseCaseProtocol { - - let action: (String) -> Void - - func invoke(url: URL, password: String) async { - action(password) - } -} +//private struct DummyUseCase: ExportBackupUseCaseProtocol { +// +// let action: (String) -> Void +// +// func invoke(password: String) async { +// action(password) +// } +//} diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupSource.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupSource.swift index 5e69f2deefe..0e2f0effb22 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupSource.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupSource.swift @@ -18,7 +18,7 @@ import Foundation import WireSettingsUI -import class WireSyncEngine.SessionManager +import WireSyncEngine struct BackupSource: BackupSourceProtocol, ExportBackupUseCaseProtocol { @@ -41,11 +41,26 @@ struct BackupSource: BackupSourceProtocol, ExportBackupUseCaseProtocol { SessionManager.shared?.clearPreviousBackups() } + var sessionManager: () -> SessionManager + + init(sessionManager: @autoclosure @escaping () -> SessionManager) { + self.sessionManager = sessionManager + } + @MainActor - func invoke(url: URL, password: String) async throws { - SessionManager.shared?.backupActiveAccount(password: password) { result in - print(result) // TODO: result contains a temporary url, use the provided url argument to move the file + func invoke( + password: String, + activityPresenter: some ExportBackupActivityPresenterProtocol + ) async throws { + let sessionManager = sessionManager() + + let url = try await withCheckedThrowingContinuation { continuation in + sessionManager.backupActiveAccount(password: password) { result in + continuation.resume(with: result) + } } + await activityPresenter.present(backup: url) + sessionManager.clearPreviousBackups() } } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift index 9da973984a8..b93fb46bae7 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/CellDescriptors/SettingsCellDescriptorFactory+Account.swift @@ -372,7 +372,7 @@ extension SettingsCellDescriptorFactory { private var backupRestoreBuilder: BackupRestoreBuilder { .init( backupPasswordValidator: BackupPasswordValidator(), - exportBackupUseCase: BackupSource() + exportBackupUseCase: BackupSource(sessionManager: .shared!) ) } From fc33103a9cb60cfe367818657a8876a9429735e3 Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Wed, 15 Jan 2025 16:03:19 +0100 Subject: [PATCH 101/107] more refactoring --- ...xportBackupActivityPresenterProtocol.swift | 2 +- .../ExportBackup/ExportBackupPreview.swift | 32 +++++++++---------- .../ExportBackup/ExportBackupView.swift | 6 +--- .../ExportBackup/ExportBackupViewModel.swift | 23 +++---------- .../ExportBackupUseCaseProtocol.swift | 5 +-- .../BackupRestoreViewController.swift | 22 +++++-------- .../Views/BackupRestoreView.swift | 26 ++++++++++++--- .../Preview/BackupRestoreViewPreview.swift | 4 ++- 8 files changed, 55 insertions(+), 65 deletions(-) diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupActivityPresenterProtocol.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupActivityPresenterProtocol.swift index 496d49b296f..2619d5708db 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupActivityPresenterProtocol.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupActivityPresenterProtocol.swift @@ -20,6 +20,6 @@ import Foundation /// A conforming type must allow the user to export/share the file at the provided `backup` URL. /// The async method must only complete when the backup file can safely be deleted. -public protocol ExportBackupActivityPresenterProtocol { +protocol ExportBackupActivityPresenterProtocol { func present(backup: URL) async } diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupPreview.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupPreview.swift index 7db1ae12592..35f1aa6850e 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupPreview.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupPreview.swift @@ -40,9 +40,7 @@ private struct ExportBackup_Preview: View { ExportBackupView( viewModel: .init( passwordValidator: MockBackupPasswordValidator(), - activityPresenter: PreviewActivityPresenter(), - alertPresenter: PreviewAlertPresenter(), - exportBackupUseCase: PreviewUseCase() + exportBackupAction: { _ in } ) ) } @@ -52,18 +50,18 @@ private struct ExportBackup_Preview: View { } } -private struct PreviewAlertPresenter: BackupRestoreAlertPresenterProtocol { - func todo() async -> Bool { - fatalError("not implemented") - } -} - -private struct PreviewUseCase: ExportBackupUseCaseProtocol { - func invoke(password: String, activityPresenter: some ExportBackupActivityPresenterProtocol) async throws { - fatalError("not implemented") - } -} +//private struct PreviewAlertPresenter: BackupRestoreAlertPresenterProtocol { +// func todo() async -> Bool { +// fatalError("not implemented") +// } +//} +// +//private struct PreviewUseCase: ExportBackupUseCaseProtocol { +// func invoke(password: String) async throws { +// fatalError("not implemented") +// } +//} -private struct PreviewActivityPresenter: ExportBackupActivityPresenterProtocol { - func present(backup: URL) async {} -} +//private struct PreviewActivityPresenter: ExportBackupActivityPresenterProtocol { +// func present(backup: URL) async {} +//} diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupView.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupView.swift index 4ce9e5b583b..7cf25a2cba7 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupView.swift @@ -39,7 +39,7 @@ struct ExportBackupView: View { .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .topBarTrailing) { - CloseButton(action: didTapClose) + CloseButton { dismiss() } .accessibilityLabel(Text(L10n.Accessibility.SetBackupPassword.Close.label)) } } @@ -92,10 +92,6 @@ struct ExportBackupView: View { } } - private func didTapClose() { - dismiss() - } - } // MARK: - Previews diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupViewModel.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupViewModel.swift index 8046ded0f6a..98411fc2be8 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupViewModel.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupViewModel.swift @@ -31,20 +31,14 @@ final class ExportBackupViewModel: ObservableObject { var localizedPasswordRules: String { passwordValidator.localizedRulesDescription } private let passwordValidator: any BackupPasswordValidatorProtocol - private let activityPresenter: any ExportBackupActivityPresenterProtocol - private let alertPresenter: any BackupRestoreAlertPresenterProtocol - private let exportBackupUseCase: any ExportBackupUseCaseProtocol + private let exportBackupAction: (_ password: String) -> Void init( passwordValidator: any BackupPasswordValidatorProtocol, - activityPresenter: any ExportBackupActivityPresenterProtocol, - alertPresenter: any BackupRestoreAlertPresenterProtocol, - exportBackupUseCase: any ExportBackupUseCaseProtocol + exportBackupAction: @escaping (_ password: String) -> Void ) { self.passwordValidator = passwordValidator - self.activityPresenter = activityPresenter - self.alertPresenter = alertPresenter - self.exportBackupUseCase = exportBackupUseCase + self.exportBackupAction = exportBackupAction } private func validatePassword() { @@ -52,15 +46,8 @@ final class ExportBackupViewModel: ObservableObject { } func triggerExport() { - let password = password - let exportBackupUseCase = exportBackupUseCase - let activityPresenter = activityPresenter - Task { - do { - try await exportBackupUseCase.invoke(password: password, activityPresenter: activityPresenter) - } catch { - fatalError("TODO: use alertPresenter") - } + if isPasswordValid { + exportBackupAction(password) } } } diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/ExportBackupUseCaseProtocol.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/ExportBackupUseCaseProtocol.swift index 53d95bbccdc..50ce05678c9 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/ExportBackupUseCaseProtocol.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/ExportBackupUseCaseProtocol.swift @@ -18,8 +18,5 @@ /// A use case to export the current app state using a provided `password`. public protocol ExportBackupUseCaseProtocol { - func invoke( - password: String, - activityPresenter: some ExportBackupActivityPresenterProtocol - ) async throws + func invoke(password: String) async throws } diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift index 197222da366..c091bed1e02 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift @@ -21,11 +21,13 @@ import SwiftUI public final class BackupRestoreViewController: UIViewController { private let viewModel: BackupRestoreViewModel + // TODO: move into ViewModel private let backupPasswordValidator: any BackupPasswordValidatorProtocol private let exportBackupUseCase: any ExportBackupUseCaseProtocol public init( viewModel: BackupRestoreViewModel, + // TODO: move into ViewModel backupPasswordValidator: any BackupPasswordValidatorProtocol, exportBackupUseCase: any ExportBackupUseCaseProtocol ) { @@ -46,22 +48,14 @@ public final class BackupRestoreViewController: UIViewController { } private func setupView() { - let backupPasswordValidator = backupPasswordValidator - let exportBackupUseCase = exportBackupUseCase + let backupRestoreView = BackupRestoreView( viewModel: viewModel, - exportBackupSheetContent: { - ExportBackupView( - viewModel: .init( - passwordValidator: backupPasswordValidator, - activityPresenter: self, - alertPresenter: DummyAlertPresenter(), // TODO: fix - exportBackupUseCase: exportBackupUseCase -// DummyUseCase { password in // TODO: fix -// self.viewModel.backupActiveAccount(password: password) -// } - ) - ) + exportBackupSheetContent: { [backupPasswordValidator] exportBackupAction in + ExportBackupView(viewModel: ExportBackupViewModel( + passwordValidator: /*viewModel.*/ backupPasswordValidator, + exportBackupAction: /*viewModel.*/ exportBackupAction + )) }, importBackupSheetContent: { NavigationStack { diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift index e117cb17c73..94dbf827f06 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift @@ -23,9 +23,11 @@ import WireReusableUIComponents struct BackupRestoreView: View { + typealias ExportBackupAction = (_ password: String) -> Void + @ObservedObject private(set) var viewModel: BackupRestoreViewModel - @ViewBuilder private(set) var exportBackupSheetContent: () -> ExportBackupSheet + @ViewBuilder private(set) var exportBackupSheetContent: (@escaping ExportBackupAction) -> ExportBackupSheet @ViewBuilder private(set) var importBackupSheetContent: () -> ImportBackupSheet @State private var isExportBackupSheetPresented: Bool = false @@ -33,19 +35,33 @@ struct BackupRestoreView: View @State private var isBackupPickerPresented: Bool = false @State private var selectedFileURL: URL? + /// `nil` means the ExportBackupSheet has not been opened or has been dismissed without the texport action. + @State private var exportBackupPassword: String? + var body: some View { List { + // backup Section(footer: Text(L10n.Localizable.Settings.ExportBackup.description)) { - Button { - isExportBackupSheetPresented.toggle() - } label: { + Button(action: { isExportBackupSheetPresented.toggle() }) { Text(L10n.Localizable.Settings.ExportBackup.action) .wireTextStyle(.body2) .foregroundStyle(Color.primaryText) } - .sheet(isPresented: $isExportBackupSheetPresented, content: exportBackupSheetContent) + .sheet( + isPresented: $isExportBackupSheetPresented, + onDismiss: { + // if the ExportBackupSheet left a password after dismiss, trigger the action + exportBackupPassword.map { viewModel.backupActiveAccount(password: $0) } + exportBackupPassword = nil + }) { + // get the password from the ExportBackupSheet + exportBackupSheetContent({ password in + exportBackupPassword = password + }) + } } + // restore Section(footer: Text(L10n.Localizable.Settings.RestoreFromBackup.description)) { Button { viewModel.confirmBackupRestore { diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/BackupRestoreViewPreview.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/BackupRestoreViewPreview.swift index 4fe3a306853..297d8a22c10 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/BackupRestoreViewPreview.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/BackupRestoreViewPreview.swift @@ -36,7 +36,9 @@ func BackupRestoreViewPreview() -> some View { ) // passwordValidator: MockBackupPasswordValidator() ), - exportBackupSheetContent: { EmptyView() }, + exportBackupSheetContent: { exportBackupAction in + EmptyView() + }, importBackupSheetContent: { EmptyView() } ) } From c002585a8a197f679e3f3ffa5ca7acbc2be9784a Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Thu, 16 Jan 2025 10:41:03 +0100 Subject: [PATCH 102/107] trial&error --- .../BackupRestore/BackupRestoreBuilder.swift | 5 ++- ...ift => ExportBackupActivityProtocol.swift} | 6 ++-- .../ExportBackupUseCaseProtocol.swift | 11 +++++- .../BackupRestoreViewController.swift | 11 +++--- .../ViewModels/BackupRestoreViewModel.swift | 36 ++++++++++++++----- .../Settings/Backup/BackupSource.swift | 4 +-- 6 files changed, 49 insertions(+), 24 deletions(-) rename WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/{ExportBackupActivityPresenterProtocol.swift => ExportBackupActivityProtocol.swift} (87%) diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/BackupRestoreBuilder.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/BackupRestoreBuilder.swift index c4ba098bff3..8c00764c6b8 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/BackupRestoreBuilder.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/BackupRestoreBuilder.swift @@ -33,11 +33,10 @@ public struct BackupRestoreBuilder { @MainActor public func build() -> UIViewController { - let viewModel = BackupRestoreViewModel() + let viewModel = BackupRestoreViewModel(exportBackupUseCase: exportBackupUseCase) return BackupRestoreViewController( viewModel: viewModel, - backupPasswordValidator: backupPasswordValidator, - exportBackupUseCase: exportBackupUseCase + backupPasswordValidator: backupPasswordValidator ) } } diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupActivityPresenterProtocol.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupActivityProtocol.swift similarity index 87% rename from WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupActivityPresenterProtocol.swift rename to WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupActivityProtocol.swift index 2619d5708db..b49da17d956 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupActivityPresenterProtocol.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupActivityProtocol.swift @@ -18,8 +18,8 @@ import Foundation -/// A conforming type must allow the user to export/share the file at the provided `backup` URL. +/// A conforming type must allow the user to export/share the file at the provided `url`. /// The async method must only complete when the backup file can safely be deleted. -protocol ExportBackupActivityPresenterProtocol { - func present(backup: URL) async +public protocol ExportBackupActivityProtocol { + func presentExportActivity(url: URL) async } diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/ExportBackupUseCaseProtocol.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/ExportBackupUseCaseProtocol.swift index 50ce05678c9..68a69cda24c 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/ExportBackupUseCaseProtocol.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Protocols/ExportBackupUseCaseProtocol.swift @@ -16,7 +16,16 @@ // along with this program. If not, see http://www.gnu.org/licenses/. // +import Foundation + /// A use case to export the current app state using a provided `password`. public protocol ExportBackupUseCaseProtocol { - func invoke(password: String) async throws + + /// A backup file will be created and saved to a temporary location and the `export` closure is called. + /// After the closure returns the backup file is cleaned up. + func invoke( + password: String, + export: @escaping (_ url: URL) async -> Void +// exportBackupActivityPresenter: some ExportBackupActivityProtocol + ) async throws } diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift index c091bed1e02..ff6071d8410 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift @@ -23,17 +23,14 @@ public final class BackupRestoreViewController: UIViewController { private let viewModel: BackupRestoreViewModel // TODO: move into ViewModel private let backupPasswordValidator: any BackupPasswordValidatorProtocol - private let exportBackupUseCase: any ExportBackupUseCaseProtocol public init( viewModel: BackupRestoreViewModel, // TODO: move into ViewModel - backupPasswordValidator: any BackupPasswordValidatorProtocol, - exportBackupUseCase: any ExportBackupUseCaseProtocol + backupPasswordValidator: any BackupPasswordValidatorProtocol ) { self.viewModel = viewModel self.backupPasswordValidator = backupPasswordValidator - self.exportBackupUseCase = exportBackupUseCase super.init(nibName: nil, bundle: nil) } @@ -92,11 +89,11 @@ public final class BackupRestoreViewController: UIViewController { // MARK: - BackupRestoreViewController + ExportBackupActivityPresenterProtocol -extension BackupRestoreViewController: ExportBackupActivityPresenterProtocol { +extension BackupRestoreViewController: ExportBackupActivityProtocol { - public func present(backup: URL) async { + public func presentExportActivity(url: URL) async { await withCheckedContinuation { continuation in - let activityViewController = UIActivityViewController(activityItems: [backup], applicationActivities: .none) + let activityViewController = UIActivityViewController(activityItems: [url], applicationActivities: .none) activityViewController.completionWithItemsHandler = { _, _, _, _ in // continuation.resume() diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewModels/BackupRestoreViewModel.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewModels/BackupRestoreViewModel.swift index b3b2a3abce1..5a414a1f2bc 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewModels/BackupRestoreViewModel.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewModels/BackupRestoreViewModel.swift @@ -18,16 +18,24 @@ import SwiftUI +@MainActor public final class BackupRestoreViewModel: ObservableObject { + + /// `nil` means no backup is in progress. + @Published private(set) var backupProgress: Float? private let backupSource: any BackupSourceProtocol private let restoreSource: any RestoreSourceProtocol private let backupResultHandler: BackupResultHandler private let restoreBackupResultHandler: RestoreBackupResultHandler + private let exportBackupUseCase: any ExportBackupUseCaseProtocol + public init( - // + exportBackupUseCase: any ExportBackupUseCaseProtocol ) { + self.exportBackupUseCase = exportBackupUseCase + // TODO: fix backupSource = DummyBackupSource() restoreSource = DummyRestoreSource() @@ -49,20 +57,32 @@ public final class BackupRestoreViewModel: ObservableObject { backupResultHandler: BackupResultHandler, restoreBackupResultHandler: RestoreBackupResultHandler ) { + exportBackupUseCase = DummyExportBackupUseCase() + self.backupSource = backupSource self.restoreSource = restoreSource self.backupResultHandler = backupResultHandler self.restoreBackupResultHandler = restoreBackupResultHandler + + struct DummyExportBackupUseCase: ExportBackupUseCaseProtocol { + func invoke(password: String, export: @escaping (URL) async -> Void) async throws { + fatalError() + } + } } - func backupActiveAccount(password: String) { - do { - let backupPath = try backupSource.backupActiveAccount(password: password) - backupResultHandler.onSuccess(backupPath) { [weak self] in - self?.backupSource.clearPreviousBackups() + func backupActiveAccount( + password: String, + export: @MainActor @escaping (_ url: URL) async -> Void + ) { + // TODO: activity indicator with progress + Task { + do { + try await exportBackupUseCase.invoke(password: password, export: export) + } catch { + fatalError("TODO") + /*backupResultHandler.onFailure()*/ } - } catch { - backupResultHandler.onFailure() } } diff --git a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupSource.swift b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupSource.swift index 0e2f0effb22..d8340b2f9b9 100644 --- a/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupSource.swift +++ b/wire-ios/Wire-iOS/Sources/UserInterface/Settings/Backup/BackupSource.swift @@ -50,7 +50,7 @@ struct BackupSource: BackupSourceProtocol, ExportBackupUseCaseProtocol { @MainActor func invoke( password: String, - activityPresenter: some ExportBackupActivityPresenterProtocol + export: @escaping (_ url: URL) async -> Void ) async throws { let sessionManager = sessionManager() @@ -59,7 +59,7 @@ struct BackupSource: BackupSourceProtocol, ExportBackupUseCaseProtocol { continuation.resume(with: result) } } - await activityPresenter.present(backup: url) + await export(url) sessionManager.clearPreviousBackups() } From d1f3c2c37f07e7e46e5f955ef7f5a4cd28a68060 Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Thu, 16 Jan 2025 11:35:38 +0100 Subject: [PATCH 103/107] finish backup for iPhone --- .../ViewControllers/BackupRestoreViewController.swift | 5 ++++- .../Account/BackupRestore/Views/BackupRestoreView.swift | 5 ++++- .../Views/Preview/BackupRestoreViewPreview.swift | 4 ++-- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift index ff6071d8410..3aa21ef1183 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift @@ -67,6 +67,9 @@ public final class BackupRestoreViewController: UIViewController { } } .presentationDetents([.medium]) + }, + presentActivityViewController: { [weak self] backupURL in + await self?.presentExportActivity(url: backupURL) } ) @@ -91,7 +94,7 @@ public final class BackupRestoreViewController: UIViewController { extension BackupRestoreViewController: ExportBackupActivityProtocol { - public func presentExportActivity(url: URL) async { + /*private*/ public func presentExportActivity(url: URL) async { await withCheckedContinuation { continuation in let activityViewController = UIActivityViewController(activityItems: [url], applicationActivities: .none) activityViewController.completionWithItemsHandler = { _, _, _, _ in diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift index 94dbf827f06..2c7f780ac39 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift @@ -29,6 +29,7 @@ struct BackupRestoreView: View @ViewBuilder private(set) var exportBackupSheetContent: (@escaping ExportBackupAction) -> ExportBackupSheet @ViewBuilder private(set) var importBackupSheetContent: () -> ImportBackupSheet + private(set) var presentActivityViewController: (_ url: URL) async -> Void @State private var isExportBackupSheetPresented: Bool = false @State private var isImportBackupSheetPresented: Bool = false @@ -51,7 +52,9 @@ struct BackupRestoreView: View isPresented: $isExportBackupSheetPresented, onDismiss: { // if the ExportBackupSheet left a password after dismiss, trigger the action - exportBackupPassword.map { viewModel.backupActiveAccount(password: $0) } + exportBackupPassword.map { password in + viewModel.backupActiveAccount(password: password, export: presentActivityViewController) + } exportBackupPassword = nil }) { // get the password from the ExportBackupSheet diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/BackupRestoreViewPreview.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/BackupRestoreViewPreview.swift index 297d8a22c10..2ecadfe7c82 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/BackupRestoreViewPreview.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/BackupRestoreViewPreview.swift @@ -34,12 +34,12 @@ func BackupRestoreViewPreview() -> some View { onConfirmation: { _ in }, onFailure: {} ) -// passwordValidator: MockBackupPasswordValidator() ), exportBackupSheetContent: { exportBackupAction in EmptyView() }, - importBackupSheetContent: { EmptyView() } + importBackupSheetContent: { EmptyView() }, + presentActivityViewController: { _ in } ) } From 02d701029f55f64314037ea9ea71b30352c3f14d Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Thu, 16 Jan 2025 11:57:07 +0100 Subject: [PATCH 104/107] attempt to present the popover via UIViewRepresentable --- .../ExportBackupActivityProtocol.swift | 25 ------------- .../BackupRestoreViewController.swift | 36 ++++--------------- .../Views/BackupRestoreView.swift | 28 +++++++++++++-- .../Preview/BackupRestoreViewPreview.swift | 2 +- 4 files changed, 33 insertions(+), 58 deletions(-) delete mode 100644 WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupActivityProtocol.swift diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupActivityProtocol.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupActivityProtocol.swift deleted file mode 100644 index b49da17d956..00000000000 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ExportBackup/ExportBackupActivityProtocol.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// Wire -// Copyright (C) 2025 Wire Swiss GmbH -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU General Public License for more details. -// -// You should have received a copy of the GNU General Public License -// along with this program. If not, see http://www.gnu.org/licenses/. -// - -import Foundation - -/// A conforming type must allow the user to export/share the file at the provided `url`. -/// The async method must only complete when the backup file can safely be deleted. -public protocol ExportBackupActivityProtocol { - func presentExportActivity(url: URL) async -} diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift index 3aa21ef1183..76398d0b301 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift @@ -68,8 +68,8 @@ public final class BackupRestoreViewController: UIViewController { } .presentationDetents([.medium]) }, - presentActivityViewController: { [weak self] backupURL in - await self?.presentExportActivity(url: backupURL) + presentActivityViewController: { [weak self] backupURL, anchorView in + await self?.presentExportActivity(url: backupURL, anchorView: anchorView) } ) @@ -88,40 +88,16 @@ public final class BackupRestoreViewController: UIViewController { hostingController.didMove(toParent: self) } -} - -// MARK: - BackupRestoreViewController + ExportBackupActivityPresenterProtocol - -extension BackupRestoreViewController: ExportBackupActivityProtocol { - /*private*/ public func presentExportActivity(url: URL) async { + private func presentExportActivity(url: URL, anchorView: UIView) async { await withCheckedContinuation { continuation in let activityViewController = UIActivityViewController(activityItems: [url], applicationActivities: .none) - activityViewController.completionWithItemsHandler = { _, _, _, _ in - // - continuation.resume() - } + activityViewController.completionWithItemsHandler = { _, _, _, _ in continuation.resume() } if let popoverPresentationController = activityViewController.popoverPresentationController { - fatalError("TODO") + popoverPresentationController.sourceView = anchorView.superview + popoverPresentationController.sourceRect = anchorView.frame } present(activityViewController, animated: true) } } } - -// TODO: remove - -private struct DummyAlertPresenter: BackupRestoreAlertPresenterProtocol { - func todo() async -> Bool { - fatalError("not implemented") - } -} - -//private struct DummyUseCase: ExportBackupUseCaseProtocol { -// -// let action: (String) -> Void -// -// func invoke(password: String) async { -// action(password) -// } -//} diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift index 2c7f780ac39..8909fcfdfeb 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift @@ -29,12 +29,13 @@ struct BackupRestoreView: View @ViewBuilder private(set) var exportBackupSheetContent: (@escaping ExportBackupAction) -> ExportBackupSheet @ViewBuilder private(set) var importBackupSheetContent: () -> ImportBackupSheet - private(set) var presentActivityViewController: (_ url: URL) async -> Void + private(set) var presentActivityViewController: (_ url: URL, _ anchor: UIView) async -> Void @State private var isExportBackupSheetPresented: Bool = false @State private var isImportBackupSheetPresented: Bool = false @State private var isBackupPickerPresented: Bool = false @State private var selectedFileURL: URL? + @State private var popoverAnchorView = UIView() /// `nil` means the ExportBackupSheet has not been opened or has been dismissed without the texport action. @State private var exportBackupPassword: String? @@ -48,12 +49,22 @@ struct BackupRestoreView: View .wireTextStyle(.body2) .foregroundStyle(Color.primaryText) } + .clipShape(Rectangle()) + .background { + // a dummy view just for providing `sourceView` and `sourceRect` for popover presentation via UIKit + PopoverAnchorView { popoverAnchorView = $0 } + } .sheet( isPresented: $isExportBackupSheetPresented, onDismiss: { // if the ExportBackupSheet left a password after dismiss, trigger the action exportBackupPassword.map { password in - viewModel.backupActiveAccount(password: password, export: presentActivityViewController) + viewModel.backupActiveAccount( + password: password, + export: { url in + await presentActivityViewController(url, .init()) + } + ) } exportBackupPassword = nil }) { @@ -93,6 +104,19 @@ struct BackupRestoreView: View } } +private struct PopoverAnchorView: UIViewRepresentable { + + let viewCreated: (UIView) -> Void + + func makeUIView(context: Context) -> UIView { + let view = UIView() + viewCreated(view) + return view + } + + func updateUIView(_ uiView: UIView, context: Context) {} +} + #Preview { BackupRestoreViewPreview() } diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/BackupRestoreViewPreview.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/BackupRestoreViewPreview.swift index 2ecadfe7c82..1a220e3227f 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/BackupRestoreViewPreview.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/Preview/BackupRestoreViewPreview.swift @@ -39,7 +39,7 @@ func BackupRestoreViewPreview() -> some View { EmptyView() }, importBackupSheetContent: { EmptyView() }, - presentActivityViewController: { _ in } + presentActivityViewController: { _, _ in } ) } From dee78709a789001d58999809d7421829199d1442 Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Thu, 16 Jan 2025 12:09:45 +0100 Subject: [PATCH 105/107] fix popover presentation for iPad --- .../BackupRestoreViewController.swift | 8 ++-- .../Views/BackupRestoreView.swift | 40 ++++++++++++------- 2 files changed, 30 insertions(+), 18 deletions(-) diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift index 76398d0b301..a5590c0477f 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/ViewControllers/BackupRestoreViewController.swift @@ -89,15 +89,15 @@ public final class BackupRestoreViewController: UIViewController { hostingController.didMove(toParent: self) } - private func presentExportActivity(url: URL, anchorView: UIView) async { + private func presentExportActivity(url: URL, anchorView: UIViewController) async { await withCheckedContinuation { continuation in let activityViewController = UIActivityViewController(activityItems: [url], applicationActivities: .none) activityViewController.completionWithItemsHandler = { _, _, _, _ in continuation.resume() } if let popoverPresentationController = activityViewController.popoverPresentationController { - popoverPresentationController.sourceView = anchorView.superview - popoverPresentationController.sourceRect = anchorView.frame + popoverPresentationController.sourceView = anchorView.view + popoverPresentationController.sourceRect = anchorView.view.bounds.insetBy(dx: -10, dy: -10) } - present(activityViewController, animated: true) + anchorView.present(activityViewController, animated: true) } } } diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift index 8909fcfdfeb..6ec21970b4a 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift @@ -29,13 +29,13 @@ struct BackupRestoreView: View @ViewBuilder private(set) var exportBackupSheetContent: (@escaping ExportBackupAction) -> ExportBackupSheet @ViewBuilder private(set) var importBackupSheetContent: () -> ImportBackupSheet - private(set) var presentActivityViewController: (_ url: URL, _ anchor: UIView) async -> Void + private(set) var presentActivityViewController: (_ url: URL, _ anchor: UIViewController) async -> Void @State private var isExportBackupSheetPresented: Bool = false @State private var isImportBackupSheetPresented: Bool = false @State private var isBackupPickerPresented: Bool = false @State private var selectedFileURL: URL? - @State private var popoverAnchorView = UIView() + @State private var popoverPresenter = UIViewController() /// `nil` means the ExportBackupSheet has not been opened or has been dismissed without the texport action. @State private var exportBackupPassword: String? @@ -49,10 +49,9 @@ struct BackupRestoreView: View .wireTextStyle(.body2) .foregroundStyle(Color.primaryText) } - .clipShape(Rectangle()) .background { - // a dummy view just for providing `sourceView` and `sourceRect` for popover presentation via UIKit - PopoverAnchorView { popoverAnchorView = $0 } + // a view controller just for presenting a popover presentation + PopoverPresenter { popoverPresenter = $0 } } .sheet( isPresented: $isExportBackupSheetPresented, @@ -62,7 +61,7 @@ struct BackupRestoreView: View viewModel.backupActiveAccount( password: password, export: { url in - await presentActivityViewController(url, .init()) + await presentActivityViewController(url, popoverPresenter) } ) } @@ -104,19 +103,32 @@ struct BackupRestoreView: View } } -private struct PopoverAnchorView: UIViewRepresentable { +private struct PopoverPresenter: UIViewControllerRepresentable { - let viewCreated: (UIView) -> Void + let viewControllerCreated: (UIViewController) -> Void - func makeUIView(context: Context) -> UIView { - let view = UIView() - viewCreated(view) - return view + func makeUIViewController(context: Context) -> UIViewController { + let viewController = UIViewController() + viewControllerCreated(viewController) + return viewController } - - func updateUIView(_ uiView: UIView, context: Context) {} + + func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} } +//private struct PopoverAnchorView: UIViewRepresentable { +// +// let viewCreated: (UIView) -> Void +// +// func makeUIView(context: Context) -> UIView { +// let view = UIView() +// viewCreated(view) +// return view +// } +// +// func updateUIView(_ uiView: UIView, context: Context) {} +//} + #Preview { BackupRestoreViewPreview() } From d64d8cf49b0de969513eaebb7dc55d86e49f14ec Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Thu, 16 Jan 2025 12:20:46 +0100 Subject: [PATCH 106/107] add comment --- .../BackupRestore/Views/BackupRestoreView.swift | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift index 6ec21970b4a..6dbd9e70f3c 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift @@ -29,6 +29,8 @@ struct BackupRestoreView: View @ViewBuilder private(set) var exportBackupSheetContent: (@escaping ExportBackupAction) -> ExportBackupSheet @ViewBuilder private(set) var importBackupSheetContent: () -> ImportBackupSheet + /// Workaround for not being able to use `ShareLink` because 1. there is no callback which allows us to delete + /// the temporary file and 2. another step would be needed: A view showing the `ShareLink` right after the backup file became ready. private(set) var presentActivityViewController: (_ url: URL, _ anchor: UIViewController) async -> Void @State private var isExportBackupSheetPresented: Bool = false @@ -105,6 +107,7 @@ struct BackupRestoreView: View private struct PopoverPresenter: UIViewControllerRepresentable { + /// Used for extracting the reference to the created view controller. let viewControllerCreated: (UIViewController) -> Void func makeUIViewController(context: Context) -> UIViewController { @@ -116,19 +119,6 @@ private struct PopoverPresenter: UIViewControllerRepresentable { func updateUIViewController(_ uiViewController: UIViewController, context: Context) {} } -//private struct PopoverAnchorView: UIViewRepresentable { -// -// let viewCreated: (UIView) -> Void -// -// func makeUIView(context: Context) -> UIView { -// let view = UIView() -// viewCreated(view) -// return view -// } -// -// func updateUIView(_ uiView: UIView, context: Context) {} -//} - #Preview { BackupRestoreViewPreview() } From 5bfbde0a6c39632cdde09ee49d920d5580ad9d63 Mon Sep 17 00:00:00 2001 From: Christoph Aldrian Date: Thu, 16 Jan 2025 13:31:24 +0100 Subject: [PATCH 107/107] minor change --- .../BackupRestore/Views/BackupRestoreView.swift | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift index 6dbd9e70f3c..84eedd3eb11 100644 --- a/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift +++ b/WireUI/Sources/WireSettingsUI/Account/BackupRestore/Views/BackupRestoreView.swift @@ -29,16 +29,18 @@ struct BackupRestoreView: View @ViewBuilder private(set) var exportBackupSheetContent: (@escaping ExportBackupAction) -> ExportBackupSheet @ViewBuilder private(set) var importBackupSheetContent: () -> ImportBackupSheet + /// Workaround for not being able to use `ShareLink` because 1. there is no callback which allows us to delete /// the temporary file and 2. another step would be needed: A view showing the `ShareLink` right after the backup file became ready. private(set) var presentActivityViewController: (_ url: URL, _ anchor: UIViewController) async -> Void - @State private var isExportBackupSheetPresented: Bool = false - @State private var isImportBackupSheetPresented: Bool = false - @State private var isBackupPickerPresented: Bool = false - @State private var selectedFileURL: URL? + @State private var isExportBackupSheetPresented = false + @State private var isBackupPickerPresented = false @State private var popoverPresenter = UIViewController() + @State private var isImportBackupSheetPresented = false + @State private var selectedFileURL: URL? + /// `nil` means the ExportBackupSheet has not been opened or has been dismissed without the texport action. @State private var exportBackupPassword: String?