diff --git a/Riot.xcodeproj/project.pbxproj b/Riot.xcodeproj/project.pbxproj index 7c1a36f6f..6ed5783ba 100644 --- a/Riot.xcodeproj/project.pbxproj +++ b/Riot.xcodeproj/project.pbxproj @@ -373,6 +373,7 @@ B1CA3A2721EF6914000D1D89 /* UIViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1CA3A2621EF6913000D1D89 /* UIViewController.swift */; }; B1CA3A2921EF692B000D1D89 /* UIView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1CA3A2821EF692B000D1D89 /* UIView.swift */; }; B1CE9EFD22148703000FAE6A /* SignOutAlertPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1CE9EFC22148703000FAE6A /* SignOutAlertPresenter.swift */; }; + B1CE9F062216FB09000FAE6A /* EncryptionKeysExportPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1CE9F052216FB09000FAE6A /* EncryptionKeysExportPresenter.swift */; }; B1D250D82118AA0A000F4E93 /* RoomPredecessorBubbleCell.m in Sources */ = {isa = PBXBuildFile; fileRef = B1D250D72118AA0A000F4E93 /* RoomPredecessorBubbleCell.m */; }; B1D4752721EE4E630067973F /* KeyboardAvoider.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D4752521EE4E620067973F /* KeyboardAvoider.swift */; }; B1D4752821EE4E630067973F /* KeyboardNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = B1D4752621EE4E620067973F /* KeyboardNotification.swift */; }; @@ -1026,6 +1027,7 @@ B1CA3A2621EF6913000D1D89 /* UIViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIViewController.swift; sourceTree = ""; }; B1CA3A2821EF692B000D1D89 /* UIView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UIView.swift; sourceTree = ""; }; B1CE9EFC22148703000FAE6A /* SignOutAlertPresenter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SignOutAlertPresenter.swift; sourceTree = ""; }; + B1CE9F052216FB09000FAE6A /* EncryptionKeysExportPresenter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EncryptionKeysExportPresenter.swift; sourceTree = ""; }; B1D250D62118AA0A000F4E93 /* RoomPredecessorBubbleCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = RoomPredecessorBubbleCell.h; sourceTree = ""; }; B1D250D72118AA0A000F4E93 /* RoomPredecessorBubbleCell.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = RoomPredecessorBubbleCell.m; sourceTree = ""; }; B1D4752521EE4E620067973F /* KeyboardAvoider.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardAvoider.swift; sourceTree = ""; }; @@ -1255,6 +1257,7 @@ B1098C0221ECFEAF000DDA48 /* Setup */, B1FDF56421F726AD00BA3834 /* Recover */, B1107ECB2201BE800038014B /* Banners */, + B1CE9F042216FB09000FAE6A /* ManualExport */, ); path = KeyBackup; sourceTree = ""; @@ -2707,6 +2710,14 @@ path = SignOut; sourceTree = ""; }; + B1CE9F042216FB09000FAE6A /* ManualExport */ = { + isa = PBXGroup; + children = ( + B1CE9F052216FB09000FAE6A /* EncryptionKeysExportPresenter.swift */, + ); + path = ManualExport; + sourceTree = ""; + }; B1D4752421EE4E620067973F /* KeyboardAvoiding */ = { isa = PBXGroup; children = ( @@ -3434,6 +3445,7 @@ B1B5574820EE6C4D00210D55 /* PeopleViewController.m in Sources */, B1B5598720EFC3E000210D55 /* Widget.m in Sources */, B1B557E320EF60B900210D55 /* MessagesSearchResultAttachmentBubbleCell.m in Sources */, + B1CE9F062216FB09000FAE6A /* EncryptionKeysExportPresenter.swift in Sources */, B1B5574420EE6C4D00210D55 /* CallViewController.m in Sources */, B1B5572220EE6C4D00210D55 /* RoomSettingsViewController.m in Sources */, B1B5577320EE702800210D55 /* JitsiViewController.m in Sources */, diff --git a/Riot/Modules/KeyBackup/ManualExport/EncryptionKeysExportPresenter.swift b/Riot/Modules/KeyBackup/ManualExport/EncryptionKeysExportPresenter.swift new file mode 100644 index 000000000..1020b0564 --- /dev/null +++ b/Riot/Modules/KeyBackup/ManualExport/EncryptionKeysExportPresenter.swift @@ -0,0 +1,147 @@ +/* + Copyright 2019 New Vector Ltd + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + */ + +import Foundation + +final class EncryptionKeysExportPresenter: NSObject { + + // MARK: - Constants + + private enum Constants { + static let keyExportFileName = "riot-keys.txt" + } + + // MARK: - Properties + + // MARK: Private + + private let session: MXSession + private let activityViewPresenter: ActivityIndicatorPresenterType + private let keyExportFileURL: URL + + private weak var presentingViewController: UIViewController? + private weak var sourceView: UIView? + private var encryptionKeysExportView: MXKEncryptionKeysExportView? + private var documentInteractionController: UIDocumentInteractionController? + + // MARK: Public + + // MARK: - Setup + + init(session: MXSession) { + self.session = session + self.activityViewPresenter = ActivityIndicatorPresenter() + self.keyExportFileURL = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent(Constants.keyExportFileName) + super.init() + } + + deinit { + self.deleteKeyExportFile() + } + + // MARK: - Public + + func present(from viewController: UIViewController, sourceView: UIView?) { + self.presentingViewController = viewController + self.sourceView = sourceView + + let keysExportView: MXKEncryptionKeysExportView = MXKEncryptionKeysExportView(matrixSession: self.session) + + // Make sure the file is empty + self.deleteKeyExportFile() + + keysExportView.show(in: viewController, + toExportKeysToFile: self.keyExportFileURL, + onLoading: { [weak self] (loading) in + + guard let sself = self else { + return + } + + if loading { + sself.activityViewPresenter.removeCurrentActivityIndicator(animated: false) + sself.activityViewPresenter.presentActivityIndicator(on: viewController.view, animated: true) + } else { + sself.activityViewPresenter.removeCurrentActivityIndicator(animated: true) + } + }, onComplete: { [weak self] (success) in + guard let sself = self else { + return + } + + guard success else { + sself.encryptionKeysExportView = nil + return + } + + sself.presentInteractionDocumentController() + }) + + self.encryptionKeysExportView = keysExportView + } + + // MARK: - Private + + private func presentInteractionDocumentController() { + + let sourceRect: CGRect + + guard let presentingView = self.presentingViewController?.view else { + self.encryptionKeysExportView = nil + return + } + + if let sourceView = self.sourceView { + sourceRect = sourceView.convert(sourceView.bounds, to: presentingView) + } else { + sourceRect = presentingView.bounds + } + + let documentInteractionController = UIDocumentInteractionController(url: self.keyExportFileURL) + documentInteractionController.delegate = self + + if documentInteractionController.presentOptionsMenu(from: sourceRect, in: presentingView, animated: true) { + self.documentInteractionController = documentInteractionController + } else { + self.encryptionKeysExportView = nil + self.deleteKeyExportFile() + } + } + + @objc private func deleteKeyExportFile() { + + let fileManager = FileManager.default + + if fileManager.fileExists(atPath: self.keyExportFileURL.path) { + try? fileManager.removeItem(atPath: self.keyExportFileURL.path) + } + } +} + +// MARK: - UIDocumentInteractionControllerDelegate +extension EncryptionKeysExportPresenter: UIDocumentInteractionControllerDelegate { + + // Note: This method is not called in all cases (see http://stackoverflow.com/a/21867096). + func documentInteractionController(_ controller: UIDocumentInteractionController, didEndSendingToApplication application: String?) { + self.deleteKeyExportFile() + self.documentInteractionController = nil + } + + func documentInteractionControllerDidDismissOptionsMenu(_ controller: UIDocumentInteractionController) { + self.encryptionKeysExportView = nil + self.documentInteractionController = nil + } +}