mirror of
https://github.com/keymanapp/keyman.git
synced 2026-10-14 18:07:31 +00:00
Merge pull request #2457 from keymanapp/ios-2356-files-integration
[iOS] In-app file browser for ad-hoc installation
This commit is contained in:
commit
d8d6a0723e
9 changed files with 142 additions and 8 deletions
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -581,4 +581,8 @@ public class ResourceDownloadManager {
|
|||
|
||||
return updateQueue
|
||||
}
|
||||
|
||||
public func installLexicalModelPackage(at packageURL: URL) -> InstallableLexicalModel? {
|
||||
return downloader.installLexicalModelPackage(downloadedPackageFile: packageURL)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@
|
|||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UISupportsDocumentBrowser</key>
|
||||
<false/>
|
||||
<true/>
|
||||
<key>UTExportedTypeDeclarations</key>
|
||||
<array>
|
||||
<dict>
|
||||
|
|
|
|||
|
|
@ -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 */,
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
78
ios/keyman/Keyman/Keyman/PackageBrowserViewController.swift
Normal file
78
ios/keyman/Keyman/Keyman/PackageBrowserViewController.swift
Normal 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)
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue