Merge pull request #2457 from keymanapp/ios-2356-files-integration

[iOS] In-app file browser for ad-hoc installation
This commit is contained in:
Joshua Horton 2020-01-10 09:04:42 +07:00 • committed by GitHub
commit d8d6a0723e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 142 additions and 8 deletions

View file

@ -44,6 +44,8 @@ public class Manager: NSObject, UIGestureRecognizerDelegate {
public static let shared = Manager()
public var fileBrowserLauncher: ((UINavigationController) -> Void)? = nil
/// Display the help bubble on first use.
public var isKeymanHelpOn = true

View file

@ -581,4 +581,8 @@ public class ResourceDownloadManager {
return updateQueue
}
public func installLexicalModelPackage(at packageURL: URL) -> InstallableLexicalModel? {
return downloader.installLexicalModelPackage(downloadedPackageFile: packageURL)
}
}

View file

@ -45,6 +45,12 @@ public class ResourceFileManager {
var destinationUrl = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
destinationUrl.appendPathComponent(url.lastPathComponent)
// Since it's possible to request an install from a KMP in our owned document space,
// we need to check that it's not already in place where we want it.
if url == destinationUrl {
return url
}
do {
try copyWithOverwrite(from: url, to: destinationUrl)
return destinationUrl
@ -103,7 +109,9 @@ public class ResourceFileManager {
})
}
public func promptPackageInstall(of package: KeymanPackage, in rootVC: UIViewController) {
public func promptPackageInstall(of package: KeymanPackage,
in rootVC: UIViewController,
successHandler: ((KeymanPackage) -> Void)? = nil) {
let vc = PackageInstallViewController(for: package, completionHandler: { error in
if let err = error {
if let kmpError = err as? KMPError {
@ -111,7 +119,9 @@ public class ResourceFileManager {
rootVC.present(alert, animated: true, completion: nil)
}
} else {
let alert = self.buildSimpleAlert(title: "Success", message: "Installed successfully.")
let alert = self.buildSimpleAlert(title: "Success", message: "Installed successfully.", completionHandler: {
successHandler?(package)
})
rootVC.present(alert, animated: true, completion: nil)
}
})
@ -124,12 +134,14 @@ public class ResourceFileManager {
return buildSimpleAlert(title: "Error", message: error.rawValue)
}
public func buildSimpleAlert(title: String, message: String) -> UIAlertController {
public func buildSimpleAlert(title: String, message: String, completionHandler: (() -> Void)? = nil ) -> UIAlertController {
let alertController = UIAlertController(title: title, message: message,
preferredStyle: UIAlertController.Style.alert)
alertController.addAction(UIAlertAction(title: "OK",
style: UIAlertAction.Style.default,
handler: nil))
handler: { _ in
completionHandler?()
}))
//UIApplication.shared.keyWindow?.rootViewController?.present(alertController, animated: true, completion: nil)
return alertController

View file

@ -70,6 +70,16 @@ open class SettingsViewController: UITableViewController {
"subtitle": "",
"reuseid" : "showgetstarted"
])
// The iOS Files app is only available with 11.0+.
if #available(iOS 11.0, *) {
itemsArray.append([
"title": "Install From File",
"subtitle": "Browse for .kmp files",
"reuseid" : "installfile"
])
}
_ = view
}
@ -85,8 +95,7 @@ open class SettingsViewController: UITableViewController {
}
override open func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 3
return itemsArray.count
}
public func frameAtRightOfCell(cell cellFrame: CGRect, controlSize: CGSize) -> CGRect {
@ -149,6 +158,8 @@ open class SettingsViewController: UITableViewController {
showAgainSwitch.rightAnchor.constraint(equalTo: cell.layoutMarginsGuide.rightAnchor).isActive = true
showAgainSwitch.centerYAnchor.constraint(equalTo: cell.layoutMarginsGuide.centerYAnchor).isActive = true
}
case "installfile":
cell.accessoryType = .disclosureIndicator
default:
log.error("unknown cellIdentifier(\"\(cellIdentifier ?? "EMPTY")\")")
cell.accessoryType = .none
@ -188,6 +199,8 @@ open class SettingsViewController: UITableViewController {
if indexPath.row == 0 {
cell.accessoryType = .disclosureIndicator
} else if indexPath.row == 3 {
cell.accessoryType = .disclosureIndicator
} else {
cell.textLabel?.isEnabled = true
cell.detailTextLabel?.isEnabled = false
@ -208,7 +221,18 @@ open class SettingsViewController: UITableViewController {
private func performAction(for indexPath: IndexPath) {
switch indexPath.section {
case 0:
showLanguages()
switch indexPath.row {
case 0:
showLanguages()
case 3:
if let block = Manager.shared.fileBrowserLauncher {
block(navigationController!)
} else {
log.info("Listener for framework signal to launch file browser is missing")
}
default:
break
}
default:
break
}

View file

@ -2,6 +2,7 @@
## 13.0 alpha
* Start version 13.0
* Adds file browsing for installable KMPs and makes KMPs for resources installed this way available to the Files app (#2457)
* Testing for upcoming patch to stable:
* Fixes for deprecated code, improving maintainability (#2282)

View file

@ -80,7 +80,7 @@
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportsDocumentBrowser</key>
<false/>
<true/>
<key>UTExportedTypeDeclarations</key>
<array>
<dict>

View file

@ -135,6 +135,7 @@
CE6138011FB99538009D0EF2 /* KeymanEngine.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = CE6137FF1FB99538009D0EF2 /* KeymanEngine.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
CE6138021FB999C8009D0EF2 /* KeymanEngine.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = CE6138031FB999C8009D0EF2 /* KeymanEngine.framework */; };
CE7C1AE2236925D800100C2C /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = CE7C1AE1236925D800100C2C /* LaunchScreen.storyboard */; };
CE7FF1F0239A0293007859D9 /* PackageBrowserViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE7FF1EF239A0293007859D9 /* PackageBrowserViewController.swift */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
@ -298,6 +299,7 @@
CE6137FF1FB99538009D0EF2 /* KeymanEngine.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = KeymanEngine.framework; sourceTree = BUILT_PRODUCTS_DIR; };
CE6138031FB999C8009D0EF2 /* KeymanEngine.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = KeymanEngine.framework; sourceTree = BUILT_PRODUCTS_DIR; };
CE7C1AE1236925D800100C2C /* LaunchScreen.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; path = LaunchScreen.storyboard; sourceTree = "<group>"; };
CE7FF1EF239A0293007859D9 /* PackageBrowserViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PackageBrowserViewController.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@ -521,6 +523,7 @@
9845A7C71A439A9200544E2E /* SetUpViewController */,
98ABADBA176935E400B62590 /* Supporting Files */,
C0FF769D1F5D4ECB00BD23C3 /* ActivityItemProvider.swift */,
CE7FF1EF239A0293007859D9 /* PackageBrowserViewController.swift */,
C0FF769C1F5D4ECA00BD23C3 /* Keyman-Bridging-Header.h */,
C0E943F71F61234C00E7D98C /* UIImage+Helpers.swift */,
C0E943F91F6124E100E7D98C /* AppDelegate.swift */,
@ -924,6 +927,7 @@
C055F0B11F60E8D400140735 /* GetStartedViewController.swift in Sources */,
C055F0B51F610FB200140735 /* DropDownListView.swift in Sources */,
C059FCC01FD927EF00BD1A64 /* Log.swift in Sources */,
CE7FF1F0239A0293007859D9 /* PackageBrowserViewController.swift in Sources */,
C0E943F81F61234C00E7D98C /* UIImage+Helpers.swift in Sources */,
C0E943FE1F61377900E7D98C /* MainViewController.swift in Sources */,
162E2C9920926C8600F40769 /* UIView+Extensions.swift in Sources */,

View file

@ -122,6 +122,15 @@ class MainViewController: UIViewController, TextViewDelegate, UIActionSheetDeleg
observer: self,
function: MainViewController.keyboardRemoved)
// Unfortunately, it's the main app with the file definitions.
// We have to gerry-rig this so that the framework-based SettingsViewController
// can launch the app-based DocumentViewController.
if #available(iOS 11.0, *) {
Manager.shared.fileBrowserLauncher = { navController in
let vc = PackageBrowserViewController()
navController.pushViewController(vc, animated: true)
}
}
}
required init?(coder aDecoder: NSCoder) {

View file

@ -0,0 +1,78 @@
//
// PackageBrowserViewController.swift
// Keyman
//
// Created by Joshua Horton on 12/6/19.
// Copyright © 2019 SIL International. All rights reserved.
//
import UIKit
import SwiftUI
import KeymanEngine
@available(iOS 11.0, *)
class PackageBrowserViewController: UIDocumentBrowserViewController, UIDocumentBrowserViewControllerDelegate {
override func viewDidLoad() {
super.viewDidLoad()
delegate = self
allowsDocumentCreation = false
allowsPickingMultipleItems = false
// Update the style of the UIDocumentBrowserViewController
// browserUserInterfaceStyle = .dark
// view.tintColor = .white
// Specify the allowed content types of your application via the Info.plist.
// Do any additional setup after loading the view.
}
// MARK: UIDocumentBrowserViewControllerDelegate
func documentBrowser(_ controller: UIDocumentBrowserViewController, didPickDocumentsAt documentURLs: [URL]) {
guard let sourceURL = documentURLs.first else { return }
// Present the Document View Controller for the first document that was picked.
// If you support picking multiple items, make sure you handle them all.
doInstall(of: sourceURL)
}
func documentBrowser(_ controller: UIDocumentBrowserViewController,
didImportDocumentAt sourceURL: URL,
toDestinationURL destinationURL: URL) {
// Present the Document View Controller for the new newly created document
doInstall(of: destinationURL)
}
func documentBrowser(_ controller: UIDocumentBrowserViewController,
failedToImportDocumentAt documentURL: URL,
error: Error?) {
// Make sure to handle the failed import appropriately, e.g., by presenting an error message to the user.
}
// MARK: Document Presentation
func doInstall(of url: URL) {
// Once selected, start the standard install process.
let rfm = ResourceFileManager.shared
guard let destinationUrl = rfm.importFile(url) else {
return
}
rfm.prepareKMPInstall(from: destinationUrl,
alertHost: self,
completionHandler: { package in
// We choose to prompt the user for comfirmation, rather
// than automatically installing the package.
rfm.promptPackageInstall(of: package, in: self, successHandler: { _ in
// Auto-dismiss the document browser upon successful KMP install.
// It's likely quite rare that someone would want to install 2+ at once.
self.navigationController?.popViewController(animated: true)
})
})
}
}