Files
simplex-chat/apps/ios/Shared/Views/Database/DatabaseErrorView.swift
Evgeny Poberezkin 6865515f43 ios: share extension (#4466)
* ios: share extension (#4414)

* ios: add share extension target

* ios: Add UI

* ios: send file from share-sheet

* image utils

* ShareError

* error handling; ui-cleanup

* progress bar; completion for direct chat

* cleanup

* cleanup

* ios: unify filter and sort between forward and share sheets

* ios: match share sheet styling with the main app

* ios: fix text input stroke width

* ios: align compose views

* more of the same...

* ShareAPI

* remove combine

* minor

* Better error descriptions

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>

* ios: enable file sending workers in share extension (#4474)

* ios: align compose background, row height and fallback images for share-sheet (#4467)

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>

* ios: coordinate database access between share extension, the app and notifications extension (#4472)

* ios: database management proposal

* Add SEState

* Global event loop

* minor

* reset state

* use apiCreateItem for local chats

* simplify waiting for suspension

* loading bar

* Dismiss share sheet with error

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>

* send image message (#4481)

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>

* ios: improve share extension completion handling (#4486)

* improve completion handling

* minor

* show only spinner for group send

* rework event loop, errorAlert

* group chat timeout loading bar

* state machine WIP

* event loop actor

* alert

* errors text

* default

* file error

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>

* ios: add remaining share types; process attachment in background on launch (#4510)

* add remaining share types; process attachment in background on launch

* cleanup diff

* revert `makeVideoQualityLower`

* reduce diff

* reduce diff

* iOS15 support

* process events when sharing link and text

* cleanup

* remove video file on failure

* cleanup CompletionHandler

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>

* ios: share extension - additional alerts and media previews (#4521)

* add remaining share types; process attachment in background on launch

* cleanup diff

* revert `makeVideoQualityLower`

* reduce diff

* reduce diff

* iOS15 support

* process events when sharing link and text

* cleanup

* remove video file on failure

* cleanup CompletionHandler

* media previews

* network timeout alert

* revert framework compiler optimisation flag

* suspend chat after sheet dismiss

* activate chat

* update

* fix search

* sendMessageColor, file preview, chat deselect, simplify error action

* cleanup

* interupt database closing when sheet is reopened quickly

* cleanup redundant alert check

* restore package

* refactor previews, remove link preview

* show link preview when becomes available

* comment

* dont fail on invalid image

* suspend

---------

Co-authored-by: Evgeny Poberezkin <evgeny@poberezkin.com>

* ios: descriptive database errors (#4527)

* ios: set share extension as inactive when suspending chat

---------

Co-authored-by: Arturs Krumins <auth@levitatingpineapple.com>
2024-07-28 17:54:58 +01:00

218 lines
8.2 KiB
Swift

//
// DatabaseErrorView.swift
// SimpleX (iOS)
//
// Created by Evgeny on 04/09/2022.
// Copyright © 2022 SimpleX Chat. All rights reserved.
//
import SwiftUI
import SimpleXChat
struct DatabaseErrorView: View {
@EnvironmentObject var m: ChatModel
@State var status: DBMigrationResult
@State private var dbKey = ""
@State private var storedDBKey = kcDatabasePassword.get()
@State private var useKeychain = storeDBPassphraseGroupDefault.get()
@State private var showRestoreDbButton = false
@State private var starting = false
var body: some View {
ZStack {
databaseErrorView().disabled(starting)
if starting {
ProgressView().scaleEffect(2)
}
}
}
@ViewBuilder private func databaseErrorView() -> some View {
VStack(alignment: .leading, spacing: 16) {
switch status {
case let .errorNotADatabase(dbFile):
if useKeychain && storedDBKey != nil && storedDBKey != "" {
titleText("Wrong database passphrase")
Text("Database passphrase is different from saved in the keychain.")
databaseKeyField(onSubmit: saveAndRunChat)
saveAndOpenButton()
fileNameText(dbFile)
} else {
titleText("Encrypted database")
Text("Database passphrase is required to open chat.")
if useKeychain {
databaseKeyField(onSubmit: saveAndRunChat)
saveAndOpenButton()
} else {
databaseKeyField(onSubmit: { runChat() })
openChatButton()
}
}
case let .errorMigration(dbFile, migrationError):
switch migrationError {
case let .upgrade(upMigrations):
titleText("Database upgrade")
Button("Upgrade and open chat") { runChat(confirmMigrations: .yesUp) }
fileNameText(dbFile)
migrationsText(upMigrations.map(\.upName))
case let .downgrade(downMigrations):
titleText("Database downgrade")
Text("Warning: you may lose some data!").bold()
Button("Downgrade and open chat") { runChat(confirmMigrations: .yesUpDown) }
fileNameText(dbFile)
migrationsText(downMigrations)
case let .migrationError(mtrError):
titleText("Incompatible database version")
fileNameText(dbFile)
Text("Error: ") + Text(mtrErrorDescription(mtrError))
}
case let .errorSQL(dbFile, migrationSQLError):
titleText("Database error")
fileNameText(dbFile)
Text("Error: \(migrationSQLError)")
case .errorKeychain:
titleText("Keychain error")
Text("Cannot access keychain to save database password")
case .invalidConfirmation:
// this can only happen if incorrect parameter is passed
Text(String("Invalid migration confirmation")).font(.title)
case let .unknown(json):
titleText("Database error")
Text("Unknown database error: \(json)")
case .ok:
EmptyView()
}
if showRestoreDbButton {
Spacer().frame(height: 10)
Text("The attempt to change database passphrase was not completed.")
restoreDbButton()
}
}
.padding()
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
.onAppear() { showRestoreDbButton = shouldShowRestoreDbButton() }
}
private func titleText(_ s: LocalizedStringKey) -> Text {
Text(s).font(.title)
}
private func fileNameText(_ f: String) -> Text {
Text("File: \((f as NSString).lastPathComponent)")
}
private func migrationsText(_ ms: [String]) -> Text {
Text("Migrations: \(ms.joined(separator: ", "))")
}
private func databaseKeyField(onSubmit: @escaping () -> Void) -> some View {
PassphraseField(key: $dbKey, placeholder: "Enter passphrase…", valid: validKey(dbKey), onSubmit: onSubmit)
}
private func saveAndOpenButton() -> some View {
Button("Save passphrase and open chat") {
saveAndRunChat()
}
}
private func openChatButton() -> some View {
Button("Open chat") {
runChat()
}
}
private func saveAndRunChat() {
if kcDatabasePassword.set(dbKey) {
storeDBPassphraseGroupDefault.set(true)
initialRandomDBPassphraseGroupDefault.set(false)
}
runChat()
}
private func runChat(confirmMigrations: MigrationConfirmation? = nil) {
starting = true
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
runChatSync(confirmMigrations: confirmMigrations)
starting = false
}
}
private func runChatSync(confirmMigrations: MigrationConfirmation? = nil) {
do {
resetChatCtrl()
try initializeChat(start: m.v3DBMigration.startChat, confirmStart: m.v3DBMigration.startChat && AppChatState.shared.value == .stopped, dbKey: useKeychain ? nil : dbKey, confirmMigrations: confirmMigrations)
if let s = m.chatDbStatus {
status = s
let am = AlertManager.shared
switch s {
case .invalidConfirmation:
am.showAlert(Alert(title: Text(String("Invalid migration confirmation"))))
case .errorNotADatabase:
am.showAlertMsg(
title: "Wrong passphrase!",
message: "Enter correct passphrase."
)
case .errorKeychain:
am.showAlertMsg(title: "Keychain error")
case let .errorSQL(_, error):
am.showAlert(Alert(
title: Text("Database error"),
message: Text(error)
))
case let .unknown(error):
am.showAlert(Alert(
title: Text("Unknown error"),
message: Text(error)
))
case .errorMigration: ()
case .ok: ()
}
}
} catch let error {
logger.error("initializeChat \(responseError(error))")
}
}
private func shouldShowRestoreDbButton() -> Bool {
if !encryptionStartedDefault.get() { return false }
let startedAt = encryptionStartedAtDefault.get()
// In case there is a small difference between saved encryptionStartedAt time and last modified timestamp on a file
let safeDiffInTime = TimeInterval(10)
return hasBackup(newerThan: startedAt - safeDiffInTime)
}
private func restoreDbButton() -> some View {
Button() {
AlertManager.shared.showAlert(Alert(
title: Text("Restore database backup?"),
message: Text("Please enter the previous password after restoring database backup. This action can not be undone."),
primaryButton: .destructive(Text("Restore")) {
restoreDb()
},
secondaryButton: .cancel()
))
} label: {
Text("Restore database backup").foregroundColor(.red)
}
}
private func restoreDb() {
do {
try restoreBackup()
showRestoreDbButton = false
encryptionStartedDefault.set(false)
} catch {
AlertManager.shared.showAlert(Alert(
title: Text("Restore database error"),
message: Text(error.localizedDescription)
))
}
}
}
struct DatabaseErrorView_Previews: PreviewProvider {
static var previews: some View {
DatabaseErrorView(status: .errorNotADatabase(dbFile: "simplex_v1_chat.db"))
}
}