Merge pull request #1824 from keymanapp/ios-ui-lm-management

[ios] UI LM Management
This commit is contained in:
rfaramir 2019-07-18 23:15:34 -05:00 committed by GitHub
commit 1827d689dc
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
25 changed files with 1678 additions and 84 deletions

1
ios/.gitignore vendored
View file

@ -2,6 +2,7 @@ DerivedData
.DS_Store
project.xcworkspace
xcuserdata
*.xcscheme
build
keyman/Keyman/KeymanEngine.framework
samples/KMSample1/KeymanEngine.framework

View file

@ -104,8 +104,27 @@
9A31E2CE224AE8B100D9A491 /* LexicalModelRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A9CB083224170F700231FB9 /* LexicalModelRepository.swift */; };
9A31E2CF224AE8B800D9A491 /* LexicalModelRepositoryDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A9CB0852241717C00231FB9 /* LexicalModelRepositoryDelegate.swift */; };
9A31E2D0224AE8BF00D9A491 /* APILexicalModelRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A9CB0812241704800231FB9 /* APILexicalModelRepository.swift */; };
9A3B14BA228D333E0052A11F /* KeyboardPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C08C62161F67CFB800268D03 /* KeyboardPickerViewController.swift */; };
9A3B14BB228F592F0052A11F /* LanguageViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C04C2A6C1F6B7D9A00BA42B6 /* LanguageViewController.swift */; };
9A3B14BC228F59380052A11F /* LanguageDetailViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C092D8381F6A70C8005C5485 /* LanguageDetailViewController.swift */; };
9A3B14BD228F593C0052A11F /* LexicalModelPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A9CB07F22416E5400231FB9 /* LexicalModelPickerViewController.swift */; };
9A3B14BE228F59420052A11F /* LexicalModelInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A4609962241B39B00B0BFD1 /* LexicalModelInfoViewController.swift */; };
9A3B14BF228F59450052A11F /* KeyboardInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C08C62141F67C8D500268D03 /* KeyboardInfoViewController.swift */; };
9A3B14C0228F59490052A11F /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A60764322893A4E003BCFBA /* SettingsViewController.swift */; };
9A3B14C1228F595A0052A11F /* KeyboardError.swift in Sources */ = {isa = PBXBuildFile; fileRef = 165EB3A02098993900040A69 /* KeyboardError.swift */; };
9A3B14C3228F59C10052A11F /* KeyboardPickerButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = C08C620F1F67BD4500268D03 /* KeyboardPickerButton.swift */; };
9A3B14C4228F59C70052A11F /* KeyboardPickerBarButtonItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = C08C620B1F6792A200268D03 /* KeyboardPickerBarButtonItem.swift */; };
9A3B14C6228F59E00052A11F /* TextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = C040E50F1F8606E300901EE4 /* TextField.swift */; };
9A3B14C7228F59E40052A11F /* TextFieldDelegateProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0324B8C1F87480700AF3785 /* TextFieldDelegateProxy.swift */; };
9A3B14C8228F59EE0052A11F /* TextView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0324B8E1F8750B700AF3785 /* TextView.swift */; };
9A3B14C9228F59F10052A11F /* TextViewDelegateProxy.swift in Sources */ = {isa = PBXBuildFile; fileRef = C0324B901F8763E100AF3785 /* TextViewDelegateProxy.swift */; };
9A3B14CA228F5AF60052A11F /* KeyboardNameTableViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = C08C62121F67C31100268D03 /* KeyboardNameTableViewCell.swift */; };
9A3B14D2229370B20052A11F /* InstalledLanguagesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A3B14D1229370B20052A11F /* InstalledLanguagesViewController.swift */; };
9A3B14D3229370B20052A11F /* InstalledLanguagesViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A3B14D1229370B20052A11F /* InstalledLanguagesViewController.swift */; };
9A4609972241B39B00B0BFD1 /* LexicalModelInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A4609962241B39B00B0BFD1 /* LexicalModelInfoViewController.swift */; };
9A4609992242047400B0BFD1 /* LexicalModelAPICall.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A4609982242047400B0BFD1 /* LexicalModelAPICall.swift */; };
9A60764422893A4E003BCFBA /* SettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A60764322893A4E003BCFBA /* SettingsViewController.swift */; };
9A7EEFA222DCF64F00877C22 /* LanguageSpecificViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A7EEFA122DCF64F00877C22 /* LanguageSpecificViewController.swift */; };
9A5091B122B418140094B99C /* KeyboardCommandStructs.swift in Sources */ = {isa = PBXBuildFile; fileRef = CE67D960228A6F190029F2B5 /* KeyboardCommandStructs.swift */; };
9A5091B222B418A90094B99C /* KeyboardPickerViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C08C62161F67CFB800268D03 /* KeyboardPickerViewController.swift */; };
9A5091B322B418CA0094B99C /* KeyboardInfoViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = C08C62141F67C8D500268D03 /* KeyboardInfoViewController.swift */; };
@ -116,6 +135,10 @@
9A9CB0822241704800231FB9 /* APILexicalModelRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A9CB0812241704800231FB9 /* APILexicalModelRepository.swift */; };
9A9CB084224170F700231FB9 /* LexicalModelRepository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A9CB083224170F700231FB9 /* LexicalModelRepository.swift */; };
9A9CB0862241717C00231FB9 /* LexicalModelRepositoryDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A9CB0852241717C00231FB9 /* LexicalModelRepositoryDelegate.swift */; };
9AD4F53C229F85AC007992D3 /* LanguageSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AD4F53A229F85AC007992D3 /* LanguageSettingsViewController.swift */; };
9AD4F53D229F85AC007992D3 /* LanguageSettingsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9AD4F53B229F85AC007992D3 /* LanguageSettingsViewController.xib */; };
9AD4F53E22A8A25B007992D3 /* LanguageSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9AD4F53A229F85AC007992D3 /* LanguageSettingsViewController.swift */; };
9AD4F53F22A8A286007992D3 /* LanguageSettingsViewController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 9AD4F53B229F85AC007992D3 /* LanguageSettingsViewController.xib */; };
C007C4651F9F52D8006461B9 /* LanguagesAPICall.swift in Sources */ = {isa = PBXBuildFile; fileRef = C007C4641F9F52D8006461B9 /* LanguagesAPICall.swift */; };
C024C9941FA6EB470060583B /* NotificationName.swift in Sources */ = {isa = PBXBuildFile; fileRef = C024C9931FA6EB470060583B /* NotificationName.swift */; };
C024C9961FA6EC650060583B /* NotificationCenter+Typed.swift in Sources */ = {isa = PBXBuildFile; fileRef = C024C9951FA6EC650060583B /* NotificationCenter+Typed.swift */; };
@ -312,13 +335,19 @@
9A079E3F223B602B00581263 /* LexicalModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LexicalModel.swift; sourceTree = "<group>"; };
9A079E42223B61AE00581263 /* FullLexicalModelID.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullLexicalModelID.swift; sourceTree = "<group>"; };
9A082558227589360051EBB0 /* Formatter+ISODateExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Formatter+ISODateExtension.swift"; sourceTree = "<group>"; };
9A3B14D1229370B20052A11F /* InstalledLanguagesViewController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = InstalledLanguagesViewController.swift; path = Settings/InstalledLanguagesViewController.swift; sourceTree = "<group>"; };
9A0FC9FC22D66D9E00D33F86 /* Reachability.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Reachability.framework; path = ../../Carthage/Build/iOS/Reachability.framework; sourceTree = "<group>"; };
9A4609962241B39B00B0BFD1 /* LexicalModelInfoViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LexicalModelInfoViewController.swift; sourceTree = "<group>"; };
9A4609982242047400B0BFD1 /* LexicalModelAPICall.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LexicalModelAPICall.swift; sourceTree = "<group>"; };
9A60763C22892485003BCFBA /* Settings.storyboard */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Settings.storyboard; path = Settings/Settings.storyboard; sourceTree = "<group>"; };
9A60764322893A4E003BCFBA /* SettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = SettingsViewController.swift; path = Settings/SettingsViewController.swift; sourceTree = "<group>"; };
9A7EEFA122DCF64F00877C22 /* LanguageSpecificViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LanguageSpecificViewController.swift; sourceTree = "<group>"; };
9A9CB07F22416E5400231FB9 /* LexicalModelPickerViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LexicalModelPickerViewController.swift; sourceTree = "<group>"; };
9A9CB0812241704800231FB9 /* APILexicalModelRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = APILexicalModelRepository.swift; sourceTree = "<group>"; };
9A9CB083224170F700231FB9 /* LexicalModelRepository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LexicalModelRepository.swift; sourceTree = "<group>"; };
9A9CB0852241717C00231FB9 /* LexicalModelRepositoryDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LexicalModelRepositoryDelegate.swift; sourceTree = "<group>"; };
9AD4F53A229F85AC007992D3 /* LanguageSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = LanguageSettingsViewController.swift; path = Settings/LanguageSettingsViewController.swift; sourceTree = "<group>"; };
9AD4F53B229F85AC007992D3 /* LanguageSettingsViewController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = LanguageSettingsViewController.xib; path = Settings/LanguageSettingsViewController.xib; sourceTree = "<group>"; };
C007C4641F9F52D8006461B9 /* LanguagesAPICall.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LanguagesAPICall.swift; sourceTree = "<group>"; };
C024C9931FA6EB470060583B /* NotificationName.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationName.swift; sourceTree = "<group>"; };
C024C9951FA6EC650060583B /* NotificationCenter+Typed.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NotificationCenter+Typed.swift"; sourceTree = "<group>"; };
@ -592,6 +621,18 @@
path = KeymanEngineTests;
sourceTree = "<group>";
};
9A60763E22892495003BCFBA /* Settings */ = {
isa = PBXGroup;
children = (
9A60763C22892485003BCFBA /* Settings.storyboard */,
9A60764322893A4E003BCFBA /* SettingsViewController.swift */,
9A3B14D1229370B20052A11F /* InstalledLanguagesViewController.swift */,
9AD4F53A229F85AC007992D3 /* LanguageSettingsViewController.swift */,
9AD4F53B229F85AC007992D3 /* LanguageSettingsViewController.xib */,
);
name = Settings;
sourceTree = "<group>";
};
C024C9921FA6EB340060583B /* Notification */ = {
isa = PBXGroup;
children = (
@ -761,6 +802,7 @@
9A9CB07F22416E5400231FB9 /* LexicalModelPickerViewController.swift */,
C092D8381F6A70C8005C5485 /* LanguageDetailViewController.swift */,
C04C2A6C1F6B7D9A00BA42B6 /* LanguageViewController.swift */,
9A7EEFA122DCF64F00877C22 /* LanguageSpecificViewController.swift */,
98C0B11817A22DB5007ECAFC /* PickerButtons */,
98C0B11C17A2302E007ECAFC /* TableViewCells */,
);
@ -777,6 +819,7 @@
C024C9921FA6EB340060583B /* Notification */,
C07A9D881FD1760800828ADD /* KeyboardRepository */,
C0A93A531F8B21240079948B /* Manager.swift */,
9A60763E22892495003BCFBA /* Settings */,
CE25CCBB1F1DA72A005AA2BC /* HTTPRequest */,
987F00041AB8FCB700998116 /* KeyboardMenuView */,
980CC8E119D51EF00089BB57 /* URLProtocol */,
@ -986,6 +1029,7 @@
isa = PBXResourcesBuildPhase;
buildActionMask = 2147483647;
files = (
9AD4F53F22A8A286007992D3 /* LanguageSettingsViewController.xib in Resources */,
9A0FCA0922D7C58B00D33F86 /* Keyman.bundle in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
@ -995,6 +1039,7 @@
buildActionMask = 2147483647;
files = (
C06D37601F82095200F61AE0 /* Keyman.bundle in Resources */,
9AD4F53D229F85AC007992D3 /* LanguageSettingsViewController.xib in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1091,6 +1136,10 @@
buildActionMask = 2147483647;
files = (
9A079E45223DBEF000581263 /* PopoverView.swift in Sources */,
9A3B14C4228F59C70052A11F /* KeyboardPickerBarButtonItem.swift in Sources */,
9A3B14BE228F59420052A11F /* LexicalModelInfoViewController.swift in Sources */,
9A3B14C7228F59E40052A11F /* TextFieldDelegateProxy.swift in Sources */,
9A3B14CA228F5AF60052A11F /* KeyboardNameTableViewCell.swift in Sources */,
9A31E2CC224AE89F00D9A491 /* NotificationCenter+Typed.swift in Sources */,
9A079E2C22361B4B00581263 /* KeyboardRepository.swift in Sources */,
9A31E2C8224AE87600D9A491 /* RegisteredFont.swift in Sources */,
@ -1104,21 +1153,28 @@
9A31E2C6224AE85000D9A491 /* UserDefaults+Types.swift in Sources */,
9A079E44223B61AE00581263 /* FullLexicalModelID.swift in Sources */,
9A079DE72236134B00581263 /* HTTPDownloader.swift in Sources */,
9AD4F53E22A8A25B007992D3 /* LanguageSettingsViewController.swift in Sources */,
9A079E46223DBEF900581263 /* KeyPreviewView.swift in Sources */,
9A5091B122B418140094B99C /* KeyboardCommandStructs.swift in Sources */,
9A079DE32231A16600581263 /* Constants.swift in Sources */,
9A31E2C1224AAD8300D9A491 /* FontManager.swift in Sources */,
9A3B14C6228F59E00052A11F /* TextField.swift in Sources */,
9A3B14C3228F59C10052A11F /* KeyboardPickerButton.swift in Sources */,
9A079E29223619F100581263 /* InputViewController.swift in Sources */,
9A079E28223619D300581263 /* Options.swift in Sources */,
9A079DE42231A1ED00581263 /* Log.swift in Sources */,
9A31E2C5224AE83D00D9A491 /* JSONDecoder.DateDecodingStrategy+ISO8601Fallback.swift in Sources */,
9A079DDB2231A0F100581263 /* KMPKeyboard.swift in Sources */,
9A3B14BD228F593C0052A11F /* LexicalModelPickerViewController.swift in Sources */,
9A079DDF2231A13A00581263 /* Keyboard.swift in Sources */,
9A5091B222B418A90094B99C /* KeyboardPickerViewController.swift in Sources */,
9A31E2C2224AAD9A00D9A491 /* Notifications.swift in Sources */,
9A079DDD2231A11C00581263 /* InstallableKeyboard.swift in Sources */,
9A7EEFA222DCF64F00877C22 /* LanguageSpecificViewController.swift in Sources */,
9A079DD2223194B100581263 /* KeymanEngineTests.swift in Sources */,
9A079E41223B602B00581263 /* LexicalModel.swift in Sources */,
9A3B14C0228F59490052A11F /* SettingsViewController.swift in Sources */,
9A3B14BB228F592F0052A11F /* LanguageViewController.swift in Sources */,
9A31E2CE224AE8B100D9A491 /* LexicalModelRepository.swift in Sources */,
9A079E2E22361B8100581263 /* KeymanResponder+Types.swift in Sources */,
9A079DE12231A14D00581263 /* Storage.swift in Sources */,
@ -1129,17 +1185,24 @@
9A31E2CB224AE89600D9A491 /* LexicalModelKeymanPackage.swift in Sources */,
9A31E2CF224AE8B800D9A491 /* LexicalModelRepositoryDelegate.swift in Sources */,
9A31E2C9224AE88600D9A491 /* LanguagesAPICall.swift in Sources */,
9A3B14C1228F595A0052A11F /* KeyboardError.swift in Sources */,
9A079E2B22361A3200581263 /* KeymanWebViewController.swift in Sources */,
9A862D2822B429FB005FE26D /* KeyboardNameTableViewCell.swift in Sources */,
9A31E2CD224AE8A700D9A491 /* NotificationObserver.swift in Sources */,
9A079E382238680700581263 /* KMPLexicalModel.swift in Sources */,
9A3B14BC228F59380052A11F /* LanguageDetailViewController.swift in Sources */,
9A31E2C4224AB2A300D9A491 /* UIButton+Helpers.swift in Sources */,
9A079DDE2231A13100581263 /* FullKeyboardID.swift in Sources */,
9A3B14C9228F59F10052A11F /* TextViewDelegateProxy.swift in Sources */,
9A31E2CA224AE88E00D9A491 /* LexicalModelAPICall.swift in Sources */,
9A079E3222361C3B00581263 /* Migrations.swift in Sources */,
9A3B14D3229370B20052A11F /* InstalledLanguagesViewController.swift in Sources */,
9A08255A227589360051EBB0 /* Formatter+ISODateExtension.swift in Sources */,
9A3B14BA228D333E0052A11F /* KeyboardPickerViewController.swift in Sources */,
9A079E2A223619FC00581263 /* KeymanWebDelegate.swift in Sources */,
9A3B14BF228F59450052A11F /* KeyboardInfoViewController.swift in Sources */,
9A31E2C3224AADBB00D9A491 /* NotificationName.swift in Sources */,
9A3B14C8228F59EE0052A11F /* TextView.swift in Sources */,
9A862D2922B42A0C005FE26D /* LanguageDetailViewController.swift in Sources */,
9A079DDA2231A0B900581263 /* KeymanPackage.swift in Sources */,
9A079DE22231A15900581263 /* Manager.swift in Sources */,
@ -1172,6 +1235,7 @@
C06D37361F81F5C300F61AE0 /* KeyboardMenuView.swift in Sources */,
CE67D961228A6F190029F2B5 /* KeyboardCommandStructs.swift in Sources */,
9A4609972241B39B00B0BFD1 /* LexicalModelInfoViewController.swift in Sources */,
9AD4F53C229F85AC007992D3 /* LanguageSettingsViewController.swift in Sources */,
9A9CB08022416E5400231FB9 /* LexicalModelPickerViewController.swift in Sources */,
9A079E3D223B5FAF00581263 /* InstallableLexicalModel.swift in Sources */,
C06D37381F81F5C400F61AE0 /* SubKeysView.swift in Sources */,
@ -1186,6 +1250,7 @@
C040E50E1F85FF8A00901EE4 /* KeyPreviewView.swift in Sources */,
9A079E3A223AFF3C00581263 /* KeyboardKeymanPackage.swift in Sources */,
C06D37401F81F5C400F61AE0 /* LanguageDetailViewController.swift in Sources */,
9A7EEFA222DCF64F00877C22 /* LanguageSpecificViewController.swift in Sources */,
9A9CB0862241717C00231FB9 /* LexicalModelRepositoryDelegate.swift in Sources */,
C0CB633C1FA6F61500617CDB /* Notifications.swift in Sources */,
C0EF3E7B1F95B65300CE9BD4 /* KeymanWebDelegate.swift in Sources */,
@ -1201,6 +1266,7 @@
C06D37431F81F5C400F61AE0 /* KeyboardPickerBarButtonItem.swift in Sources */,
C082CE151F90AFD400860F02 /* Collection+SafeAccess.swift in Sources */,
C06D37441F81F5C400F61AE0 /* KeyboardNameTableViewCell.swift in Sources */,
9A3B14D2229370B20052A11F /* InstalledLanguagesViewController.swift in Sources */,
C07A9D8A1FD1762700828ADD /* KeyboardRepository.swift in Sources */,
C08E69911FDA6F6F0026056B /* FullKeyboardID.swift in Sources */,
9A9CB0822241704800231FB9 /* APILexicalModelRepository.swift in Sources */,
@ -1219,6 +1285,7 @@
C024C9941FA6EB470060583B /* NotificationName.swift in Sources */,
C0C16A891FA8146300F090BA /* KeymanWebViewController.swift in Sources */,
9A079DCA222E050E00581263 /* LexicalModelKeymanPackage.swift in Sources */,
9A60764422893A4E003BCFBA /* SettingsViewController.swift in Sources */,
C06085B41F9485E40057E5B9 /* UIButton+Helpers.swift in Sources */,
C07A9D8E1FD1798900828ADD /* APIKeyboardRepository.swift in Sources */,
C0959CD41F99C44E00B616BC /* Constants.swift in Sources */,

View file

@ -9,7 +9,8 @@
import Foundation
public extension UserDefaults {
func installableKeyboards(forKey key: String) -> [InstallableKeyboard]? {
func installableKeyboards(forKey key: String) -> [InstallableKeyboard]? {
guard let array = array(forKey: key) as? [Data] else {
return nil
}
@ -190,6 +191,12 @@ public extension UserDefaults {
func userLexicalModel(withFullID fullID: FullLexicalModelID) -> InstallableLexicalModel? {
return userLexicalModels?.first { $0.fullID == fullID }
}
func userLexicalModelsForLanguage(languageID: String) -> [InstallableLexicalModel]? {
return userLexicalModels?.filter({
$0.languageID == languageID
}) ?? []
}
var migrationLevel: Int {
get {

View file

@ -75,7 +75,7 @@ public class APILexicalModelRepository: LexicalModelRepository {
log.info("Request list completed -- \(result.lexicalModels.count) lexical models.")
DispatchQueue.main.async {
self.delegate?.lexicalModelRepositoryDidFetch(self)
self.delegate?.lexicalModelRepositoryDidFetchList(self)
fetchCompletionHandler(lexicalModels, nil)
}
}

View file

@ -9,6 +9,6 @@
import Foundation
public protocol LexicalModelRepositoryDelegate: class {
func lexicalModelRepositoryDidFetch(_ repository: LexicalModelRepository)
func lexicalModelRepositoryDidFetchList(_ repository: LexicalModelRepository)
func lexicalModelRepository(_ repository: LexicalModelRepository, didFailFetch error: Error)
}

View file

@ -35,7 +35,7 @@ class KeyboardInfoViewController: UITableViewController, UIAlertViewDelegate {
])
}
infoArray.append([
"title": "Delete",
"title": "Uninstall keyboard",
"subtitle": ""
])
}
@ -43,7 +43,8 @@ class KeyboardInfoViewController: UITableViewController, UIAlertViewDelegate {
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
navigationController?.setToolbarHidden(true, animated: true)
}
log.info("didAppear: KeyboardInfoViewController")
}
override func numberOfSections(in tableView: UITableView) -> Int {
return 1

View file

@ -53,6 +53,21 @@ class KeyboardPickerViewController: UITableViewController, UIAlertViewDelegate {
forName: Notifications.keyboardDownloadFailed,
observer: self,
function: KeyboardPickerViewController.keyboardDownloadFailed)
lexicalModelDownloadStartedObserver = NotificationCenter.default.addObserver(
forName: Notifications.lexicalModelDownloadStarted,
observer: self,
function: KeyboardPickerViewController.lexicalModelDownloadStarted)
lexicalModelDownloadCompletedObserver = NotificationCenter.default.addObserver(
forName: Notifications.lexicalModelDownloadCompleted,
observer: self,
function: KeyboardPickerViewController.lexicalModelDownloadCompleted)
lexicalModelDownloadFailedObserver = NotificationCenter.default.addObserver(
forName: Notifications.lexicalModelDownloadFailed,
observer: self,
function: KeyboardPickerViewController.lexicalModelDownloadFailed)
log.info("didLoad: KeyboardPickerViewController (registered lexicalModelDownloadCompleted et al)")
}
override func viewWillAppear(_ animated: Bool) {
@ -85,6 +100,7 @@ class KeyboardPickerViewController: UITableViewController, UIAlertViewDelegate {
navigationController?.setToolbarHidden(false, animated: true)
scroll(toSelectedKeyboard: false)
log.info("didAppear: KeyboardPickerViewController")
}
override func numberOfSections(in tableView: UITableView) -> Int {
@ -182,12 +198,14 @@ class KeyboardPickerViewController: UITableViewController, UIAlertViewDelegate {
}
private func keyboardDownloadStarted(_ keyboards: [InstallableKeyboard]) {
log.info("keyboardDownloadStarted")
view.isUserInteractionEnabled = false
navigationItem.leftBarButtonItem?.isEnabled = false
navigationItem.rightBarButtonItem?.isEnabled = false
}
private func lexicalModelDownloadStarted(_ lexicalModels: [InstallableLexicalModel]) {
log.info("lexicalModelDownloadStarted")
view.isUserInteractionEnabled = false
navigationItem.leftBarButtonItem?.isEnabled = false
navigationItem.rightBarButtonItem?.isEnabled = false
@ -212,11 +230,8 @@ class KeyboardPickerViewController: UITableViewController, UIAlertViewDelegate {
Manager.shared.downloadKeyboard(withID: kbID, languageID: langID, isUpdate: true)
} else {
loadUserKeyboards()
view.isUserInteractionEnabled = true
navigationItem.leftBarButtonItem?.isEnabled = true
if navigationItem.rightBarButtonItem != nil {
navigationItem.rightBarButtonItem?.isEnabled = true
}
restoreNavigation()
updateQueue = nil
let label = navigationController?.toolbar?.viewWithTag(toolbarLabelTag) as? UILabel
label?.text = "Keyboards successfully updated!"
@ -231,11 +246,7 @@ class KeyboardPickerViewController: UITableViewController, UIAlertViewDelegate {
Timer.scheduledTimer(timeInterval: 3.0, target: self, selector: #selector(self.hideToolbarDelayed),
userInfo: nil, repeats: false)
view.isUserInteractionEnabled = true
navigationItem.leftBarButtonItem?.isEnabled = true
if navigationItem.rightBarButtonItem != nil {
navigationItem.rightBarButtonItem?.isEnabled = true
}
restoreNavigation()
// Add keyboard.
for keyboard in keyboards {
@ -245,9 +256,30 @@ class KeyboardPickerViewController: UITableViewController, UIAlertViewDelegate {
navigationController?.popToRootViewController(animated: true)
}
log.info("keyboardDownloadCompleted KeyboardPicker")
}
private func restoreNavigation() {
view.isUserInteractionEnabled = true
navigationItem.leftBarButtonItem?.isEnabled = true
if navigationItem.rightBarButtonItem != nil {
navigationItem.rightBarButtonItem?.isEnabled = true
}
}
private func lexicalModelDownloadCompleted(_ lexicalModels: [InstallableLexicalModel]) {
log.info("lexicalModelDownloadCompleted KeyboardPicker")
// Add models.
for lexicalModel in lexicalModels {
Manager.shared.addLexicalModel(lexicalModel)
_ = Manager.shared.registerLexicalModel(lexicalModel)
}
restoreNavigation()
navigationController?.popToRootViewController(animated: true)
}
private func keyboardDownloadFailed(_ notification: KeyboardDownloadFailedNotification) {
log.info("keyboardDownloadFailed KeyboardPicker")
view.isUserInteractionEnabled = true
navigationItem.leftBarButtonItem?.isEnabled = true
if let item = navigationItem.rightBarButtonItem {
@ -271,6 +303,28 @@ class KeyboardPickerViewController: UITableViewController, UIAlertViewDelegate {
self.present(alertController, animated: true, completion: nil)
}
private func lexicalModelDownloadFailed(_ notification: LexicalModelDownloadFailedNotification) {
log.info("lexicalModelDownloadFailed KeyboardPicker")
restoreNavigation()
let title: String
if view == navigationController?.topViewController?.view {
updateQueue = nil
title = "Dictionary Update Error"
} else {
title = "Dictionary Download Error"
}
navigationController?.setToolbarHidden(true, animated: true)
let alertController = UIAlertController(title: title, message: notification.error.localizedDescription,
preferredStyle: UIAlertControllerStyle.alert)
alertController.addAction(UIAlertAction(title: "OK",
style: UIAlertActionStyle.cancel,
handler: nil))
self.present(alertController, animated: true, completion: nil)
}
private func switchKeyboard(_ index: Int) {
// Switch keyboard and register to user defaults.

View file

@ -44,6 +44,13 @@ class LanguageDetailViewController: UITableViewController, UIAlertViewDelegate {
forName: Notifications.keyboardDownloadFailed,
observer: self,
function: LanguageDetailViewController.keyboardDownloadFailed)
log.info("viewDidLoad: LanguageDetailViewController (registered for keyboardDownloadStarted)")
}
override open func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
log.info("didAppear: LanguageDetailViewController (actually willAppear)")
}
override func numberOfSections(in tableView: UITableView) -> Int {
@ -121,6 +128,7 @@ class LanguageDetailViewController: UITableViewController, UIAlertViewDelegate {
}
private func keyboardDownloadStarted() {
log.info("keyboardDownloadStarted: LanguageDetailViewController")
view.isUserInteractionEnabled = false
navigationItem.setHidesBackButton(true, animated: true)
@ -153,6 +161,7 @@ class LanguageDetailViewController: UITableViewController, UIAlertViewDelegate {
}
private func keyboardDownloadFailed() {
log.info("keyboardDownloadFailed: LanguageDetailViewController")
view.isUserInteractionEnabled = true
navigationItem.setHidesBackButton(false, animated: true)
}

View file

@ -0,0 +1,29 @@
//
// LanguageSpecificViewController.swift
// KeymanEngine
//
// Created by Randy Boring on 7/15/19.
// Copyright © 2019 SIL International. All rights reserved.
//
import UIKit
class LanguageSpecificViewController: LanguageViewController {
var language: Language!
init(_ keyboardRepository: KeyboardRepository, language: Language) {
super.init(keyboardRepository)
self.language = language
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func postLanguageLoad(languageDict: [String: Language]) {
super .postLanguageLoad(languageDict: languageDict)
let langIdx = idxOfLanguage(languageID: language.id)
self.showLanguageDetailView(title: self.title!, languageIndex: langIdx)
}
}

View file

@ -40,20 +40,41 @@ class LanguageViewController: UITableViewController, UIAlertViewDelegate {
super.init(nibName: nil, bundle: nil)
lexicalModelRepository.delegate = self
}
init(keyboardRep keyboardRepository: KeyboardRepository, modelRep lexicalModelRepository: LexicalModelRepository) {
self.keyboardRepository = keyboardRepository
self.lexicalModelRepository = lexicalModelRepository
super.init(nibName: nil, bundle: nil)
keyboardRepository.delegate = self
lexicalModelRepository.delegate = self
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func postLanguageLoad(languageDict: [String: Language]) {
languages = languageList(languageDict)
}
func idxOfLanguage(languageID: String) -> Int {
let langIdx = self.languages.firstIndex(where: {
$0.id == languageID
}) ?? 0
return langIdx
}
override func loadView() {
super.loadView()
if let languageDict = keyboardRepository?.languages {
languages = languageList(languageDict)
self.postLanguageLoad(languageDict: languageDict)
} else {
log.info("Fetching repository from API for keyboard download (LanguageViewController)")
keyboardRepository?.fetch()
}
loadUserKeyboards()
loadUserLexicalModels()
}
override func viewDidLoad() {
@ -76,8 +97,11 @@ class LanguageViewController: UITableViewController, UIAlertViewDelegate {
// if no rows to show yet, show a loading indicator
if numberOfSections(in: tableView) == 0 {
showActivityView()
log.info("didAppear: LanguageViewController, but no rows to show")
} else {
log.info("didAppear: LanguageViewController")
}
}
}
override func numberOfSections(in tableView: UITableView) -> Int {
return languages.count
@ -203,7 +227,7 @@ class LanguageViewController: UITableViewController, UIAlertViewDelegate {
showLanguageDetailView(title: title, languageIndex: indexPath.section)
}
private func showLanguageDetailView(title: String, languageIndex: Int) {
func showLanguageDetailView(title: String, languageIndex: Int) {
let langDetailView = LanguageDetailViewController(language: languages[languageIndex])
langDetailView.title = title
navigationController?.pushViewController(langDetailView, animated: true)
@ -332,7 +356,7 @@ class LanguageViewController: UITableViewController, UIAlertViewDelegate {
extension LanguageViewController: KeyboardRepositoryDelegate {
func keyboardRepositoryDidFetch(_ repository: KeyboardRepository) {
if let languageDict = repository.languages {
languages = languageList(languageDict)
self.postLanguageLoad(languageDict: languageDict)
}
self.dismissActivityView()
self.tableView.reloadData()
@ -354,11 +378,11 @@ extension LanguageViewController: KeyboardRepositoryDelegate {
}
// MARK: - LexicalModelRepositoryDelegate
//may not need this, as we don't plan ever to fetch the whole lexical model repository (or even the list of all available)
extension LanguageViewController: LexicalModelRepositoryDelegate {
func lexicalModelRepositoryDidFetch(_ repository: LexicalModelRepository) {
func lexicalModelRepositoryDidFetchList(_ repository: LexicalModelRepository) {
if let languageDict = repository.languages {
languages = languageList(languageDict)
self.postLanguageLoad(languageDict: languageDict)
}
self.dismissActivityView()
self.tableView.reloadData()

View file

@ -5,7 +5,7 @@
// Created by Randy Boring on 3/19/19.
// Copyright © 2019 SIL International. All rights reserved.
//
import UIKit
import Foundation
class LexicalModelInfoViewController: UITableViewController, UIAlertViewDelegate {
@ -24,7 +24,7 @@ class LexicalModelInfoViewController: UITableViewController, UIAlertViewDelegate
infoArray = [[String: String]]()
infoArray.append([
"title": "Lexical model version",
"title": "Model version",
"subtitle": lexicalModelVersion
])
@ -35,7 +35,7 @@ class LexicalModelInfoViewController: UITableViewController, UIAlertViewDelegate
])
}
infoArray.append([
"title": "Delete",
"title": "Uninstall model",
"subtitle": ""
])
}
@ -43,6 +43,7 @@ class LexicalModelInfoViewController: UITableViewController, UIAlertViewDelegate
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
navigationController?.setToolbarHidden(true, animated: true)
log.info("didAppear: LexicalModelInfoViewController")
}
override func numberOfSections(in tableView: UITableView) -> Int {

View file

@ -15,6 +15,7 @@ private let toolbarActivityIndicatorTag = 102
class LexicalModelPickerViewController: UITableViewController, UIAlertViewDelegate {
private var userLexicalModels: [InstallableLexicalModel] = [InstallableLexicalModel]()
public var language: Language? = nil
private var updateQueue: [InstallableLexicalModel]?
private var _isDoneButtonEnabled = false
private var isDidUpdateCheck = false
@ -26,8 +27,14 @@ class LexicalModelPickerViewController: UITableViewController, UIAlertViewDelega
override func viewDidLoad() {
super.viewDidLoad()
title = "LexicalModels"
setIsDoneButtonEnabled(false)
if let langName = language?.name {
title = "\(langName) Lexical Models"
} else {
title = "Lexical Models"
}
if language == nil {
setIsDoneButtonEnabled(false)
}
isDidUpdateCheck = false
updateQueue = nil
if Manager.shared.canAddNewLexicalModels {
@ -51,6 +58,8 @@ class LexicalModelPickerViewController: UITableViewController, UIAlertViewDelega
forName: Notifications.lexicalModelDownloadFailed,
observer: self,
function: LexicalModelPickerViewController.lexicalModelDownloadFailed)
log.info("didLoad: LexicalModelPickerViewController (registered lexicalModelDownloadCompleted et al)")
}
override func viewWillAppear(_ animated: Bool) {
@ -62,6 +71,7 @@ class LexicalModelPickerViewController: UITableViewController, UIAlertViewDelega
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
log.info("didAppear: LexicalModelPickerViewController")
if isDidUpdateCheck || !checkUpdates() {
return
}
@ -83,7 +93,9 @@ class LexicalModelPickerViewController: UITableViewController, UIAlertViewDelega
navigationController?.setToolbarHidden(false, animated: true)
scroll(toSelectedLexicalModel: false)
}
}
// MARK: - Table view data source UITableViewDataSource
override func numberOfSections(in tableView: UITableView) -> Int {
return 1
@ -112,13 +124,7 @@ class LexicalModelPickerViewController: UITableViewController, UIAlertViewDelega
return false
}
if !Manager.shared.canRemoveDefaultLexicalModel {
return indexPath.row != 0
}
if indexPath.row > 0 {
return true
}
return userLexicalModels.count > 1
return true
}
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle,
@ -127,7 +133,8 @@ class LexicalModelPickerViewController: UITableViewController, UIAlertViewDelega
return
}
if Manager.shared.removeLexicalModel(at: indexPath.row) {
let globalIndex = local2globalIndex(indexPath.row)
if Manager.shared.removeLexicalModel(at: globalIndex) {
loadUserLexicalModels()
}
setIsDoneButtonEnabled(true)
@ -139,14 +146,16 @@ class LexicalModelPickerViewController: UITableViewController, UIAlertViewDelega
}
func showLexicalModelInfoView(with index: Int) {
setIsDoneButtonEnabled(true)
if language == nil {
setIsDoneButtonEnabled(true)
}
let lm = userLexicalModels[index]
let version = lm.version
let infoView = LexicalModelInfoViewController()
infoView.title = lm.name
infoView.lexicalModelCount = userLexicalModels.count
infoView.lexicalModelIndex = index
infoView.lexicalModelIndex = local2globalIndex(index)
infoView.lexicalModelID = lm.id
infoView.languageID = lm.languageID
infoView.lexicalModelVersion = version
@ -154,20 +163,22 @@ class LexicalModelPickerViewController: UITableViewController, UIAlertViewDelega
navigationController?.pushViewController(infoView, animated: true)
}
// MARK: - UITableViewDelegate
override func tableView(_ tableView: UITableView,
willDisplay cell: UITableViewCell,
forRowAt indexPath: IndexPath) {
cell.selectionStyle = .none
let lm = userLexicalModels[indexPath.row]
cell.textLabel?.text = lm.languageID // maybe do a lookup for lm.languageName
cell.detailTextLabel?.text = lm.name
cell.textLabel?.text = lm.name
cell.detailTextLabel?.text = lm.id // maybe put a longer description here?
cell.tag = indexPath.row
if Manager.shared.currentLexicalModelID == lm.fullID {
cell.selectionStyle = .blue
cell.isSelected = true
cell.accessoryType = .detailDisclosureButton
cell.accessoryType = .checkmark
} else {
cell.selectionStyle = .none
cell.isSelected = false
@ -179,6 +190,19 @@ class LexicalModelPickerViewController: UITableViewController, UIAlertViewDelega
switchLexicalModel(indexPath.row)
}
func local2globalIndex(_ localIndex: Int) -> Int {
let globalIndex: Int
if let lang = self.language {
let lm = userLexicalModels[localIndex]
globalIndex = Storage.active.userDefaults.userLexicalModels?.firstIndex(where: {
$0.languageID == lang.id && $0.id == lm.id
}) ?? 0
} else {
globalIndex = localIndex
}
return globalIndex
}
private func lexicalModelDownloadStarted(_ lexicalModels: [InstallableLexicalModel]) {
view.isUserInteractionEnabled = false
navigationItem.leftBarButtonItem?.isEnabled = false
@ -186,6 +210,7 @@ class LexicalModelPickerViewController: UITableViewController, UIAlertViewDelega
}
private func lexicalModelDownloadCompleted(_ lexicalModels: [InstallableLexicalModel]) {
log.info("lexicalModelDownloadCompleted LexicalModelPicker")
if view == navigationController?.topViewController?.view {
if updateQueue == nil {
return
@ -275,8 +300,15 @@ class LexicalModelPickerViewController: UITableViewController, UIAlertViewDelega
}
}
// if we have a language set, filter models down to those of that language
private func loadUserLexicalModels() {
userLexicalModels = Storage.active.userDefaults.userLexicalModels ?? []
if let lang = self.language {
userLexicalModels = Storage.active.userDefaults.userLexicalModels?.filter({
$0.languageID == lang.id
}) ?? []
} else {
userLexicalModels = Storage.active.userDefaults.userLexicalModels ?? []
}
tableView.reloadData()
}
@ -391,7 +423,9 @@ class LexicalModelPickerViewController: UITableViewController, UIAlertViewDelega
button?.isEnabled = false
let vc = LanguageViewController(Manager.shared.apiLexicalModelRepository) //may need to be different for models
navigationController?.pushViewController(vc, animated: true)
setIsDoneButtonEnabled(true)
if language == nil {
setIsDoneButtonEnabled(true)
}
}
}

View file

@ -121,10 +121,9 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
public var canRemoveLexicalModels = true
/// Allow the default lexical model to be removed.
/// The last lexical model cannot be removed, so the default lexical model cannot be removed if it
/// is the only lexical model in the list, regardless of the value of this property.
/// The default value is false.
public var canRemoveDefaultLexicalModel = false
/// The last lexical model CAN be removed, as this is an optional feature
/// The default value is true.
public var canRemoveDefaultLexicalModel = true
public let apiLexicalModelRepository: APILexicalModelRepository
@ -136,7 +135,24 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
public var openURL: ((URL) -> Bool)?
var currentKeyboardID: FullKeyboardID?
var currentLexicalModelID: FullLexicalModelID?
private var _currentLexicalModelID: FullLexicalModelID?
var currentLexicalModelID: FullLexicalModelID? {
get {
if _currentLexicalModelID == nil {
let userData = Util.isSystemKeyboard ? UserDefaults.standard : Storage.active.userDefaults
_currentLexicalModelID = userData.currentLexicalModelID
}
return _currentLexicalModelID
}
set(value) {
_currentLexicalModelID = value
let userData = Util.isSystemKeyboard ? UserDefaults.standard : Storage.active.userDefaults
userData.currentLexicalModelID = _currentLexicalModelID
userData.synchronize()
}
}
var currentRequest: HTTPDownloadRequest?
var shouldReloadKeyboard = false
var shouldReloadLexicalModel = false
@ -219,6 +235,15 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
}
// MARK: - Keyboard management
public func showKeymanEngineSettings(inVC: UIViewController) -> Void {
hideKeyboard()
let settingsVC = SettingsViewController()
let nc = UINavigationController(rootViewController: settingsVC)
nc.modalTransitionStyle = .coverVertical
nc.modalPresentationStyle = .pageSheet
inVC.present(nc, animated: true)
}
/// Sets the current keyboard, querying from the user's list of keyboards.
///
@ -276,7 +301,7 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
inputViewController.showHelpBubble(afterDelay: 1.5)
}
//HERE getAssociatedLexicalModel(langID) and setLexicalModel
// getAssociatedLexicalModel(langID) and setLexicalModel
NotificationCenter.default.post(name: Notifications.keyboardChanged,
object: self,
@ -284,7 +309,6 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
let userDefaults: UserDefaults = Storage.active.userDefaults
// If we have a lexical model for the keyboard's language, activate it.
//let lm = Storage.active.userDefaults.userLexicalModel(withFullID: valid_models[0].fullID)!
if let preferred_model = preferredLexicalModel(userDefaults, forLanguage: kb.languageID) {
_ = Manager.shared.registerLexicalModel(preferred_model)
}
@ -342,10 +366,6 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
inputViewController.registerLexicalModel(lm)
let userData = Util.isSystemKeyboard ? UserDefaults.standard : Storage.active.userDefaults
userData.currentLexicalModelID = lm.fullID
userData.synchronize()
if isKeymanHelpOn {
inputViewController.showHelpBubble(afterDelay: 1.5)
}
@ -358,8 +378,11 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
return true
}
/// Adds a new lexical model to the list in the lexical model picker if it doesn't already exist.
/// The lexical model must be downloaded (see `downloadLexicalModel()`) or preloaded (see `preloadLanguageFile()`)
/** Adds a new lexical model to the list in the lexical model picker if it doesn't already exist.
* The lexical model must be downloaded (see `downloadLexicalModel()`) or preloaded (see `preloadLanguageFile()`)
* I believe this is background-thread-safe (no UI done)
*/
public func addLexicalModel(_ lexicalModel: InstallableLexicalModel) {
let lexicalModelPath = Storage.active.lexicalModelURL(for: lexicalModel).path
if !FileManager.default.fileExists(atPath: lexicalModelPath) {
@ -381,7 +404,8 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
userDefaults.userLexicalModels = userLexicalModels
userDefaults.set([Date()], forKey: Key.synchronizeSWLexicalModel)
userDefaults.synchronize()
}
log.info("Added lexical model ID: \(lexicalModel.id) name: \(lexicalModel.name)")
}
/// Removes a keyboard from the list in the keyboard picker if it exists.
/// - Returns: The keyboard exists and was removed
@ -469,21 +493,21 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
return false
}
let kb = userLexicalModels[index]
let lm = userLexicalModels[index]
userLexicalModels.remove(at: index)
userData.userLexicalModels = userLexicalModels
userData.set([Date()], forKey: Key.synchronizeSWLexicalModel)
userData.synchronize()
log.info("Removing lexical model with ID \(kb.id) and languageID \(kb.languageID)")
log.info("Removing lexical model with ID \(lm.id) and languageID \(lm.languageID)")
// Set a new lexical model if deleting the current one
if kb.fullID == currentLexicalModelID {
if lm.fullID == currentLexicalModelID {
_ = registerLexicalModel(userLexicalModels[0])
}
if !userLexicalModels.contains(where: { $0.id == kb.id }) {
let lexicalModelDir = Storage.active.lexicalModelDir(forID: kb.id)
if !userLexicalModels.contains(where: { $0.id == lm.id }) {
let lexicalModelDir = Storage.active.lexicalModelDir(forID: lm.id)
FontManager.shared.unregisterFonts(in: lexicalModelDir, fromSystemOnly: false)
log.info("Deleting directory \(lexicalModelDir)")
if (try? FileManager.default.removeItem(at: lexicalModelDir)) == nil {
@ -493,7 +517,7 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
log.info("User has another language installed. Skipping delete of lexical model files.")
}
NotificationCenter.default.post(name: Notifications.lexicalModelRemoved, object: self, value: kb)
NotificationCenter.default.post(name: Notifications.lexicalModelRemoved, object: self, value: lm)
return true
}
@ -970,16 +994,19 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
return nil
}
func installLexicalModelPackage(downloadedPackageFile: URL) {
// return a lexical model so caller can use it in a downloadSucceeded call
func installLexicalModelPackage(downloadedPackageFile: URL) -> InstallableLexicalModel? {
var installedLexicalModel: InstallableLexicalModel? = nil
let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
var destination = documentsDirectory
destination.appendPathComponent("temp/\(downloadedPackageFile.lastPathComponent)")
KeymanPackage.extract(fileUrl: downloadedPackageFile, destination: destination, complete: { kmp in
if let kmp = kmp {
if let kmp = kmp as! LexicalModelKeymanPackage? {
do {
try Manager.shared.parseLMKMP(kmp.sourceFolder)
log.info("successfully installed the lexical model from: \(kmp.sourceFolder)")
log.info("successfully parsed the lexical model in: \(kmp.sourceFolder)")
installedLexicalModel = kmp.models[0].installableLexicalModels[0]
//this can fail gracefully and not show errors to users
try FileManager.default.removeItem(at: downloadedPackageFile)
} catch {
@ -989,6 +1016,7 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
log.error("Error extracting the lexical model from the package: \(KMPError.invalidPackage)")
}
})
return installedLexicalModel
}
func downloadLexicalModelPackage(string lexicalModelPackageURLString: String) -> Void {
@ -1003,20 +1031,28 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
error: Error?) {
if let error = error {
log.error("Failed to fetch lexical model KMP file")
downloadFailed(forKeyboards: [], error: error)
downloadFailed(forLexicalModelPackage: lexicalModelPackageURLString, error: error)
} else {
do {
try data!.write(to: dest)
} catch {
log.error("Error writing the lexical model download data: \(error)")
}
installLexicalModelPackage(downloadedPackageFile: dest)
if let lm = installLexicalModelPackage(downloadedPackageFile: dest) {
downloadSucceeded(forLexicalModel: lm)
} else {
let installError = NSError(domain: "Keyman", code: 0,
userInfo: [NSLocalizedDescriptionKey: "installError"])
downloadFailed(forLexicalModelPackage: lexicalModelPackageURLString, error: installError )
}
}
}
log.info("downloading lexical model from Keyman cloud: \(lexicalModelKMPURL).")
let task = URLSession.shared.dataTask(with: lexicalModelKMPURL) { (data, response, error) in
lexicalModelDownloaded(data: data, response: response, dest: destinationUrl, error: error)
DispatchQueue.main.async {
lexicalModelDownloaded(data: data, response: response, dest: destinationUrl, error: error)
}
}
task.resume()
@ -1230,7 +1266,7 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
downloadQueue!.userInfo = commonUserData
let request = HTTPDownloadRequest(url: lexicalModelURL, userInfo: commonUserData)
request.destinationFile = Storage.active.lexicalModelURL(forID: lexicalModel.id, version: lexicalModel.version ?? "0.1.0").path
request.destinationFile = Storage.active.lexicalModelURL(forID: lexicalModel.id, version: lexicalModel.version ?? InstallableConstants.defaultVersion).path
request.tag = 0
downloadQueue!.addRequest(request)
@ -1381,6 +1417,20 @@ public class Manager: NSObject, HTTPDownloadDelegate, UIGestureRecognizerDelegat
object: self,
value: notification)
}
private func downloadFailed(forLexicalModelPackage packageURL: String, error: Error) {
let notification = LexicalModelDownloadFailedNotification(lmOrLanguageID: packageURL, error: error)
NotificationCenter.default.post(name: Notifications.lexicalModelDownloadFailed,
object: self,
value: notification)
}
private func downloadSucceeded(forLexicalModel lm: InstallableLexicalModel) {
let notification = LexicalModelDownloadCompletedNotification([lm])
NotificationCenter.default.post(name: Notifications.lexicalModelDownloadCompleted,
object: self,
value: notification)
}
// MARK: - Loading custom keyboards
/// Preloads the JS and font files required for a keyboard.

View file

@ -8,6 +8,10 @@
import Foundation
struct InstallableConstants {
static let defaultVersion = "0.1.0"
}
/// Mainly differs from the API `LexicalModel` by having an associated language.
public struct InstallableLexicalModel: Codable {
public var id: String
@ -38,7 +42,7 @@ public struct InstallableLexicalModel: Codable {
self.name = lexicalModel.name
self.languageID = languageID
// self.languageName = language.name
self.version = lexicalModel.version ?? "0.1.0"
self.version = lexicalModel.version ?? InstallableConstants.defaultVersion
self.isCustom = isCustom
}
}

View file

@ -19,7 +19,7 @@ public struct Language: Codable {
/// Corresponding Keyboard objects.
public let keyboards: [Keyboard]?
/// Corresponding Keyboard objects.
/// Corresponding lexical models.
public let lexicalModels: [LexicalModel]?
/// Font for input fields (and OSK if `oskFont` is not present).

View file

@ -9,7 +9,7 @@
import Foundation
public class LexicalModelKeymanPackage : KeymanPackage {
private var models : [KMPLexicalModel]!
public var models : [KMPLexicalModel]!
public override func defaultInfoHtml() -> String {
var str = "Found lexical models in package:<br/>"

View file

@ -0,0 +1,485 @@
//
// InstalledLanguagesViewController.swift
// KeymanEngine
//
// Created by Randy Boring on 5/20/19.
// Copyright © 2019 SIL International. All rights reserved.
//
//
import QuartzCore
import UIKit
private let activityViewTag = -2
private let toolbarButtonTag = 100
private let toolbarLabelTag = 101
private let toolbarActivityIndicatorTag = 102
class InstalledLanguagesViewController: UITableViewController, UIAlertViewDelegate {
private var userKeyboards: [String: InstallableKeyboard] = [:]
private var userLexicalModels: [String: InstallableLexicalModel] = [:]
private var sectionIndexTitles: [String] = []
private var indices: [Int] = []
private var selectedSection = 0
private var isUpdate = false
private var installedLanguages: [String: Language]
private var languages: [Language] = []
private let keyboardRepository: KeyboardRepository?
private let lexicalModelRepository: LexicalModelRepository?
private var keyboardDownloadStartedObserver: NotificationObserver?
private var keyboardDownloadCompletedObserver: NotificationObserver?
private var keyboardDownloadFailedObserver: NotificationObserver?
private var lexicalModelDownloadStartedObserver: NotificationObserver?
private var lexicalModelDownloadCompletedObserver: NotificationObserver?
private var lexicalModelDownloadFailedObserver: NotificationObserver?
init(_ givenLanguages: [String: Language]) {
self.installedLanguages = givenLanguages
self.keyboardRepository = nil
self.lexicalModelRepository = nil
super.init(nibName: nil, bundle: nil)
// keyboardRepository.delegate = self
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func loadView() {
super.loadView()
languages = languageList(installedLanguages)
}
override func viewDidLoad() {
super.viewDidLoad()
title = "Installed Languages"
selectedSection = NSNotFound
keyboardDownloadStartedObserver = NotificationCenter.default.addObserver(
forName: Notifications.keyboardDownloadStarted,
observer: self,
function: InstalledLanguagesViewController.keyboardDownloadStarted)
keyboardDownloadCompletedObserver = NotificationCenter.default.addObserver(
forName: Notifications.keyboardDownloadCompleted,
observer: self,
function: InstalledLanguagesViewController.keyboardDownloadCompleted)
keyboardDownloadFailedObserver = NotificationCenter.default.addObserver(
forName: Notifications.keyboardDownloadFailed,
observer: self,
function: InstalledLanguagesViewController.keyboardDownloadFailed)
lexicalModelDownloadStartedObserver = NotificationCenter.default.addObserver(
forName: Notifications.lexicalModelDownloadStarted,
observer: self,
function: InstalledLanguagesViewController.lexicalModelDownloadStarted)
lexicalModelDownloadCompletedObserver = NotificationCenter.default.addObserver(
forName: Notifications.lexicalModelDownloadCompleted,
observer: self,
function: InstalledLanguagesViewController.lexicalModelDownloadCompleted)
lexicalModelDownloadFailedObserver = NotificationCenter.default.addObserver(
forName: Notifications.lexicalModelDownloadFailed,
observer: self,
function: InstalledLanguagesViewController.lexicalModelDownloadFailed)
if Manager.shared.canAddNewKeyboards {
let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self,
action: #selector(self.addClicked))
navigationItem.rightBarButtonItem = addButton
}
log.info("viewDidLoad: InstalledLanguagesViewController (registered for keyboardDownloadStarted)")
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
loadUserKeyboards()
loadUserLexicalModels()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
navigationController?.setToolbarHidden(true, animated: true)
// if no rows to show yet, show a loading indicator
if numberOfSections(in: tableView) == 0 {
showActivityView()
}
log.info("didAppear: InstalledLanguagesViewController")
}
override func numberOfSections(in tableView: UITableView) -> Int {
return languages.count
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 1
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifierType1 = "CellType1"
let cellIdentifierType2 = "CellType2"
let keyboards = languages[indexPath.section].keyboards!
let cellIdentifier = (keyboards.count < 2) ? cellIdentifierType1 : cellIdentifierType2
let cell: UITableViewCell
if let reusedCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) {
cell = reusedCell
} else {
let selectionColor = UIView()
selectionColor.backgroundColor = UIColor(red: 204.0 / 255.0, green: 136.0 / 255.0,
blue: 34.0 / 255.0, alpha: 1.0)
if keyboards.count < 2 {
cell = KeyboardNameTableViewCell(style: .subtitle, reuseIdentifier: cellIdentifier)
} else {
cell = UITableViewCell(style: .subtitle, reuseIdentifier: cellIdentifier)
}
cell.selectedBackgroundView = selectionColor
cell.accessoryType = .disclosureIndicator
}
cell.detailTextLabel?.text = keyboards.count < 2 ? (keyboards.first?.name ?? "") : "\(keyboards.count) installed"
return cell
}
// Create sections by first letter
override func sectionIndexTitles(for tableView: UITableView) -> [String] {
if !sectionIndexTitles.isEmpty {
return sectionIndexTitles
}
sectionIndexTitles = []
indices = []
for (index, language) in languages.enumerated() {
let firstLetter = String(language.name.prefix(1)).uppercased()
if !sectionIndexTitles.contains(firstLetter) {
sectionIndexTitles.append(firstLetter)
indices.append(index)
}
}
return sectionIndexTitles
}
override func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
return indices[index]
}
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
guard let language = languages[safe: indexPath.section] else {
return
}
cell.textLabel?.text = language.name
if cell.accessoryType == .disclosureIndicator {
return
}
guard let cell = cell as? KeyboardNameTableViewCell else {
return
}
let keyboardID = language.keyboards![0].id
cell.accessoryType = .none
cell.isUserInteractionEnabled = true
cell.textLabel?.isEnabled = true
cell.detailTextLabel?.isEnabled = true
let kbState = Manager.shared.stateForKeyboard(withID: keyboardID)
cell.setKeyboardState(kbState, selected: false, defaultAccessoryType: cell.accessoryType)
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
selectedSection = indexPath.section
tableView.cellForRow(at: indexPath)?.isSelected = false
if tableView.cellForRow(at: indexPath)?.accessoryType == .disclosureIndicator {
let title = tableView.cellForRow(at: indexPath)?.textLabel?.text ?? ""
showLanguageSettingsView(title: title, languageIndex: indexPath.section)
return
}
let language = languages[indexPath.section]
let keyboardIndex = 0;
let keyboard = language.keyboards![keyboardIndex]
//TODO: launch real Language Settings controller here
let languageSettingsController = UIAlertController(title: "\(language.name): \(keyboard.name)",
message: "This should be Language Settings",
preferredStyle: UIAlertControllerStyle.alert)
languageSettingsController.addAction(UIAlertAction(title: "Cancel",
style: UIAlertActionStyle.cancel,
handler: nil))
// languageSettingsController.addAction(UIAlertAction(title: "Download",
// style: UIAlertActionStyle.default,
// handler: {_ in self.downloadHandler(keyboardIndex)} ))
self.present(languageSettingsController, animated: true, completion: nil)
}
override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
let title = tableView.cellForRow(at: indexPath)?.textLabel?.text ?? ""
showLanguageSettingsView(title: title, languageIndex: indexPath.section)
}
private func showLanguageSettingsView(title: String, languageIndex: Int) {
let langSettingsView = LanguageSettingsViewController(languages[languageIndex])
langSettingsView.title = title
navigationController?.pushViewController(langSettingsView, animated: true)
}
func errorAcknowledgmentHandler(withAction action: UIAlertAction) {
if !languages.isEmpty {
navigationController?.popToRootViewController(animated: true)
}
}
func downloadHandler(_ keyboardIndex: Int) {
let language = languages[selectedSection]
let keyboard = language.keyboards![keyboardIndex]
Manager.shared.downloadKeyboard(withID: keyboard.id, languageID: language.id, isUpdate: isUpdate)
}
private func restoreNavigation() {
view.isUserInteractionEnabled = true
navigationItem.setHidesBackButton(false, animated: true)
navigationItem.leftBarButtonItem?.isEnabled = true
if navigationItem.rightBarButtonItem != nil {
navigationItem.rightBarButtonItem?.isEnabled = true
}
}
private func showDownloading(_ downloadLabel: String) {
guard let toolbar = navigationController?.toolbar else {
return
}
let labelFrame = CGRect(origin: toolbar.frame.origin,
size: CGSize(width: toolbar.frame.width * 0.95,
height: toolbar.frame.height * 0.7))
let label = UILabel(frame: labelFrame)
label.backgroundColor = UIColor.clear
label.textColor = UIColor.white
label.textAlignment = .center
label.center = CGPoint(x: toolbar.frame.width * 0.5, y: toolbar.frame.height * 0.5)
label.text = "Downloading \(downloadLabel)\u{2026}"
label.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin, .flexibleTopMargin,
.flexibleBottomMargin, .flexibleWidth, .flexibleHeight]
label.tag = toolbarLabelTag
let indicatorView = UIActivityIndicatorView(activityIndicatorStyle: .gray)
indicatorView.center = CGPoint(x: toolbar.frame.width - indicatorView.frame.width,
y: toolbar.frame.height * 0.5)
indicatorView.autoresizingMask = [.flexibleLeftMargin, .flexibleTopMargin, .flexibleBottomMargin]
indicatorView.tag = toolbarActivityIndicatorTag
indicatorView.startAnimating()
toolbar.viewWithTag(toolbarButtonTag)?.removeFromSuperview()
toolbar.viewWithTag(toolbarLabelTag)?.removeFromSuperview()
toolbar.viewWithTag(toolbarActivityIndicatorTag)?.removeFromSuperview()
toolbar.addSubview(label)
toolbar.addSubview(indicatorView)
navigationController?.setToolbarHidden(false, animated: true)
}
private func hideDownloading() {
navigationController?.toolbar?.viewWithTag(toolbarActivityIndicatorTag)?.removeFromSuperview()
navigationController?.toolbar?.viewWithTag(toolbarLabelTag)?.removeFromSuperview()
Timer.scheduledTimer(timeInterval: 3.0, target: self, selector: #selector(self.hideToolbarDelayed),
userInfo: nil, repeats: false)
}
private func keyboardDownloadStarted() {
log.info("keyboardDownloadStarted: InstalledLanguagesViewController")
view.isUserInteractionEnabled = false
navigationItem.setHidesBackButton(true, animated: true)
showDownloading("keyboard")
}
private func lexicalModelDownloadStarted(_ lexicalModels: [InstallableLexicalModel]) {
log.info("lexicalModelDownloadStarted")
showDownloading("dictionary")
}
private func keyboardDownloadCompleted(_ keyboards: [InstallableKeyboard]) {
log.info("keyboardDownloadCompleted: InstalledLanguagesViewController")
Manager.shared.shouldReloadKeyboard = true
// Update keyboard version
for keyboard in keyboards {
Manager.shared.updateUserKeyboards(with: keyboard)
}
let label = navigationController?.toolbar?.viewWithTag(toolbarLabelTag) as? UILabel
label?.text = "Keyboard successfully downloaded!"
hideDownloading()
restoreNavigation()
// Add keyboard.
for keyboard in keyboards {
Manager.shared.addKeyboard(keyboard)
_ = Manager.shared.setKeyboard(keyboard)
}
navigationController?.popToRootViewController(animated: true)
}
private func lexicalModelDownloadCompleted(_ lexicalModels: [InstallableLexicalModel]) {
log.info("lexicalModelDownloadCompleted: InstalledLanguagesViewController")
// Add models.
for lexicalModel in lexicalModels {
Manager.shared.addLexicalModel(lexicalModel)
_ = Manager.shared.registerLexicalModel(lexicalModel)
}
hideDownloading()
restoreNavigation()
navigationController?.popToRootViewController(animated: true)
}
private func keyboardDownloadFailed() {
log.info("keyboardDownloadFailed: InstalledLanguagesViewController")
restoreNavigation()
}
private func lexicalModelDownloadFailed() {
log.info("lexicalModelDownloadFailed: InstalledLanguagesViewController")
restoreNavigation()
let title = "Lexical Model Download Error"
navigationController?.setToolbarHidden(true, animated: true)
let alertController = UIAlertController(title: title, message: "",
preferredStyle: UIAlertControllerStyle.alert)
alertController.addAction(UIAlertAction(title: "OK",
style: UIAlertActionStyle.cancel,
handler: nil))
self.present(alertController, animated: true, completion: nil)
}
func showActivityView() {
view.isUserInteractionEnabled = false
let indicatorView = UIActivityIndicatorView(activityIndicatorStyle: .whiteLarge)
let activityView = UIView(frame: indicatorView.bounds.insetBy(dx: -10.0, dy: -10.0))
activityView.backgroundColor = UIColor(white: 0.5, alpha: 0.8)
activityView.layer.cornerRadius = 6.0
activityView.center = view.center
activityView.tag = activityViewTag
activityView.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin, .flexibleTopMargin,
.flexibleBottomMargin]
indicatorView.center = CGPoint(x: activityView.bounds.size.width * 0.5, y: activityView.bounds.size.height * 0.5)
indicatorView.startAnimating()
activityView.addSubview(indicatorView)
view.addSubview(activityView)
}
func dismissActivityView() {
let activityView = view.viewWithTag(activityViewTag)
activityView?.removeFromSuperview()
view.isUserInteractionEnabled = true
}
func loadUserKeyboards() {
userKeyboards = [:]
guard let userKbList = Storage.active.userDefaults.userKeyboards else {
return
}
for kb in userKbList {
let dictKey = "\(kb.languageID)_\(kb.id)"
userKeyboards[dictKey] = kb
}
tableView.reloadData()
}
func loadUserLexicalModels() {
userLexicalModels = [:]
guard let userLmList = Storage.active.userDefaults.userLexicalModels else {
return
}
for lm in userLmList {
let dictKey = "\(lm.languageID)_\(lm.id)"
userLexicalModels[dictKey] = lm
}
tableView.reloadData()
}
private func isAdded(languageID: String?, keyboardID: String?) -> Bool {
guard let languageID = languageID, let keyboardID = keyboardID else {
return false
}
return userKeyboards["\(languageID)_\(keyboardID)"] != nil
}
private func showConnectionErrorAlert() {
dismissActivityView()
let alertController = UIAlertController(title: "Connection Error",
message: "Could not reach Keyman server. Please try again later.",
preferredStyle: UIAlertControllerStyle.alert)
alertController.addAction(UIAlertAction(title: "OK",
style: UIAlertActionStyle.default,
handler: errorAcknowledgmentHandler))
self.present(alertController, animated: true, completion: nil)
}
}
// MARK: - KeyboardRepositoryDelegate
extension InstalledLanguagesViewController: KeyboardRepositoryDelegate {
func keyboardRepositoryDidFetch(_ repository: KeyboardRepository) {
if let languageDict = repository.languages {
languages = languageList(languageDict)
}
self.dismissActivityView()
self.tableView.reloadData()
if self.numberOfSections(in: self.tableView) == 0 {
self.showConnectionErrorAlert()
}
}
func keyboardRepository(_ repository: KeyboardRepository, didFailFetch error: Error) {
dismissActivityView()
showConnectionErrorAlert()
}
private func languageList(_ languageDict: [String: Language]) -> [Language] {
return languageDict.values.sorted { a, b -> Bool in
a.name.localizedCaseInsensitiveCompare(b.name) == .orderedAscending
}
}
}
// MARK: - LexicalModelRepositoryDelegate
extension InstalledLanguagesViewController: LexicalModelRepositoryDelegate {
/** lexicalModelRepositoryDidFetchList callback on successful fetching of list of lexical models for a language
* caller should wrap in DispatchQueue.main.async {} if not already on main thread
*/
func lexicalModelRepositoryDidFetchList(_ repository: LexicalModelRepository) {
if let languageDict = repository.languages {
languages = languageList(languageDict)
}
self.dismissActivityView()
self.tableView.reloadData()
if self.numberOfSections(in: self.tableView) == 0 {
self.showConnectionErrorAlert()
}
}
/** lexicalModelRepository didFailFetch callback on failure to fetch list of lexical models for a language
* caller should wrap in DispatchQueue.main.async {} if not already on main thread
*/
func lexicalModelRepository(_ repository: LexicalModelRepository, didFailFetch error: Error) {
dismissActivityView()
showConnectionErrorAlert()
}
@objc func hideToolbarDelayed(_ timer: Timer) {
navigationController?.setToolbarHidden(true, animated: true)
}
@objc func addClicked(_ sender: Any) {
showAddKeyboard()
}
func showAddKeyboard() {
let button: UIButton? = (navigationController?.toolbar?.viewWithTag(toolbarButtonTag) as? UIButton)
button?.isEnabled = false
let vc = LanguageViewController(keyboardRep: Manager.shared.apiKeyboardRepository, modelRep: Manager.shared.apiLexicalModelRepository)
navigationController?.pushViewController(vc, animated: true)
}
}

View file

@ -0,0 +1,317 @@
//
// LanguageSettingsViewController.swift
// KeymanEngine
//
// Created by Randy Boring on 5/29/19.
// Copyright © 2019 SIL International. All rights reserved.
//
import UIKit
private let toolbarButtonTag = 100
class LanguageSettingsViewController: UITableViewController {
let language: Language
private var userKeyboards: [String: Language] = [:]
private var settingsArray = [[String: String]]()
public init(_ inLanguage: Language) {
language = inLanguage
super.init(nibName: nil, bundle: nil)
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
open override func loadView() {
super.loadView()
tableView?.delegate = self
}
override func viewDidLoad() {
super.viewDidLoad()
title = "\(language.name) Settings"
log.info("viewDidLoad: LanguageSettingsViewController title: \(title ?? "<empty>")")
if Manager.shared.canAddNewKeyboards {
let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self,
action: #selector(self.addClicked))
navigationItem.rightBarButtonItem = addButton
}
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
log.info("didAppear: LanguageSettingsViewController")
}
// MARK: - Table view data source UITableViewDataSource
override func numberOfSections(in tableView: UITableView) -> Int {
return 2
}
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
if 0 == section {
// so far as I know, there is always at least one keyboard or else we won't show
let kbct = language.keyboards!.count
return kbct
} else {
return 3 // 4 in future when we can manage a user dictionary
}
}
public func frameAtRightOfCell(cell cellFrame: CGRect, controlSize: CGSize) -> CGRect {
let rightOffset = cellFrame.size.width
let switchWidth: CGFloat = 20
let switchX = rightOffset - switchWidth
let switchHeight = controlSize.height
let cellSwitchHeightDiff = cellFrame.size.height - switchHeight
let switchY = cellFrame.origin.y + 0.5 * cellSwitchHeightDiff
let switchFrame = CGRect(x: switchX,
y: switchY,
width: switchWidth,
height: cellFrame.size.height)
return switchFrame
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = 0 == indexPath.section ? "KeyboardInLanguageSettingsCell" : "LanguageSettingsCell"
let reusableCell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier)
if let cell = reusableCell {
return cell
}
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: cellIdentifier)
if 1 == indexPath.section {
//TODO: find the settings these are to show
if 0 == indexPath.row {
cell.accessoryType = .none
let doPredictionsSwitch = UISwitch()
let switchFrame = frameAtRightOfCell(cell: cell.frame, controlSize: doPredictionsSwitch.frame.size)
doPredictionsSwitch.frame = switchFrame
doPredictionsSwitch.isOn = false
// showBannerSwitch.addTarget(self, action: #selector(self.predictionSwitchValueChanged), for: .valueChanged)
cell.addSubview(doPredictionsSwitch)
} else if 1 == indexPath.row {
cell.accessoryType = .none
let doCorrectionsSwitch = UISwitch()
let switchFrame = frameAtRightOfCell(cell: cell.frame, controlSize: doCorrectionsSwitch.frame.size)
doCorrectionsSwitch.frame = switchFrame
doCorrectionsSwitch.isOn = false
// showBannerSwitch.addTarget(self, action: #selector(self.correctionSwitchValueChanged), for: .valueChanged)
cell.addSubview(doCorrectionsSwitch)
} else { // rows 3 and 4
cell.accessoryType = .disclosureIndicator
}
}
let selectionColor = UIView()
selectionColor.backgroundColor = UIColor(red: 95.0 / 255.0, green: 196.0 / 255.0, blue: 217.0 / 255.0, alpha: 1.0)
cell.selectedBackgroundView = selectionColor
cell.textLabel?.font = cell.textLabel?.font?.withSize(16.0)
return cell
}
// MARK: - UITableViewDelegate
// fixed font style. use custom view (UILabel) if you want something different
override func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
var title: String
switch (section) {
case 0:
title = "keyboards"
case 1:
title = "language settings"
default:
title = "unknown header"
}
return title
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat
{
if (section == 0 || section == 1)
{
return 32.0;
}
else
{
return CGFloat.leastNormalMagnitude;
}
}
override func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
return CGFloat.leastNonzeroMagnitude
}
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cell.isUserInteractionEnabled = true
cell.textLabel?.isEnabled = true
cell.detailTextLabel?.isEnabled = true
if 0 == indexPath.section { // keyboard list
guard let keyboard = language.keyboards?[safe: indexPath.row] else {
return
}
cell.textLabel?.text = keyboard.name
cell.detailTextLabel?.text = keyboard.version + " " + keyboard.id
cell.accessoryType = .disclosureIndicator
} else { // language settings
cell.accessoryType = .none
switch indexPath.row {
case 0:
cell.textLabel?.text = "Enable corrections"
case 1:
cell.textLabel?.text = "Enable predictions"
case 2:
cell.textLabel?.text = "Model"
cell.accessoryType = .disclosureIndicator
if let modelCt = language.lexicalModels?.count {
switch modelCt {
case 0:
cell.detailTextLabel?.text = "no models installed"
case 1:
cell.detailTextLabel?.text = "one model installed"
default:
cell.detailTextLabel?.text = "\(modelCt) models installed"
}
} else {
cell.detailTextLabel?.text = "no models installed"
}
case 3: // future
cell.textLabel?.text = "Manage dictionary"
cell.accessoryType = .disclosureIndicator
cell.isUserInteractionEnabled = false
default:
cell.textLabel?.text = "error"
}
}
}
override open func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.cellForRow(at: indexPath)?.isSelected = false
performAction(for: indexPath)
}
override open func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
performAction(for: indexPath)
}
@objc func addClicked(_ sender: Any) {
showAddLanguageKeyboard()
}
func showAddLanguageKeyboard() {
let button: UIButton? = (navigationController?.toolbar?.viewWithTag(toolbarButtonTag) as? UIButton)
button?.isEnabled = false
let vc = LanguageSpecificViewController(Manager.shared.apiKeyboardRepository, language: language)
vc.title = "Add new \(language.name) keyboard"
navigationController?.pushViewController(vc, animated: true)
}
private func performAction(for indexPath: IndexPath) {
switch indexPath.section {
case 0:
showKeyboardInfoView(kb: (language.keyboards?[safe: indexPath.row])!)
case 1:
switch indexPath.row {
case 2:
showLexicalModelsView()
default:
break
}
default:
break
}
}
func showKeyboardInfoView(kb: Keyboard) {
let version = kb.version
let matchingFullID = FullKeyboardID(keyboardID: kb.id, languageID: language.id)
let userData = Storage.active.userDefaults
// If user defaults for keyboards list does not exist, do nothing.
guard var globalUserKeyboards = userData.userKeyboards else {
log.error("no keyboards in the global keyboards list!")
return
}
if let index = globalUserKeyboards.index(where: { $0.fullID == matchingFullID }) {
guard index < globalUserKeyboards.count else {
return
}
let kbIndex:Int = index
let thisKb = globalUserKeyboards[kbIndex]
let infoView = KeyboardInfoViewController()
infoView.title = thisKb.name
infoView.keyboardCount = userKeyboards.count
infoView.keyboardIndex = index
infoView.keyboardID = thisKb.id
infoView.languageID = language.id
infoView.keyboardVersion = version
infoView.isCustomKeyboard = thisKb.isCustom
navigationController?.pushViewController(infoView, animated: true)
} else {
log.error("this keyboard \(matchingFullID) not found among user's installed keyboards!")
return
}
}
func showLexicalModelsView() {
//LanguageLexicalModelPickerViewController? (should show just the models for this language)
let lmListView = LexicalModelPickerViewController()
lmListView.language = self.language
navigationController?.pushViewController(lmListView, animated: true)
}
func showLexicalModelInfoView() {
if let lm = language.lexicalModels?[safe: 0] {
let version = lm.version
let matchingFullID = FullLexicalModelID(lexicalModelID: lm.id, languageID: language.id)
let userData = Storage.active.userDefaults
if let globalUserLexicalModels = userData.userLexicalModels {
if let index = globalUserLexicalModels.index(where: { $0.fullID == matchingFullID }) {
guard index < globalUserLexicalModels.count else {
return
}
let lmIndex:Int = index
let thisLm = globalUserLexicalModels[lmIndex]
let infoView = LexicalModelInfoViewController()
infoView.title = thisLm.name
infoView.lexicalModelCount = globalUserLexicalModels.count
infoView.lexicalModelIndex = index
infoView.lexicalModelID = thisLm.id
infoView.languageID = language.id
infoView.lexicalModelVersion = version ?? InstallableConstants.defaultVersion
infoView.isCustomLexicalModel = thisLm.isCustom
navigationController?.pushViewController(infoView, animated: true)
} else {
log.error("this lexical model \(matchingFullID) not found among language's installed lexical model!")
}
} else {
log.error("no lexical models in the global models list!")
}
}
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}

View file

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="LanguageSettingsViewController" customModule="KeymanEngine" customModuleProvider="target">
<connections>
<outlet property="view" destination="i5M-Pr-FkT" id="sfx-zR-JGt"/>
</connections>
</placeholder>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<tableView opaque="NO" clipsSubviews="YES" clearsContextBeforeDrawing="NO" contentMode="scaleToFill" bouncesZoom="NO" style="plain" separatorStyle="default" rowHeight="44" sectionHeaderHeight="22" sectionFooterHeight="22" id="i5M-Pr-FkT">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<viewLayoutGuide key="safeArea" id="vLr-E1-eTs"/>
<connections>
<outlet property="dataSource" destination="-1" id="Tng-2m-Rnh"/>
<outlet property="delegate" destination="-1" id="9aC-8N-iBw"/>
</connections>
</tableView>
</objects>
</document>

View file

@ -0,0 +1,109 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.Storyboard.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<scenes>
<!--Keyman Engine Settings-->
<scene sceneID="x6t-fa-5lr">
<objects>
<tableViewController id="TZd-6d-oTq" customClass="SettingsViewController" customModule="KeymanEngine" customModuleProvider="target" sceneMemberID="viewController">
<tableView key="view" clipsSubviews="YES" contentMode="scaleToFill" alwaysBounceVertical="YES" dataMode="prototypes" style="plain" separatorStyle="default" allowsSelection="NO" rowHeight="-1" estimatedRowHeight="-1" sectionHeaderHeight="28" sectionFooterHeight="28" id="mfL-xR-a5l">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
<prototypes>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" restorationIdentifier="languages" preservesSuperviewLayoutMargins="YES" selectionStyle="none" accessoryType="disclosureIndicator" indentationWidth="10" reuseIdentifier="languages" textLabel="D3M-Jf-Lhg" style="IBUITableViewCellStyleDefault" id="ebU-bs-BGU" userLabel="Languages">
<rect key="frame" x="0.0" y="28" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="ebU-bs-BGU" id="ckE-eL-A5F">
<rect key="frame" x="0.0" y="0.0" width="341" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" multipleTouchEnabled="YES" contentMode="left" insetsLayoutMarginsFromSafeArea="NO" text="Installed Languages..." textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" id="D3M-Jf-Lhg">
<rect key="frame" x="16" y="0.0" width="324" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" restorationIdentifier="showbanner" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="showbanner" id="qXA-sF-V3I" userLabel="Show Banner">
<rect key="frame" x="0.0" y="72" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="qXA-sF-V3I" id="eRJ-8o-lZq">
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Show Banner" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="hOh-Ke-SIE">
<rect key="frame" x="31" y="11" width="101" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" translatesAutoresizingMaskIntoConstraints="NO" id="JGV-aw-EHN">
<rect key="frame" x="299" y="6" width="49" height="31"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
</switch>
</subviews>
</tableViewCellContentView>
</tableViewCell>
<tableViewCell clipsSubviews="YES" contentMode="scaleToFill" restorationIdentifier="getstarted" preservesSuperviewLayoutMargins="YES" selectionStyle="default" indentationWidth="10" reuseIdentifier="showgetstarted" id="TRl-40-iBT" userLabel="Show Get Started">
<rect key="frame" x="0.0" y="116" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
<tableViewCellContentView key="contentView" opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="center" preservesSuperviewLayoutMargins="YES" insetsLayoutMarginsFromSafeArea="NO" tableViewCell="TRl-40-iBT" id="uo0-bk-fdu">
<rect key="frame" x="0.0" y="0.0" width="375" height="43.5"/>
<autoresizingMask key="autoresizingMask"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" fixedFrame="YES" text="Show &quot;Get Started&quot; on startup" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="taa-7T-GwB">
<rect key="frame" x="31" y="11" width="233" height="21"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
<switch opaque="NO" contentMode="scaleToFill" horizontalHuggingPriority="750" verticalHuggingPriority="750" fixedFrame="YES" contentHorizontalAlignment="center" contentVerticalAlignment="center" on="YES" translatesAutoresizingMaskIntoConstraints="NO" id="KSR-uB-Zv4">
<rect key="frame" x="299" y="6" width="49" height="31"/>
<autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMaxY="YES"/>
</switch>
</subviews>
</tableViewCellContentView>
</tableViewCell>
</prototypes>
<connections>
<outlet property="dataSource" destination="TZd-6d-oTq" id="hPN-E6-yrw"/>
<outlet property="delegate" destination="TZd-6d-oTq" id="M58-0E-gaS"/>
</connections>
</tableView>
<navigationItem key="navigationItem" title="Keyman Engine Settings" id="2W5-db-3iS"/>
</tableViewController>
<placeholder placeholderIdentifier="IBFirstResponder" id="yus-Dr-rO9" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-692" y="-316.19190404797604"/>
</scene>
<!--Navigation Controller-->
<scene sceneID="iZl-H8-fgH">
<objects>
<navigationController id="ywo-q8-YG6" sceneMemberID="viewController">
<navigationBar key="navigationBar" contentMode="scaleToFill" insetsLayoutMarginsFromSafeArea="NO" id="Z19-HK-IbM">
<rect key="frame" x="0.0" y="20" width="375" height="44"/>
<autoresizingMask key="autoresizingMask"/>
</navigationBar>
<connections>
<segue destination="TZd-6d-oTq" kind="relationship" relationship="rootViewController" id="ACP-ni-DZE"/>
</connections>
</navigationController>
<placeholder placeholderIdentifier="IBFirstResponder" id="pBS-uI-1Pj" userLabel="First Responder" sceneMemberID="firstResponder"/>
</objects>
<point key="canvasLocation" x="-1644" y="-316"/>
</scene>
</scenes>
</document>

View file

@ -0,0 +1,328 @@
//
// SettingsViewController.swift
// KeymanEngine
//
// Created by Randy Boring on 5/13/19.
// Copyright © 2019 SIL International. All rights reserved.
//
import UIKit
open class SettingsViewController: UITableViewController {
private var itemsArray = [[String: String]]()
private var userLanguages: [String: Language] = [:]
override open func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
loadUserLanguages()
log.info("didAppear: SettingsViewController (actually willAppear)")
}
override open func viewDidLoad() {
super.viewDidLoad()
title = "Keyman Settings"
let doneButton = UIBarButtonItem(title: "Done", style: .plain, target: self,
action: #selector(self.doneClicked))
navigationItem.leftBarButtonItem = doneButton
navigationController?.toolbar?.barTintColor = UIColor(red: 0.5, green: 0.75,
blue: 0.25, alpha: 0.9)
}
@objc func doneClicked(_ sender: Any) {
Manager.shared.dismissKeyboardPicker(self)
}
open func launchSettings(launchingVC: UIViewController, sender: Any?) -> Void {
let sb : UIStoryboard = UIStoryboard(name: "Settings", bundle: nil)
if let vc = sb.instantiateInitialViewController() {
launchingVC.present(vc, animated: true, completion: {
log.info("presented settings")
})
}
}
//
// convenience init() {
// self.init(style: UITableViewStyle.grouped)
// }
init(/*storage: Storage*/) {
// self.storage = storage
super.init(nibName: nil, bundle: nil)
itemsArray = [[String: String]]()
itemsArray.append([
"title": "Installed Languages",
"subtitle": "0", //count of installed languages as string
"reuseid" : "languages"
])
itemsArray.append([
"title": "Show Banner",
"subtitle": "",
"reuseid" : "showbanner"
])
itemsArray.append([
"title": "Show 'Get Started' at startup",
"subtitle": "",
"reuseid" : "showgetstarted"
])
_ = view
}
required public init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Table view data source
override open func numberOfSections(in tableView: UITableView) -> Int {
// #warning Incomplete implementation, return the number of sections
return 1
}
override open func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
// #warning Incomplete implementation, return the number of rows
return 3
}
public func frameAtRightOfCell(cell cellFrame: CGRect, controlSize: CGSize) -> CGRect {
let rightOffset = cellFrame.size.width
let switchWidth: CGFloat = 20
let switchX = rightOffset - switchWidth
let switchHeight = controlSize.height
let cellSwitchHeightDiff = cellFrame.size.height - switchHeight
let switchY = cellFrame.origin.y + 0.5 * cellSwitchHeightDiff
let switchFrame = CGRect(x: switchX,
y: switchY,
width: switchWidth,
height: cellFrame.size.height)
return switchFrame
}
override open func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cellIdentifier = itemsArray[indexPath.row]["reuseid"]
if let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier!) {
return cell
}
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: cellIdentifier)
cell.selectionStyle = .none
switch(cellIdentifier) {
case "languages":
cell.accessoryType = .disclosureIndicator
case "showbanner":
cell.accessoryType = .none
let showBannerSwitch = UISwitch()
let switchFrame = frameAtRightOfCell(cell: cell.frame, controlSize: showBannerSwitch.frame.size)
showBannerSwitch.frame = switchFrame
showBannerSwitch.isOn = false //TODO: find the setting this is to show!
showBannerSwitch.addTarget(self, action: #selector(self.bannerSwitchValueChanged),
for: .valueChanged)
cell.addSubview(showBannerSwitch)
case "showgetstarted":
cell.accessoryType = .none
let dontShowAgainSwitch = UISwitch()
// let rightOffset = cell.frame.size.width
// let switchWidth: CGFloat = 20
// let switchX = rightOffset - switchWidth
// let dontShowAgainSwitch = UISwitch()
// let switchHeight = dontShowAgainSwitch.frame.size.height
// let cellSwitchHeightDiff = cell.frame.size.height - switchHeight
// let switchY = cell.frame.origin.y + 0.5 * cellSwitchHeightDiff
// let switchFrame = CGRect(x: switchX,
// y: switchY,
// width: switchWidth,
// height: cell.frame.size.height)
let switchFrame = frameAtRightOfCell(cell: cell.frame, controlSize: dontShowAgainSwitch.frame.size)
dontShowAgainSwitch.frame = switchFrame
dontShowAgainSwitch.isOn = dontShowGetStarted
dontShowAgainSwitch.addTarget(self, action: #selector(self.showGetStartedSwitchValueChanged),
for: .valueChanged)
cell.addSubview(dontShowAgainSwitch)
default:
log.error("unknown cellIdentifier(\"\(cellIdentifier ?? "EMPTY")\")")
cell.accessoryType = .none
}
return cell
}
@objc func bannerSwitchValueChanged(_ sender: Any) {
let userData = Storage.active.userDefaults
if let toggle = sender as? UISwitch {
// actually this should call into KMW, which controls the banner
userData.set(toggle.isOn, forKey: "ShouldShowBanner") //???
userData.synchronize()
}
}
@objc func showGetStartedSwitchValueChanged(_ sender: Any) {
let userData = Storage.active.userDefaults
if let toggle = sender as? UISwitch {
userData.set(toggle.isOn, forKey: "ShouldShowGetStarted")
userData.synchronize()
}
}
private var dontShowGetStarted: Bool {
let userData = Storage.active.userDefaults
return !userData.bool(forKey: "ShouldShowGetStarted")
}
override open func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
cell.accessoryType = .none
cell.textLabel?.text = itemsArray[indexPath.row]["title"]
cell.detailTextLabel?.text = itemsArray[indexPath.row]["subtitle"]
cell.tag = indexPath.row
cell.isUserInteractionEnabled = true
if indexPath.row == 0 {
cell.accessoryType = .disclosureIndicator
} else {
cell.textLabel?.isEnabled = true
cell.detailTextLabel?.isEnabled = false
}
}
// In a xib-based application, navigation from a table can be handled in -tableView:didSelectRowAtIndexPath:
override open func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.cellForRow(at: indexPath)?.isSelected = false
performAction(for: indexPath)
}
override open func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) {
performAction(for: indexPath)
}
private func performAction(for indexPath: IndexPath) {
switch indexPath.section {
case 0:
showLanguages()
default:
break
}
}
/*
// Override to support conditional editing of the table view.
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
// Return false if you do not want the specified item to be editable.
return true
}
*/
/*
// Override to support editing the table view.
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCell.EditingStyle, forRowAt indexPath: IndexPath) {
if editingStyle == .delete {
// Delete the row from the data source
tableView.deleteRows(at: [indexPath], with: .fade)
} else if editingStyle == .insert {
// Create a new instance of the appropriate class, insert it into the array, and add a new row to the table view
}
}
*/
/*
// Override to support rearranging the table view.
override func tableView(_ tableView: UITableView, moveRowAt fromIndexPath: IndexPath, to: IndexPath) {
}
*/
/*
// Override to support conditional rearranging of the table view.
override func tableView(_ tableView: UITableView, canMoveRowAt indexPath: IndexPath) -> Bool {
// Return false if you do not want the item to be re-orderable.
return true
}
*/
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override open func prepare(for segue: UIStoryboardSegue, sender: Any?) {
log.info("prepare for segue")
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
// MARK: - language access -
private func installed2API(_ installedList: [InstallableLexicalModel]) -> [LexicalModel] {
var returnList = [LexicalModel]()
for ilm in installedList {
returnList.append(LexicalModel(id: ilm.id, name: ilm.name, license: "", version: ilm.version, languages: [], authorName: "", fileSize: 0, filename: "no filename", sourcePath: nil, authorEmail: nil, description: nil, packageFileSize: 0, packageFilename: "", packageIncludes: nil, isDefault: false, lastModified: nil, minKeymanVersion: nil))
}
return returnList
}
// MARK: - language access -
private func loadUserLanguages() {
//iterate the list of installed languages and save their names
// Get keyboards list if it exists in user defaults, otherwise create a new one
let userDefaults = Storage.active.userDefaults
let userKeyboards = userDefaults.userKeyboards ?? []
var keyboardLanguages = [String: Language]()
for k in userKeyboards {
let l = k.languageID
var kbds: [Keyboard]
if let existingLanguage = keyboardLanguages[l] {
kbds = existingLanguage.keyboards ?? []
kbds.append(Keyboard(name: k.name, id: k.id, filename: "no filename", isDefault: nil, isRTL: k.isRTL, lastModified: Date(), fileSize: 0, version: k.version, languages: nil, font: nil, oskFont: nil))
} else {
kbds = [Keyboard(name: k.name, id: k.id, filename: "no filename", isDefault: nil, isRTL: k.isRTL, lastModified: Date(), fileSize: 0, version: k.version, languages: nil, font: nil, oskFont: nil)]
}
let userDefaults : UserDefaults = Storage.active.userDefaults
let lmListInstalled: [InstallableLexicalModel] = userDefaults.userLexicalModelsForLanguage(languageID: l) ?? []
let lmList = installed2API(lmListInstalled)
keyboardLanguages[l] = Language(name: k.languageName, id: k.languageID, keyboards: kbds, lexicalModels: lmList, font: nil, oskFont: nil)
}
// there shouldn't be any lexical models for languages that don't have a keyboard installed
// but check
let userLexicalModels = userDefaults.userLexicalModels ?? []
for lm in userLexicalModels {
let l = lm.languageID
if let langName = keyboardLanguages[l]?.name {
log.info("keyboard language \(l) \(langName) has lexical model")
} else {
log.error("lexical model language \(l) has no keyboard installed!")
}
}
userLanguages = keyboardLanguages
itemsArray[0]["subtitle"] = "\(userLanguages.count) languages installed"
}
public func setIsDoneButtonEnabled(_ nc: UINavigationController, _ value: Bool) {
let doneOrCancel = value
if doneOrCancel {
let doneButton = UIBarButtonItem(title: "Done", style: .plain, target: self,
action: nil /* #selector(self.doneClicked) */ )
nc.navigationItem.leftBarButtonItem = doneButton
} else {
let cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self,
action: nil /* #selector(self.cancelClicked) */ )
nc.navigationItem.leftBarButtonItem = cancelButton
}
}
func showLanguages() {
let vc = InstalledLanguagesViewController(userLanguages)
if let nc = navigationController {
nc.pushViewController(vc, animated: true)
setIsDoneButtonEnabled(nc, true)
} else {
log.error("no navigation controller for showing languages???")
}
}
}

View file

@ -185,7 +185,27 @@ class GetStartedViewController: UIViewController, UITableViewDelegate, UITableVi
}
private var dontShowGetStarted: Bool {
return !shouldShowGetStarted
}
private var shouldShowGetStarted: Bool {
let userData = AppDelegate.activeUserDefaults()
return userData.bool(forKey: dontShowGetStartedKey)
var value: Any? = userData.object(forKey: shouldShowGetStartedKey)
if nil == value { // new key not present, try the old key
value = userData.object(forKey: dontShowGetStartedKey)
if nil == value {
userData.set(true, forKey: shouldShowGetStartedKey)
userData.synchronize()
return true // default to showing when no preference has been set
// so it shows at startup
} else {
userData.removeObject(forKey: dontShowGetStartedKey)
userData.set(false, forKey: shouldShowGetStartedKey)
userData.synchronize()
// remove the old, confusing value and set the new value
return false
}
}
return userData.bool(forKey: shouldShowGetStartedKey)
}
}

View file

@ -18,7 +18,8 @@ private let checkedProfilesKey = "CheckedProfiles"
// External strings
let userTextKey = "UserText"
let userTextSizeKey = "UserTextSize"
let dontShowGetStartedKey = "DontShowGetStarted"
let dontShowGetStartedKey = "DontShowGetStarted" // older preference setting name, use shouldShowGetStartedKey
let shouldShowGetStartedKey = "ShouldShowGetStarted"
let launchedFromUrlNotification = NSNotification.Name("LaunchedFromUrlNotification")
let urlKey = "url"
@ -269,7 +270,14 @@ class MainViewController: UIViewController, TextViewDelegate, UIActionSheetDeleg
orientation: orientation)
actionButton.title = "Share"
dropdownItems = [textSizeButton, trashButton, getStartedButton, infoButton]
let settingsButton = createNavBarButton(with: #imageLiteral(resourceName: "more.png"), // should find a gear image and use that instead of re-using 'more'
highlightedImage: #imageLiteral(resourceName: "more-selected.png"),
imageScale: imageScaleF,
action: #selector(self.settingsButtonClick),
orientation: orientation)
settingsButton.title = "Settings"
dropdownItems = [textSizeButton, trashButton, infoButton, getStartedButton, settingsButton]
var scaleF: CGFloat = (UIScreen.main.scale == 2.0) ? 2.0 : 1.5
scaleF *= (UIDevice.current.userInterfaceIdiom == .phone) ? 1.0 : 2.0
@ -535,6 +543,13 @@ class MainViewController: UIViewController, TextViewDelegate, UIActionSheetDeleg
}
}
@objc func settingsButtonClick(_ sender: Any) {
_ = dismissDropDownMenu()
popover?.dismiss(animated: false)
Manager.shared.showKeymanEngineSettings(inVC: self)
}
@objc func actionButtonClick(_ sender: Any) {
_ = dismissDropDownMenu()
popover?.dismiss(animated: false)
@ -981,6 +996,9 @@ class MainViewController: UIViewController, TextViewDelegate, UIActionSheetDeleg
if userData.bool(forKey: dontShowGetStartedKey) {
return false
}
if !userData.bool(forKey: shouldShowGetStartedKey) {
return false
}
if !AppDelegate.isKeymanEnabledSystemWide() {
return true

View file

@ -1,7 +1,12 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="6254" systemVersion="13F34" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES">
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14490.70" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6247"/>
<deployment identifier="iOS"/>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="14490.49"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" customClass="SetUpViewController">
@ -25,10 +30,10 @@
</navigationBar>
<webView contentMode="scaleToFill" translatesAutoresizingMaskIntoConstraints="NO" id="hiW-6P-SIQ">
<rect key="frame" x="0.0" y="64" width="320" height="504"/>
<color key="backgroundColor" white="1" alpha="1" colorSpace="calibratedWhite"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
</webView>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="hiW-6P-SIQ" firstAttribute="leading" secondItem="i5M-Pr-FkT" secondAttribute="leading" id="BAe-Qs-TXg"/>
<constraint firstAttribute="trailing" secondItem="w92-oV-dEk" secondAttribute="trailing" id="D7B-0S-v1R"/>

3
mac/.gitignore vendored
View file

@ -1,10 +1,11 @@
**/DerivedData
**/project.xcworkspace
**/xcuserdata
**/*.xcscheme
Keyman4MacIM/output/*
Keyman4MacIM/Keyman4MacIM/Frameworks/**
Keyman4MacIM/Pods/**
Keyman4Mac/Keyman4Mac/Frameworks/**
Carthage/**
**/build
**/output
**/output