diff --git a/App/client-ios.xcodeproj/project.pbxproj b/App/client-ios.xcodeproj/project.pbxproj index bddd3873d8a672f63620f6732ff81d1cc9826096..c5951402ab44c15cbccd0d4017c7b572c1365596 100644 --- a/App/client-ios.xcodeproj/project.pbxproj +++ b/App/client-ios.xcodeproj/project.pbxproj @@ -448,7 +448,7 @@ CODE_SIGN_ENTITLEMENTS = "client-ios/Resources/client-ios.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 266; + CURRENT_PROJECT_VERSION = 267; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = S6JDM2WW29; ENABLE_BITCODE = NO; @@ -487,7 +487,7 @@ CODE_SIGN_ENTITLEMENTS = "client-ios/Resources/client-ios.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 266; + CURRENT_PROJECT_VERSION = 267; DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; DEVELOPMENT_TEAM = S6JDM2WW29; ENABLE_BITCODE = NO; @@ -522,7 +522,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationExtension/NotificationExtension.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 266; + CURRENT_PROJECT_VERSION = 267; DEVELOPMENT_TEAM = S6JDM2WW29; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( @@ -553,7 +553,7 @@ CODE_SIGN_ENTITLEMENTS = NotificationExtension/NotificationExtension.entitlements; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 266; + CURRENT_PROJECT_VERSION = 267; DEVELOPMENT_TEAM = S6JDM2WW29; ENABLE_BITCODE = NO; FRAMEWORK_SEARCH_PATHS = ( diff --git a/Sources/BackupFeature/Service/BackupService.swift b/Sources/BackupFeature/Service/BackupService.swift index 1d4857924384d0cc263f947f46a87fc0bd8bc86f..b71916f161e461ef6a48338070a9150b04bd017a 100644 --- a/Sources/BackupFeature/Service/BackupService.swift +++ b/Sources/BackupFeature/Service/BackupService.swift @@ -22,24 +22,17 @@ public final class BackupService { @Dependency var driveService: GoogleDriveInterface @KeyObject(.username, defaultValue: nil) var username: String? - @KeyObject(.backupSettings, defaultValue: Data()) private var storedSettings: Data + @KeyObject(.backupSettings, defaultValue: nil) var storedSettings: Data? public var settingsPublisher: AnyPublisher<BackupSettings, Never> { settings.handleEvents(receiveSubscription: { [weak self] _ in guard let self = self else { return } - - let lastRefreshDate = self.settingsLastRefreshedDate ?? Date.distantPast - - if Date().timeIntervalSince(lastRefreshDate) < 10 { return } - - self.settingsLastRefreshedDate = Date() self.refreshConnections() self.refreshBackups() }).eraseToAnyPublisher() } private var connType: ConnectionType = .wifi - private var settingsLastRefreshedDate: Date? private var cancellables = Set<AnyCancellable>() private lazy var settings = CurrentValueSubject<BackupSettings, Never>(.init(fromData: storedSettings)) @@ -140,6 +133,8 @@ extension BackupService { if isAutomaticEnabled && hasEnabledService { performBackup() } + + refreshBackups() } public func setBackupOnlyOnWifi(_ enabled: Bool) { @@ -226,7 +221,11 @@ extension BackupService { } private func refreshBackups() { + print(">>> Refreshing backups...") + if icloudService.isAuthorized() { + print(">>> Refreshing icloud backup...") + icloudService.downloadMetadata { [weak settings] in guard let settings = settings else { return } @@ -244,6 +243,8 @@ extension BackupService { } if sftpService.isAuthorized() { + print(">>> Refreshing sftp backup...") + sftpService.fetchMetadata { [weak settings] in guard let settings = settings else { return } @@ -261,6 +262,8 @@ extension BackupService { } if dropboxService.isAuthorized() { + print(">>> Refreshing dropbox backup...") + dropboxService.downloadMetadata { [weak settings] in guard let settings = settings else { return } @@ -278,6 +281,7 @@ extension BackupService { } driveService.isAuthorized { [weak settings] isAuthorized in + print(">>> Refreshing drive backup...") guard let settings = settings else { return } if isAuthorized { @@ -315,6 +319,7 @@ extension BackupService { switch enabledService { case .drive: + print(">>> Performing upload on drive") driveService.uploadBackup(url) { switch $0 { case .success(let metadata): @@ -328,6 +333,7 @@ extension BackupService { } } case .icloud: + print(">>> Performing upload on iCloud") icloudService.uploadBackup(url) { switch $0 { case .success(let metadata): @@ -341,19 +347,25 @@ extension BackupService { } } case .dropbox: + print(">>> Performing upload on dropbox") dropboxService.uploadBackup(url) { switch $0 { case .success(let metadata): + print(">>> Performed upload on dropbox: \(metadata)") + self.settings.value.backups[.dropbox] = .init( id: metadata.path, date: metadata.modifiedDate, size: metadata.size ) + + self.refreshBackups() case .failure(let error): print(error.localizedDescription) } } case .sftp: + print(">>> Performing upload on sftp") sftpService.uploadBackup(url: url) { switch $0 { case .success(let backup): diff --git a/Sources/BackupFeature/ViewModels/BackupConfigViewModel.swift b/Sources/BackupFeature/ViewModels/BackupConfigViewModel.swift index 74ce209fbf6b2d71a81bb3e93a88fa9838150ea3..0b205733e2f44e8554a8c3df9add0d8382d72af7 100644 --- a/Sources/BackupFeature/ViewModels/BackupConfigViewModel.swift +++ b/Sources/BackupFeature/ViewModels/BackupConfigViewModel.swift @@ -79,6 +79,7 @@ extension BackupConfigViewModel { }, lastBackup: { context.service.settingsPublisher + .print(">>> lastBackup updated!") .map { guard let enabledService = $0.enabledService else { return nil } return $0.backups[enabledService] diff --git a/Sources/ChatFeature/Controllers/GroupChatController.swift b/Sources/ChatFeature/Controllers/GroupChatController.swift index d147ebbd99e224c4482f484042059250d2b9aa21..6080ba62c24a8096e3ec4d4ecaee1fbf20fcb89b 100644 --- a/Sources/ChatFeature/Controllers/GroupChatController.swift +++ b/Sources/ChatFeature/Controllers/GroupChatController.swift @@ -439,6 +439,35 @@ extension GroupChatController: UICollectionViewDataSource { return cell } + } else if item.status == .sendingTimedOut { + if let replyMessageId = item.replyMessageId { + let cell: OutgoingFailedGroupReplyCell = collectionView.dequeueReusableCell(forIndexPath: indexPath) + + Bubbler.buildReplyGroup( + bubble: cell.rightView, + with: item, + reply: replyContent(replyMessageId), + sender: name(item.senderId) + ) + + cell.canReply = false + cell.performReply = performReply + + return cell + } else { + let cell: OutgoingFailedGroupTextCell = collectionView.dequeueReusableCell(forIndexPath: indexPath) + + Bubbler.buildGroup( + bubble: cell.rightView, + with: item, + with: name(item.senderId) + ) + + cell.canReply = false + cell.performReply = performReply + + return cell + } } else { if let replyMessageId = item.replyMessageId { let cell: OutgoingGroupReplyCell = collectionView.dequeueReusableCell(forIndexPath: indexPath) diff --git a/Sources/ChatFeature/Helpers/BubbleBuilder.swift b/Sources/ChatFeature/Helpers/BubbleBuilder.swift index ae33fd01e60bfaf501583779e11eb59575444b4f..e6d71c81ee3557385b2d9ec3e349ece13948ac33 100644 --- a/Sources/ChatFeature/Helpers/BubbleBuilder.swift +++ b/Sources/ChatFeature/Helpers/BubbleBuilder.swift @@ -235,7 +235,15 @@ final class Bubbler { bubble.replyView.space.backgroundColor = Asset.brandPrimary.color bubble.lockerImageView.removeFromSuperview() bubble.revertBottomStackOrder() - case .sendingFailed, .sendingTimedOut: + case .sendingTimedOut: + bubble.senderLabel.removeFromSuperview() + bubble.backgroundColor = Asset.accentWarning.color + bubble.textView.textColor = Asset.neutralWhite.color + bubble.dateLabel.textColor = Asset.neutralWhite.color + roundButtonColor = Asset.neutralWhite.color + bubble.replyView.space.backgroundColor = Asset.neutralWhite.color + bubble.replyView.container.backgroundColor = Asset.brandLight.color + case .sendingFailed: bubble.senderLabel.removeFromSuperview() bubble.backgroundColor = Asset.accentDanger.color bubble.textView.textColor = Asset.neutralWhite.color @@ -293,7 +301,13 @@ final class Bubbler { roundButtonColor = Asset.neutralDisabled.color bubble.lockerImageView.removeFromSuperview() bubble.revertBottomStackOrder() - case .sendingFailed, .sendingTimedOut: + case .sendingTimedOut: + bubble.senderLabel.removeFromSuperview() + bubble.backgroundColor = Asset.accentWarning.color + bubble.textView.textColor = Asset.neutralWhite.color + bubble.dateLabel.textColor = Asset.neutralWhite.color + roundButtonColor = Asset.neutralWhite.color + case .sendingFailed: bubble.senderLabel.removeFromSuperview() bubble.backgroundColor = Asset.accentDanger.color bubble.textView.textColor = Asset.neutralWhite.color diff --git a/Sources/DropboxFeature/Resources/Dropbox-Keys.plist b/Sources/DropboxFeature/Resources/Dropbox-Keys.plist index ecf15d0188386cac204a2e243e59320620a6c9c5..5348e76e769e5604dfacdb05e1cd396bbb39a15b 100644 --- a/Sources/DropboxFeature/Resources/Dropbox-Keys.plist +++ b/Sources/DropboxFeature/Resources/Dropbox-Keys.plist @@ -3,6 +3,6 @@ <plist version="1.0"> <dict> <key>DROPBOX_APP_KEY</key> - <string></string> + <string>ppx0de5f16p9aq2</string> </dict> </plist> diff --git a/Sources/GoogleDriveFeature/Resources/GoogleDrive-Keys.plist b/Sources/GoogleDriveFeature/Resources/GoogleDrive-Keys.plist index 614bac60d3fd5014a33613ee3e83c72656d0854d..d25a7ec0e57eaa6ec8785dd599263d2ef82ae613 100644 --- a/Sources/GoogleDriveFeature/Resources/GoogleDrive-Keys.plist +++ b/Sources/GoogleDriveFeature/Resources/GoogleDrive-Keys.plist @@ -3,6 +3,6 @@ <plist version="1.0"> <dict> <key>DRIVE_API_KEY</key> - <string></string> + <string>AIzaSyCbI2yQ7pbuVSRvraqanjGcS9CDrjD7lNU</string> </dict> </plist> diff --git a/Sources/Models/BackupSettings.swift b/Sources/Models/BackupSettings.swift index d52cb1f0e33149b70a22337f56c23d6814d7f7be..41b5d13d19dccc4aa84fea4a980d6efe0ee26eb7 100644 --- a/Sources/Models/BackupSettings.swift +++ b/Sources/Models/BackupSettings.swift @@ -25,15 +25,24 @@ public struct BackupSettings: Equatable, Codable { (try? PropertyListEncoder().encode(self)) ?? Data() } - public init(fromData data: Data) { - let settings = try? PropertyListDecoder().decode(BackupSettings.self, from: data) + public init(fromData data: Data?) { + if let data = data, let settings = try? PropertyListDecoder().decode(BackupSettings.self, from: data) { self.init( - wifiOnlyBackup: settings?.wifiOnlyBackup ?? false, - automaticBackups: settings?.automaticBackups ?? true, - enabledService: settings?.enabledService, - connectedServices: settings?.connectedServices ?? [], - backups: settings?.backups ?? [:] + wifiOnlyBackup: settings.wifiOnlyBackup, + automaticBackups: settings.automaticBackups, + enabledService: settings.enabledService, + connectedServices: settings.connectedServices, + backups: settings.backups ) + } else { + self.init( + wifiOnlyBackup: false, + automaticBackups: true, + enabledService: nil, + connectedServices: [], + backups: [:] + ) + } } } diff --git a/Sources/ProfileFeature/ViewModels/ProfileCodeViewModel.swift b/Sources/ProfileFeature/ViewModels/ProfileCodeViewModel.swift index d7eb5db970117df0d24c0fe33a464bef841d141e..b7564509410a86b480489e08206abe80e188893d 100644 --- a/Sources/ProfileFeature/ViewModels/ProfileCodeViewModel.swift +++ b/Sources/ProfileFeature/ViewModels/ProfileCodeViewModel.swift @@ -6,95 +6,99 @@ import Defaults import XXClient import Foundation import InputField +import BackupFeature import CombineSchedulers import XXMessengerClient import DependencyInjection struct ProfileCodeViewState: Equatable { - var input: String = "" - var status: InputField.ValidationStatus = .unknown(nil) - var resendDebouncer: Int = 0 + var input: String = "" + var status: InputField.ValidationStatus = .unknown(nil) + var resendDebouncer: Int = 0 } final class ProfileCodeViewModel { - @Dependency var messenger: Messenger + @Dependency var messenger: Messenger + @Dependency var backupService: BackupService - @KeyObject(.email, defaultValue: nil) var email: String? - @KeyObject(.phone, defaultValue: nil) var phone: String? + @KeyObject(.email, defaultValue: nil) var email: String? + @KeyObject(.phone, defaultValue: nil) var phone: String? - let confirmation: AttributeConfirmation + let confirmation: AttributeConfirmation - var timer: Timer? + var timer: Timer? - var completionPublisher: AnyPublisher<AttributeConfirmation, Never> { completionRelay.eraseToAnyPublisher() } - private let completionRelay = PassthroughSubject<AttributeConfirmation, Never>() + var completionPublisher: AnyPublisher<AttributeConfirmation, Never> { completionRelay.eraseToAnyPublisher() } + private let completionRelay = PassthroughSubject<AttributeConfirmation, Never>() - var hud: AnyPublisher<HUDStatus, Never> { hudRelay.eraseToAnyPublisher() } - private let hudRelay = CurrentValueSubject<HUDStatus, Never>(.none) + var hud: AnyPublisher<HUDStatus, Never> { hudRelay.eraseToAnyPublisher() } + private let hudRelay = CurrentValueSubject<HUDStatus, Never>(.none) - var state: AnyPublisher<ProfileCodeViewState, Never> { stateRelay.eraseToAnyPublisher() } - private let stateRelay = CurrentValueSubject<ProfileCodeViewState, Never>(.init()) + var state: AnyPublisher<ProfileCodeViewState, Never> { stateRelay.eraseToAnyPublisher() } + private let stateRelay = CurrentValueSubject<ProfileCodeViewState, Never>(.init()) - var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler() + var backgroundScheduler: AnySchedulerOf<DispatchQueue> = DispatchQueue.global().eraseToAnyScheduler() - init(_ confirmation: AttributeConfirmation) { - self.confirmation = confirmation - didTapResend() - } + init(_ confirmation: AttributeConfirmation) { + self.confirmation = confirmation + didTapResend() + } - func didInput(_ string: String) { - stateRelay.value.input = string - validate() - } + func didInput(_ string: String) { + stateRelay.value.input = string + validate() + } - func didTapResend() { - guard stateRelay.value.resendDebouncer == 0 else { return } + func didTapResend() { + guard stateRelay.value.resendDebouncer == 0 else { return } - stateRelay.value.resendDebouncer = 60 + stateRelay.value.resendDebouncer = 60 - timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] in - guard let self = self, self.stateRelay.value.resendDebouncer > 0 else { - $0.invalidate() - return - } + timer = Timer.scheduledTimer(withTimeInterval: 1, repeats: true) { [weak self] in + guard let self = self, self.stateRelay.value.resendDebouncer > 0 else { + $0.invalidate() + return + } - self.stateRelay.value.resendDebouncer -= 1 - } + self.stateRelay.value.resendDebouncer -= 1 } + } - func didTapNext() { - hudRelay.send(.on) - - backgroundScheduler.schedule { [weak self] in - guard let self = self else { return } - - do { - try self.messenger.ud.get()!.confirmFact( - confirmationId: self.confirmation.confirmationId!, - code: self.stateRelay.value.input - ) - - if self.confirmation.isEmail { - self.email = self.confirmation.content - } else { - self.phone = self.confirmation.content - } - - self.timer?.invalidate() - self.hudRelay.send(.none) - self.completionRelay.send(self.confirmation) - } catch { - self.hudRelay.send(.error(.init(with: error))) - } - } - } + func didTapNext() { + hudRelay.send(.on) - private func validate() { - switch Validator.code.validate(stateRelay.value.input) { - case .success: - stateRelay.value.status = .valid(nil) - case .failure(let error): - stateRelay.value.status = .invalid(error) + backgroundScheduler.schedule { [weak self] in + guard let self = self else { return } + + do { + try self.messenger.ud.get()!.confirmFact( + confirmationId: self.confirmation.confirmationId!, + code: self.stateRelay.value.input + ) + + if self.confirmation.isEmail { + self.email = self.confirmation.content + } else { + self.phone = self.confirmation.content } + + self.timer?.invalidate() + self.hudRelay.send(.none) + self.completionRelay.send(self.confirmation) + + self.backupService.performBackup() + } catch { + self.hudRelay.send(.error(.init(with: error))) + } + } + } + + private func validate() { + switch Validator.code.validate(stateRelay.value.input) { + case .success: + stateRelay.value.status = .valid(nil) + case .failure(let error): + stateRelay.value.status = .invalid(error) } + } } diff --git a/Sources/ProfileFeature/ViewModels/ProfileViewModel.swift b/Sources/ProfileFeature/ViewModels/ProfileViewModel.swift index 1d01467d8e20b16aff653bb67e5a441292b802d6..b1bddd42bbbe751e5194848fca749bb76d85106d 100644 --- a/Sources/ProfileFeature/ViewModels/ProfileViewModel.swift +++ b/Sources/ProfileFeature/ViewModels/ProfileViewModel.swift @@ -8,9 +8,10 @@ import XXClient import Countries import Foundation import Permissions +import BackupFeature +import XXMessengerClient import CombineSchedulers import DependencyInjection -import XXMessengerClient enum ProfileNavigationRoutes { case none @@ -33,7 +34,8 @@ final class ProfileViewModel { @KeyObject(.sharingPhone, defaultValue: false) var isPhoneSharing: Bool @Dependency var messenger: Messenger - @Dependency private var permissions: PermissionHandling + @Dependency var backupService: BackupService + @Dependency var permissions: PermissionHandling var name: String { username! } @@ -106,6 +108,7 @@ final class ProfileViewModel { self.isPhoneSharing = false } + self.backupService.performBackup() self.hudRelay.send(.none) self.refresh() } catch {