mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2026-03-31 14:06:05 +00:00
* 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>
218 lines
8.2 KiB
Swift
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"))
|
|
}
|
|
}
|