mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-15 21:29:23 +00:00
Merge pull request #1824 from keymanapp/ios-ui-lm-management
[ios] UI LM Management
This commit is contained in:
commit
1827d689dc
25 changed files with 1678 additions and 84 deletions
1
ios/.gitignore
vendored
1
ios/.gitignore
vendored
|
|
@ -2,6 +2,7 @@ DerivedData
|
|||
.DS_Store
|
||||
project.xcworkspace
|
||||
xcuserdata
|
||||
*.xcscheme
|
||||
build
|
||||
keyman/Keyman/KeymanEngine.framework
|
||||
samples/KMSample1/KeymanEngine.framework
|
||||
|
|
|
|||
|
|
@ -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 */,
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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).
|
||||
|
|
|
|||
|
|
@ -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/>"
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -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.
|
||||
}
|
||||
*/
|
||||
|
||||
}
|
||||
|
|
@ -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>
|
||||
|
|
@ -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 "Get Started" 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>
|
||||
|
|
@ -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???")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
3
mac/.gitignore
vendored
|
|
@ -1,10 +1,11 @@
|
|||
**/DerivedData
|
||||
**/project.xcworkspace
|
||||
**/xcuserdata
|
||||
**/*.xcscheme
|
||||
Keyman4MacIM/output/*
|
||||
Keyman4MacIM/Keyman4MacIM/Frameworks/**
|
||||
Keyman4MacIM/Pods/**
|
||||
Keyman4Mac/Keyman4Mac/Frameworks/**
|
||||
Carthage/**
|
||||
**/build
|
||||
**/output
|
||||
**/output
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue