2022-06-24 13:52:20 +01:00
//
// D a t a b a s e V i e w . s w i f t
// S i m p l e X ( i O S )
//
// C r e a t e d b y E v g e n y o n 1 9 / 0 6 / 2 0 2 2 .
// C o p y r i g h t © 2 0 2 2 S i m p l e X C h a t . A l l r i g h t s r e s e r v e d .
//
import SwiftUI
import SimpleXChat
enum DatabaseAlert : Identifiable {
case stopChat
2022-09-07 12:49:41 +01:00
case exportProhibited
2022-06-24 13:52:20 +01:00
case importArchive
case archiveImported
2023-05-24 14:22:12 +04:00
case archiveImportedWithErrors ( archiveErrors : [ ArchiveError ] )
2024-08-02 20:23:54 +01:00
case archiveExportedWithErrors ( archivePath : URL , archiveErrors : [ ArchiveError ] )
2022-06-24 13:52:20 +01:00
case deleteChat
case chatDeleted
case deleteLegacyDatabase
2022-09-19 19:05:29 +04:00
case deleteFilesAndMedia
2022-10-03 16:42:43 +04:00
case setChatItemTTL ( ttl : ChatItemTTL )
2025-05-26 16:57:18 +01:00
case error ( title : String , error : String = " " )
2022-06-24 13:52:20 +01:00
var id : String {
switch self {
case . stopChat : return " stopChat "
2022-09-07 12:49:41 +01:00
case . exportProhibited : return " exportProhibited "
2022-06-24 13:52:20 +01:00
case . importArchive : return " importArchive "
case . archiveImported : return " archiveImported "
2023-05-24 14:22:12 +04:00
case . archiveImportedWithErrors : return " archiveImportedWithErrors "
2024-08-02 20:23:54 +01:00
case . archiveExportedWithErrors : return " archiveExportedWithErrors "
2022-06-24 13:52:20 +01:00
case . deleteChat : return " deleteChat "
case . chatDeleted : return " chatDeleted "
case . deleteLegacyDatabase : return " deleteLegacyDatabase "
2022-09-19 19:05:29 +04:00
case . deleteFilesAndMedia : return " deleteFilesAndMedia "
2022-10-03 16:42:43 +04:00
case . setChatItemTTL : return " setChatItemTTL "
2022-06-24 13:52:20 +01:00
case let . error ( title , _ ) : return " error \( title ) "
}
}
}
struct DatabaseView : View {
@ EnvironmentObject var m : ChatModel
2024-07-03 22:42:13 +01:00
@ EnvironmentObject var theme : AppTheme
2024-10-07 20:30:17 +03:00
let dismissSettingsSheet : DismissAction
2022-06-24 13:52:20 +01:00
@ State private var runChat = false
2024-11-30 23:29:27 +07:00
@ State private var stoppingChat = false
2022-06-24 13:52:20 +01:00
@ State private var alert : DatabaseAlert ? = nil
@ State private var showFileImporter = false
@ State private var importedArchivePath : URL ?
@ State private var progressIndicator = false
@ AppStorage ( DEFAULT_CHAT_ARCHIVE_NAME ) private var chatArchiveName : String ?
@ AppStorage ( DEFAULT_CHAT_ARCHIVE_TIME ) private var chatArchiveTime : Double = 0
@ State private var dbContainer = dbContainerGroupDefault . get ( )
@ State private var legacyDatabase = hasLegacyDatabase ( )
2022-09-08 17:36:16 +01:00
@ State private var useKeychain = storeDBPassphraseGroupDefault . get ( )
2022-09-19 19:05:29 +04:00
@ State private var appFilesCountAndSize : ( Int , Int ) ?
2022-06-24 13:52:20 +01:00
2024-11-30 23:29:27 +07:00
@ State private var showDatabaseEncryptionView = false
2022-10-03 16:42:43 +04:00
@ State var chatItemTTL : ChatItemTTL
@ State private var currentChatItemTTL : ChatItemTTL = . none
2022-06-24 13:52:20 +01:00
var body : some View {
ZStack {
chatDatabaseView ( )
if progressIndicator {
ProgressView ( ) . scaleEffect ( 2 )
}
}
}
2024-11-30 23:29:27 +07:00
@ ViewBuilder
2022-06-24 13:52:20 +01:00
private func chatDatabaseView ( ) -> some View {
2024-11-30 23:29:27 +07:00
NavigationLink ( isActive : $ showDatabaseEncryptionView ) {
DatabaseEncryptionView ( useKeychain : $ useKeychain , migration : false , stopChatRunBlockStartChat : { progressIndicator , block in
stopChatRunBlockStartChat ( false , progressIndicator , block )
} )
. navigationTitle ( " Database passphrase " )
. modifier ( ThemedBackground ( grouped : true ) )
} label : {
EmptyView ( )
}
. frame ( width : 1 , height : 1 )
. hidden ( )
2022-06-24 13:52:20 +01:00
List {
let stopped = m . chatRunning = = false
2023-01-20 12:38:38 +00:00
Section {
Picker ( " Delete messages after " , selection : $ chatItemTTL ) {
ForEach ( ChatItemTTL . values ) { ttl in
Text ( ttl . deleteAfterText ) . tag ( ttl )
}
if case . seconds = chatItemTTL {
Text ( chatItemTTL . deleteAfterText ) . tag ( chatItemTTL )
}
}
. frame ( height : 36 )
2023-04-19 15:21:28 +04:00
. disabled ( stopped || progressIndicator )
2023-01-20 12:38:38 +00:00
} header : {
Text ( " Messages " )
2024-07-03 22:42:13 +01:00
. foregroundColor ( theme . colors . secondary )
2023-01-20 12:38:38 +00:00
} footer : {
Text ( " This setting applies to messages in your current chat profile ** \( m . currentUser ? . displayName ? ? " " ) **. " )
2024-07-03 22:42:13 +01:00
. foregroundColor ( theme . colors . secondary )
2023-01-20 12:38:38 +00:00
}
2022-06-24 13:52:20 +01:00
Section {
settingsRow (
stopped ? " exclamationmark.octagon.fill " : " play.fill " ,
color : stopped ? . red : . green
) {
Toggle (
stopped ? " Chat is stopped " : " Chat is running " ,
isOn : $ runChat
)
. onChange ( of : runChat ) { _ in
2024-11-30 23:29:27 +07:00
if runChat {
DatabaseView . startChat ( $ runChat , $ progressIndicator )
} else if ! stoppingChat {
stoppingChat = false
2022-06-24 13:52:20 +01:00
alert = . stopChat
}
}
}
} header : {
Text ( " Run chat " )
2024-07-03 22:42:13 +01:00
. foregroundColor ( theme . colors . secondary )
2022-06-24 13:52:20 +01:00
} footer : {
if case . documents = dbContainer {
Text ( " Database will be migrated when the app restarts " )
2024-07-03 22:42:13 +01:00
. foregroundColor ( theme . colors . secondary )
2022-06-24 13:52:20 +01:00
}
}
Section {
2022-09-07 12:49:41 +01:00
let unencrypted = m . chatDbEncrypted = = false
2024-07-03 22:42:13 +01:00
let color : Color = unencrypted ? . orange : theme . colors . secondary
2022-09-08 17:36:16 +01:00
settingsRow ( unencrypted ? " lock.open " : useKeychain ? " key " : " lock " , color : color ) {
2022-09-07 12:49:41 +01:00
NavigationLink {
2024-11-30 23:29:27 +07:00
DatabaseEncryptionView ( useKeychain : $ useKeychain , migration : false , stopChatRunBlockStartChat : { progressIndicator , block in
stopChatRunBlockStartChat ( false , progressIndicator , block )
} )
2022-09-07 12:49:41 +01:00
. navigationTitle ( " Database passphrase " )
2024-07-03 22:42:13 +01:00
. modifier ( ThemedBackground ( grouped : true ) )
2022-06-24 13:52:20 +01:00
} label : {
2022-09-07 12:49:41 +01:00
Text ( " Database passphrase " )
}
}
2024-07-03 22:42:13 +01:00
settingsRow ( " square.and.arrow.up " , color : theme . colors . secondary ) {
2022-09-07 12:49:41 +01:00
Button ( " Export database " ) {
2022-09-22 13:10:25 +01:00
if initialRandomDBPassphraseGroupDefault . get ( ) && ! unencrypted {
2024-11-30 23:29:27 +07:00
showDatabaseEncryptionView = true
DispatchQueue . main . asyncAfter ( deadline : . now ( ) + 1 ) {
alert = . exportProhibited
}
2022-09-07 12:49:41 +01:00
} else {
2024-11-30 23:29:27 +07:00
stopChatRunBlockStartChat ( stopped , $ progressIndicator ) {
await exportArchive ( )
}
2022-09-07 12:49:41 +01:00
}
2022-06-24 13:52:20 +01:00
}
}
2024-07-03 22:42:13 +01:00
settingsRow ( " square.and.arrow.down " , color : theme . colors . secondary ) {
2022-09-07 12:49:41 +01:00
Button ( " Import database " , role : . destructive ) {
2022-06-24 13:52:20 +01:00
showFileImporter = true
}
}
2024-07-03 22:42:13 +01:00
settingsRow ( " trash.slash " , color : theme . colors . secondary ) {
2022-09-07 12:49:41 +01:00
Button ( " Delete database " , role : . destructive ) {
2022-06-24 13:52:20 +01:00
alert = . deleteChat
}
}
} header : {
Text ( " Chat database " )
2024-07-03 22:42:13 +01:00
. foregroundColor ( theme . colors . secondary )
2022-06-24 13:52:20 +01:00
} footer : {
2024-11-30 23:29:27 +07:00
Text ( " You must use the most recent version of your chat database on one device ONLY, otherwise you may stop receiving the messages from some contacts. " )
2024-07-03 22:42:13 +01:00
. foregroundColor ( theme . colors . secondary )
2022-06-24 13:52:20 +01:00
}
2024-11-30 23:29:27 +07:00
. disabled ( progressIndicator )
2022-06-24 13:52:20 +01:00
if case . group = dbContainer , legacyDatabase {
2024-07-03 22:42:13 +01:00
Section ( header : Text ( " Old database " ) . foregroundColor ( theme . colors . secondary ) ) {
settingsRow ( " trash " , color : theme . colors . secondary ) {
2022-09-07 12:49:41 +01:00
Button ( " Delete old database " ) {
2022-06-24 13:52:20 +01:00
alert = . deleteLegacyDatabase
}
}
}
}
2022-09-19 19:05:29 +04:00
Section {
2023-01-20 12:38:38 +00:00
Button ( m . users . count > 1 ? " Delete files for all chat profiles " : " Delete all files " , role : . destructive ) {
2022-09-19 19:05:29 +04:00
alert = . deleteFilesAndMedia
}
2024-11-30 23:29:27 +07:00
. disabled ( progressIndicator || appFilesCountAndSize ? . 0 = = 0 )
2022-09-19 19:05:29 +04:00
} header : {
2023-01-20 12:38:38 +00:00
Text ( " Files & media " )
2024-07-03 22:42:13 +01:00
. foregroundColor ( theme . colors . secondary )
2022-09-19 19:05:29 +04:00
} footer : {
if let ( fileCount , size ) = appFilesCountAndSize {
if fileCount = = 0 {
Text ( " No received or sent files " )
2024-07-03 22:42:13 +01:00
. foregroundColor ( theme . colors . secondary )
2022-09-19 19:05:29 +04:00
} else {
2023-03-28 22:20:06 +04:00
Text ( " \( fileCount ) file(s) with total size of \( ByteCountFormatter . string ( fromByteCount : Int64 ( size ) , countStyle : . binary ) ) " )
2024-07-03 22:42:13 +01:00
. foregroundColor ( theme . colors . secondary )
2022-09-19 19:05:29 +04:00
}
}
}
}
. onAppear {
runChat = m . chatRunning ? ? true
appFilesCountAndSize = directoryFileCountAndSize ( getAppFilesDirectory ( ) )
2022-10-03 16:42:43 +04:00
currentChatItemTTL = chatItemTTL
}
. onChange ( of : chatItemTTL ) { ttl in
if ttl < currentChatItemTTL {
alert = . setChatItemTTL ( ttl : ttl )
} else if ttl != currentChatItemTTL {
setCiTTL ( ttl )
}
2022-06-24 13:52:20 +01:00
}
. alert ( item : $ alert ) { item in databaseAlert ( item ) }
. fileImporter (
isPresented : $ showFileImporter ,
allowedContentTypes : [ . zip ] ,
allowsMultipleSelection : false
) { result in
if case let . success ( files ) = result , let fileURL = files . first {
importedArchivePath = fileURL
alert = . importArchive
}
}
}
private func databaseAlert ( _ alertItem : DatabaseAlert ) -> Alert {
switch alertItem {
case . stopChat :
return Alert (
title : Text ( " Stop chat? " ) ,
message : Text ( " Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped. " ) ,
primaryButton : . destructive ( Text ( " Stop " ) ) {
2022-09-08 17:36:16 +01:00
authStopChat ( )
2022-06-24 13:52:20 +01:00
} ,
secondaryButton : . cancel {
withAnimation { runChat = true }
}
)
2022-09-07 12:49:41 +01:00
case . exportProhibited :
return Alert (
title : Text ( " Set passphrase to export " ) ,
message : Text ( " Database is encrypted using a random passphrase. Please change it before exporting. " )
)
2022-06-24 13:52:20 +01:00
case . importArchive :
if let fileURL = importedArchivePath {
return Alert (
title : Text ( " Import chat database? " ) ,
2022-07-08 22:42:38 +01:00
message : Text ( " Your current chat database will be DELETED and REPLACED with the imported one. " ) + Text ( " This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost. " ) ,
2022-06-24 13:52:20 +01:00
primaryButton : . destructive ( Text ( " Import " ) ) {
2024-11-30 23:29:27 +07:00
stopChatRunBlockStartChat ( m . chatRunning = = false , $ progressIndicator ) {
2024-12-22 23:18:45 +07:00
await DatabaseView . importArchive ( fileURL , $ progressIndicator , $ alert , false )
2024-11-30 23:29:27 +07:00
}
2022-06-24 13:52:20 +01:00
} ,
secondaryButton : . cancel ( )
)
} else {
return Alert ( title : Text ( " Error: no database file " ) )
}
case . archiveImported :
2024-11-30 23:29:27 +07:00
let ( title , message ) = archiveImportedAlertText ( )
return Alert ( title : Text ( title ) , message : Text ( message ) )
2024-08-02 20:23:54 +01:00
case let . archiveImportedWithErrors ( errs ) :
2024-11-30 23:29:27 +07:00
let ( title , message ) = archiveImportedWithErrorsAlertText ( errs : errs )
return Alert ( title : Text ( title ) , message : Text ( message ) )
2024-08-02 20:23:54 +01:00
case let . archiveExportedWithErrors ( archivePath , errs ) :
return Alert (
title : Text ( " Chat database exported " ) ,
2025-03-07 16:50:44 +04:00
message : Text ( " You may save the exported archive. " ) + textNewLine + Text ( " Some file(s) were not exported: " ) + Text ( archiveErrorsText ( errs ) ) ,
2024-08-02 20:23:54 +01:00
dismissButton : . default ( Text ( " Continue " ) ) {
showShareSheet ( items : [ archivePath ] )
}
2023-05-24 14:22:12 +04:00
)
2022-06-24 13:52:20 +01:00
case . deleteChat :
return Alert (
title : Text ( " Delete chat profile? " ) ,
message : Text ( " This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost. " ) ,
primaryButton : . destructive ( Text ( " Delete " ) ) {
2024-11-30 23:29:27 +07:00
let wasStopped = m . chatRunning = = false
stopChatRunBlockStartChat ( wasStopped , $ progressIndicator ) {
_ = await deleteChat ( )
return true
}
2022-06-24 13:52:20 +01:00
} ,
secondaryButton : . cancel ( )
)
case . chatDeleted :
2024-11-30 23:29:27 +07:00
let ( title , message ) = chatDeletedAlertText ( )
return Alert ( title : Text ( title ) , message : Text ( message ) )
2022-06-24 13:52:20 +01:00
case . deleteLegacyDatabase :
return Alert (
title : Text ( " Delete old database? " ) ,
message : Text ( " The old database was not removed during the migration, it can be deleted. " ) ,
primaryButton : . destructive ( Text ( " Delete " ) ) {
deleteLegacyDatabase ( )
} ,
secondaryButton : . cancel ( )
)
2022-09-19 19:05:29 +04:00
case . deleteFilesAndMedia :
return Alert (
title : Text ( " Delete files and media? " ) ,
message : Text ( " This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain. " ) ,
primaryButton : . destructive ( Text ( " Delete " ) ) {
2024-11-30 23:29:27 +07:00
stopChatRunBlockStartChat ( m . chatRunning = = false , $ progressIndicator ) {
deleteFiles ( )
return true
}
2022-09-19 19:05:29 +04:00
} ,
secondaryButton : . cancel ( )
)
2022-10-03 16:42:43 +04:00
case let . setChatItemTTL ( ttl ) :
return Alert (
title : Text ( " Enable automatic message deletion? " ) ,
2022-10-04 09:53:43 +01:00
message : Text ( " This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes. " ) ,
2022-10-03 16:42:43 +04:00
primaryButton : . destructive ( Text ( " Delete messages " ) ) {
setCiTTL ( ttl )
} ,
secondaryButton : . cancel ( ) {
chatItemTTL = currentChatItemTTL
}
)
2022-06-24 13:52:20 +01:00
case let . error ( title , error ) :
2022-11-25 13:50:26 +00:00
return Alert ( title : Text ( title ) , message : Text ( error ) )
2022-06-24 13:52:20 +01:00
}
}
2024-11-30 23:29:27 +07:00
private func authStopChat ( _ onStop : ( ( ) -> Void ) ? = nil ) {
2022-09-08 17:36:16 +01:00
if UserDefaults . standard . bool ( forKey : DEFAULT_PERFORM_LA ) {
authenticate ( reason : NSLocalizedString ( " Stop SimpleX " , comment : " authentication reason " ) ) { laResult in
switch laResult {
2024-11-30 23:29:27 +07:00
case . success : stopChat ( onStop )
case . unavailable : stopChat ( onStop )
2022-09-08 17:36:16 +01:00
case . failed : withAnimation { runChat = true }
}
}
} else {
2024-11-30 23:29:27 +07:00
stopChat ( onStop )
2022-09-08 17:36:16 +01:00
}
}
2024-11-30 23:29:27 +07:00
private func stopChat ( _ onStop : ( ( ) -> Void ) ? = nil ) {
2022-06-24 13:52:20 +01:00
Task {
do {
2023-05-09 10:33:30 +02:00
try await stopChatAsync ( )
2024-11-30 23:29:27 +07:00
onStop ? ( )
2022-06-24 13:52:20 +01:00
} catch let error {
await MainActor . run {
runChat = true
2024-11-30 23:29:27 +07:00
showAlert ( " Error stopping chat " , message : responseError ( error ) )
2022-06-24 13:52:20 +01:00
}
}
}
}
2024-11-30 23:29:27 +07:00
func stopChatRunBlockStartChat (
_ stopped : Bool ,
_ progressIndicator : Binding < Bool > ,
_ block : @ escaping ( ) async throws -> Bool
) {
// i f t h e c h a t w a s r u n n i n g , t h e s e q u e n c e i s : s t o p c h a t , r u n b l o c k , s t a r t c h a t .
// O t h e r w i s e , j u s t r u n b l o c k a n d d o n o t h i n g - t h e t o g g l e w i l l b e v i s i b l e a n y w a y a n d t h e u s e r c a n s t a r t t h e c h a t o r n o t
if stopped {
Task {
do {
_ = try await block ( )
} catch {
logger . error ( " Error while executing block: \( error ) " )
}
}
} else {
authStopChat {
stoppingChat = true
runChat = false
Task {
// i f i t t h r o w s , l e t ' s s t a r t c h a t a g a i n a n y w a y
var canStart = false
do {
canStart = try await block ( )
} catch {
logger . error ( " Error executing block: \( error ) " )
canStart = true
2024-08-02 20:23:54 +01:00
}
2024-11-30 23:29:27 +07:00
if canStart {
await MainActor . run {
DatabaseView . startChat ( $ runChat , $ progressIndicator )
}
}
}
}
}
}
static func startChat ( _ runChat : Binding < Bool > , _ progressIndicator : Binding < Bool > ) {
progressIndicator . wrappedValue = true
let m = ChatModel . shared
if m . chatDbChanged {
DispatchQueue . main . asyncAfter ( deadline : . now ( ) + 1 ) {
resetChatCtrl ( )
do {
let hadDatabase = hasDatabase ( )
try initializeChat ( start : true )
m . chatDbChanged = false
AppChatState . shared . set ( . active )
if m . chatDbStatus != . ok || ! hadDatabase {
// H i d e c u r r e n t v i e w a n d s h o w ` D a t a b a s e E r r o r V i e w `
dismissAllSheets ( animated : true )
}
} catch let error {
fatalError ( " Error starting chat \( responseError ( error ) ) " )
2024-08-02 20:23:54 +01:00
}
2024-11-30 23:29:27 +07:00
progressIndicator . wrappedValue = false
}
} else {
do {
_ = try apiStartChat ( )
runChat . wrappedValue = true
m . chatRunning = true
ChatReceiver . shared . start ( )
chatLastStartGroupDefault . set ( Date . now )
AppChatState . shared . set ( . active )
2022-06-24 13:52:20 +01:00
} catch let error {
2024-11-30 23:29:27 +07:00
runChat . wrappedValue = false
showAlert ( NSLocalizedString ( " Error starting chat " , comment : " " ) , message : responseError ( error ) )
}
progressIndicator . wrappedValue = false
}
}
private func exportArchive ( ) async -> Bool {
await MainActor . run {
progressIndicator = true
}
do {
let ( archivePath , archiveErrors ) = try await exportChatArchive ( )
if archiveErrors . isEmpty {
showShareSheet ( items : [ archivePath ] )
await MainActor . run { progressIndicator = false }
} else {
2022-06-24 13:52:20 +01:00
await MainActor . run {
2024-11-30 23:29:27 +07:00
alert = . archiveExportedWithErrors ( archivePath : archivePath , archiveErrors : archiveErrors )
2022-06-24 13:52:20 +01:00
progressIndicator = false
}
}
2024-11-30 23:29:27 +07:00
} catch let error {
await MainActor . run {
2025-05-26 16:57:18 +01:00
alert = . error ( title : NSLocalizedString ( " Error exporting chat database " , comment : " alert title " ) , error : responseError ( error ) )
2024-11-30 23:29:27 +07:00
progressIndicator = false
}
2022-06-24 13:52:20 +01:00
}
2024-11-30 23:29:27 +07:00
return false
2022-06-24 13:52:20 +01:00
}
2024-11-30 23:29:27 +07:00
static func importArchive (
_ archivePath : URL ,
_ progressIndicator : Binding < Bool > ,
2024-12-22 23:18:45 +07:00
_ alert : Binding < DatabaseAlert ? > ,
_ migration : Bool
2024-11-30 23:29:27 +07:00
) async -> Bool {
2022-06-24 13:52:20 +01:00
if archivePath . startAccessingSecurityScopedResource ( ) {
2024-12-22 23:18:45 +07:00
defer {
archivePath . stopAccessingSecurityScopedResource ( )
}
2024-11-30 23:29:27 +07:00
await MainActor . run {
progressIndicator . wrappedValue = true
}
do {
try await apiDeleteStorage ( )
try ? FileManager . default . createDirectory ( at : getWallpaperDirectory ( ) , withIntermediateDirectories : true )
2022-06-24 13:52:20 +01:00
do {
2024-11-30 23:29:27 +07:00
let config = ArchiveConfig ( archivePath : archivePath . path )
let archiveErrors = try await apiImportArchive ( config : config )
shouldImportAppSettingsDefault . set ( true )
_ = kcDatabasePassword . remove ( )
if archiveErrors . isEmpty {
await operationEnded ( . archiveImported , progressIndicator , alert )
2024-12-22 23:18:45 +07:00
return true
2024-11-30 23:29:27 +07:00
} else {
await operationEnded ( . archiveImportedWithErrors ( archiveErrors : archiveErrors ) , progressIndicator , alert )
2024-12-22 23:18:45 +07:00
return migration
2022-06-24 13:52:20 +01:00
}
} catch let error {
2025-05-26 16:57:18 +01:00
await operationEnded ( . error ( title : NSLocalizedString ( " Error importing chat database " , comment : " alert title " ) , error : responseError ( error ) ) , progressIndicator , alert )
2022-06-24 13:52:20 +01:00
}
2024-11-30 23:29:27 +07:00
} catch let error {
2025-05-26 16:57:18 +01:00
await operationEnded ( . error ( title : NSLocalizedString ( " Error deleting chat database " , comment : " alert title " ) , error : responseError ( error ) ) , progressIndicator , alert )
2022-06-24 13:52:20 +01:00
}
} else {
2024-11-30 23:29:27 +07:00
showAlert ( " Error accessing database file " )
2022-06-24 13:52:20 +01:00
}
2024-11-30 23:29:27 +07:00
return false
2022-06-24 13:52:20 +01:00
}
2024-11-30 23:29:27 +07:00
private func deleteChat ( ) async -> Bool {
await MainActor . run {
progressIndicator = true
}
do {
try await deleteChatAsync ( )
appFilesCountAndSize = directoryFileCountAndSize ( getAppFilesDirectory ( ) )
await DatabaseView . operationEnded ( . chatDeleted , $ progressIndicator , $ alert )
return true
} catch let error {
2025-05-26 16:57:18 +01:00
await DatabaseView . operationEnded ( . error ( title : NSLocalizedString ( " Error deleting database " , comment : " alert title " ) , error : responseError ( error ) ) , $ progressIndicator , $ alert )
2024-11-30 23:29:27 +07:00
return false
2022-06-24 13:52:20 +01:00
}
}
private func deleteLegacyDatabase ( ) {
if removeLegacyDatabaseAndFiles ( ) {
legacyDatabase = false
} else {
2025-05-26 16:57:18 +01:00
alert = . error ( title : NSLocalizedString ( " Error deleting old database " , comment : " alert title " ) )
2022-06-24 13:52:20 +01:00
}
}
2024-11-30 23:29:27 +07:00
private static func operationEnded ( _ dbAlert : DatabaseAlert , _ progressIndicator : Binding < Bool > , _ alert : Binding < DatabaseAlert ? > ) async {
2022-06-24 13:52:20 +01:00
await MainActor . run {
2024-11-30 23:29:27 +07:00
let m = ChatModel . shared
2022-06-24 13:52:20 +01:00
m . chatDbChanged = true
2022-09-23 12:51:40 +01:00
m . chatInitialized = false
2024-11-30 23:29:27 +07:00
progressIndicator . wrappedValue = false
2022-06-24 13:52:20 +01:00
}
2024-11-30 23:29:27 +07:00
await withCheckedContinuation { cont in
let okAlertActionWaiting = UIAlertAction ( title : NSLocalizedString ( " Ok " , comment : " alert button " ) , style : . default , handler : { _ in cont . resume ( ) } )
// s h o w t h e s e a l e r t s g l o b a l l y s o t h e y a r e v i s i b l e w h e n a l l s h e e t s w i l l b e h i d d e n
if case . archiveImported = dbAlert {
let ( title , message ) = archiveImportedAlertText ( )
showAlert ( title , message : message , actions : { [ okAlertActionWaiting ] } )
} else if case . archiveImportedWithErrors ( let errs ) = dbAlert {
let ( title , message ) = archiveImportedWithErrorsAlertText ( errs : errs )
showAlert ( title , message : message , actions : { [ okAlertActionWaiting ] } )
} else if case . chatDeleted = dbAlert {
let ( title , message ) = chatDeletedAlertText ( )
showAlert ( title , message : message , actions : { [ okAlertActionWaiting ] } )
2024-12-22 23:18:45 +07:00
} else if case let . error ( title , error ) = dbAlert {
2025-05-26 16:57:18 +01:00
showAlert ( title , message : error , actions : { [ okAlertActionWaiting ] } )
2024-11-30 23:29:27 +07:00
} else {
alert . wrappedValue = dbAlert
cont . resume ( )
2022-06-24 13:52:20 +01:00
}
}
}
2022-09-19 19:05:29 +04:00
2022-10-03 16:42:43 +04:00
private func setCiTTL ( _ ttl : ChatItemTTL ) {
logger . debug ( " DatabaseView setChatItemTTL \( ttl . seconds ? ? - 1 ) " )
progressIndicator = true
Task {
do {
try await setChatItemTTL ( ttl )
await MainActor . run {
m . chatItemTTL = ttl
currentChatItemTTL = ttl
2022-10-07 10:55:54 +04:00
afterSetCiTTL ( )
2022-10-03 16:42:43 +04:00
}
} catch {
await MainActor . run {
2025-05-26 16:57:18 +01:00
alert = . error ( title : NSLocalizedString ( " Error changing setting " , comment : " alert title " ) , error : responseError ( error ) )
2022-10-03 16:42:43 +04:00
chatItemTTL = currentChatItemTTL
2022-10-07 10:55:54 +04:00
afterSetCiTTL ( )
2022-10-03 16:42:43 +04:00
}
}
}
}
2022-10-07 10:55:54 +04:00
private func afterSetCiTTL ( ) {
progressIndicator = false
appFilesCountAndSize = directoryFileCountAndSize ( getAppFilesDirectory ( ) )
do {
let chats = try apiGetChats ( )
2024-08-20 09:29:52 +01:00
m . updateChats ( chats )
2022-10-07 10:55:54 +04:00
} catch let error {
logger . error ( " apiGetChats: cannot update chats \( responseError ( error ) ) " )
}
}
2022-09-19 19:05:29 +04:00
private func deleteFiles ( ) {
deleteAppFiles ( )
appFilesCountAndSize = directoryFileCountAndSize ( getAppFilesDirectory ( ) )
}
2022-06-24 13:52:20 +01:00
}
2024-12-22 23:18:45 +07:00
func archiveImportedAlertText ( ) -> ( String , String ) {
2024-11-30 23:29:27 +07:00
(
NSLocalizedString ( " Chat database imported " , comment : " " ) ,
NSLocalizedString ( " Restart the app to use imported chat database " , comment : " " )
)
}
2024-12-22 23:18:45 +07:00
func archiveImportedWithErrorsAlertText ( errs : [ ArchiveError ] ) -> ( String , String ) {
2024-11-30 23:29:27 +07:00
(
NSLocalizedString ( " Chat database imported " , comment : " " ) ,
NSLocalizedString ( " Restart the app to use imported chat database " , comment : " " ) + " \n " + NSLocalizedString ( " Some non-fatal errors occurred during import: " , comment : " " ) + archiveErrorsText ( errs )
)
}
private func chatDeletedAlertText ( ) -> ( String , String ) {
(
NSLocalizedString ( " Chat database deleted " , comment : " " ) ,
NSLocalizedString ( " Restart the app to create a new chat profile " , comment : " " )
)
}
func archiveErrorsText ( _ errs : [ ArchiveError ] ) -> String {
return " \n " + errs . map ( showArchiveError ) . joined ( separator : " \n " )
2024-08-02 20:23:54 +01:00
func showArchiveError ( _ err : ArchiveError ) -> String {
switch err {
case let . import ( importError ) : importError
case let . fileError ( file , fileError ) : " \( file ) : \( fileError ) "
}
}
}
2023-05-09 10:33:30 +02:00
func stopChatAsync ( ) async throws {
try await apiStopChat ( )
ChatReceiver . shared . stop ( )
await MainActor . run { ChatModel . shared . chatRunning = false }
2023-12-11 12:34:56 +00:00
AppChatState . shared . set ( . stopped )
2023-05-09 10:33:30 +02:00
}
func deleteChatAsync ( ) async throws {
try await apiDeleteStorage ( )
_ = kcDatabasePassword . remove ( )
storeDBPassphraseGroupDefault . set ( true )
2024-01-10 04:01:41 +07:00
deleteAppDatabaseAndFiles ( )
2024-03-11 21:17:28 +07:00
// C l e a n s t a t e s o w h e n c r e a t i n g n e w u s e r t h e a p p w i l l s t a r t c h a t a u t o m a t i c a l l y ( s e e C r e a t e P r o f i l e : c r e a t e P r o f i l e ( ) )
DispatchQueue . main . async {
ChatModel . shared . users = [ ]
}
2023-05-09 10:33:30 +02:00
}
2022-06-24 13:52:20 +01:00
struct DatabaseView_Previews : PreviewProvider {
2024-10-07 20:30:17 +03:00
@ Environment ( \ . dismiss ) static var mockDismiss
2022-06-24 13:52:20 +01:00
static var previews : some View {
2024-10-07 20:30:17 +03:00
DatabaseView ( dismissSettingsSheet : mockDismiss , chatItemTTL : . none )
2022-06-24 13:52:20 +01:00
}
}