mirror of
https://github.com/keymanapp/keyman.git
synced 2026-08-06 17:05:34 +00:00
450 lines
17 KiB
Swift
450 lines
17 KiB
Swift
//
|
|
// LexicalModelPickerViewController.swift
|
|
// KeymanEngine
|
|
//
|
|
// Created by Randy Boring on 3/19/19.
|
|
// Copyright © 2019 SIL International. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
import UIKit
|
|
|
|
private let toolbarButtonTag = 100
|
|
private let toolbarLabelTag = 101
|
|
private let toolbarActivityIndicatorTag = 102
|
|
|
|
class LexicalModelPickerViewController: UITableViewController, UIAlertViewDelegate {
|
|
private var userLexicalModels: [InstallableLexicalModel] = [InstallableLexicalModel]()
|
|
public var language: Language!
|
|
private var updateQueue: [InstallableLexicalModel]?
|
|
private var _isDoneButtonEnabled = false
|
|
private var isDidUpdateCheck = false
|
|
|
|
private var lexicalModelDownloadStartedObserver: NotificationObserver?
|
|
private var lexicalModelDownloadCompletedObserver: NotificationObserver?
|
|
private var lexicalModelDownloadFailedObserver: NotificationObserver?
|
|
|
|
public init(_ language: Language) {
|
|
self.language = language
|
|
super.init(nibName: nil, bundle: nil)
|
|
}
|
|
|
|
required init?(coder aDecoder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
override func viewDidLoad() {
|
|
super.viewDidLoad()
|
|
|
|
title = "\(language.name) Dictionaries"
|
|
isDidUpdateCheck = false
|
|
updateQueue = nil
|
|
if Manager.shared.canAddNewLexicalModels {
|
|
let addButton = UIBarButtonItem(barButtonSystemItem: .add, target: self,
|
|
action: #selector(self.addClicked))
|
|
navigationItem.rightBarButtonItem = addButton
|
|
}
|
|
|
|
navigationController?.toolbar?.barTintColor = UIColor(red: 0.5, green: 0.75,
|
|
blue: 0.25, alpha: 0.9)
|
|
|
|
lexicalModelDownloadStartedObserver = NotificationCenter.default.addObserver(
|
|
forName: Notifications.lexicalModelDownloadStarted,
|
|
observer: self,
|
|
function: LexicalModelPickerViewController.lexicalModelDownloadStarted)
|
|
lexicalModelDownloadCompletedObserver = NotificationCenter.default.addObserver(
|
|
forName: Notifications.lexicalModelDownloadCompleted,
|
|
observer: self,
|
|
function: LexicalModelPickerViewController.lexicalModelDownloadCompleted)
|
|
lexicalModelDownloadFailedObserver = NotificationCenter.default.addObserver(
|
|
forName: Notifications.lexicalModelDownloadFailed,
|
|
observer: self,
|
|
function: LexicalModelPickerViewController.lexicalModelDownloadFailed)
|
|
|
|
log.info("didLoad: LexicalModelPickerViewController (registered lexicalModelDownloadCompleted et al)")
|
|
}
|
|
|
|
override func viewWillAppear(_ animated: Bool) {
|
|
super.viewWillAppear(animated)
|
|
|
|
loadUserLexicalModels()
|
|
scroll(toSelectedLexicalModel: false)
|
|
}
|
|
|
|
override func viewDidAppear(_ animated: Bool) {
|
|
super.viewDidAppear(animated)
|
|
log.info("didAppear: LexicalModelPickerViewController")
|
|
if isDidUpdateCheck || !checkUpdates() {
|
|
return
|
|
}
|
|
|
|
let toolbarFrame = navigationController!.toolbar!.frame
|
|
let button = UIButton(type: .roundedRect)
|
|
button.addTarget(self, action: #selector(self.updateClicked), for: .touchUpInside)
|
|
|
|
button.frame = CGRect(x: toolbarFrame.origin.x, y: toolbarFrame.origin.y,
|
|
width: toolbarFrame.width * 0.95, height: toolbarFrame.height * 0.7)
|
|
button.center = CGPoint(x: toolbarFrame.width / 2, y: toolbarFrame.height / 2)
|
|
button.tintColor = UIColor(red: 0.75, green: 1.0, blue: 0.5, alpha: 1.0)
|
|
button.setTitleColor(UIColor.white, for: .normal)
|
|
button.setTitle("Update available", for: .normal)
|
|
button.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin, .flexibleTopMargin,
|
|
.flexibleBottomMargin, .flexibleWidth, .flexibleHeight]
|
|
button.tag = toolbarButtonTag
|
|
navigationController?.toolbar?.addSubview(button)
|
|
|
|
navigationController?.setToolbarHidden(false, animated: true)
|
|
scroll(toSelectedLexicalModel: false)
|
|
}
|
|
|
|
// MARK: - Table view data source UITableViewDataSource
|
|
|
|
override func numberOfSections(in tableView: UITableView) -> Int {
|
|
return 1
|
|
}
|
|
|
|
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
|
return userLexicalModels.count
|
|
}
|
|
|
|
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
|
let cellIdentifier = "Cell"
|
|
if let cell = tableView.dequeueReusableCell(withIdentifier: cellIdentifier) {
|
|
return cell
|
|
}
|
|
|
|
let cell = UITableViewCell(style: .subtitle, reuseIdentifier: cellIdentifier)
|
|
let selectionColor = UIView()
|
|
selectionColor.backgroundColor = UIColor(red: 204.0 / 255.0, green: 136.0 / 255.0, blue: 34.0 / 255.0, alpha: 1.0)
|
|
cell.selectedBackgroundView = selectionColor
|
|
return cell
|
|
}
|
|
|
|
// TODO: Refactor. Duplicated in LexicalModelInfoViewController
|
|
override func tableView(_ tableView: UITableView, canEditRowAt indexPath: IndexPath) -> Bool {
|
|
if !Manager.shared.canRemoveLexicalModels {
|
|
return false
|
|
}
|
|
|
|
return true
|
|
}
|
|
|
|
override func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle,
|
|
forRowAt indexPath: IndexPath) {
|
|
if editingStyle != .delete {
|
|
return
|
|
}
|
|
|
|
let globalIndex = local2globalIndex(indexPath.row)
|
|
if Manager.shared.removeLexicalModel(at: globalIndex) {
|
|
loadUserLexicalModels()
|
|
}
|
|
}
|
|
|
|
override func tableView(_ tableView: UITableView,
|
|
accessoryButtonTappedForRowWith indexPath: IndexPath) {
|
|
showLexicalModelInfoView(with: indexPath.row)
|
|
}
|
|
|
|
func showLexicalModelInfoView(with index: Int) {
|
|
let lm = userLexicalModels[index]
|
|
let version = lm.version
|
|
|
|
let infoView = LexicalModelInfoViewController()
|
|
infoView.title = lm.name
|
|
infoView.lexicalModelCount = userLexicalModels.count
|
|
infoView.lexicalModelIndex = local2globalIndex(index)
|
|
infoView.lexicalModelID = lm.id
|
|
infoView.languageID = lm.languageID
|
|
infoView.lexicalModelVersion = version
|
|
infoView.isCustomLexicalModel = lm.isCustom
|
|
navigationController?.pushViewController(infoView, animated: true)
|
|
}
|
|
|
|
// MARK: - UITableViewDelegate
|
|
|
|
override func tableView(_ tableView: UITableView,
|
|
willDisplay cell: UITableViewCell,
|
|
forRowAt indexPath: IndexPath) {
|
|
cell.selectionStyle = .none
|
|
let lm = userLexicalModels[indexPath.row]
|
|
|
|
cell.textLabel?.text = lm.name
|
|
cell.detailTextLabel?.text = lm.id // maybe put a longer description here?
|
|
cell.tag = indexPath.row
|
|
|
|
if Manager.shared.currentLexicalModelID == lm.fullID {
|
|
cell.selectionStyle = .blue
|
|
cell.isSelected = true
|
|
cell.accessoryType = .detailDisclosureButton
|
|
} else {
|
|
cell.selectionStyle = .none
|
|
cell.isSelected = false
|
|
cell.accessoryType = .detailDisclosureButton
|
|
}
|
|
}
|
|
|
|
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
|
switchLexicalModel(indexPath.row)
|
|
}
|
|
|
|
func local2globalIndex(_ localIndex: Int) -> Int {
|
|
let globalIndex: Int
|
|
if let lang = self.language {
|
|
let lm = userLexicalModels[localIndex]
|
|
globalIndex = Storage.active.userDefaults.userLexicalModels?.firstIndex(where: {
|
|
$0.languageID == lang.id && $0.id == lm.id
|
|
}) ?? 0
|
|
} else {
|
|
globalIndex = localIndex
|
|
}
|
|
return globalIndex
|
|
}
|
|
|
|
private func lexicalModelDownloadStarted(_ lexicalModels: [InstallableLexicalModel]) {
|
|
view.isUserInteractionEnabled = false
|
|
navigationItem.leftBarButtonItem?.isEnabled = false
|
|
navigationItem.rightBarButtonItem?.isEnabled = false
|
|
}
|
|
|
|
private func lexicalModelDownloadCompleted(_ lexicalModels: [InstallableLexicalModel]) {
|
|
log.info("lexicalModelDownloadCompleted LexicalModelPicker")
|
|
if view == navigationController?.topViewController?.view {
|
|
if updateQueue == nil {
|
|
return
|
|
}
|
|
Manager.shared.shouldReloadLexicalModel = true
|
|
|
|
// Update lexicalModel version
|
|
for lexicalModel in lexicalModels {
|
|
Manager.shared.updateUserLexicalModels(with: lexicalModel)
|
|
}
|
|
|
|
updateQueue!.remove(at: 0)
|
|
if !updateQueue!.isEmpty {
|
|
let langID = updateQueue![0].languageID
|
|
let lmID = updateQueue![0].id
|
|
Manager.shared.downloadLexicalModel(withID: lmID, languageID: langID, isUpdate: true)
|
|
} else {
|
|
loadUserLexicalModels()
|
|
view.isUserInteractionEnabled = true
|
|
navigationItem.leftBarButtonItem?.isEnabled = true
|
|
if navigationItem.rightBarButtonItem != nil {
|
|
navigationItem.rightBarButtonItem?.isEnabled = true
|
|
}
|
|
updateQueue = nil
|
|
let label = navigationController?.toolbar?.viewWithTag(toolbarLabelTag) as? UILabel
|
|
label?.text = "Dictionaries successfully updated!"
|
|
navigationController?.toolbar?.viewWithTag(toolbarActivityIndicatorTag)?.removeFromSuperview()
|
|
Timer.scheduledTimer(timeInterval: 3.0, target: self, selector: #selector(self.hideToolbarDelayed),
|
|
userInfo: nil, repeats: false)
|
|
}
|
|
} else {
|
|
let label = navigationController?.toolbar?.viewWithTag(toolbarLabelTag) as? UILabel
|
|
label?.text = "Dictionary successfully downloaded!"
|
|
navigationController?.toolbar?.viewWithTag(toolbarActivityIndicatorTag)?.removeFromSuperview()
|
|
Timer.scheduledTimer(timeInterval: 3.0, target: self, selector: #selector(self.hideToolbarDelayed),
|
|
userInfo: nil, repeats: false)
|
|
|
|
view.isUserInteractionEnabled = true
|
|
navigationItem.leftBarButtonItem?.isEnabled = true
|
|
if navigationItem.rightBarButtonItem != nil {
|
|
navigationItem.rightBarButtonItem?.isEnabled = true
|
|
}
|
|
|
|
// Add lexicalModel.
|
|
for lexicalModel in lexicalModels {
|
|
Manager.shared.addLexicalModel(lexicalModel)
|
|
_ = Manager.shared.registerLexicalModel(lexicalModel)
|
|
}
|
|
|
|
navigationController?.popToRootViewController(animated: true)
|
|
}
|
|
}
|
|
|
|
private func lexicalModelDownloadFailed(_ notification: LexicalModelDownloadFailedNotification) {
|
|
view.isUserInteractionEnabled = true
|
|
navigationItem.leftBarButtonItem?.isEnabled = true
|
|
if let item = navigationItem.rightBarButtonItem {
|
|
item.isEnabled = true
|
|
}
|
|
|
|
let title: String
|
|
if view == navigationController?.topViewController?.view {
|
|
updateQueue = nil
|
|
title = "Dictionary Update Error"
|
|
} else {
|
|
title = "Dictionary Download Error"
|
|
}
|
|
navigationController?.setToolbarHidden(true, animated: true)
|
|
|
|
let alertController = UIAlertController(title: title, message: notification.error.localizedDescription,
|
|
preferredStyle: UIAlertControllerStyle.alert)
|
|
alertController.addAction(UIAlertAction(title: "OK",
|
|
style: UIAlertActionStyle.cancel,
|
|
handler: nil))
|
|
|
|
self.present(alertController, animated: true, completion: nil)
|
|
}
|
|
|
|
private func switchLexicalModel(_ index: Int) {
|
|
// Switch lexicalModel and register to user defaults.
|
|
let lm = userLexicalModels[index]
|
|
if Manager.shared.registerLexicalModel(lm) {
|
|
Storage.active.userDefaults.set(preferredLexicalModelID: lm.id, forKey: lm.languageID)
|
|
tableView.reloadData()
|
|
}
|
|
|
|
if !_isDoneButtonEnabled {
|
|
Manager.shared.dismissLexicalModelPicker(self)
|
|
}
|
|
}
|
|
|
|
// if we have a language set, filter models down to those of that language
|
|
private func loadUserLexicalModels() {
|
|
if let lang = self.language {
|
|
userLexicalModels = Storage.active.userDefaults.userLexicalModels?.filter({
|
|
$0.languageID == lang.id
|
|
}) ?? []
|
|
} else {
|
|
userLexicalModels = Storage.active.userDefaults.userLexicalModels ?? []
|
|
}
|
|
tableView.reloadData()
|
|
}
|
|
|
|
private func isAdded(languageID langID: String, lexicalModelID lmID: String) -> Bool {
|
|
return userLexicalModels.contains { lm in lm.languageID == langID && lm.id == lmID }
|
|
}
|
|
|
|
@objc func doneClicked(_ sender: Any) {
|
|
Manager.shared.dismissLexicalModelPicker(self)
|
|
}
|
|
|
|
@objc func cancelClicked(_ sender: Any) {
|
|
Manager.shared.dismissLexicalModelPicker(self)
|
|
}
|
|
|
|
@objc func addClicked(_ sender: Any) {
|
|
showAddLexicalModel()
|
|
}
|
|
|
|
@objc func updateClicked(_ sender: Any) {
|
|
navigationController?.toolbar?.viewWithTag(toolbarButtonTag)?.removeFromSuperview()
|
|
let toolbarFrame = navigationController!.toolbar!.frame
|
|
let width = toolbarFrame.width * 0.95
|
|
let height = toolbarFrame.height * 0.7
|
|
let labelFrame = CGRect(x: toolbarFrame.origin.x, y: toolbarFrame.origin.y,
|
|
width: width, height: height)
|
|
|
|
let label = UILabel(frame: labelFrame)
|
|
label.backgroundColor = UIColor.clear
|
|
label.textColor = UIColor.white
|
|
label.textAlignment = .center
|
|
label.center = CGPoint(x: width * 0.5, y: height * 0.5)
|
|
label.text = "Updating\u{2026}"
|
|
label.autoresizingMask = [.flexibleLeftMargin, .flexibleRightMargin, .flexibleTopMargin,
|
|
.flexibleBottomMargin, .flexibleWidth, .flexibleHeight]
|
|
label.tag = toolbarLabelTag
|
|
|
|
let indicatorView = UIActivityIndicatorView(activityIndicatorStyle: .gray)
|
|
indicatorView.center = CGPoint(x: width - indicatorView.frame.width, y: height * 0.5)
|
|
indicatorView.autoresizingMask = [.flexibleLeftMargin, .flexibleTopMargin, .flexibleBottomMargin]
|
|
indicatorView.tag = toolbarActivityIndicatorTag
|
|
indicatorView.startAnimating()
|
|
navigationController?.toolbar?.addSubview(label)
|
|
navigationController?.toolbar?.addSubview(indicatorView)
|
|
setIsDoneButtonEnabled(true)
|
|
updateLexicalModels()
|
|
}
|
|
|
|
private func checkUpdates() -> Bool {
|
|
if Manager.shared.apiLexicalModelRepository.lexicalModels == nil {
|
|
return false
|
|
}
|
|
|
|
isDidUpdateCheck = true
|
|
return userLexicalModels.contains { lexicalModel in
|
|
let lmID = lexicalModel.id
|
|
return Manager.shared.stateForLexicalModel(withID: lmID) == .needsUpdate
|
|
}
|
|
}
|
|
|
|
private func updateLexicalModels() {
|
|
updateQueue = []
|
|
var lmIDs = Set<String>()
|
|
for lm in userLexicalModels {
|
|
let lmState = Manager.shared.stateForLexicalModel(withID: lm.id)
|
|
if lmState == .needsUpdate {
|
|
if !lmIDs.contains(lm.id) {
|
|
lmIDs.insert(lm.id)
|
|
updateQueue!.append(lm)
|
|
}
|
|
}
|
|
}
|
|
|
|
if !updateQueue!.isEmpty {
|
|
let langID = updateQueue![0].languageID
|
|
let lmID = updateQueue![0].id
|
|
Manager.shared.downloadLexicalModel(withID: lmID, languageID: langID, isUpdate: true)
|
|
}
|
|
}
|
|
|
|
private func scroll(toSelectedLexicalModel animated: Bool) {
|
|
let index = userLexicalModels.index { lm in
|
|
return Manager.shared.currentLexicalModelID == lm.fullID
|
|
}
|
|
|
|
if let index = index {
|
|
let indexPath = IndexPath(row: index, section: 0)
|
|
tableView.scrollToRow(at: indexPath, at: .middle, animated: animated)
|
|
|
|
}
|
|
}
|
|
|
|
private func setIsDoneButtonEnabled(_ value: Bool) {
|
|
_isDoneButtonEnabled = value
|
|
if _isDoneButtonEnabled {
|
|
let doneButton = UIBarButtonItem(title: "Done", style: .plain, target: self,
|
|
action: #selector(self.doneClicked))
|
|
navigationItem.leftBarButtonItem = doneButton
|
|
} else {
|
|
let cancelButton = UIBarButtonItem(barButtonSystemItem: .cancel, target: self,
|
|
action: #selector(self.cancelClicked))
|
|
navigationItem.leftBarButtonItem = cancelButton
|
|
}
|
|
}
|
|
|
|
@objc func hideToolbarDelayed(_ timer: Timer) {
|
|
navigationController?.setToolbarHidden(true, animated: true)
|
|
}
|
|
|
|
func showAddLexicalModel() {
|
|
//get list of lexical models for this languageID and show it
|
|
func listCompletionHandler(lexicalModels: [LexicalModel]?, error: Error?) -> Void {
|
|
if let error = error {
|
|
log.info("Failed to fetch lexical model list for "+language.id+". error: "+(error as! String))
|
|
DispatchQueue.main.async {
|
|
self.lexicalModelDownloadFailed(LexicalModelDownloadFailedNotification(lmOrLanguageID: self.language.id, error: error))
|
|
}
|
|
} else if nil == lexicalModels {
|
|
log.info("No lexical models available for language \(language.id) (nil)")
|
|
} else if 0 == lexicalModels?.count {
|
|
log.info("No lexical models available for language \(language.id) (empty)")
|
|
} else {
|
|
log.info("Fetched lexical model list for "+language.id+".")
|
|
// show the list of lexical models (on the main thread)
|
|
DispatchQueue.main.async {
|
|
let button: UIButton? = (self.navigationController?.toolbar?.viewWithTag(toolbarButtonTag) as? UIButton)
|
|
button?.isEnabled = false
|
|
let vc = LanguageLMDetailViewController(language: self.language)
|
|
vc.lexicalModels = lexicalModels!
|
|
self.navigationController?.pushViewController(vc, animated: true)
|
|
}
|
|
}
|
|
}
|
|
|
|
Manager.shared.apiLexicalModelRepository.fetchList(languageID: language.id, completionHandler: listCompletionHandler)
|
|
}
|
|
|
|
}
|
|
|