| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387 |
- //
- // SettingsViewController.swift
- // LoopFollow
- //
- // Created by Jon Fawcett on 6/3/20.
- // Copyright © 2020 Jon Fawcett. All rights reserved.
- //
- import UIKit
- import Eureka
- import EventKit
- import EventKitUI
- class SettingsViewController: FormViewController {
- var tokenRow: TextRow?
- var appStateController: AppStateController?
- var statusLabelRow: LabelRow!
- func showHideNSDetails() {
- var isHidden = false
- var isEnabled = true
- var isLoopHidden = false;
- if UserDefaultsRepository.url.value == "" || !UserDefaultsRepository.loopUser.value {
- isHidden = true
- isEnabled = false
- }
-
- if let row1 = form.rowBy(tag: "informationDisplaySettings") as? ButtonRow {
- row1.hidden = .function(["hide"], {form in
- return isHidden
- })
- row1.evaluateHidden()
- }
-
- if UserDefaultsRepository.url.value != "" {
- isEnabled = true
- }
-
- guard let nightscoutTab = self.tabBarController?.tabBar.items![3] else { return }
- nightscoutTab.isEnabled = isEnabled
- }
-
- override func viewDidLoad() {
- super.viewDidLoad()
- if UserDefaultsRepository.forceDarkMode.value {
- overrideUserInterfaceStyle = .dark
- }
- UserDefaultsRepository.showNS.value = false
- UserDefaultsRepository.showDex.value = false
-
- let buildDetails = BuildDetails.default
- let formattedBuildDate = dateTimeUtils.formattedDate(from: buildDetails.buildDate())
- let branchAndSha = buildDetails.branchAndSha
- let expiration = dateTimeUtils.formattedDate(from: buildDetails.calculateExpirationDate())
- let expirationHeaderString = buildDetails.expirationHeaderString
- let versionManager = AppVersionManager()
- let version = versionManager.version()
- form
- +++ Section(header: "Data Settings", footer: "")
- <<< SegmentedRow<String>("units") { row in
- row.title = "Units"
- row.options = ["mg/dL", "mmol/L"]
- row.value = UserDefaultsRepository.units.value
- }.onChange { row in
- guard let value = row.value else { return }
- UserDefaultsRepository.units.value = value
- }
- <<< SwitchRow("showNS"){ row in
- row.title = "Show Nightscout Settings"
- row.value = UserDefaultsRepository.showNS.value
- }.onChange { [weak self] row in
- guard let value = row.value else { return }
- UserDefaultsRepository.showNS.value = value
- }
- <<< TextRow() { row in
- row.title = "URL"
- row.placeholder = "https://mycgm.herokuapp.com"
- row.value = UserDefaultsRepository.url.value
- row.hidden = "$showNS == false"
- }.cellSetup { (cell, row) in
- cell.textField.autocorrectionType = .no
- cell.textField.autocapitalizationType = .none
- }.onChange { row in
- guard let value = row.value else {
- UserDefaultsRepository.url.value = ""
- self.showHideNSDetails()
- return
- }
-
- var useTokenUrl = false
-
- // Attempt to handle special case: pasted URL including token
- if let urlComponents = URLComponents(string: value), let queryItems = urlComponents.queryItems {
- if let tokenItem = queryItems.first(where: { $0.name.lowercased() == "token" }) {
- let tokenPattern = "^[^-\\s]+-[0-9a-fA-F]{16}$"
- if let token = tokenItem.value, let _ = token.range(of: tokenPattern, options: .regularExpression) {
- var baseComponents = urlComponents
- baseComponents.queryItems = nil
- if let baseURL = baseComponents.string {
- UserDefaultsRepository.token.value = token
- self.tokenRow?.value = token
- self.tokenRow?.updateCell()
- UserDefaultsRepository.url.value = baseURL
- row.value = baseURL
- row.updateCell()
- useTokenUrl = true
- }
- }
- }
- }
-
- if !useTokenUrl {
- // Normalize input: remove unwanted characters and lowercase
- let filtered = value.replacingOccurrences(of: "[^A-Za-z0-9:/._-]", with: "", options: .regularExpression).lowercased()
-
- // Further clean-up: Remove trailing slashes
- var cleanURL = filtered
- while cleanURL.last == "/" {
- cleanURL = String(cleanURL.dropLast())
- }
-
- UserDefaultsRepository.url.value = cleanURL
- row.value = cleanURL
- row.updateCell()
- }
-
- self.showHideNSDetails()
- globalVariables.nsVerifiedAlert = 0
-
- // Verify Nightscout URL and token
- self.checkNightscoutStatus()
- }
- <<< TextRow() { row in
- row.title = "NS Token"
- row.placeholder = "Leave blank if not using tokens"
- row.value = UserDefaultsRepository.token.value
- row.hidden = "$showNS == false"
- self.tokenRow = row
- }.cellSetup { (cell, row) in
- cell.textField.autocorrectionType = .no
- cell.textField.autocapitalizationType = .none
- cell.textField.textContentType = .password
- }.onChange { row in
- if row.value == nil {
- UserDefaultsRepository.token.value = ""
- }
- guard let value = row.value else { return }
- UserDefaultsRepository.token.value = value
- globalVariables.nsVerifiedAlert = 0
-
- // Verify Nightscout URL and token
- self.checkNightscoutStatus()
- }
- <<< LabelRow() { row in
- row.title = "NS Status"
- row.value = "Checking..."
- statusLabelRow = row
- row.hidden = "$showNS == false"
- }
- <<< SwitchRow("loopUser"){ row in
- row.title = "Download Loop/iAPS Data"
- row.tag = "loopUser"
- row.value = UserDefaultsRepository.loopUser.value
- row.hidden = "$showNS == false"
- }.onChange { row in
- guard let value = row.value else { return }
- UserDefaultsRepository.loopUser.value = value
- }
-
- <<< SwitchRow("showDex"){ row in
- row.title = "Show Dexcom Settings"
- row.value = UserDefaultsRepository.showDex.value
- }.onChange { row in
- guard let value = row.value else { return }
- UserDefaultsRepository.showDex.value = value
- }
- <<< TextRow(){ row in
- row.title = "User Name"
- row.value = UserDefaultsRepository.shareUserName.value
- row.hidden = "$showDex == false"
- }.cellSetup { (cell, row) in
- cell.textField.autocorrectionType = .no
- cell.textField.autocapitalizationType = .none
- }.onChange { row in
- if row.value == nil {
- UserDefaultsRepository.shareUserName.value = ""
- }
- guard let value = row.value else { return }
- UserDefaultsRepository.shareUserName.value = value
- globalVariables.dexVerifiedAlert = 0
- }
- <<< TextRow(){ row in
- row.title = "Password"
- row.value = UserDefaultsRepository.sharePassword.value
- row.hidden = "$showDex == false"
- }.cellSetup { (cell, row) in
- cell.textField.autocorrectionType = .no
- cell.textField.isSecureTextEntry = true
- cell.textField.autocapitalizationType = .none
- }.onChange { row in
- if row.value == nil {
- UserDefaultsRepository.sharePassword.value = ""
- }
- guard let value = row.value else { return }
- UserDefaultsRepository.sharePassword.value = value
- globalVariables.dexVerifiedAlert = 0
- }
- <<< SegmentedRow<String>("shareServer") { row in
- row.title = "Server"
- row.options = ["US", "NON-US"]
- row.value = UserDefaultsRepository.shareServer.value
- row.hidden = "$showDex == false"
- }.onChange { row in
- guard let value = row.value else { return }
- UserDefaultsRepository.shareServer.value = value
- }
-
- +++ Section("App Settings")
-
- <<< ButtonRow() {
- $0.title = "General Settings"
- $0.presentationMode = .show(
- controllerProvider: .callback(builder: {
- let controller = GeneralSettingsViewController()
- controller.appStateController = self.appStateController
- return controller
- }
- ), onDismiss: nil)
- }
- <<< ButtonRow("graphSettings") {
- $0.title = "Graph Settings"
- $0.presentationMode = .show(
- controllerProvider: .callback(builder: {
- let controller = GraphSettingsViewController()
- controller.appStateController = self.appStateController
- return controller
- }
- ), onDismiss: nil)
- }
- <<< ButtonRow("informationDisplaySettings") {
- $0.title = "Information Display Settings"
- $0.presentationMode = .show(
- controllerProvider: .callback(builder: {
- let controller = InfoDisplaySettingsViewController()
- controller.appStateController = self.appStateController
- return controller
- }
- ), onDismiss: nil)
-
- }
-
- +++ Section("Integrations")
- <<< ButtonRow() {
- $0.title = "Apple Watch and Carplay"
- $0.presentationMode = .show(
- controllerProvider: .callback(builder: {
- let controller = WatchSettingsViewController()
- controller.appStateController = self.appStateController
- return controller
- }
- ), onDismiss: nil)
-
- }
-
- +++ Section("Advanced Settings")
- <<< ButtonRow() {
- $0.title = "Advanced Settings"
- $0.presentationMode = .show(
- controllerProvider: .callback(builder: {
- let controller = AdvancedSettingsViewController()
- controller.appStateController = self.appStateController
- return controller
- }
- ), onDismiss: nil)
-
- }
- +++ Section("Build Information")
- <<< LabelRow() {
- $0.title = "Version"
- $0.value = version
- $0.tag = "currentVersionRow"
- }
- <<< LabelRow() {
- $0.title = "Latest version"
- $0.value = "Fetching..."
- $0.tag = "latestVersionRow"
- }
- <<< LabelRow() {
- $0.title = expirationHeaderString
- $0.value = expiration
- $0.hidden = Condition(booleanLiteral: isMacApp())
- }
- <<< LabelRow() {
- $0.title = "Built"
- $0.value = formattedBuildDate
- }
- <<< LabelRow() {
- $0.title = "Branch"
- $0.value = branchAndSha
- }
-
- showHideNSDetails()
- checkNightscoutStatus()
- }
-
- override func viewWillAppear(_ animated: Bool) {
- super.viewWillAppear(animated)
-
- refreshVersionInfo()
- checkNightscoutStatus()
- }
-
- func refreshVersionInfo() {
- let versionManager = AppVersionManager()
- versionManager.checkForNewVersion { latestVersion, isNewer, isBlacklisted in
- DispatchQueue.main.async {
- if let currentVersionRow = self.form.rowBy(tag: "currentVersionRow") as? LabelRow {
- currentVersionRow.cell.detailTextLabel?.textColor = self.getColor(isBlacklisted: isBlacklisted, isNewer: isNewer, isCurrent: latestVersion == versionManager.version())
- currentVersionRow.updateCell()
- }
-
- if let latestVersionRow = self.form.rowBy(tag: "latestVersionRow") as? LabelRow {
- latestVersionRow.value = latestVersion ?? "Unknown"
- latestVersionRow.updateCell()
- }
- }
- }
- }
-
- private func getColor(isBlacklisted: Bool, isNewer: Bool, isCurrent: Bool) -> UIColor {
- if isBlacklisted {
- return .red
- } else if isNewer {
- return .orange
- } else if isCurrent {
- return .green
- } else {
- return .secondaryLabel
- }
- }
-
- func isMacApp() -> Bool {
- #if targetEnvironment(macCatalyst)
- return true
- #else
- return false
- #endif
- }
-
- func updateStatusLabel(error: NightscoutUtils.NightscoutError?) {
- if let error = error {
- switch error {
- case .invalidURL:
- statusLabelRow.value = "Invalid URL"
- case .networkError:
- statusLabelRow.value = "Network Error"
- case .invalidToken:
- statusLabelRow.value = "Invalid Token"
- case .tokenRequired:
- statusLabelRow.value = "Token Required"
- case .siteNotFound:
- statusLabelRow.value = "Site Not Found"
- case .unknown:
- statusLabelRow.value = "Unknown Error"
- case .emptyAddress:
- statusLabelRow.value = "Address Empty"
- }
- } else {
- statusLabelRow.value = "OK"
- NotificationCenter.default.post(name: NSNotification.Name("refresh"), object: nil)
- }
- statusLabelRow.updateCell()
- }
-
- func checkNightscoutStatus() {
- NightscoutUtils.verifyURLAndToken(urlUser: UserDefaultsRepository.url.value, token: UserDefaultsRepository.token.value) { error in
- DispatchQueue.main.async {
- self.updateStatusLabel(error: error)
- }
- }
- }
- }
|