From b0429ed1aa191ef0822a83399c3cc69a4f0c33a6 Mon Sep 17 00:00:00 2001 From: Marc Durdin Date: Tue, 21 May 2019 06:55:53 +1000 Subject: [PATCH] [iOS] FV app code cleanup, refactor, and implement upgrade --- oem/firstvoices/ios/FVShared/FVShared.swift | 28 +- .../ios/FirstVoices.xcodeproj/project.pbxproj | 92 +------ .../ios/FirstVoices/AppDelegate.swift | 4 +- .../ios/FirstVoices/FVKeyboardList.swift | 245 ++++++++++++++++++ .../FirstVoices/FirstVoices-Bridging-Header.h | 4 - .../FirstVoices/KeyboardsViewController.swift | 156 ++--------- .../ios/FirstVoices/ViewController.swift | 14 +- .../SWKeyboard/FVInputViewController.swift | 7 +- .../SWKeyboard/SWKeyboard-Bridging-Header.h | 4 - 9 files changed, 316 insertions(+), 238 deletions(-) create mode 100644 oem/firstvoices/ios/FirstVoices/FVKeyboardList.swift delete mode 100644 oem/firstvoices/ios/FirstVoices/FirstVoices-Bridging-Header.h delete mode 100644 oem/firstvoices/ios/SWKeyboard/SWKeyboard-Bridging-Header.h diff --git a/oem/firstvoices/ios/FVShared/FVShared.swift b/oem/firstvoices/ios/FVShared/FVShared.swift index 6fd313a7fa..22106f9721 100644 --- a/oem/firstvoices/ios/FVShared/FVShared.swift +++ b/oem/firstvoices/ios/FVShared/FVShared.swift @@ -9,15 +9,37 @@ import Foundation -let FVGroupID: String = "group.FVKeyboards" -let kFVLoadedKeyboardList: String = "FVLoadedKeyboardList" +struct FVConstants { + static let groupID: String = "group.FVKeyboards" + static let kFVLoadedKeyboardList: String = "FVLoadedKeyboardList" + + // Legacy keys + static let legacy_kFVKeyboardList = "FVKeyboardList" + static let legacy_kKeyboardsFileLastModDateKey = "KeyboardsFileLastModDate" + + static let legacy_kFVKeyboardCheckStateKey = "FVKeyboardCheckState" + static let legacy_kFVKeyboardFilenameKey = "FVKeyboardFilename" + + // Keyboard files and paths + static let keyboardsPath = "Keyboards/files" + static let keyboardsCSVPath = "Keyboards" + static let keyboardsCSVName = "keyboards" + static let keyboardsCSVType = "csv" + static let keyboardInfoType = "keyboard_info" + static let keyboardType = "js" + + // Instructions web page + static let instructionsName = "setup" + static let instructionsType = "html" + static let instructionsPath = "Instructions" +} class FVShared { class func userDefaults() -> UserDefaults { var _userDefaults: UserDefaults? = nil if UserDefaults.standard.responds(to: #selector(UserDefaults.init(suiteName:))) { - _userDefaults = UserDefaults(suiteName: FVGroupID) + _userDefaults = UserDefaults(suiteName: FVConstants.groupID) } if _userDefaults == nil { _userDefaults = UserDefaults.standard diff --git a/oem/firstvoices/ios/FirstVoices.xcodeproj/project.pbxproj b/oem/firstvoices/ios/FirstVoices.xcodeproj/project.pbxproj index b68de1d914..d1c23d893d 100644 --- a/oem/firstvoices/ios/FirstVoices.xcodeproj/project.pbxproj +++ b/oem/firstvoices/ios/FirstVoices.xcodeproj/project.pbxproj @@ -7,6 +7,8 @@ objects = { /* Begin PBXBuildFile section */ + 37C183B922932AD2009F9EFD /* FVKeyboardList.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37C183B822932AD2009F9EFD /* FVKeyboardList.swift */; }; + 37C183BD2293321C009F9EFD /* Instructions in Resources */ = {isa = PBXBuildFile; fileRef = 37C183BC2293321C009F9EFD /* Instructions */; }; 37EA1F1E228A180A003E710C /* KeymanEngine.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 37EA1F1D228A1809003E710C /* KeymanEngine.framework */; }; 37EA1F1F228A1823003E710C /* KeymanEngine.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 37EA1F1D228A1809003E710C /* KeymanEngine.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; }; 37EA1F20228A183E003E710C /* KeymanEngine.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 37EA1F1D228A1809003E710C /* KeymanEngine.framework */; }; @@ -38,12 +40,9 @@ 98395E361C1E61AF007CD675 /* 739-question.png in Resources */ = {isa = PBXBuildFile; fileRef = 98395E301C1E61AF007CD675 /* 739-question.png */; }; 98395E371C1E61AF007CD675 /* 739-question@2x.png in Resources */ = {isa = PBXBuildFile; fileRef = 98395E311C1E61AF007CD675 /* 739-question@2x.png */; }; 98395E381C1E61AF007CD675 /* 739-question@3x.png in Resources */ = {isa = PBXBuildFile; fileRef = 98395E321C1E61AF007CD675 /* 739-question@3x.png */; }; - 984471A61C1F796D00D3C9DA /* languagegeek.png in Resources */ = {isa = PBXBuildFile; fileRef = 984471A51C1F796D00D3C9DA /* languagegeek.png */; }; 9889E8C41BFA9CEC00019560 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9889E8C21BFA9CEC00019560 /* Main.storyboard */; }; 9889E8C61BFA9CEC00019560 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 9889E8C51BFA9CEC00019560 /* Assets.xcassets */; }; 9889E8C91BFA9CEC00019560 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 9889E8C71BFA9CEC00019560 /* LaunchScreen.storyboard */; }; - 989618CA1C194AD90012E3EB /* fpcc.svg in Resources */ = {isa = PBXBuildFile; fileRef = 989618C81C194AD90012E3EB /* fpcc.svg */; }; - 989618CB1C194AD90012E3EB /* nrt.svg in Resources */ = {isa = PBXBuildFile; fileRef = 989618C91C194AD90012E3EB /* nrt.svg */; }; 98C7F7F51BFD4649007F17B7 /* AudioToolbox.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 98C9A9E71BFC0974009E9A4F /* AudioToolbox.framework */; }; 98C7F7F61BFD464F007F17B7 /* CoreText.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 98C9A9ED1BFC098F009E9A4F /* CoreText.framework */; }; 98C7F7F71BFD4655007F17B7 /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 98C9A9F71BFC09ED009E9A4F /* CoreGraphics.framework */; }; @@ -74,21 +73,6 @@ 98C9A9F41BFC09C5009E9A4F /* MobileCoreServices.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 98C9A9F31BFC09C5009E9A4F /* MobileCoreServices.framework */; }; 98C9A9F61BFC09DE009E9A4F /* UIKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 98C9A9F51BFC09DE009E9A4F /* UIKit.framework */; }; 98C9A9F81BFC09ED009E9A4F /* CoreGraphics.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 98C9A9F71BFC09ED009E9A4F /* CoreGraphics.framework */; }; - 98FAB7FD1C0E775500C31263 /* globe.svg in Resources */ = {isa = PBXBuildFile; fileRef = 98FAB7EA1C0E775500C31263 /* globe.svg */; }; - 98FAB7FE1C0E775500C31263 /* ios-instructions.css in Resources */ = {isa = PBXBuildFile; fileRef = 98FAB7EB1C0E775500C31263 /* ios-instructions.css */; }; - 98FAB7FF1C0E775500C31263 /* keyman.svg in Resources */ = {isa = PBXBuildFile; fileRef = 98FAB7EC1C0E775500C31263 /* keyman.svg */; }; - 98FAB8011C0E775500C31263 /* nunito-regular-webfont.eot in Resources */ = {isa = PBXBuildFile; fileRef = 98FAB7EE1C0E775500C31263 /* nunito-regular-webfont.eot */; }; - 98FAB8021C0E775500C31263 /* nunito-regular-webfont.svg in Resources */ = {isa = PBXBuildFile; fileRef = 98FAB7EF1C0E775500C31263 /* nunito-regular-webfont.svg */; }; - 98FAB8031C0E775500C31263 /* nunito-regular-webfont.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 98FAB7F01C0E775500C31263 /* nunito-regular-webfont.ttf */; }; - 98FAB8041C0E775500C31263 /* nunito-regular-webfont.woff in Resources */ = {isa = PBXBuildFile; fileRef = 98FAB7F11C0E775500C31263 /* nunito-regular-webfont.woff */; }; - 98FAB8051C0E775500C31263 /* nunito-regular-webfont.woff2 in Resources */ = {isa = PBXBuildFile; fileRef = 98FAB7F21C0E775500C31263 /* nunito-regular-webfont.woff2 */; }; - 98FAB8061C0E775500C31263 /* opensans-regular-webfont.eot in Resources */ = {isa = PBXBuildFile; fileRef = 98FAB7F31C0E775500C31263 /* opensans-regular-webfont.eot */; }; - 98FAB8071C0E775500C31263 /* opensans-regular-webfont.svg in Resources */ = {isa = PBXBuildFile; fileRef = 98FAB7F41C0E775500C31263 /* opensans-regular-webfont.svg */; }; - 98FAB8081C0E775500C31263 /* opensans-regular-webfont.ttf in Resources */ = {isa = PBXBuildFile; fileRef = 98FAB7F51C0E775500C31263 /* opensans-regular-webfont.ttf */; }; - 98FAB8091C0E775500C31263 /* opensans-regular-webfont.woff in Resources */ = {isa = PBXBuildFile; fileRef = 98FAB7F61C0E775500C31263 /* opensans-regular-webfont.woff */; }; - 98FAB80A1C0E775500C31263 /* opensans-regular-webfont.woff2 in Resources */ = {isa = PBXBuildFile; fileRef = 98FAB7F71C0E775500C31263 /* opensans-regular-webfont.woff2 */; }; - 98FAB80B1C0E775500C31263 /* setup.html in Resources */ = {isa = PBXBuildFile; fileRef = 98FAB7F81C0E775500C31263 /* setup.html */; }; - 98FAB80D1C0E799100C31263 /* firstvoices-logo.gif in Resources */ = {isa = PBXBuildFile; fileRef = 98FAB80C1C0E799100C31263 /* firstvoices-logo.gif */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -131,15 +115,15 @@ /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ + 37C183B822932AD2009F9EFD /* FVKeyboardList.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FVKeyboardList.swift; sourceTree = ""; }; + 37C183BC2293321C009F9EFD /* Instructions */ = {isa = PBXFileReference; lastKnownFileType = folder; name = Instructions; path = FirstVoices/Instructions; sourceTree = ""; }; 37EA1F1D228A1809003E710C /* KeymanEngine.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = KeymanEngine.framework; sourceTree = ""; }; - 37EA1F21228A1F57003E710C /* SWKeyboard-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SWKeyboard-Bridging-Header.h"; sourceTree = ""; }; 37EA1F22228A1F57003E710C /* FVInputViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FVInputViewController.swift; sourceTree = ""; }; 37EA1F25228B5D6E003E710C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 37EA1F27228B5E2B003E710C /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = ""; }; 37EA1F29228B69AF003E710C /* KeyboardsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardsViewController.swift; sourceTree = ""; }; 37EA1F2E228B7458003E710C /* CheckBox.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CheckBox.swift; path = Checkbox/CheckBox.swift; sourceTree = ""; }; 37EA1F30228B7661003E710C /* FVShared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = FVShared.swift; path = FVShared/FVShared.swift; sourceTree = ""; }; - 37EA1F33228BCDF8003E710C /* FirstVoices-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "FirstVoices-Bridging-Header.h"; sourceTree = ""; }; 37EA1F34228CA3BD003E710C /* ObjcExceptionBridging.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ObjcExceptionBridging.framework; path = ../../../ios/Carthage/Build/iOS/ObjcExceptionBridging.framework; sourceTree = ""; }; 37EA1F36228CA3D2003E710C /* XCGLogger.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCGLogger.framework; path = ../../../ios/Carthage/Build/iOS/XCGLogger.framework; sourceTree = ""; }; 37EA1F38228CA3E4003E710C /* Zip.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Zip.framework; path = ../../../ios/Carthage/Build/iOS/Zip.framework; sourceTree = ""; }; @@ -153,14 +137,11 @@ 98395E301C1E61AF007CD675 /* 739-question.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "739-question.png"; sourceTree = ""; }; 98395E311C1E61AF007CD675 /* 739-question@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "739-question@2x.png"; sourceTree = ""; }; 98395E321C1E61AF007CD675 /* 739-question@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "739-question@3x.png"; sourceTree = ""; }; - 984471A51C1F796D00D3C9DA /* languagegeek.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = languagegeek.png; sourceTree = ""; }; 9889E8B61BFA9CEC00019560 /* FirstVoices.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = FirstVoices.app; sourceTree = BUILT_PRODUCTS_DIR; }; 9889E8C31BFA9CEC00019560 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; 9889E8C51BFA9CEC00019560 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 9889E8C81BFA9CEC00019560 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 9889E8CA1BFA9CEC00019560 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; - 989618C81C194AD90012E3EB /* fpcc.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = fpcc.svg; sourceTree = ""; }; - 989618C91C194AD90012E3EB /* nrt.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = nrt.svg; sourceTree = ""; }; 98C9A8BC1BFBF23C009E9A4F /* SWKeyboard.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = SWKeyboard.appex; sourceTree = BUILT_PRODUCTS_DIR; }; 98C9A8C11BFBF23C009E9A4F /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 98C9A8DD1BFBF6C1009E9A4F /* banner-Landscape-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "banner-Landscape-568h@2x.png"; sourceTree = ""; }; @@ -185,21 +166,6 @@ 98C9A9F71BFC09ED009E9A4F /* CoreGraphics.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = CoreGraphics.framework; path = System/Library/Frameworks/CoreGraphics.framework; sourceTree = SDKROOT; }; 98C9A9F91BFC10D2009E9A4F /* FirstVoices.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = FirstVoices.entitlements; sourceTree = ""; }; 98C9A9FA1BFC1111009E9A4F /* SWKeyboard.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = SWKeyboard.entitlements; sourceTree = ""; }; - 98FAB7EA1C0E775500C31263 /* globe.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = globe.svg; sourceTree = ""; }; - 98FAB7EB1C0E775500C31263 /* ios-instructions.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = "ios-instructions.css"; sourceTree = ""; }; - 98FAB7EC1C0E775500C31263 /* keyman.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = keyman.svg; sourceTree = ""; }; - 98FAB7EE1C0E775500C31263 /* nunito-regular-webfont.eot */ = {isa = PBXFileReference; lastKnownFileType = file; path = "nunito-regular-webfont.eot"; sourceTree = ""; }; - 98FAB7EF1C0E775500C31263 /* nunito-regular-webfont.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = "nunito-regular-webfont.svg"; sourceTree = ""; }; - 98FAB7F01C0E775500C31263 /* nunito-regular-webfont.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "nunito-regular-webfont.ttf"; sourceTree = ""; }; - 98FAB7F11C0E775500C31263 /* nunito-regular-webfont.woff */ = {isa = PBXFileReference; lastKnownFileType = file; path = "nunito-regular-webfont.woff"; sourceTree = ""; }; - 98FAB7F21C0E775500C31263 /* nunito-regular-webfont.woff2 */ = {isa = PBXFileReference; lastKnownFileType = file; path = "nunito-regular-webfont.woff2"; sourceTree = ""; }; - 98FAB7F31C0E775500C31263 /* opensans-regular-webfont.eot */ = {isa = PBXFileReference; lastKnownFileType = file; path = "opensans-regular-webfont.eot"; sourceTree = ""; }; - 98FAB7F41C0E775500C31263 /* opensans-regular-webfont.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = "opensans-regular-webfont.svg"; sourceTree = ""; }; - 98FAB7F51C0E775500C31263 /* opensans-regular-webfont.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "opensans-regular-webfont.ttf"; sourceTree = ""; }; - 98FAB7F61C0E775500C31263 /* opensans-regular-webfont.woff */ = {isa = PBXFileReference; lastKnownFileType = file; path = "opensans-regular-webfont.woff"; sourceTree = ""; }; - 98FAB7F71C0E775500C31263 /* opensans-regular-webfont.woff2 */ = {isa = PBXFileReference; lastKnownFileType = file; path = "opensans-regular-webfont.woff2"; sourceTree = ""; }; - 98FAB7F81C0E775500C31263 /* setup.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = setup.html; sourceTree = ""; }; - 98FAB80C1C0E799100C31263 /* firstvoices-logo.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = "firstvoices-logo.gif"; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -264,6 +230,7 @@ 9889E8AD1BFA9CEC00019560 = { isa = PBXGroup; children = ( + 37C183BC2293321C009F9EFD /* Instructions */, 37EA1F472292238E003E710C /* Keyboards */, 9889E8B81BFA9CEC00019560 /* FirstVoices */, 98C9A9FB1BFC19ED009E9A4F /* Frameworks */, @@ -287,16 +254,15 @@ isa = PBXGroup; children = ( 98C9A9F91BFC10D2009E9A4F /* FirstVoices.entitlements */, - 37EA1F33228BCDF8003E710C /* FirstVoices-Bridging-Header.h */, 9889E8CA1BFA9CEC00019560 /* Info.plist */, 37EA1F25228B5D6E003E710C /* AppDelegate.swift */, 37EA1F2E228B7458003E710C /* CheckBox.swift */, + 37C183B822932AD2009F9EFD /* FVKeyboardList.swift */, 37EA1F4522921BAD003E710C /* KeyboardInfo.swift */, 37EA1F29228B69AF003E710C /* KeyboardsViewController.swift */, 37EA1F27228B5E2B003E710C /* ViewController.swift */, 9889E8C51BFA9CEC00019560 /* Assets.xcassets */, 98395E2C1C1E61AF007CD675 /* Icons */, - 98FAB7E51C0E775500C31263 /* Instructions */, 9889E8C71BFA9CEC00019560 /* LaunchScreen.storyboard */, 9889E8C21BFA9CEC00019560 /* Main.storyboard */, 9889E8B91BFA9CEC00019560 /* Supporting Files */, @@ -326,7 +292,6 @@ 98C9A8DC1BFBF6C1009E9A4F /* Images */, 37EA1F22228A1F57003E710C /* FVInputViewController.swift */, 98C9A8C11BFBF23C009E9A4F /* Info.plist */, - 37EA1F21228A1F57003E710C /* SWKeyboard-Bridging-Header.h */, ); path = SWKeyboard; sourceTree = ""; @@ -370,31 +335,6 @@ name = Frameworks; sourceTree = ""; }; - 98FAB7E51C0E775500C31263 /* Instructions */ = { - isa = PBXGroup; - children = ( - 98FAB80C1C0E799100C31263 /* firstvoices-logo.gif */, - 989618C81C194AD90012E3EB /* fpcc.svg */, - 98FAB7EA1C0E775500C31263 /* globe.svg */, - 98FAB7EB1C0E775500C31263 /* ios-instructions.css */, - 98FAB7EC1C0E775500C31263 /* keyman.svg */, - 984471A51C1F796D00D3C9DA /* languagegeek.png */, - 989618C91C194AD90012E3EB /* nrt.svg */, - 98FAB7EE1C0E775500C31263 /* nunito-regular-webfont.eot */, - 98FAB7EF1C0E775500C31263 /* nunito-regular-webfont.svg */, - 98FAB7F01C0E775500C31263 /* nunito-regular-webfont.ttf */, - 98FAB7F11C0E775500C31263 /* nunito-regular-webfont.woff */, - 98FAB7F21C0E775500C31263 /* nunito-regular-webfont.woff2 */, - 98FAB7F31C0E775500C31263 /* opensans-regular-webfont.eot */, - 98FAB7F41C0E775500C31263 /* opensans-regular-webfont.svg */, - 98FAB7F51C0E775500C31263 /* opensans-regular-webfont.ttf */, - 98FAB7F61C0E775500C31263 /* opensans-regular-webfont.woff */, - 98FAB7F71C0E775500C31263 /* opensans-regular-webfont.woff2 */, - 98FAB7F81C0E775500C31263 /* setup.html */, - ); - path = Instructions; - sourceTree = ""; - }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -493,35 +433,18 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 98FAB7FD1C0E775500C31263 /* globe.svg in Resources */, 9889E8C91BFA9CEC00019560 /* LaunchScreen.storyboard in Resources */, + 37C183BD2293321C009F9EFD /* Instructions in Resources */, 9800EC5C1C029F2200BF0FB5 /* Settings.bundle in Resources */, - 98FAB8021C0E775500C31263 /* nunito-regular-webfont.svg in Resources */, - 989618CA1C194AD90012E3EB /* fpcc.svg in Resources */, - 98FAB8061C0E775500C31263 /* opensans-regular-webfont.eot in Resources */, - 98FAB80B1C0E775500C31263 /* setup.html in Resources */, - 98FAB8071C0E775500C31263 /* opensans-regular-webfont.svg in Resources */, 9889E8C61BFA9CEC00019560 /* Assets.xcassets in Resources */, - 98FAB7FF1C0E775500C31263 /* keyman.svg in Resources */, 98395E331C1E61AF007CD675 /* 739-question-selected.png in Resources */, 37EA1F482292238F003E710C /* Keyboards in Resources */, - 98FAB8041C0E775500C31263 /* nunito-regular-webfont.woff in Resources */, - 98FAB8091C0E775500C31263 /* opensans-regular-webfont.woff in Resources */, - 989618CB1C194AD90012E3EB /* nrt.svg in Resources */, - 984471A61C1F796D00D3C9DA /* languagegeek.png in Resources */, 98395E371C1E61AF007CD675 /* 739-question@2x.png in Resources */, 9889E8C41BFA9CEC00019560 /* Main.storyboard in Resources */, - 98FAB7FE1C0E775500C31263 /* ios-instructions.css in Resources */, 98395E341C1E61AF007CD675 /* 739-question-selected@2x.png in Resources */, - 98FAB8051C0E775500C31263 /* nunito-regular-webfont.woff2 in Resources */, - 98FAB8081C0E775500C31263 /* opensans-regular-webfont.ttf in Resources */, - 98FAB80A1C0E775500C31263 /* opensans-regular-webfont.woff2 in Resources */, - 98FAB8011C0E775500C31263 /* nunito-regular-webfont.eot in Resources */, 98395E361C1E61AF007CD675 /* 739-question.png in Resources */, - 98FAB80D1C0E799100C31263 /* firstvoices-logo.gif in Resources */, 98395E381C1E61AF007CD675 /* 739-question@3x.png in Resources */, 98395E351C1E61AF007CD675 /* 739-question-selected@3x.png in Resources */, - 98FAB8031C0E775500C31263 /* nunito-regular-webfont.ttf in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -572,6 +495,7 @@ 37EA1F2F228B7458003E710C /* CheckBox.swift in Sources */, 37EA1F31228B7661003E710C /* FVShared.swift in Sources */, 37EA1F28228B5E2B003E710C /* ViewController.swift in Sources */, + 37C183B922932AD2009F9EFD /* FVKeyboardList.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/oem/firstvoices/ios/FirstVoices/AppDelegate.swift b/oem/firstvoices/ios/FirstVoices/AppDelegate.swift index d2b094c243..f0ee3e3ee5 100644 --- a/oem/firstvoices/ios/FirstVoices/AppDelegate.swift +++ b/oem/firstvoices/ios/FirstVoices/AppDelegate.swift @@ -17,7 +17,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil) -> Bool { -#warning("TODO: we need to upgrade existing installations, which involves mapping the horrific old data structure plus keyboard file renames") + FVRegionStorage.upgrade() //#if DEBUG KeymanEngine.log.outputLevel = .debug @@ -27,7 +27,7 @@ class AppDelegate: UIResponder, UIApplicationDelegate { //#endif // Replace with your application group id - Manager.applicationGroupIdentifier = "group.FVKeyboards" + Manager.applicationGroupIdentifier = FVConstants.groupID return true } } diff --git a/oem/firstvoices/ios/FirstVoices/FVKeyboardList.swift b/oem/firstvoices/ios/FirstVoices/FVKeyboardList.swift new file mode 100644 index 0000000000..39dbe4a0ea --- /dev/null +++ b/oem/firstvoices/ios/FirstVoices/FVKeyboardList.swift @@ -0,0 +1,245 @@ +// +// FVKeyboardList.swift +// FirstVoices app +// +// License: MIT +// +// Copyright © 2019 FirstVoices. +// +// Created by Marc Durdin on 21/05/2019. +// + +import Foundation +import KeymanEngine + +/// A single keyboard in the list of available keyboards +class FVKeyboard { + let id, name, legacyId: String + init(id: String, name: String, legacyId: String) { + self.id = id + self.name = name + self.legacyId = legacyId + } +} + +typealias FVKeyboardList = [FVKeyboard] + +/// A region with a list of keyboards +class FVRegion { + let name: String + var keyboards: FVKeyboardList = [] + init(name: String) { + self.name = name + } +} +typealias FVRegionList = [FVRegion] + +class FVRegionStorage { + + /// Loads the available keyboards from keyboards.csv to present + /// in the user interface. This keyboards.csv file is maintained + /// manually but the keyboards listed within are imported into the + /// app during build (into the Keyboards/files directory) from the + /// keymanapp/keyboards repository. This means that a rebuild of the + /// app always gets the latest version of the keyboards. Adding a new + /// keyboard to the app is as simple as updating keyboards.csv and + /// rebuilding. + /// + /// The keyboards.csv file has 5 columns: + /// - shortname - the keyboards repository shortname (e.g. fv) + /// - id - the identifier of the keyboard + /// - name - the name of the keyboard to be shown in UI in app + /// (sometimes may differ from the compiled keyboard) + /// - regionName - the name of the region to group the keyboard under + /// - legacyId - the legacy filename of the keyboard, used only + /// during upgrades + /// The file is sorted according to how the keyboards should be + /// displayed in the Keyboard Selection UI. + class func load() -> FVRegionList { + var regionList = FVRegionList() + do { + let keyboardsFile: String = Bundle.main.path(forResource: FVConstants.keyboardsCSVName, + ofType: FVConstants.keyboardsCSVType, + inDirectory: FVConstants.keyboardsCSVPath)! + + let fileContents: String = try String(contentsOfFile: keyboardsFile, encoding: .utf8).replacingOccurrences(of: "\r", with: "") + let lines: [String] = fileContents.components(separatedBy: "\n") + for i in 1.. 0 { + // Columns: shortname,id,name,region,legacyid + let kbId: String = values[1] + let kbName: String = values[2] + let regionName: String = values[3] + let legacyId: String = values[4] + + var region = regionList.first(where: { $0.name == regionName }) + if region == nil { + region = FVRegion(name: regionName) + regionList.append(region!) + } + + let kb = FVKeyboard(id: kbId, name: kbName, legacyId: legacyId) + region!.keyboards.append(kb) + } + } + } catch { + print("Unexpected error loading keyboards.csv: \(error).") + } + return regionList + } + + /// Load shared user data from earlier versions of the app and + /// convert into the desired format, then unload the old keyboards + /// and load the new ones corresponding. If a keyboard is not available + /// in the new version, it will be uninstalled. + class func upgrade() { + + let sharedData: UserDefaults = FVShared.userDefaults() + let list = sharedData.array(forKey: FVConstants.legacy_kFVKeyboardList) + if(list == nil) { + // No legacy data to upgrade + return + } + + // + // The legacy format is a bit messy. It mixes static data and configuration. + // Fortunately, we only need the checkstatekey value (and corresponding + // keyboard name) in order to transfer state to the new app. + // array( + // 0: region_name + // 1: array( + // dictionary( + // checkstatekey + // filenamekey + // ... + // ) + // ) + // ... + // ) + // + + let regions = self.load() + var loadedKeyboards: [String] = [] + + var i = 1 + while i < list!.count { + let keyboards = list![i] as? [[String:String]] + if let keyboards = keyboards { + for keyboard in keyboards { + + if keyboard[FVConstants.legacy_kFVKeyboardCheckStateKey] == "YES" { + + let legacyId = keyboard[FVConstants.legacy_kFVKeyboardFilenameKey] + print("Legacy keyboard \(legacyId!) is installed") + + for region in regions { + for rk in region.keyboards { + if rk.legacyId == legacyId { + print("Found corresponding keyboard \(rk.id)") + loadedKeyboards.append(rk.id) + } + } + } + } + } + } else { + // Some intermediate debug builds had different formats + // so we just abandon data from those + print("Unable to load corrupted data") + break + } + i += 2 + } + + // Save the new configuration (which could be empty), and update Keyman Engine + self.saveKeyboardListToUserDefaults(loadedKeyboards: loadedKeyboards) + self.updateActiveKeyboardsList(keyboardList: regions, loadedKeyboards: loadedKeyboards) + + // Finally, remove the legacy data + sharedData.removeObject(forKey: FVConstants.legacy_kFVKeyboardList) + sharedData.removeObject(forKey: FVConstants.legacy_kKeyboardsFileLastModDateKey) + } + + /// Loads the list of keyboards that the user has selected; in theory we could + /// collect these from Keyman Engine but at present there is no API to iterate + /// over the installed keyboards. + class func loadKeyboardListFromUserDefaults() -> [String] { + let sharedData: UserDefaults = FVShared.userDefaults() + let keyboards = sharedData.array(forKey: FVConstants.kFVLoadedKeyboardList) + if keyboards != nil { + return keyboards as! [String] + } else { + return [] + } + } + + /// Saves the list of keyboards to shared data + class func saveKeyboardListToUserDefaults(loadedKeyboards: [String]) { + let sharedData: UserDefaults = FVShared.userDefaults() + sharedData.set(loadedKeyboards, forKey: FVConstants.kFVLoadedKeyboardList) + sharedData.synchronize() + } + + /// Updates Keyman Engine with the list of installed keyboards. Removes all + /// the keyboards and reinstalls them to maintain a clean configuration. + class func updateActiveKeyboardsList(keyboardList: FVRegionList, loadedKeyboards: [String]) { + + // Remove all installed keyboards first -- we'll re-add them below + + while Manager.shared.removeKeyboard(at: 0) { + } + + // Iterate through the available keyboards + + for region in keyboardList { + let keyboards = region.keyboards + for kb in keyboards { + if loadedKeyboards.contains(kb.id) { + + // Load the .keyboard_info file and find its first language code + + var keyboardInfo: KeyboardInfo + do { + let kbinfoFilename: String = Bundle.main.path(forResource: kb.id, ofType: FVConstants.keyboardInfoType, inDirectory: FVConstants.keyboardsPath)! + keyboardInfo = try KeyboardInfoParser.decode(file: kbinfoFilename) + } catch { + print("Failed to load keyboard info for " + kb.id+": " + error.localizedDescription) + continue + } + + // Preload the keyboard + + do { + let kbPath: String = Bundle.main.path(forResource: kb.id, + ofType: FVConstants.keyboardType, + inDirectory: FVConstants.keyboardsPath)! + let pathUrl = URL(fileURLWithPath: kbPath) + try Manager.shared.preloadFiles(forKeyboardID: kb.id, at: [pathUrl], shouldOverwrite: true) + } catch { + print("Failed to load preload "+kb.id+": " + error.localizedDescription) + continue + } + + // Install the keyboard into Keyman Engine + + let keyboard: InstallableKeyboard = InstallableKeyboard.init(id: kb.id, + name: kb.name, + languageID: keyboardInfo.languages.keys[keyboardInfo.languages.startIndex], + languageName: kb.name, + version: keyboardInfo.version, + isRTL: false, + font: nil, + oskFont: nil, + isCustom: true) + Manager.shared.addKeyboard(keyboard) + } + } + } + } + +} diff --git a/oem/firstvoices/ios/FirstVoices/FirstVoices-Bridging-Header.h b/oem/firstvoices/ios/FirstVoices/FirstVoices-Bridging-Header.h deleted file mode 100644 index 1b2cb5d6d0..0000000000 --- a/oem/firstvoices/ios/FirstVoices/FirstVoices-Bridging-Header.h +++ /dev/null @@ -1,4 +0,0 @@ -// -// Use this file to import your target's public headers that you would like to expose to Swift. -// - diff --git a/oem/firstvoices/ios/FirstVoices/KeyboardsViewController.swift b/oem/firstvoices/ios/FirstVoices/KeyboardsViewController.swift index 83ccbd6426..9d45a70c75 100644 --- a/oem/firstvoices/ios/FirstVoices/KeyboardsViewController.swift +++ b/oem/firstvoices/ios/FirstVoices/KeyboardsViewController.swift @@ -14,28 +14,26 @@ let helpLink: String = "https://help.keyman.com/keyboard/" let tHelpButtonTag: NSInteger = 1000 let tCheckBoxTag: NSInteger = 2000 -class FVKeyboard { - let id, name: String - init(id: String, name: String) { - self.id = id - self.name = name - } -} -typealias FVKeyboardList = [FVKeyboard] -class FVRegion { - var name: String = "" - var keyboards: FVKeyboardList = [] -} -typealias FVRegionList = [FVRegion] - class KeyboardsViewController: UIViewController, UITableViewDelegate, UITableViewDataSource { @IBOutlet var tableView: UITableView! - var _keyboardList: FVRegionList = [] - var _loadedKeyboards: [String] = [] + + private var _keyboardList: FVRegionList! + + private var keyboardList: FVRegionList { + get { + if _keyboardList == nil { + _keyboardList = FVRegionStorage.load() + } + return _keyboardList! + } + } + + private var _loadedKeyboards: [String] = [] + override func viewDidLoad() { - loadKeyboardListFromUserDefaults() + _loadedKeyboards = FVRegionStorage.loadKeyboardListFromUserDefaults() super.viewDidLoad() // Do any additional setup after loading the view, typically from a nib. } @@ -52,11 +50,11 @@ class KeyboardsViewController: UIViewController, UITableViewDelegate, UITableVie // MARK: - Table view data source func numberOfSections(in tableView: UITableView) -> Int { - return self.keyboardList().count + return self.keyboardList.count } func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { - let kbArray = self.keyboardList()[section].keyboards + let kbArray = self.keyboardList[section].keyboards return kbArray.count } @@ -98,14 +96,14 @@ class KeyboardsViewController: UIViewController, UITableViewDelegate, UITableVie func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? { - return self.keyboardList()[section].name + return self.keyboardList[section].name } // MARK: - Table view delegate func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { cell.selectionStyle = .none cell.accessoryType = .none - let keyboards = (self.keyboardList()[indexPath.section]).keyboards + let keyboards = (self.keyboardList[indexPath.section]).keyboards let kb = keyboards[indexPath.row] cell.textLabel!.text = kb.name let checkbox: CheckBox = (cell.accessoryView!.viewWithTag(tCheckBoxTag) as! CheckBox) @@ -116,52 +114,13 @@ class KeyboardsViewController: UIViewController, UITableViewDelegate, UITableVie // } - func keyboardList() -> FVRegionList { - if _keyboardList.count == 0 { - do { - #warning("TODO: refactor the .csv load + related struct/class types into a separate module") - let keyboardsFile: String = Bundle.main.path(forResource: "keyboards", ofType: "csv", inDirectory: "Keyboards")! - - let fileContents: String = try String(contentsOfFile: keyboardsFile, encoding: .utf8).replacingOccurrences(of: "\r", with: "") - let lines: [String] = fileContents.components(separatedBy: "\n") - for i in 1.. 0 { - // Columns: shortname,id,name,region,legacyid - let kbId: String = values[1] - let kbName: String = values[2] - let regionName: String = values[3] - // TODO: let legacyId: String = values[3] - #warning("TODO: deal with legacy ids") - var region = _keyboardList.first(where: { $0.name == regionName }) - if region == nil { - region = FVRegion.init() - region!.name = regionName - _keyboardList.append(region!) - } - - let kb = FVKeyboard(id: kbId, name: kbName) - region!.keyboards.append(kb) - } - } - } catch { - NSLog("Unexpected error: \(error).") - } - } - return _keyboardList - } - @objc func helpButtonTapped(_ sender: Any, forEvent event: UIEvent) { let touches: Set? = event.allTouches let touch: UITouch = touches!.first! let currentTouchPosition: CGPoint = touch.location(in: self.tableView) let indexPath: IndexPath? = self.tableView!.indexPathForRow(at: currentTouchPosition) if indexPath != nil { - let kbArray: FVKeyboardList = self.keyboardList()[indexPath!.section].keyboards + let kbArray: FVKeyboardList = self.keyboardList[indexPath!.section].keyboards let kbDict: FVKeyboard = kbArray[indexPath!.row] let helpUrl: URL = URL.init(string: "\(helpLink)\(kbDict.id)")! UIApplication.shared.openURL(helpUrl) @@ -175,33 +134,19 @@ class KeyboardsViewController: UIViewController, UITableViewDelegate, UITableVie let indexPath: IndexPath? = self.tableView!.indexPathForRow(at: currentTouchPosition) if indexPath != nil { // Update the checkState with the new checked state - let keyboards = self.keyboardList()[indexPath!.section].keyboards + let keyboards = self.keyboardList[indexPath!.section].keyboards let kb = keyboards[indexPath!.row] _loadedKeyboards = _loadedKeyboards.filter { $0 != kb.id } if (sender as! CheckBox).isChecked { _loadedKeyboards += [kb.id] } - self.saveKeyboardListToUserDefaults() - self.updateActiveKeyboardsList() + FVRegionStorage.saveKeyboardListToUserDefaults(loadedKeyboards: self._loadedKeyboards) + FVRegionStorage.updateActiveKeyboardsList(keyboardList: self.keyboardList, + loadedKeyboards: self._loadedKeyboards) } } - func loadKeyboardListFromUserDefaults() { - let sharedData: UserDefaults = FVShared.userDefaults() - let keyboards = sharedData.array(forKey: kFVLoadedKeyboardList) - if keyboards != nil { - _loadedKeyboards = keyboards as! [String] - } else { - _loadedKeyboards = [] - } - } - - func saveKeyboardListToUserDefaults() { - let sharedData: UserDefaults = FVShared.userDefaults() - sharedData.set(_loadedKeyboards, forKey: kFVLoadedKeyboardList) - sharedData.synchronize() - } func reportFatalError(message: String) { let alertController = UIAlertController(title: title, message: message, @@ -216,58 +161,5 @@ class KeyboardsViewController: UIViewController, UITableViewDelegate, UITableVie // some reporting mechanism } - func updateActiveKeyboardsList() { - - // Remove all installed keyboards first -- we'll re-add them below - - while Manager.shared.removeKeyboard(at: 0) { - } - - // Iterate through the available keyboards - - let kbList = self.keyboardList() - for i in 0...kbList.count-1 { - let keyboards = kbList[i].keyboards - for kb in keyboards { - if _loadedKeyboards.contains(kb.id) { - - // Load the .keyboard_info file and find its first language code - - var keyboardInfo: KeyboardInfo - do { - let kbinfoFilename: String = Bundle.main.path(forResource: kb.id, ofType: "keyboard_info", inDirectory: "Keyboards/files")! - keyboardInfo = try KeyboardInfoParser.decode(file: kbinfoFilename) - } catch { - reportFatalError(message: "Failed to load keyboard info for " + kb.id+": " + error.localizedDescription) - continue - } - - // Preload the keyboard - - do { - let kbPath: String = Bundle.main.path(forResource: kb.id, ofType: "js", inDirectory: "Keyboards/files")! - let pathUrl = URL(fileURLWithPath: kbPath) - try Manager.shared.preloadFiles(forKeyboardID: kb.id, at: [pathUrl], shouldOverwrite: true) - } catch { - reportFatalError(message: "Failed to load preload "+kb.id+": " + error.localizedDescription) - continue - } - - // Install the keyboard into Keyman Engine - - let keyboard: InstallableKeyboard = InstallableKeyboard.init(id: kb.id, - name: kb.name, - languageID: keyboardInfo.languages.keys[keyboardInfo.languages.startIndex], - languageName: kb.name, - version: keyboardInfo.version, - isRTL: false, - font: nil, - oskFont: nil, - isCustom: true) - Manager.shared.addKeyboard(keyboard) - } - } - } - } } diff --git a/oem/firstvoices/ios/FirstVoices/ViewController.swift b/oem/firstvoices/ios/FirstVoices/ViewController.swift index b16e6b5103..82960e88fe 100644 --- a/oem/firstvoices/ios/FirstVoices/ViewController.swift +++ b/oem/firstvoices/ios/FirstVoices/ViewController.swift @@ -18,10 +18,11 @@ class ViewController: UIViewController, UIWebViewDelegate { super.viewDidLoad() // - // Preload all the .js files, including their file versions + // Install all the .js files into Keyman Engine (doesn't show them in the keyboard list) // - let kbPaths: [String] = Bundle.main.paths(forResourcesOfType:"js", inDirectory: "Keyboards/files") + let kbPaths: [String] = Bundle.main.paths(forResourcesOfType: FVConstants.keyboardType, + inDirectory: FVConstants.keyboardsPath) for kbPath in kbPaths { let pathUrl = URL(fileURLWithPath: kbPath) let id = pathUrl.deletingPathExtension().lastPathComponent @@ -31,8 +32,15 @@ class ViewController: UIViewController, UIWebViewDelegate { print("Failed to preload "+id+": "+error.localizedDescription) } } + + // + // Show Instructions page + // + self.webView!.delegate = self - let filePath: String? = Bundle.main.path(forResource: "setup", ofType: "html", inDirectory: nil) + let filePath: String? = Bundle.main.path(forResource: FVConstants.instructionsName, + ofType: FVConstants.instructionsType, + inDirectory: FVConstants.instructionsPath) self.webView!.loadRequest(URLRequest.init(url: URL.init(fileURLWithPath: filePath!))) } diff --git a/oem/firstvoices/ios/SWKeyboard/FVInputViewController.swift b/oem/firstvoices/ios/SWKeyboard/FVInputViewController.swift index 5e1d62cd4f..75cedf2fbd 100644 --- a/oem/firstvoices/ios/SWKeyboard/FVInputViewController.swift +++ b/oem/firstvoices/ios/SWKeyboard/FVInputViewController.swift @@ -20,14 +20,9 @@ class FVInputViewController: InputViewController { // KeymanEngine.log.outputLevel = .warning //#endif - print("FVIVC: Starting app") - Manager.applicationGroupIdentifier = "group.FVKeyboards" - //[self setGlobeKeyTapBehaviour:GKTapSwitchToNextKeyboard]; - //[self setMenuBehaviour:MenuShowAlways]; - //[self setMenuCloseButtonTitle:@"Switch to other keyboard"]; + Manager.applicationGroupIdentifier = FVConstants.groupID super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) - print("FVIVC: After super.init") self.globeKeyTapBehaviour = .switchToNextKeyboard self.menuBehaviour = .showAlways diff --git a/oem/firstvoices/ios/SWKeyboard/SWKeyboard-Bridging-Header.h b/oem/firstvoices/ios/SWKeyboard/SWKeyboard-Bridging-Header.h deleted file mode 100644 index 1b2cb5d6d0..0000000000 --- a/oem/firstvoices/ios/SWKeyboard/SWKeyboard-Bridging-Header.h +++ /dev/null @@ -1,4 +0,0 @@ -// -// Use this file to import your target's public headers that you would like to expose to Swift. -// -