diff --git a/Keyboards/DataManager/DataContract.swift b/Keyboards/DataManager/DataContract.swift index 0eb96a77..4ca89639 100644 --- a/Keyboards/DataManager/DataContract.swift +++ b/Keyboards/DataManager/DataContract.swift @@ -38,7 +38,7 @@ struct DeclensionSection: Codable { let declensionForms: [Int: DeclensionNode]? } -struct DeclensionNode: Codable { +class DeclensionNode: Codable { let label: String? let value: String? let displayValue: String? diff --git a/Keyboards/KeyboardsBase/KeyboardViewController.swift b/Keyboards/KeyboardsBase/KeyboardViewController.swift index d74847f9..61dfee62 100644 --- a/Keyboards/KeyboardsBase/KeyboardViewController.swift +++ b/Keyboards/KeyboardsBase/KeyboardViewController.swift @@ -290,6 +290,100 @@ class KeyboardViewController: UIInputViewController { pluralKey.isHidden = state } + // MARK: Download Data Button + + /// A button shown above the keyboard when no language data has been downloaded. + var downloadDataBtn: UIButton? + + /// Checks whether language data has been downloaded for the current keyboard language. + func hasLanguageData() -> Bool { + let langAbbr = getControllerLanguageAbbr() + guard !langAbbr.isEmpty, + let containerURL = FileManager.default.containerURL( + forSecurityApplicationGroupIdentifier: "group.be.scri.userDefaultsContainer" + ) else { + return true // default to true to avoid showing the button unnecessarily + } + let dbPath = containerURL.appendingPathComponent("\(langAbbr.uppercased())LanguageData.sqlite").path + return FileManager.default.fileExists(atPath: dbPath) + } + + /// Opens the Scribe app via the responder chain so the user can download language data. + @objc func openScribeApp() { + guard let url = URL(string: "scribe://") else { return } + var responder: UIResponder? = self + while responder != nil { + if let application = responder as? UIApplication { + application.open(url) + return + } + responder = responder?.next + } + } + + /// Shows or hides the download data button based on whether language data is available. + func conditionallyShowDownloadDataBtn() { + // Only show the download button in idle state (not during commands). + guard commandState == .idle else { + downloadDataBtn?.isHidden = true + return + } + if hasLanguageData() { + downloadDataBtn?.isHidden = true + } else { + showDownloadDataBtn() + } + } + + /// Creates and displays the download data button above the keyboard. + func showDownloadDataBtn() { + // Remove any existing button first. + downloadDataBtn?.removeFromSuperview() + + let btn = UIButton(type: .system) + btn.translatesAutoresizingMaskIntoConstraints = false + btn.backgroundColor = scribeCTAColor + btn.setTitleColor(.white, for: .normal) + btn.setTitle(downloadDataMsg, for: .normal) + btn.titleLabel?.font = .systemFont(ofSize: 16, weight: .medium) + btn.layer.cornerRadius = commandKeyCornerRadius + btn.layer.masksToBounds = true + + // Add a download icon on the left side. + let iconConfig = UIImage.SymbolConfiguration(pointSize: 14, weight: .medium) + btn.setImage(UIImage(systemName: "arrow.down.circle.fill", withConfiguration: iconConfig), for: .normal) + btn.tintColor = .white + if #available(iOS 15.0, *) { + var config = UIButton.Configuration.plain() + config.baseForegroundColor = .white + config.image = UIImage(systemName: "arrow.down.circle.fill", withConfiguration: iconConfig) + config.imagePadding = 8 + config.imagePlacement = .leading + config.title = downloadDataMsg + config.attributedTitle = AttributedString( + downloadDataMsg, + attributes: AttributeContainer([.font: UIFont.systemFont(ofSize: 16, weight: .medium)]) + ) + btn.configuration = config + btn.backgroundColor = scribeCTAColor + } else { + btn.imageEdgeInsets = UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 8) + btn.contentEdgeInsets = UIEdgeInsets(top: 8, left: 12, bottom: 8, right: 12) + } + + btn.addTarget(self, action: #selector(openScribeApp), for: .touchUpInside) + + view.addSubview(btn) + downloadDataBtn = btn + + NSLayoutConstraint.activate([ + btn.leadingAnchor.constraint(equalTo: view.leadingAnchor, constant: 8), + btn.trailingAnchor.constraint(equalTo: view.trailingAnchor, constant: -8), + btn.topAnchor.constraint(equalTo: view.topAnchor, constant: 4), + btn.heightAnchor.constraint(equalToConstant: scribeKey.frame.height > 0 ? scribeKey.frame.height : 36) + ]) + } + /// Logic to create notification tooltip. func createInformationStateDatasource(text: NSMutableAttributedString, backgroundColor: UIColor) -> ToolTipViewDatasource { @@ -1714,6 +1808,7 @@ class KeyboardViewController: UIInputViewController { deactivateBtn(btn: padEmojiKey2) setInformationState() + downloadDataBtn?.isHidden = true return // return to skip normal keyboard setup } @@ -1835,6 +1930,7 @@ class KeyboardViewController: UIInputViewController { } setKeyPadding() + conditionallyShowDownloadDataBtn() } func setCommaAndPeriodKeysConditionally() { diff --git a/Keyboards/KeyboardsBase/NavigationStructure.swift b/Keyboards/KeyboardsBase/NavigationStructure.swift index 54f5d558..64ef35f3 100644 --- a/Keyboards/KeyboardsBase/NavigationStructure.swift +++ b/Keyboards/KeyboardsBase/NavigationStructure.swift @@ -6,15 +6,20 @@ import Foundation /// Represents a single option in the navigation. -enum NavigationNode { +indirect enum NavigationNode { case nextLevel(NavigationLevel, displayValue: String?) // navigate deeper, with optional display value case finalValue(String) // terminal node, insert this text } /// Represents a level in the navigation hierarchy. -struct NavigationLevel { +class NavigationLevel { let title: String // title for command bar let options: [(label: String, node: NavigationNode)] // buttons to display + + init(title: String, options: [(label: String, node: NavigationNode)]) { + self.title = title + self.options = options + } } /// Builds navigation trees for conjugations and declensions. diff --git a/Keyboards/KeyboardsBase/ScribeFunctionality/CommandVariables.swift b/Keyboards/KeyboardsBase/ScribeFunctionality/CommandVariables.swift index 236fd9e5..43778e4f 100644 --- a/Keyboards/KeyboardsBase/ScribeFunctionality/CommandVariables.swift +++ b/Keyboards/KeyboardsBase/ScribeFunctionality/CommandVariables.swift @@ -177,3 +177,8 @@ var pluralPromptAndCursor = "" var pluralPromptAndPlaceholder = "" var pluralPromptAndColorPlaceholder = NSMutableAttributedString() var alreadyPluralMsg = "" + +// MARK: Download Data Variables + +/// The message shown on the download data button when no language data has been downloaded. +var downloadDataMsg = "Please download language data" diff --git a/Keyboards/LanguageKeyboards/English/ENInterfaceVariables.swift b/Keyboards/LanguageKeyboards/English/ENInterfaceVariables.swift index 28f5f4fc..4812c060 100644 --- a/Keyboards/LanguageKeyboards/English/ENInterfaceVariables.swift +++ b/Keyboards/LanguageKeyboards/English/ENInterfaceVariables.swift @@ -294,4 +294,5 @@ func setENKeyboardLayout() { withColor: UIColor(cgColor: commandBarPlaceholderColorCG) ) alreadyPluralMsg = "Already plural" + downloadDataMsg = "Please download language data" } diff --git a/Keyboards/LanguageKeyboards/French/FR-AZERTYInterfaceVariables.swift b/Keyboards/LanguageKeyboards/French/FR-AZERTYInterfaceVariables.swift index 8be89a71..8882feca 100644 --- a/Keyboards/LanguageKeyboards/French/FR-AZERTYInterfaceVariables.swift +++ b/Keyboards/LanguageKeyboards/French/FR-AZERTYInterfaceVariables.swift @@ -293,4 +293,5 @@ func setFRKeyboardLayout() { withColor: UIColor(cgColor: commandBarPlaceholderColorCG) ) alreadyPluralMsg = "Déjà pluriel" + downloadDataMsg = "Veuillez télécharger les données linguistiques" } diff --git a/Keyboards/LanguageKeyboards/German/DEInterfaceVariables.swift b/Keyboards/LanguageKeyboards/German/DEInterfaceVariables.swift index de063eb9..4da5a232 100644 --- a/Keyboards/LanguageKeyboards/German/DEInterfaceVariables.swift +++ b/Keyboards/LanguageKeyboards/German/DEInterfaceVariables.swift @@ -356,4 +356,5 @@ func setDEKeyboardLayout() { withColor: UIColor(cgColor: commandBarPlaceholderColorCG) ) alreadyPluralMsg = "Schon Plural" + downloadDataMsg = "Bitte Sprachdaten herunterladen" } diff --git a/Keyboards/LanguageKeyboards/Hebrew/HEInterfaceVariables.swift b/Keyboards/LanguageKeyboards/Hebrew/HEInterfaceVariables.swift index 3e3f6319..5cc7cee4 100644 --- a/Keyboards/LanguageKeyboards/Hebrew/HEInterfaceVariables.swift +++ b/Keyboards/LanguageKeyboards/Hebrew/HEInterfaceVariables.swift @@ -254,4 +254,5 @@ func setHEKeyboardLayout() { withColor: UIColor(cgColor: commandBarPlaceholderColorCG) ) alreadyPluralMsg = "כבר בצורת רבים" + downloadDataMsg = "אנא הורד נתוני שפה" } diff --git a/Keyboards/LanguageKeyboards/Indonesian/IDInterfaceVariables.swift b/Keyboards/LanguageKeyboards/Indonesian/IDInterfaceVariables.swift index 8acfbe95..57b04d32 100644 --- a/Keyboards/LanguageKeyboards/Indonesian/IDInterfaceVariables.swift +++ b/Keyboards/LanguageKeyboards/Indonesian/IDInterfaceVariables.swift @@ -244,4 +244,5 @@ func setIDKeyboardLayout() { textForAttribute: translatePlaceholder, withColor: UIColor(cgColor: commandBarPlaceholderColorCG) ) + downloadDataMsg = "Unduh data bahasa" } diff --git a/Keyboards/LanguageKeyboards/Italian/ITInterfaceVariables.swift b/Keyboards/LanguageKeyboards/Italian/ITInterfaceVariables.swift index 8337b9f7..256ee789 100644 --- a/Keyboards/LanguageKeyboards/Italian/ITInterfaceVariables.swift +++ b/Keyboards/LanguageKeyboards/Italian/ITInterfaceVariables.swift @@ -281,4 +281,5 @@ func setITKeyboardLayout() { withColor: UIColor(cgColor: commandBarPlaceholderColorCG) ) alreadyPluralMsg = "Già plurale" + downloadDataMsg = "Scarica i dati della lingua" } diff --git a/Keyboards/LanguageKeyboards/Norwegian/NBInterfaceVariables.swift b/Keyboards/LanguageKeyboards/Norwegian/NBInterfaceVariables.swift index 2d7e8a74..9e08ac1e 100644 --- a/Keyboards/LanguageKeyboards/Norwegian/NBInterfaceVariables.swift +++ b/Keyboards/LanguageKeyboards/Norwegian/NBInterfaceVariables.swift @@ -300,4 +300,5 @@ func setNBKeyboardLayout() { withColor: UIColor(cgColor: commandBarPlaceholderColorCG) ) alreadyPluralMsg = "Allerede flertall" + downloadDataMsg = "Last ned språkdata" } diff --git a/Keyboards/LanguageKeyboards/Portuguese/PTInterfaceVariables.swift b/Keyboards/LanguageKeyboards/Portuguese/PTInterfaceVariables.swift index eeee3642..4d5f19d3 100644 --- a/Keyboards/LanguageKeyboards/Portuguese/PTInterfaceVariables.swift +++ b/Keyboards/LanguageKeyboards/Portuguese/PTInterfaceVariables.swift @@ -279,4 +279,5 @@ func setPTKeyboardLayout() { withColor: UIColor(cgColor: commandBarPlaceholderColorCG) ) alreadyPluralMsg = "Já plural" + downloadDataMsg = "Por favor baixe os dados do idioma" } diff --git a/Keyboards/LanguageKeyboards/Russian/RUInterfaceVariables.swift b/Keyboards/LanguageKeyboards/Russian/RUInterfaceVariables.swift index 40e17c4c..282238e8 100644 --- a/Keyboards/LanguageKeyboards/Russian/RUInterfaceVariables.swift +++ b/Keyboards/LanguageKeyboards/Russian/RUInterfaceVariables.swift @@ -269,4 +269,5 @@ func setRUKeyboardLayout() { withColor: UIColor(cgColor: commandBarPlaceholderColorCG) ) alreadyPluralMsg = "Уже во множ-ом" + downloadDataMsg = "Загрузите языковые данные" } diff --git a/Keyboards/LanguageKeyboards/Spanish/ESInterfaceVariables.swift b/Keyboards/LanguageKeyboards/Spanish/ESInterfaceVariables.swift index 200d9b7b..b33cdb99 100644 --- a/Keyboards/LanguageKeyboards/Spanish/ESInterfaceVariables.swift +++ b/Keyboards/LanguageKeyboards/Spanish/ESInterfaceVariables.swift @@ -353,4 +353,5 @@ func setESKeyboardLayout() { withColor: UIColor(cgColor: commandBarPlaceholderColorCG) ) alreadyPluralMsg = "Ya en plural" + downloadDataMsg = "Por favor descarga los datos del idioma" } diff --git a/Keyboards/LanguageKeyboards/Swedish/SVInterfaceVariables.swift b/Keyboards/LanguageKeyboards/Swedish/SVInterfaceVariables.swift index 3943138d..f326ba38 100644 --- a/Keyboards/LanguageKeyboards/Swedish/SVInterfaceVariables.swift +++ b/Keyboards/LanguageKeyboards/Swedish/SVInterfaceVariables.swift @@ -349,4 +349,5 @@ func setSVKeyboardLayout() { withColor: UIColor(cgColor: commandBarPlaceholderColorCG) ) alreadyPluralMsg = "Redan plural" + downloadDataMsg = "Ladda ner språkdata" } diff --git a/Scribe/AppDelegate.swift b/Scribe/AppDelegate.swift index b00aa906..e86a60b8 100644 --- a/Scribe/AppDelegate.swift +++ b/Scribe/AppDelegate.swift @@ -82,6 +82,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate { */ } + func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any] = [:]) -> Bool { + // Handle scribe:// URL scheme to navigate to the download data screen. + if url.scheme == "scribe" { + NotificationCenter.default.post(name: NSNotification.Name("NavigateToDownloadScreen"), object: nil) + return true + } + return false + } + func applicationDidBecomeActive(_: UIApplication) { /* Restart any tasks that were paused (or not yet started) while the application was inactive. diff --git a/Scribe/Info.plist b/Scribe/Info.plist index 633b5171..5c2f37fe 100644 --- a/Scribe/Info.plist +++ b/Scribe/Info.plist @@ -46,5 +46,16 @@ UIViewControllerBasedStatusBarAppearance + CFBundleURLTypes + + + CFBundleURLSchemes + + scribe + + CFBundleURLName + be.scri.scribe + +