Newer
Older
import Foundation
import XCTestDynamicOverlay
import XXMessengerClient
public enum Field: String, Hashable {
case passphrase
}
public struct File: Equatable {
public init(name: String, data: Data) {
self.name = name
self.data = data
}
public var name: String
public var data: Data
}
public init(
file: File? = nil,
fileImportFailure: String? = nil,
focusedField: Field? = nil,
isImportingFile: Bool = false,
passphrase: String = "",
isRestoring: Bool = false
) {
self.file = file
self.fileImportFailure = fileImportFailure
self.focusedField = focusedField
self.isImportingFile = isImportingFile
self.passphrase = passphrase
self.isRestoring = isRestoring
}
public var file: File?
public var fileImportFailure: String?
@BindableState public var focusedField: Field?
@BindableState public var isImportingFile: Bool
@BindableState public var passphrase: String
@BindableState public var isRestoring: Bool
public enum RestoreAction: Equatable, BindableAction {
case importFileTapped
case fileImport(Result<URL, NSError>)
case restoreTapped
case binding(BindingAction<RestoreState>)
public init(
messenger: Messenger,
mainQueue: AnySchedulerOf<DispatchQueue>,
bgQueue: AnySchedulerOf<DispatchQueue>
) {
self.messenger = messenger
self.mainQueue = mainQueue
self.bgQueue = bgQueue
}
public var messenger: Messenger
public var loadData: URLDataLoader
public var mainQueue: AnySchedulerOf<DispatchQueue>
public var bgQueue: AnySchedulerOf<DispatchQueue>
public static let unimplemented = RestoreEnvironment(
messenger: .unimplemented,
mainQueue: .unimplemented,
bgQueue: .unimplemented
)
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
public let restoreReducer = Reducer<RestoreState, RestoreAction, RestoreEnvironment>
{ state, action, env in
switch action {
case .importFileTapped:
state.isImportingFile = true
state.fileImportFailure = nil
return .none
case .fileImport(.success(let url)):
state.isImportingFile = false
do {
state.file = .init(
name: url.lastPathComponent,
data: try env.loadData(url)
)
state.fileImportFailure = nil
} catch {
state.file = nil
state.fileImportFailure = error.localizedDescription
}
return .none
case .fileImport(.failure(let error)):
state.isImportingFile = false
state.file = nil
state.fileImportFailure = error.localizedDescription
return .none
case .restoreTapped:
guard let backupData = state.file?.data, backupData.count > 0 else { return .none }
let backupPassphrase = state.passphrase
state.isRestoring = true
backupData: backupData,
backupPassphrase: backupPassphrase
)
let facts = try env.messenger.ud.tryGet().getFacts()
try env.db().saveContact(Contact(
id: try env.messenger.e2e.tryGet().getContact().getId(),
username: facts.get(.username)?.value,
email: facts.get(.email)?.value,
phone: facts.get(.phone)?.value,
try result.restoredContacts.forEach { contactId in
if try env.db().fetchContacts(.init(id: [contactId])).isEmpty {
try env.db().saveContact(Contact(
id: contactId,
createdAt: env.now()
))
return .success(.finished)
} catch {
var errors = [error as NSError]
do {
try env.messenger.destroy()
} catch {
errors.append(error as NSError)
}
return .success(.failed(errors))
}
}
.subscribe(on: env.bgQueue)
.receive(on: env.mainQueue)
.eraseToEffect()
case .finished:
state.isRestoring = false
return .none
state.restoreFailures = errors.map(\.localizedDescription)
return .none
case .binding(_):
return .none
}
}
.binding()