changes in UI
This commit is contained in:
@@ -36,6 +36,7 @@ enum DatabaseEncryptionAlert: Identifiable {
|
||||
struct DatabaseEncryptionView: View {
|
||||
@EnvironmentObject private var m: ChatModel
|
||||
@Binding var useKeychain: Bool
|
||||
var migration: Bool
|
||||
@State private var alert: DatabaseEncryptionAlert? = nil
|
||||
@State private var progressIndicator = false
|
||||
@State private var useKeychainToggle = storeDBPassphraseGroupDefault.get()
|
||||
@@ -48,7 +49,13 @@ struct DatabaseEncryptionView: View {
|
||||
|
||||
var body: some View {
|
||||
ZStack {
|
||||
databaseEncryptionView()
|
||||
List {
|
||||
if migration {
|
||||
chatStoppedView()
|
||||
Text("Set database passphrase to migrate it")
|
||||
}
|
||||
databaseEncryptionView()
|
||||
}
|
||||
if progressIndicator {
|
||||
ProgressView().scaleEffect(2)
|
||||
}
|
||||
@@ -56,72 +63,76 @@ struct DatabaseEncryptionView: View {
|
||||
}
|
||||
|
||||
private func databaseEncryptionView() -> some View {
|
||||
List {
|
||||
Section {
|
||||
Section {
|
||||
if !migration {
|
||||
settingsRow(storedKey ? "key.fill" : "key", color: storedKey ? .green : .secondary) {
|
||||
Toggle("Save passphrase in Keychain", isOn: $useKeychainToggle)
|
||||
.onChange(of: useKeychainToggle) { _ in
|
||||
if useKeychainToggle {
|
||||
setUseKeychain(true)
|
||||
} else if storedKey {
|
||||
alert = .keychainRemoveKey
|
||||
} else {
|
||||
setUseKeychain(false)
|
||||
.onChange(of: useKeychainToggle) { _ in
|
||||
if useKeychainToggle {
|
||||
setUseKeychain(true)
|
||||
} else if storedKey {
|
||||
alert = .keychainRemoveKey
|
||||
} else {
|
||||
setUseKeychain(false)
|
||||
}
|
||||
}
|
||||
}
|
||||
.disabled(initialRandomDBPassphrase)
|
||||
.disabled(initialRandomDBPassphrase)
|
||||
}
|
||||
}
|
||||
|
||||
if !initialRandomDBPassphrase && m.chatDbEncrypted == true {
|
||||
PassphraseField(key: $currentKey, placeholder: "Current passphrase…", valid: validKey(currentKey))
|
||||
if !initialRandomDBPassphrase && m.chatDbEncrypted == true {
|
||||
PassphraseField(key: $currentKey, placeholder: "Current passphrase…", valid: validKey(currentKey))
|
||||
}
|
||||
|
||||
PassphraseField(key: $newKey, placeholder: "New passphrase…", valid: validKey(newKey), showStrength: true)
|
||||
PassphraseField(key: $confirmNewKey, placeholder: "Confirm new passphrase…", valid: confirmNewKey == "" || newKey == confirmNewKey)
|
||||
|
||||
settingsRow("lock.rotation") {
|
||||
Button(migration ? "Set passphrase" : "Update database passphrase") {
|
||||
alert = currentKey == ""
|
||||
? (useKeychain ? .encryptDatabaseSaved : .encryptDatabase)
|
||||
: (useKeychain ? .changeDatabaseKeySaved : .changeDatabaseKey)
|
||||
}
|
||||
|
||||
PassphraseField(key: $newKey, placeholder: "New passphrase…", valid: validKey(newKey), showStrength: true)
|
||||
PassphraseField(key: $confirmNewKey, placeholder: "Confirm new passphrase…", valid: confirmNewKey == "" || newKey == confirmNewKey)
|
||||
|
||||
settingsRow("lock.rotation") {
|
||||
Button("Update database passphrase") {
|
||||
alert = currentKey == ""
|
||||
? (useKeychain ? .encryptDatabaseSaved : .encryptDatabase)
|
||||
: (useKeychain ? .changeDatabaseKeySaved : .changeDatabaseKey)
|
||||
}
|
||||
}
|
||||
.disabled(
|
||||
(m.chatDbEncrypted == true && currentKey == "") ||
|
||||
currentKey == newKey ||
|
||||
newKey != confirmNewKey ||
|
||||
newKey == "" ||
|
||||
!validKey(currentKey) ||
|
||||
!validKey(newKey)
|
||||
)
|
||||
} header: {
|
||||
}
|
||||
.disabled(
|
||||
(m.chatDbEncrypted == true && currentKey == "") ||
|
||||
currentKey == newKey ||
|
||||
newKey != confirmNewKey ||
|
||||
newKey == "" ||
|
||||
!validKey(currentKey) ||
|
||||
!validKey(newKey)
|
||||
)
|
||||
} header: {
|
||||
if !migration {
|
||||
Text("")
|
||||
} footer: {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
if m.chatDbEncrypted == false {
|
||||
Text("Your chat database is not encrypted - set passphrase to encrypt it.")
|
||||
} else if useKeychain {
|
||||
if storedKey {
|
||||
Text("iOS Keychain is used to securely store passphrase - it allows receiving push notifications.")
|
||||
}
|
||||
} footer: {
|
||||
VStack(alignment: .leading, spacing: 16) {
|
||||
if m.chatDbEncrypted == false {
|
||||
Text("Your chat database is not encrypted - set passphrase to encrypt it.")
|
||||
} else if useKeychain {
|
||||
if storedKey {
|
||||
Text("iOS Keychain is used to securely store passphrase - it allows receiving push notifications.")
|
||||
if !migration {
|
||||
if initialRandomDBPassphrase {
|
||||
Text("Database is encrypted using a random passphrase, you can change it.")
|
||||
} else {
|
||||
Text("**Please note**: you will NOT be able to recover or change passphrase if you lose it.")
|
||||
}
|
||||
} else {
|
||||
Text("iOS Keychain will be used to securely store passphrase after you restart the app or change passphrase - it will allow receiving push notifications.")
|
||||
}
|
||||
} else {
|
||||
Text("You have to enter passphrase every time the app starts - it is not stored on the device.")
|
||||
Text("**Please note**: you will NOT be able to recover or change passphrase if you lose it.")
|
||||
if m.notificationMode == .instant && m.notificationPreview != .hidden {
|
||||
Text("**Warning**: Instant push notifications require passphrase saved in Keychain.")
|
||||
}
|
||||
Text("iOS Keychain will be used to securely store passphrase after you restart the app or change passphrase - it will allow receiving push notifications.")
|
||||
}
|
||||
} else {
|
||||
Text("You have to enter passphrase every time the app starts - it is not stored on the device.")
|
||||
Text("**Please note**: you will NOT be able to recover or change passphrase if you lose it.")
|
||||
if m.notificationMode == .instant && m.notificationPreview != .hidden {
|
||||
Text("**Warning**: Instant push notifications require passphrase saved in Keychain.")
|
||||
}
|
||||
}
|
||||
.padding(.top, 1)
|
||||
.font(.callout)
|
||||
}
|
||||
.padding(.top, 1)
|
||||
.font(.callout)
|
||||
}
|
||||
.onAppear {
|
||||
if initialRandomDBPassphrase { currentKey = kcDatabasePassword.get() ?? "" }
|
||||
@@ -346,6 +357,6 @@ func validKey(_ s: String) -> Bool {
|
||||
|
||||
struct DatabaseEncryptionView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
DatabaseEncryptionView(useKeychain: Binding.constant(true))
|
||||
DatabaseEncryptionView(useKeychain: Binding.constant(true), migration: false)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -116,7 +116,7 @@ struct DatabaseView: View {
|
||||
let color: Color = unencrypted ? .orange : .secondary
|
||||
settingsRow(unencrypted ? "lock.open" : useKeychain ? "key" : "lock", color: color) {
|
||||
NavigationLink {
|
||||
DatabaseEncryptionView(useKeychain: $useKeychain)
|
||||
DatabaseEncryptionView(useKeychain: $useKeychain, migration: false)
|
||||
.navigationTitle("Database passphrase")
|
||||
} label: {
|
||||
Text("Database passphrase")
|
||||
|
||||
@@ -16,19 +16,23 @@ public enum MigrationState: Equatable {
|
||||
case passphraseNotSet
|
||||
case passphraseConfirmation
|
||||
case uploadConfirmation
|
||||
case uploadProgress(uploadedKb: Int64, totalKb: Int64)
|
||||
case uploadDone(link: String)
|
||||
case archiving
|
||||
case uploadProgress(uploadedBytes: Int64, totalBytes: Int64, archivePath: URL)
|
||||
case linkCreation(totalBytes: Int64, archivePath: URL)
|
||||
case linkShown(link: String, archivePath: URL)
|
||||
}
|
||||
|
||||
struct MigrateToAnotherDevice: View {
|
||||
@EnvironmentObject var m: ChatModel
|
||||
@State private var migrationState: MigrationState = .initial
|
||||
@State private var useKeychain = storeDBPassphraseGroupDefault.get()
|
||||
@State private var initialRandomDBPassphrase = initialRandomDBPassphraseGroupDefault.get()
|
||||
@State private var alert: MigrateToAnotherDeviceViewAlert?
|
||||
@State private var confirmPassphraseAlert: PassphraseConfirmationView.PassphraseConfirmationViewAlert?
|
||||
private let chatWasStoppedInitially: Bool = AppChatState.shared.value == .stopped
|
||||
|
||||
enum MigrateToAnotherDeviceViewAlert: Identifiable {
|
||||
case error(title: LocalizedStringKey, error: LocalizedStringKey = "")
|
||||
case error(title: LocalizedStringKey, error: String = "")
|
||||
|
||||
var id: String {
|
||||
switch self {
|
||||
@@ -39,30 +43,32 @@ struct MigrateToAnotherDevice: View {
|
||||
|
||||
var body: some View {
|
||||
VStack {
|
||||
List {
|
||||
switch migrationState {
|
||||
case .initial: EmptyView()
|
||||
case .chatStopInProgress:
|
||||
progressView("Stopping chat")
|
||||
case let .chatStopFailed(reason):
|
||||
chatStopFailedView(reason)
|
||||
case .passphraseNotSet:
|
||||
passphraseNotSetView()
|
||||
case .passphraseConfirmation:
|
||||
PassphraseConfirmationView(migrationState: $migrationState)
|
||||
case .uploadConfirmation:
|
||||
uploadConfirmationView()
|
||||
case let .uploadProgress(uploaded, total):
|
||||
uploadProgressView(uploaded, totalKb: total)
|
||||
case let .uploadDone(link):
|
||||
uploadDoneView(link)
|
||||
}
|
||||
switch migrationState {
|
||||
case .initial: EmptyView()
|
||||
case .chatStopInProgress:
|
||||
chatStopInProgressView()
|
||||
case let .chatStopFailed(reason):
|
||||
chatStopFailedView(reason)
|
||||
case .passphraseNotSet:
|
||||
passphraseNotSetView()
|
||||
case .passphraseConfirmation:
|
||||
PassphraseConfirmationView(migrationState: $migrationState, alert: $confirmPassphraseAlert)
|
||||
case .uploadConfirmation:
|
||||
uploadConfirmationView()
|
||||
case .archiving:
|
||||
archivingView()
|
||||
case let .uploadProgress(uploaded, total, archivePath):
|
||||
uploadProgressView(uploaded, totalBytes: total, archivePath)
|
||||
case let .linkCreation(totalBytes, archivePath):
|
||||
linkCreationView(totalBytes, archivePath)
|
||||
case let .linkShown(link, archivePath):
|
||||
linkView(link, archivePath)
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
if case .initial = migrationState {
|
||||
if AppChatState.shared.value == .stopped {
|
||||
migrationState = initialRandomDBPassphraseGroupDefault.get() ? .passphraseNotSet : .passphraseConfirmation
|
||||
migrationState = initialRandomDBPassphrase ? .passphraseNotSet : .passphraseConfirmation
|
||||
} else {
|
||||
migrationState = .chatStopInProgress
|
||||
stopChat()
|
||||
@@ -70,7 +76,7 @@ struct MigrateToAnotherDevice: View {
|
||||
}
|
||||
}
|
||||
.onDisappear {
|
||||
if !chatWasStoppedInitially {
|
||||
if case .linkShown = migrationState {} else if !chatWasStoppedInitially {
|
||||
Task {
|
||||
try? startChat(refreshInvitations: true)
|
||||
}
|
||||
@@ -82,6 +88,33 @@ struct MigrateToAnotherDevice: View {
|
||||
return Alert(title: Text(title), message: Text(error))
|
||||
}
|
||||
}
|
||||
.alert(item: $confirmPassphraseAlert) { alert in
|
||||
switch alert {
|
||||
case let .wrongPassphrase(title, message):
|
||||
return Alert(title: Text(title), message: Text(message))
|
||||
case let .invalidConfirmation(title):
|
||||
return Alert(title: Text(title))
|
||||
case let .keychainError(title):
|
||||
return Alert(title: Text(title))
|
||||
case let .databaseError(title, message):
|
||||
return Alert(title: Text(title), message: Text(message))
|
||||
case let .unknownError(title, message):
|
||||
return Alert(title: Text(title), message: Text(message))
|
||||
case let .error(title, error):
|
||||
return Alert(title: Text(title), message: Text(error))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func chatStopInProgressView() -> some View {
|
||||
ZStack {
|
||||
List {
|
||||
Section {} header: {
|
||||
Text("Stopping chat")
|
||||
}
|
||||
}
|
||||
progressView()
|
||||
}
|
||||
}
|
||||
|
||||
private func chatStopFailedView(_ reason: String) -> some View {
|
||||
@@ -100,50 +133,80 @@ struct MigrateToAnotherDevice: View {
|
||||
}
|
||||
|
||||
private func passphraseNotSetView() -> some View {
|
||||
Section {
|
||||
Text("Database is encrypted using a random passphrase. Please set your own password before migrating.")
|
||||
NavigationLink {
|
||||
DatabaseEncryptionView(useKeychain: $useKeychain)
|
||||
.navigationTitle("Database passphrase")
|
||||
} label: {
|
||||
settingsRow("lock.open", color: .secondary) {
|
||||
Text("Set passphrase")
|
||||
DatabaseEncryptionView(useKeychain: $useKeychain, migration: true)
|
||||
.onChange(of: initialRandomDBPassphrase) { initial in
|
||||
if !initial {
|
||||
migrationState = .uploadConfirmation
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
Text("Set passphrase to export")
|
||||
}
|
||||
.onAppear {
|
||||
if !initialRandomDBPassphraseGroupDefault.get() {
|
||||
migrationState = .uploadConfirmation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func uploadConfirmationView() -> some View {
|
||||
Section {
|
||||
Button(action: startUploading) {
|
||||
settingsRow("tray.and.arrow.up", color: .secondary) {
|
||||
Text("Start uploading")
|
||||
List {
|
||||
Section {
|
||||
Text("All your contacts, conversations and files will be archived and uploaded as encrypted file to configured XFTP relays")
|
||||
Button(action: { migrationState = .archiving }) {
|
||||
settingsRow("tray.and.arrow.up", color: .secondary) {
|
||||
Text("Archive and upload")
|
||||
}
|
||||
}
|
||||
} header: {
|
||||
Text("Confirm upload")
|
||||
}
|
||||
} header: {
|
||||
Text("Confirm upload")
|
||||
} footer: {
|
||||
Text("Do you want to start uploading now?")
|
||||
}
|
||||
}
|
||||
|
||||
private func uploadProgressView(_ uploadedKb: Int64, totalKb: Int64) -> some View {
|
||||
progressView("Uploaded \(ByteCountFormatter.string(fromByteCount: uploadedKb, countStyle: .binary)) from \(ByteCountFormatter.string(fromByteCount: totalKb, countStyle: .binary))")
|
||||
private func archivingView() -> some View {
|
||||
ZStack {
|
||||
List {
|
||||
Section {} header: {
|
||||
Text("Archiving database…")
|
||||
}
|
||||
}
|
||||
progressView()
|
||||
}
|
||||
.onAppear {
|
||||
exportArchive()
|
||||
}
|
||||
}
|
||||
|
||||
private func uploadDoneView(_ link: String) -> some View {
|
||||
Section {
|
||||
SimpleXLinkQRCode(uri: link)
|
||||
shareLinkButton(link)
|
||||
} header: {
|
||||
Text("Link to uploaded archive")
|
||||
private func uploadProgressView(_ uploadedBytes: Int64, totalBytes: Int64, _ archivePath: URL) -> some View {
|
||||
ZStack {
|
||||
List {
|
||||
Section {} header: {
|
||||
Text("Uploading archive…")
|
||||
}
|
||||
}
|
||||
let ratio = Float(uploadedBytes) / Float(totalBytes)
|
||||
largeProgressView(ratio, "\(Int(ratio * 100))%\n\(ByteCountFormatter.string(fromByteCount: totalBytes, countStyle: .binary)) uploaded")
|
||||
}
|
||||
.onAppear {
|
||||
startUploading(totalBytes, archivePath)
|
||||
}
|
||||
}
|
||||
|
||||
private func linkCreationView(_ totalBytes: Int64, _ archivePath: URL) -> some View {
|
||||
ZStack {
|
||||
List {
|
||||
Section {} header: {
|
||||
Text("Creating archive link…")
|
||||
}
|
||||
}
|
||||
largeProgressView(1, "\(ByteCountFormatter.string(fromByteCount: totalBytes, countStyle: .binary))")
|
||||
}
|
||||
.onAppear {
|
||||
createLink(archivePath)
|
||||
}
|
||||
}
|
||||
|
||||
private func linkView(_ link: String, _ archivePath: URL) -> some View {
|
||||
List {
|
||||
Section {
|
||||
SimpleXLinkQRCode(uri: link)
|
||||
shareLinkButton(link)
|
||||
} header: {
|
||||
Text("Link to uploaded archive")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -155,6 +218,25 @@ struct MigrateToAnotherDevice: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func largeProgressView(_ value: Float, _ text: LocalizedStringKey) -> some View {
|
||||
ZStack {
|
||||
Text(text)
|
||||
.font(.title3)
|
||||
.multilineTextAlignment(.center)
|
||||
|
||||
Circle()
|
||||
.trim(from: 0, to: CGFloat(value))
|
||||
.stroke(
|
||||
Color.accentColor,
|
||||
style: StrokeStyle(lineWidth: 5)
|
||||
)
|
||||
.rotationEffect(.degrees(-90))
|
||||
.animation(.linear, value: value)
|
||||
.frame(width: 250, height: 250)
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity )
|
||||
}
|
||||
|
||||
private func stopChat() {
|
||||
Task {
|
||||
do {
|
||||
@@ -166,19 +248,49 @@ struct MigrateToAnotherDevice: View {
|
||||
}
|
||||
}
|
||||
|
||||
private func startUploading() {
|
||||
private func exportArchive() {
|
||||
Task {
|
||||
for i in 1...10 {
|
||||
try? await Task.sleep(nanoseconds: 100_000000)
|
||||
do {
|
||||
let archivePath = try await exportChatArchive()
|
||||
if let attrs = try? FileManager.default.attributesOfItem(atPath: archivePath.path),
|
||||
let totalBytes = attrs[.size] as? Int64 {
|
||||
await MainActor.run {
|
||||
migrationState = .uploadProgress(uploadedBytes: 0, totalBytes: totalBytes, archivePath: archivePath)
|
||||
}
|
||||
} else {
|
||||
await MainActor.run {
|
||||
alert = .error(title: "Exported file doesn't exist")
|
||||
migrationState = .uploadConfirmation
|
||||
}
|
||||
}
|
||||
} catch let error {
|
||||
await MainActor.run {
|
||||
migrationState = .uploadProgress(uploadedKb: Int64(100 * i), totalKb: 1000)
|
||||
if i == 10 {
|
||||
migrationState = .uploadDone(link: "https://simplex.chat")
|
||||
alert = .error(title: "Error exporting chat database", error: responseError(error))
|
||||
migrationState = .uploadConfirmation
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func startUploading(_ totalBytes: Int64, _ archivePath: URL) {
|
||||
Task {
|
||||
for i in 1...20 {
|
||||
try? await Task.sleep(nanoseconds: 50_000000)
|
||||
await MainActor.run {
|
||||
migrationState = .uploadProgress(uploadedBytes: Int64(100_000 * i), totalBytes: 2_000_000, archivePath: archivePath)
|
||||
if i == 20 {
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
migrationState = .linkCreation(totalBytes: totalBytes, archivePath: archivePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func createLink(_ archivePath: URL) {
|
||||
migrationState = .linkShown(link: "https://simplex.chat", archivePath: archivePath)
|
||||
}
|
||||
}
|
||||
|
||||
private struct PassphraseConfirmationView: View {
|
||||
@@ -186,9 +298,9 @@ private struct PassphraseConfirmationView: View {
|
||||
@State private var useKeychain = storeDBPassphraseGroupDefault.get()
|
||||
@State private var currentKey: String = ""
|
||||
@State private var verifyingPassphrase: Bool = false
|
||||
@State private var alert: PassphraseConfirmationViewAlert?
|
||||
@Binding var alert: PassphraseConfirmationViewAlert?
|
||||
|
||||
enum PassphraseConfirmationViewAlert: Identifiable {
|
||||
public enum PassphraseConfirmationViewAlert: Identifiable {
|
||||
case wrongPassphrase(title: LocalizedStringKey = "Wrong passphrase!", message: LocalizedStringKey = "Enter correct passphrase.")
|
||||
case invalidConfirmation(title: LocalizedStringKey = "Invalid migration confirmation")
|
||||
case keychainError(_ title: LocalizedStringKey = "Keychain error")
|
||||
@@ -209,86 +321,76 @@ private struct PassphraseConfirmationView: View {
|
||||
}
|
||||
|
||||
var body: some View {
|
||||
ZStack(alignment: .topLeading) {
|
||||
VStack {
|
||||
ZStack {
|
||||
List {
|
||||
chatStoppedView()
|
||||
Section {
|
||||
VStack {
|
||||
PassphraseField(key: $currentKey, placeholder: "Current passphrase…", valid: validKey(currentKey))
|
||||
Button(action: { checkDatabasePassphrase(currentKey, $verifyingPassphrase) }) {
|
||||
settingsRow(useKeychain ? "key" : "lock", color: .secondary) {
|
||||
Text("Check passphrase")
|
||||
}
|
||||
PassphraseField(key: $currentKey, placeholder: "Current passphrase…", valid: validKey(currentKey))
|
||||
Button(action: {
|
||||
verifyingPassphrase = true
|
||||
hideKeyboard()
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
|
||||
verifyDatabasePassphrase(currentKey, $verifyingPassphrase)
|
||||
}
|
||||
}) {
|
||||
settingsRow(useKeychain ? "key" : "lock", color: .secondary) {
|
||||
Text("Verify passphrase")
|
||||
}
|
||||
}
|
||||
.disabled(currentKey.isEmpty)
|
||||
} header: {
|
||||
Text("Enter database passphrase")
|
||||
Text("Verify database passphrase to migrate it")
|
||||
} footer: {
|
||||
Text("Make sure you remember database passphrase before migrating")
|
||||
}
|
||||
.font(.callout)
|
||||
}
|
||||
if (verifyingPassphrase) {
|
||||
progressView("Checking passphrase…")
|
||||
}
|
||||
}
|
||||
.alert(item: $alert) { alert in
|
||||
switch alert {
|
||||
case let .wrongPassphrase(title, message):
|
||||
return Alert(title: Text(title), message: Text(message))
|
||||
case let .invalidConfirmation(title):
|
||||
return Alert(title: Text(title))
|
||||
case let .keychainError(title):
|
||||
return Alert(title: Text(title))
|
||||
case let .databaseError(title, message):
|
||||
return Alert(title: Text(title), message: Text(message))
|
||||
case let .unknownError(title, message):
|
||||
return Alert(title: Text(title), message: Text(message))
|
||||
case let .error(title, error):
|
||||
return Alert(title: Text(title), message: Text(error))
|
||||
if verifyingPassphrase {
|
||||
progressView()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func checkDatabasePassphrase(_ dbKey: String, _ verifyingPassphrase: Binding<Bool>) {
|
||||
verifyingPassphrase.wrappedValue = true
|
||||
defer {
|
||||
private func verifyDatabasePassphrase(_ dbKey: String, _ verifyingPassphrase: Binding<Bool>) {
|
||||
let m = ChatModel.shared
|
||||
resetChatCtrl()
|
||||
m.ctrlInitInProgress = true
|
||||
defer {
|
||||
m.ctrlInitInProgress = false
|
||||
verifyingPassphrase.wrappedValue = false
|
||||
}
|
||||
do {
|
||||
resetChatCtrl()
|
||||
try initializeChat(start: false, confirmStart: false, dbKey: dbKey, confirmMigrations: nil)
|
||||
if let s = ChatModel.shared.chatDbStatus {
|
||||
let am = AlertManager.shared
|
||||
switch s {
|
||||
case .invalidConfirmation:
|
||||
am.showAlert(Alert(title: Text(String("Invalid migration confirmation"))))
|
||||
case .errorNotADatabase:
|
||||
alert = .wrongPassphrase()
|
||||
case .errorKeychain:
|
||||
alert = .keychainError()
|
||||
case let .errorSQL(_, error):
|
||||
alert = .databaseError(message: error)
|
||||
case let .unknown(error):
|
||||
alert = .unknownError(message: error)
|
||||
case .errorMigration: ()
|
||||
case .ok:
|
||||
migrationState = .uploadConfirmation
|
||||
}
|
||||
}
|
||||
} catch let error {
|
||||
logger.error("initializeChat \(responseError(error))")
|
||||
let (_, status) = chatMigrateInit(dbKey, confirmMigrations: .error)
|
||||
switch status {
|
||||
case .invalidConfirmation:
|
||||
alert = .invalidConfirmation()
|
||||
case .errorNotADatabase:
|
||||
alert = .wrongPassphrase()
|
||||
case .errorKeychain:
|
||||
alert = .keychainError()
|
||||
case let .errorSQL(_, error):
|
||||
alert = .databaseError(message: error)
|
||||
case let .unknown(error):
|
||||
alert = .unknownError(message: error)
|
||||
case .errorMigration: ()
|
||||
case .ok:
|
||||
migrationState = .uploadConfirmation
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func progressView(_ text: LocalizedStringKey) -> some View {
|
||||
private func progressView() -> some View {
|
||||
VStack {
|
||||
ProgressView().scaleEffect(2)
|
||||
Text(text)
|
||||
.padding()
|
||||
}
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity )
|
||||
}
|
||||
|
||||
func chatStoppedView() -> some View {
|
||||
settingsRow("exclamationmark.octagon.fill", color: .red) {
|
||||
Text("Chat is stopped")
|
||||
}
|
||||
}
|
||||
|
||||
struct MigrateToAnotherDevice_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
MigrateToAnotherDevice()
|
||||
|
||||
@@ -163,49 +163,49 @@ struct SettingsView: View {
|
||||
NavigationView {
|
||||
List {
|
||||
Section("You") {
|
||||
if let user = user {
|
||||
NavigationLink {
|
||||
UserProfile()
|
||||
.navigationTitle("Your current profile")
|
||||
} label: {
|
||||
ProfilePreview(profileOf: user)
|
||||
.padding(.leading, -8)
|
||||
}
|
||||
}
|
||||
|
||||
NavigationLink {
|
||||
UserProfilesView(showSettings: $showSettings)
|
||||
} label: {
|
||||
settingsRow("person.crop.rectangle.stack") { Text("Your chat profiles") }
|
||||
}
|
||||
|
||||
|
||||
if let user = user {
|
||||
NavigationLink {
|
||||
UserAddressView(shareViaProfile: user.addressShared)
|
||||
.navigationTitle("SimpleX address")
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
} label: {
|
||||
settingsRow("qrcode") { Text("Your SimpleX address") }
|
||||
Group {
|
||||
if let user = user {
|
||||
NavigationLink {
|
||||
UserProfile()
|
||||
.navigationTitle("Your current profile")
|
||||
} label: {
|
||||
ProfilePreview(profileOf: user)
|
||||
.padding(.leading, -8)
|
||||
}
|
||||
}
|
||||
|
||||
NavigationLink {
|
||||
PreferencesView(profile: user.profile, preferences: user.fullPreferences, currentPreferences: user.fullPreferences)
|
||||
.navigationTitle("Your preferences")
|
||||
UserProfilesView(showSettings: $showSettings)
|
||||
} label: {
|
||||
settingsRow("switch.2") { Text("Chat preferences") }
|
||||
settingsRow("person.crop.rectangle.stack") { Text("Your chat profiles") }
|
||||
}
|
||||
|
||||
|
||||
if let user = user {
|
||||
NavigationLink {
|
||||
UserAddressView(shareViaProfile: user.addressShared)
|
||||
.navigationTitle("SimpleX address")
|
||||
.navigationBarTitleDisplayMode(.large)
|
||||
} label: {
|
||||
settingsRow("qrcode") { Text("Your SimpleX address") }
|
||||
}
|
||||
|
||||
NavigationLink {
|
||||
PreferencesView(profile: user.profile, preferences: user.fullPreferences, currentPreferences: user.fullPreferences)
|
||||
.navigationTitle("Your preferences")
|
||||
} label: {
|
||||
settingsRow("switch.2") { Text("Chat preferences") }
|
||||
}
|
||||
}
|
||||
|
||||
NavigationLink {
|
||||
ConnectDesktopView(viaSettings: true)
|
||||
} label: {
|
||||
settingsRow("desktopcomputer") { Text("Use from desktop") }
|
||||
}
|
||||
}
|
||||
.disabled(chatModel.chatRunning != true)
|
||||
|
||||
NavigationLink {
|
||||
ConnectDesktopView(viaSettings: true)
|
||||
} label: {
|
||||
settingsRow("desktopcomputer") { Text("Use from desktop") }
|
||||
}
|
||||
}
|
||||
.disabled(chatModel.chatRunning != true)
|
||||
|
||||
Section {
|
||||
NavigationLink {
|
||||
MigrateToAnotherDevice()
|
||||
.navigationTitle("Migrate to another device")
|
||||
@@ -214,7 +214,6 @@ struct SettingsView: View {
|
||||
settingsRow("tray.and.arrow.up") { Text("Migrate to another device") }
|
||||
}
|
||||
}
|
||||
|
||||
Section("Settings") {
|
||||
NavigationLink {
|
||||
NotificationsView()
|
||||
|
||||
Reference in New Issue
Block a user