diff --git a/FlowCrypt.xcworkspace/xcshareddata/swiftpm/Package.resolved b/FlowCrypt.xcworkspace/xcshareddata/swiftpm/Package.resolved index ba7a595ad..23f7bb755 100644 --- a/FlowCrypt.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/FlowCrypt.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -96,8 +96,8 @@ "repositoryURL": "https://github.com/realm/realm-cocoa", "state": { "branch": null, - "revision": "7ca0ce1dd58553d5be1ec9cc7283b068c256979d", - "version": "10.17.0" + "revision": "9f43d0da902c55b493d6c8bb63203764caa8acbe", + "version": "10.18.0" } }, { diff --git a/FlowCrypt/Controllers/Compose/ComposeViewController.swift b/FlowCrypt/Controllers/Compose/ComposeViewController.swift index e93e5e3c5..2f769b799 100644 --- a/FlowCrypt/Controllers/Compose/ComposeViewController.swift +++ b/FlowCrypt/Controllers/Compose/ComposeViewController.swift @@ -425,7 +425,7 @@ extension ComposeViewController { message: sendableMsg, threadId: input.threadId, progressHandler: { [weak self] progress in - self?.updateSpinner(progress: progress) + self?.updateSpinner(progress: progress, systemImageName: "checkmark.circle") } ) handleSuccessfullySentMessage() diff --git a/FlowCrypt/Controllers/Inbox/InboxViewController.swift b/FlowCrypt/Controllers/Inbox/InboxViewController.swift index fdccd4b09..2b33cb3f8 100644 --- a/FlowCrypt/Controllers/Inbox/InboxViewController.swift +++ b/FlowCrypt/Controllers/Inbox/InboxViewController.swift @@ -5,7 +5,6 @@ import AsyncDisplayKit import FlowCryptCommon import FlowCryptUI -import Promises /** * View controller which shows message list of selected folder or inbox @@ -369,7 +368,7 @@ extension InboxViewController: ASTableDataSource, ASTableDelegate { func tableNode(_ tableNode: ASTableNode, didSelectRowAt indexPath: IndexPath) { tableNode.deselectRow(at: indexPath, animated: true) guard let message = messages[safe: indexPath.row] else { return } - msgListOpenMsgElseShowToast(with: message, path: viewModel.path) + msgListOpenMsg(with: message, path: viewModel.path) // TODO: uncomment in "sent message from draft" feature // if viewModel.isDrafts { diff --git a/FlowCrypt/Controllers/MessageList Extension/MsgListViewConroller.swift b/FlowCrypt/Controllers/MessageList Extension/MsgListViewConroller.swift index 295652496..e1f58b584 100644 --- a/FlowCrypt/Controllers/MessageList Extension/MsgListViewConroller.swift +++ b/FlowCrypt/Controllers/MessageList Extension/MsgListViewConroller.swift @@ -9,27 +9,23 @@ import UIKit protocol MsgListViewConroller { - func msgListOpenMsgElseShowToast(with message: Message, path: String) + func msgListOpenMsg(with message: Message, path: String) func msgListGetIndex(message: Message) -> Array.Index? func msgListUpdateReadFlag(message: Message, at index: Int) func msgListRenderAsRemoved(message _: Message, at index: Int) } extension MsgListViewConroller where Self: UIViewController { - func msgListOpenMsgElseShowToast(with message: Message, path: String) { - if message.size ?? 0 > GeneralConstants.Global.messageSizeLimit { - showToast("Messages larger than 5MB are not supported yet") - } else { - let messageInput = MessageViewController.Input( - objMessage: message, - bodyMessage: nil, - path: path - ) - let msgVc = MessageViewController(input: messageInput) { [weak self] operation, message in - self?.msgListHandleOperation(message: message, operation: operation) - } - navigationController?.pushViewController(msgVc, animated: true) + func msgListOpenMsg(with message: Message, path: String) { + let messageInput = MessageViewController.Input( + objMessage: message, + bodyMessage: nil, + path: path + ) + let msgVc = MessageViewController(input: messageInput) { [weak self] operation, message in + self?.msgListHandleOperation(message: message, operation: operation) } + navigationController?.pushViewController(msgVc, animated: true) } private func msgListHandleOperation(message: Message, operation: MessageViewController.MessageAction) { diff --git a/FlowCrypt/Controllers/Msg/MessageViewController.swift b/FlowCrypt/Controllers/Msg/MessageViewController.swift index 5c5f7adec..d1fc2e6e7 100644 --- a/FlowCrypt/Controllers/Msg/MessageViewController.swift +++ b/FlowCrypt/Controllers/Msg/MessageViewController.swift @@ -148,13 +148,17 @@ final class MessageViewController: TableNodeViewController { } // MARK: - Message - extension MessageViewController { private func fetchDecryptAndRenderMsg() { - showSpinner("loading_title".localized, isUserInteractionEnabled: true) + handleFetchProgress(state: .fetch) + Task { do { - processedMessage = try await serviceActor.fetchDecryptAndRenderMsg(message: input.objMessage, path: input.path) + processedMessage = try await serviceActor.fetchDecryptAndRenderMsg(message: input.objMessage, + path: input.path, + progressHandler: { [weak self] state in + self?.handleFetchProgress(state: state) + }) handleReceivedMessage() } catch { handleError(error) @@ -179,6 +183,17 @@ extension MessageViewController { } } + private func handleFetchProgress(state: MessageFetchState) { + switch state { + case .fetch: + showSpinner("loading_title".localized, isUserInteractionEnabled: true) + case .download(let progress): + updateSpinner(label: "downloading_title".localized, progress: progress) + case .decrypt: + updateSpinner(label: "decrypting_title".localized) + } + } + private func handleReceivedMessage() { hideSpinner() node.reloadData() @@ -539,16 +554,20 @@ private actor ServiceActor { self.messageProvider = messageProvider } - func fetchDecryptAndRenderMsg(message: Message, path: String) async throws -> ProcessedMessage { - let rawMimeData = try awaitPromise(messageProvider.fetchMsg(message: message, folder: path)) + func fetchDecryptAndRenderMsg(message: Message, path: String, + progressHandler: ((MessageFetchState) -> Void)?) async throws -> ProcessedMessage { + let rawMimeData = try await messageProvider.fetchMsg(message: message, + folder: path, + progressHandler: progressHandler) + progressHandler?(.decrypt) return try await messageService.decryptAndProcessMessage(mime: rawMimeData) } func checkAndPotentiallySaveEnteredPassPhrase(_ passPhrase: String) async throws -> Bool { - return try await messageService.checkAndPotentiallySaveEnteredPassPhrase(passPhrase) + try await messageService.checkAndPotentiallySaveEnteredPassPhrase(passPhrase) } func decryptAndProcessMessage(mime: Data) async throws -> ProcessedMessage { - return try await messageService.decryptAndProcessMessage(mime: mime) + try await messageService.decryptAndProcessMessage(mime: mime) } } diff --git a/FlowCrypt/Controllers/Search/SearchViewController.swift b/FlowCrypt/Controllers/Search/SearchViewController.swift index ea3f71a13..e363a39ee 100644 --- a/FlowCrypt/Controllers/Search/SearchViewController.swift +++ b/FlowCrypt/Controllers/Search/SearchViewController.swift @@ -196,7 +196,7 @@ extension SearchViewController: ASTableDataSource, ASTableDelegate { tableNode.deselectRow(at: indexPath, animated: false) guard let message = state.messages[safe: indexPath.row] else { return } - msgListOpenMsgElseShowToast(with: message, path: folderPath) + msgListOpenMsg(with: message, path: folderPath) } } diff --git a/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift b/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift index f0bec4316..4a913bfa2 100644 --- a/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupBackupsViewController.swift @@ -5,7 +5,6 @@ import AsyncDisplayKit import FlowCryptCommon import FlowCryptUI -import Promises /** * Scene which is responsible for recovering user account with backups from inbox and entered pass phrase diff --git a/FlowCrypt/Controllers/Setup/SetupCreatePassphraseAbstractViewController.swift b/FlowCrypt/Controllers/Setup/SetupCreatePassphraseAbstractViewController.swift index 00d8caada..a834d3a08 100644 --- a/FlowCrypt/Controllers/Setup/SetupCreatePassphraseAbstractViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupCreatePassphraseAbstractViewController.swift @@ -9,7 +9,6 @@ import AsyncDisplayKit import FlowCryptCommon import FlowCryptUI -import Promises /** * Controller which decalres a base logic for passphrase setup diff --git a/FlowCrypt/Controllers/Setup/SetupEKMKeyViewController.swift b/FlowCrypt/Controllers/Setup/SetupEKMKeyViewController.swift index 9bf577908..7f808ad1f 100644 --- a/FlowCrypt/Controllers/Setup/SetupEKMKeyViewController.swift +++ b/FlowCrypt/Controllers/Setup/SetupEKMKeyViewController.swift @@ -9,7 +9,6 @@ import AsyncDisplayKit import FlowCryptCommon import FlowCryptUI -import Promises enum CreatePassphraseWithExistingKeyError: Error { // No private key was found diff --git a/FlowCrypt/Controllers/SideMenu/Menu/MyMenuViewController.swift b/FlowCrypt/Controllers/SideMenu/Menu/MyMenuViewController.swift index 8caa99c7f..9c30747f4 100644 --- a/FlowCrypt/Controllers/SideMenu/Menu/MyMenuViewController.swift +++ b/FlowCrypt/Controllers/SideMenu/Menu/MyMenuViewController.swift @@ -5,7 +5,6 @@ import AsyncDisplayKit import ENSwiftSideMenu import FlowCryptUI -import Promises /** * Menu view controller diff --git a/FlowCrypt/Extensions/UIViewController+Spinner.swift b/FlowCrypt/Extensions/UIViewController+Spinner.swift index ae72e0eec..f8d3cdf8a 100644 --- a/FlowCrypt/Extensions/UIViewController+Spinner.swift +++ b/FlowCrypt/Extensions/UIViewController+Spinner.swift @@ -10,6 +10,10 @@ import MBProgressHUD import UIKit extension UIViewController { + var currentProgressHUD: MBProgressHUD { + MBProgressHUD.forView(view) ?? MBProgressHUD.showAdded(to: view, animated: true) + } + func showSpinner(_ message: String = "loading_title".localized, isUserInteractionEnabled: Bool = false) { DispatchQueue.main.async { guard self.view.subviews.first(where: { $0 is MBProgressHUD }) == nil else { @@ -29,16 +33,18 @@ extension UIViewController { systemImageName: String? = nil) { DispatchQueue.main.async { if let progress = progress { - if progress >= 1 { + if progress >= 1, let imageName = systemImageName { self.updateSpinner(label: "compose_sent".localized, - systemImageName: "checkmark.circle") + systemImageName: imageName) } else { self.showProgressHUD(progress: progress, label: label) } } else if let imageName = systemImageName { self.showProgressHUDWithCustomImage(imageName: imageName, label: label) + } else { + self.currentProgressHUD.mode = .indeterminate + self.currentProgressHUD.label.text = label } - } } @@ -54,22 +60,18 @@ extension UIViewController { extension UIViewController { private func showProgressHUD(progress: Float, label: String) { - guard let hud = MBProgressHUD.forView(view) else { return } - let percent = Int(progress * 100) - hud.label.text = "\(label) \(percent)%" - hud.progress = progress - hud.mode = .annularDeterminate + currentProgressHUD.label.text = "\(label) \(percent)%" + currentProgressHUD.progress = progress + currentProgressHUD.mode = .annularDeterminate } private func showProgressHUDWithCustomImage(imageName: String, label: String) { - guard let hud = MBProgressHUD.forView(view) else { return } - let configuration = UIImage.SymbolConfiguration(pointSize: 36) let imageView = UIImageView(image: .init(systemName: imageName, withConfiguration: configuration)) - hud.minSize = CGSize(width: 150, height: 90) - hud.customView = imageView - hud.mode = .customView - hud.label.text = label + currentProgressHUD.minSize = CGSize(width: 150, height: 90) + currentProgressHUD.customView = imageView + currentProgressHUD.mode = .customView + currentProgressHUD.label.text = label } } diff --git a/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift b/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift index 25270ee35..aeefa2614 100644 --- a/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift +++ b/FlowCrypt/Functionality/DataManager/Encrypted Storage/EncryptedStorage.swift @@ -8,7 +8,6 @@ import FlowCryptCommon import Foundation -import Promises import RealmSwift // swiftlint:disable force_try diff --git a/FlowCrypt/Functionality/DataManager/UserAccountService.swift b/FlowCrypt/Functionality/DataManager/UserAccountService.swift index 04a06affb..f3cdd901b 100644 --- a/FlowCrypt/Functionality/DataManager/UserAccountService.swift +++ b/FlowCrypt/Functionality/DataManager/UserAccountService.swift @@ -8,7 +8,6 @@ import FlowCryptCommon import Foundation -import Promises protocol UserAccountServiceType { func startSessionFor(user type: SessionType) diff --git a/FlowCrypt/Functionality/Mail Provider/Message Provider/Gmail+Message.swift b/FlowCrypt/Functionality/Mail Provider/Message Provider/Gmail+Message.swift index 6cd6abab0..749c88321 100644 --- a/FlowCrypt/Functionality/Mail Provider/Message Provider/Gmail+Message.swift +++ b/FlowCrypt/Functionality/Mail Provider/Message Provider/Gmail+Message.swift @@ -7,35 +7,86 @@ // import GoogleAPIClientForREST_Gmail -import Promises +import GTMSessionFetcherCore extension GmailService: MessageProvider { - func fetchMsg(message: Message, folder: String) -> Promise { - Promise { resolve, reject in + func fetchMsg(message: Message, + folder: String, + progressHandler: ((MessageFetchState) -> Void)?) async throws -> Data { + try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in guard let identifier = message.identifier.stringId else { - return reject(GmailServiceError.missedMessageInfo("id")) + continuation.resume(throwing: GmailServiceError.missedMessageInfo("id")) + return } - let query = GTLRGmailQuery_UsersMessagesGet.query(withUserId: .me, identifier: identifier) - query.format = kGTLRGmailFormatRaw + Task { + let messageSize = try await self.fetchMessageSize(identifier: identifier) + let fetcher = self.createMessageFetcher(identifier: identifier) + fetcher.receivedProgressBlock = { _, received in + let progress = min(Float(received)/messageSize, 1) + progressHandler?(.download(progress)) + } + fetcher.beginFetch { data, error in + if let error = error { + continuation.resume(throwing: GmailServiceError.providerError(error)) + return + } + + guard let data = data, + let dictionary = try? JSONSerialization.jsonObject(with: data, options: []) as? [String: Any], + let raw = dictionary["raw"] as? String + else { + continuation.resume(throwing: GmailServiceError.missedMessageInfo("raw")) + return + } + + guard let data = GTLRDecodeWebSafeBase64(raw) else { + continuation.resume(throwing: GmailServiceError.missedMessageInfo("data")) + return + } + + continuation.resume(returning: data) + } + } + } + } + + private func fetchMessageSize(identifier: String) async throws -> Float { + try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in + let query = createMessageQuery(identifier: identifier, format: kGTLRGmailFormatMetadata) self.gmailService.executeQuery(query) { _, data, error in if let error = error { - reject(GmailServiceError.providerError(error)) + continuation.resume(throwing: GmailServiceError.providerError(error)) return } + guard let gmailMessage = data as? GTLRGmail_Message else { - return reject(AppErr.cast("GTLRGmail_Message")) - } - guard let raw = gmailMessage.raw else { - return reject(GmailServiceError.missedMessageInfo("raw")) + continuation.resume(throwing: AppErr.cast("GTLRGmail_Message")) + return } - guard let data = GTLRDecodeWebSafeBase64(raw) else { - return reject(GmailServiceError.missedMessageInfo("data")) + guard let sizeEstimate = gmailMessage.sizeEstimate?.floatValue else { + continuation.resume(throwing: GmailServiceError.missedMessageInfo("sizeEstimate")) + return } - resolve(data) + + // google returns smaller estimated size + let totalSize = sizeEstimate * Float(1.3) + continuation.resume(with: .success(totalSize)) } } } + + private func createMessageFetcher(identifier: String) -> GTMSessionFetcher { + let query = createMessageQuery(identifier: identifier, format: kGTLRGmailFormatRaw) + let request = gmailService.request(for: query) as URLRequest + return gmailService.fetcherService.fetcher(with: request) + } + + private func createMessageQuery(identifier: String, format: String) -> GTLRGmailQuery_UsersMessagesGet { + let query = GTLRGmailQuery_UsersMessagesGet.query(withUserId: .me, identifier: identifier) + query.format = format + return query + } } diff --git a/FlowCrypt/Functionality/Mail Provider/Message Provider/Imap+Message.swift b/FlowCrypt/Functionality/Mail Provider/Message Provider/Imap+Message.swift index b450e8f18..784e18ea2 100644 --- a/FlowCrypt/Functionality/Mail Provider/Message Provider/Imap+Message.swift +++ b/FlowCrypt/Functionality/Mail Provider/Message Provider/Imap+Message.swift @@ -10,18 +10,29 @@ import Foundation import Promises extension Imap: MessageProvider { - func fetchMsg(message: Message, folder: String) -> Promise { - Promise { [weak self] resolve, reject in - guard let self = self else { - return reject(AppErr.nilSelf) - } - + func fetchMsg(message: Message, + folder: String, + progressHandler: ((MessageFetchState) -> Void)?) async throws -> Data { + try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) in guard let identifier = message.identifier.intId else { assertionFailure() - return reject(AppErr.unexpected("Missed message identifier")) + continuation.resume(throwing: AppErr.unexpected("Missed message identifier")) + return } - let retry = { self.fetchMsg(message: message, folder: folder) } + let resolve = { continuation.resume(returning: $0) } + let reject = { continuation.resume(throwing: $0) } + let retry = { Promise { resolve, reject in + Task { + do { + let data = try await self.fetchMsg(message: message, folder: folder, progressHandler: progressHandler) + resolve(data) + } catch { + reject(error) + } + } + }} + self.imapSess? .fetchMessageOperation(withFolder: folder, uid: UInt32(identifier)) .start(self.finalize("fetchMsg", resolve, reject, retry: retry)) diff --git a/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageProvider.swift b/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageProvider.swift index 75a4c3c87..4664aef35 100644 --- a/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageProvider.swift +++ b/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageProvider.swift @@ -7,8 +7,9 @@ // import Foundation -import Promises protocol MessageProvider { - func fetchMsg(message: Message, folder: String) -> Promise + func fetchMsg(message: Message, + folder: String, + progressHandler: ((MessageFetchState) -> Void)?) async throws -> Data } diff --git a/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift b/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift index 04447a0ca..4d4d4ba61 100644 --- a/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift +++ b/FlowCrypt/Functionality/Mail Provider/Message Provider/MessageService.swift @@ -7,7 +7,6 @@ // import Foundation -import Promises import FlowCryptCommon // MARK: - MessageAttachment @@ -20,6 +19,11 @@ struct MessageAttachment: FileType { } } +// MARK: - MessageFetchState +enum MessageFetchState { + case fetch, download(Float), decrypt +} + // MARK: - ProcessedMessage struct ProcessedMessage { enum MessageType { diff --git a/FlowCrypt/Functionality/Services/Account Server Services/BackendApi.swift b/FlowCrypt/Functionality/Services/Account Server Services/BackendApi.swift index 7594b4146..7f1dc4d35 100644 --- a/FlowCrypt/Functionality/Services/Account Server Services/BackendApi.swift +++ b/FlowCrypt/Functionality/Services/Account Server Services/BackendApi.swift @@ -3,7 +3,6 @@ // import Foundation -import Promises /// Backend API for regular consumers and small businesses /// (not implemented on iOS yet) diff --git a/FlowCrypt/Functionality/Services/Backup Services/BackupService.swift b/FlowCrypt/Functionality/Services/Backup Services/BackupService.swift index 623e04218..1fb7e6f25 100644 --- a/FlowCrypt/Functionality/Services/Backup Services/BackupService.swift +++ b/FlowCrypt/Functionality/Services/Backup Services/BackupService.swift @@ -7,7 +7,6 @@ // import Combine -import Promises import UIKit final class BackupService { diff --git a/FlowCrypt/Functionality/Services/Backup Services/BackupServiceType.swift b/FlowCrypt/Functionality/Services/Backup Services/BackupServiceType.swift index 943d389c6..1fd5ea9a8 100644 --- a/FlowCrypt/Functionality/Services/Backup Services/BackupServiceType.swift +++ b/FlowCrypt/Functionality/Services/Backup Services/BackupServiceType.swift @@ -6,7 +6,6 @@ // Copyright © 2017-present FlowCrypt a. s. All rights reserved. // -import Promises import UIKit protocol BackupServiceType { diff --git a/FlowCrypt/Functionality/Services/Client Configuration Service/LocalClientConfiguration.swift b/FlowCrypt/Functionality/Services/Client Configuration Service/LocalClientConfiguration.swift index 0e894ef9d..a4029e0f7 100644 --- a/FlowCrypt/Functionality/Services/Client Configuration Service/LocalClientConfiguration.swift +++ b/FlowCrypt/Functionality/Services/Client Configuration Service/LocalClientConfiguration.swift @@ -7,7 +7,6 @@ // import Foundation -import Promises import RealmSwift import IDZSwiftCommonCrypto diff --git a/FlowCrypt/Functionality/Services/GeneralConstants.swift b/FlowCrypt/Functionality/Services/GeneralConstants.swift index df1b95244..950764d1a 100644 --- a/FlowCrypt/Functionality/Services/GeneralConstants.swift +++ b/FlowCrypt/Functionality/Services/GeneralConstants.swift @@ -13,7 +13,6 @@ enum GeneralConstants { enum Global { static let generalError = -1 - static let messageSizeLimit: Int = 5_000_000 static let attachmentSizeLimit: Int = 10_000_000 } diff --git a/FlowCrypt/Functionality/Services/GlobalRouter.swift b/FlowCrypt/Functionality/Services/GlobalRouter.swift index 1fcd302fa..10154e8b4 100644 --- a/FlowCrypt/Functionality/Services/GlobalRouter.swift +++ b/FlowCrypt/Functionality/Services/GlobalRouter.swift @@ -7,7 +7,6 @@ // import FlowCryptCommon -import Promises import UIKit protocol GlobalRouterType { diff --git a/FlowCrypt/Functionality/Services/Local Pub Key Services/ContactsService.swift b/FlowCrypt/Functionality/Services/Local Pub Key Services/ContactsService.swift index 29034bcda..c71dfb9e9 100644 --- a/FlowCrypt/Functionality/Services/Local Pub Key Services/ContactsService.swift +++ b/FlowCrypt/Functionality/Services/Local Pub Key Services/ContactsService.swift @@ -7,7 +7,6 @@ // import Foundation -import Promises enum ContactsError: Error { case keyMissing diff --git a/FlowCrypt/Functionality/Services/Local Pub Key Services/LocalContactsProvider.swift b/FlowCrypt/Functionality/Services/Local Pub Key Services/LocalContactsProvider.swift index 41ab53e04..55514c4a4 100644 --- a/FlowCrypt/Functionality/Services/Local Pub Key Services/LocalContactsProvider.swift +++ b/FlowCrypt/Functionality/Services/Local Pub Key Services/LocalContactsProvider.swift @@ -7,7 +7,6 @@ // import Foundation -import Promises import RealmSwift protocol LocalContactsProviderType: PublicKeyProvider { diff --git a/FlowCrypt/Resources/en.lproj/Localizable.strings b/FlowCrypt/Resources/en.lproj/Localizable.strings index c0aa6539d..15be9c74b 100644 --- a/FlowCrypt/Resources/en.lproj/Localizable.strings +++ b/FlowCrypt/Resources/en.lproj/Localizable.strings @@ -1,6 +1,8 @@ // COMMON "loading_title" = "Loading"; +"downloading_title" = "Downloading"; "encrypting_title" = "Encrypting..."; +"decrypting_title" = "Decrypting..."; "sending_title" = "Sending"; "unknown_title" = "(unknown)"; "retry_title" = "Retry";