[iOS] FV app code cleanup, refactor, and implement upgrade

This commit is contained in:
Marc Durdin 2019-05-21 06:55:53 +10:00
parent 0ac9545fb8
commit b0429ed1aa
9 changed files with 316 additions and 238 deletions

View file

@ -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

View file

@ -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 = "<group>"; };
37C183BC2293321C009F9EFD /* Instructions */ = {isa = PBXFileReference; lastKnownFileType = folder; name = Instructions; path = FirstVoices/Instructions; sourceTree = "<group>"; };
37EA1F1D228A1809003E710C /* KeymanEngine.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; path = KeymanEngine.framework; sourceTree = "<group>"; };
37EA1F21228A1F57003E710C /* SWKeyboard-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "SWKeyboard-Bridging-Header.h"; sourceTree = "<group>"; };
37EA1F22228A1F57003E710C /* FVInputViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FVInputViewController.swift; sourceTree = "<group>"; };
37EA1F25228B5D6E003E710C /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
37EA1F27228B5E2B003E710C /* ViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewController.swift; sourceTree = "<group>"; };
37EA1F29228B69AF003E710C /* KeyboardsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardsViewController.swift; sourceTree = "<group>"; };
37EA1F2E228B7458003E710C /* CheckBox.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CheckBox.swift; path = Checkbox/CheckBox.swift; sourceTree = "<group>"; };
37EA1F30228B7661003E710C /* FVShared.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = FVShared.swift; path = FVShared/FVShared.swift; sourceTree = "<group>"; };
37EA1F33228BCDF8003E710C /* FirstVoices-Bridging-Header.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "FirstVoices-Bridging-Header.h"; sourceTree = "<group>"; };
37EA1F34228CA3BD003E710C /* ObjcExceptionBridging.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = ObjcExceptionBridging.framework; path = ../../../ios/Carthage/Build/iOS/ObjcExceptionBridging.framework; sourceTree = "<group>"; };
37EA1F36228CA3D2003E710C /* XCGLogger.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = XCGLogger.framework; path = ../../../ios/Carthage/Build/iOS/XCGLogger.framework; sourceTree = "<group>"; };
37EA1F38228CA3E4003E710C /* Zip.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Zip.framework; path = ../../../ios/Carthage/Build/iOS/Zip.framework; sourceTree = "<group>"; };
@ -153,14 +137,11 @@
98395E301C1E61AF007CD675 /* 739-question.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "739-question.png"; sourceTree = "<group>"; };
98395E311C1E61AF007CD675 /* 739-question@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "739-question@2x.png"; sourceTree = "<group>"; };
98395E321C1E61AF007CD675 /* 739-question@3x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "739-question@3x.png"; sourceTree = "<group>"; };
984471A51C1F796D00D3C9DA /* languagegeek.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = languagegeek.png; sourceTree = "<group>"; };
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 = "<group>"; };
9889E8C51BFA9CEC00019560 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
9889E8C81BFA9CEC00019560 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
9889E8CA1BFA9CEC00019560 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
989618C81C194AD90012E3EB /* fpcc.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = fpcc.svg; sourceTree = "<group>"; };
989618C91C194AD90012E3EB /* nrt.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = nrt.svg; sourceTree = "<group>"; };
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 = "<group>"; };
98C9A8DD1BFBF6C1009E9A4F /* banner-Landscape-568h@2x.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = "banner-Landscape-568h@2x.png"; sourceTree = "<group>"; };
@ -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 = "<group>"; };
98C9A9FA1BFC1111009E9A4F /* SWKeyboard.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = SWKeyboard.entitlements; sourceTree = "<group>"; };
98FAB7EA1C0E775500C31263 /* globe.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = globe.svg; sourceTree = "<group>"; };
98FAB7EB1C0E775500C31263 /* ios-instructions.css */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.css; path = "ios-instructions.css"; sourceTree = "<group>"; };
98FAB7EC1C0E775500C31263 /* keyman.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = keyman.svg; sourceTree = "<group>"; };
98FAB7EE1C0E775500C31263 /* nunito-regular-webfont.eot */ = {isa = PBXFileReference; lastKnownFileType = file; path = "nunito-regular-webfont.eot"; sourceTree = "<group>"; };
98FAB7EF1C0E775500C31263 /* nunito-regular-webfont.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = "nunito-regular-webfont.svg"; sourceTree = "<group>"; };
98FAB7F01C0E775500C31263 /* nunito-regular-webfont.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "nunito-regular-webfont.ttf"; sourceTree = "<group>"; };
98FAB7F11C0E775500C31263 /* nunito-regular-webfont.woff */ = {isa = PBXFileReference; lastKnownFileType = file; path = "nunito-regular-webfont.woff"; sourceTree = "<group>"; };
98FAB7F21C0E775500C31263 /* nunito-regular-webfont.woff2 */ = {isa = PBXFileReference; lastKnownFileType = file; path = "nunito-regular-webfont.woff2"; sourceTree = "<group>"; };
98FAB7F31C0E775500C31263 /* opensans-regular-webfont.eot */ = {isa = PBXFileReference; lastKnownFileType = file; path = "opensans-regular-webfont.eot"; sourceTree = "<group>"; };
98FAB7F41C0E775500C31263 /* opensans-regular-webfont.svg */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xml; path = "opensans-regular-webfont.svg"; sourceTree = "<group>"; };
98FAB7F51C0E775500C31263 /* opensans-regular-webfont.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "opensans-regular-webfont.ttf"; sourceTree = "<group>"; };
98FAB7F61C0E775500C31263 /* opensans-regular-webfont.woff */ = {isa = PBXFileReference; lastKnownFileType = file; path = "opensans-regular-webfont.woff"; sourceTree = "<group>"; };
98FAB7F71C0E775500C31263 /* opensans-regular-webfont.woff2 */ = {isa = PBXFileReference; lastKnownFileType = file; path = "opensans-regular-webfont.woff2"; sourceTree = "<group>"; };
98FAB7F81C0E775500C31263 /* setup.html */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.html; path = setup.html; sourceTree = "<group>"; };
98FAB80C1C0E799100C31263 /* firstvoices-logo.gif */ = {isa = PBXFileReference; lastKnownFileType = image.gif; path = "firstvoices-logo.gif"; sourceTree = "<group>"; };
/* 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 = "<group>";
@ -370,31 +335,6 @@
name = Frameworks;
sourceTree = "<group>";
};
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 = "<group>";
};
/* 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;
};

View file

@ -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
}
}

View file

@ -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..<lines.count { // skip first line; it is a header row
let line: String = lines[i]
if line.count == 0 {
continue
}
let values: [String] = line.components(separatedBy: ",")
if values.count > 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)
}
}
}
}
}

View file

@ -1,4 +0,0 @@
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//

View file

@ -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..<lines.count { // skip first line; it is a header row
let line: String = lines[i]
if line.count == 0 {
continue
}
let values: [String] = line.components(separatedBy: ",")
if values.count > 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<UITouch>? = 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)
}
}
}
}
}

View file

@ -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!)))
}

View file

@ -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

View file

@ -1,4 +0,0 @@
//
// Use this file to import your target's public headers that you would like to expose to Swift.
//