fix #80 - improvements nightscout settings
- a new row in the NightScout settings which allow to test the url and api key - by default set url https://yoursitename.herokuapp.com
This commit is contained in:
parent
820166dcca
commit
710932352b
|
@ -124,5 +124,7 @@ enum ConstantsLog {
|
|||
/// bluetoothPeripheralViewController
|
||||
static let bluetoothPeripheralViewController = "blePeripheralViewController "
|
||||
|
||||
static let nightScoutSettingsViewModel = "nightScoutSettingsViewModel "
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -2,4 +2,9 @@ enum ConstantsNightScout {
|
|||
|
||||
/// maximum number of days to upload
|
||||
static let maxDaysToUpload = 7
|
||||
|
||||
/// - default nightscout url
|
||||
/// - used in settings, when setting first time nightscout url
|
||||
static let defaultNightScoutUrl = "https://yoursitename.herokuapp.com"
|
||||
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
"nightscouttestresult_verificationsuccessfulalerttitle" = "Verification Successful";
|
||||
"nightscouttestresult_verificationsuccessfulalertbody" = "Your Nightscout account was verified successfully";
|
||||
"nightscouttestresult_verificationerroralerttitle" = "Verification Error";
|
||||
"warningAPIKeyOrURLIsnil" = "Url and API Key must be set";
|
||||
"nightScoutAPIKeyAndURLStarted" = "Test started";
|
||||
|
|
|
@ -66,3 +66,4 @@
|
|||
"displayDeltaInCalendarEvent" = "Display Delta";
|
||||
"infoCalendarAccessDeniedByUser" = "You previously denied access to your Calendar.\n\nTo enable it go to your device settings, privacy, calendars and turn on";
|
||||
"infoCalendarAccessRestricted" = "You can not authorization the app to access your Calendar, possibly due to active restrictions such as parental controls being in place.";
|
||||
"testUrlAndAPIKey" = "Test Url and API Key";
|
||||
|
|
|
@ -16,4 +16,12 @@ class Texts_NightScoutTestResult {
|
|||
return NSLocalizedString("nightscouttestresult_verificationerroralerttitle", tableName: filename, bundle: Bundle.main, value: "Verification Error", comment: "POP up after verifying nightscout credentials, to say that verification of url and api key was not successful - this is the title")
|
||||
}()
|
||||
|
||||
static let warningAPIKeyOrURLIsnil: String = {
|
||||
|
||||
return NSLocalizedString("warningAPIKeyOrURLIsnil", tableName: filename, bundle: Bundle.main, value: "Url and API Key must be set", comment: "in settings screen, user tries to test url and API Key but one of them is not set")
|
||||
}()
|
||||
|
||||
static let nightScoutAPIKeyAndURLStarted : String = {
|
||||
return NSLocalizedString("nightScoutAPIKeyAndURLStarted", tableName: filename, bundle: Bundle.main, value: "Test started", comment: "in settings screen, user clicked test button for nightscout url and apikey")
|
||||
}()
|
||||
}
|
||||
|
|
|
@ -205,6 +205,10 @@ class Texts_SettingsView {
|
|||
static let uploadSensorStartTime: String = {
|
||||
return NSLocalizedString("uploadSensorStartTime", tableName: filename, bundle: Bundle.main, value: "Upload Sensor start time", comment: "nightscout settings, title of row")
|
||||
}()
|
||||
|
||||
static let testUrlAndAPIKey: String = {
|
||||
return NSLocalizedString("testUrlAndAPIKey", tableName: filename, bundle: Bundle.main, value: "Test Url and API Key", comment: "nightscout settings, when clicking the cell, test the url and api key")
|
||||
}()
|
||||
|
||||
// MARK: - Section Speak
|
||||
|
||||
|
|
|
@ -57,6 +57,13 @@ protocol SettingsViewModelProtocol {
|
|||
/// This function will verify if complete reload is needed or not
|
||||
func completeSettingsViewRefreshNeeded(index:Int) -> Bool
|
||||
|
||||
/// a view model may want to pass information back to the viewcontroller asynchronously. Example SettingsViewNightScoutSettingsViewModel will initiate a credential test. The response will come asynchronously and a text needs to return to the viewcontroller, to be shown to the user.
|
||||
///
|
||||
/// The viewmodel must call the messageHandler on the main thread.
|
||||
/// - parameters:
|
||||
/// - two strings, a title and a message.
|
||||
func storeMessageHandler(messageHandler : @escaping ((String, String) -> Void))
|
||||
|
||||
}
|
||||
|
||||
/// to make the coding a bit easier, just one function defined for now, which is to get the viewModel for a specific setting
|
||||
|
|
|
@ -9,6 +9,10 @@ fileprivate enum Setting:Int, CaseIterable {
|
|||
|
||||
struct SettingsViewM5StackBluetoothSettingsViewModel: SettingsViewModelProtocol {
|
||||
|
||||
func storeMessageHandler(messageHandler: ((String, String) -> Void)) {
|
||||
// this ViewModel does need to send back messages to the viewcontroller asynchronously
|
||||
}
|
||||
|
||||
func sectionTitle() -> String? {
|
||||
return Texts_SettingsView.m5StackSectionTitleBluetooth
|
||||
}
|
||||
|
|
|
@ -9,6 +9,10 @@ fileprivate enum Setting:Int, CaseIterable {
|
|||
|
||||
struct SettingsViewM5StackGeneralSettingsViewModel: SettingsViewModelProtocol {
|
||||
|
||||
func storeMessageHandler(messageHandler: ((String, String) -> Void)) {
|
||||
// this ViewModel does need to send back messages to the viewcontroller asynchronously
|
||||
}
|
||||
|
||||
func sectionTitle() -> String? {
|
||||
return Texts_SettingsView.sectionTitleGeneral
|
||||
}
|
||||
|
|
|
@ -24,6 +24,10 @@ fileprivate enum Setting:Int, CaseIterable {
|
|||
|
||||
struct SettingsViewM5StackWiFiSettingsViewModel: SettingsViewModelProtocol {
|
||||
|
||||
func storeMessageHandler(messageHandler: ((String, String) -> Void)) {
|
||||
// this ViewModel does need to send back messages to the viewcontroller asynchronously
|
||||
}
|
||||
|
||||
func sectionTitle() -> String? {
|
||||
return Texts_Common.WiFi
|
||||
}
|
||||
|
|
|
@ -15,6 +15,12 @@ final class SettingsViewController: UIViewController {
|
|||
/// reference to soundPlayer
|
||||
private var soundPlayer:SoundPlayer?
|
||||
|
||||
/// will show pop up with title and message
|
||||
private var messageHandler: ((String, String) -> Void)?
|
||||
|
||||
/// UIAlertController used by messageHandler
|
||||
private var messageHandlerUiAlertController: UIAlertController?
|
||||
|
||||
// MARK:- public functions
|
||||
|
||||
/// configure
|
||||
|
@ -23,16 +29,49 @@ final class SettingsViewController: UIViewController {
|
|||
self.coreDataManager = coreDataManager
|
||||
self.soundPlayer = soundPlayer
|
||||
|
||||
// create messageHandler
|
||||
messageHandler = {
|
||||
(title, message) in
|
||||
|
||||
// piece of code that we need two times
|
||||
let createAndPresentMessageHandlerUIAlertController = {
|
||||
|
||||
self.messageHandlerUiAlertController = UIAlertController(title: title, message: message, actionHandler: nil)
|
||||
|
||||
if let messageHandlerUiAlertController = self.messageHandlerUiAlertController {
|
||||
self.present(messageHandlerUiAlertController, animated: true, completion: nil)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// first check if messageHandlerUiAlertController is not nil and is presenting. If it is, dismiss it and when completed call createAndPresentMessageHandlerUIAlertController
|
||||
if let messageHandlerUiAlertController = self.messageHandlerUiAlertController {
|
||||
if messageHandlerUiAlertController.isBeingPresented {
|
||||
|
||||
messageHandlerUiAlertController.dismiss(animated: true, completion: createAndPresentMessageHandlerUIAlertController)
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// we're here which means there wasn't a messageHandlerUiAlertController being presented, so present it now
|
||||
createAndPresentMessageHandlerUIAlertController()
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - View Life Cycle
|
||||
|
||||
override func viewDidLoad() {
|
||||
|
||||
super.viewDidLoad()
|
||||
|
||||
title = Texts_SettingsView.screenTitle
|
||||
|
||||
setupView()
|
||||
|
||||
}
|
||||
|
||||
// MARK: - other overriden functions
|
||||
|
@ -84,7 +123,7 @@ final class SettingsViewController: UIViewController {
|
|||
tableView.delegate = self
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension SettingsViewController:UITableViewDataSource, UITableViewDelegate {
|
||||
|
@ -195,6 +234,10 @@ extension SettingsViewController:UITableViewDataSource, UITableViewDelegate {
|
|||
|
||||
let viewModel = section.viewModel()
|
||||
|
||||
if let messageHandler = messageHandler {
|
||||
viewModel.storeMessageHandler(messageHandler: messageHandler)
|
||||
}
|
||||
|
||||
if viewModel.isEnabled(index: indexPath.row) {
|
||||
|
||||
let selectedRowAction = viewModel.onRowSelect(index: indexPath.row)
|
||||
|
|
|
@ -10,6 +10,10 @@ fileprivate enum Setting:Int, CaseIterable {
|
|||
/// conforms to SettingsViewModelProtocol for all alert settings in the first sections screen
|
||||
struct SettingsViewAlertSettingsViewModel:SettingsViewModelProtocol {
|
||||
|
||||
func storeMessageHandler(messageHandler: ((String, String) -> Void)) {
|
||||
// this ViewModel does need to send back messages to the viewcontroller asynchronously
|
||||
}
|
||||
|
||||
func completeSettingsViewRefreshNeeded(index: Int) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -30,6 +30,10 @@ class SettingsViewAppleWatchSettingsViewModel: SettingsViewModelProtocol {
|
|||
/// used for requesting authorization to access calendar
|
||||
let eventStore = EKEventStore()
|
||||
|
||||
func storeMessageHandler(messageHandler: ((String, String) -> Void)) {
|
||||
// this ViewModel does need to send back messages to the viewcontroller asynchronously
|
||||
}
|
||||
|
||||
func sectionTitle() -> String? {
|
||||
return Texts_SettingsView.appleWatchSectionTitle
|
||||
}
|
||||
|
|
|
@ -12,6 +12,10 @@ fileprivate enum Setting:Int, CaseIterable {
|
|||
|
||||
struct SettingsViewDevelopmentSettingsViewModel:SettingsViewModelProtocol {
|
||||
|
||||
func storeMessageHandler(messageHandler: ((String, String) -> Void)) {
|
||||
// this ViewModel does need to send back messages to the viewcontroller asynchronously
|
||||
}
|
||||
|
||||
func sectionTitle() -> String? {
|
||||
return "Developer Settings"
|
||||
}
|
||||
|
|
|
@ -21,6 +21,10 @@ fileprivate enum Setting:Int, CaseIterable {
|
|||
/// conforms to SettingsViewModelProtocol for all Dexcom settings in the first sections screen
|
||||
class SettingsViewDexcomSettingsViewModel:SettingsViewModelProtocol {
|
||||
|
||||
func storeMessageHandler(messageHandler: ((String, String) -> Void)) {
|
||||
// this ViewModel does need to send back messages to the viewcontroller asynchronously
|
||||
}
|
||||
|
||||
func completeSettingsViewRefreshNeeded(index: Int) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -28,6 +28,10 @@ fileprivate enum Setting:Int, CaseIterable {
|
|||
/// conforms to SettingsViewModelProtocol for all general settings in the first sections screen
|
||||
struct SettingsViewGeneralSettingsViewModel:SettingsViewModelProtocol {
|
||||
|
||||
func storeMessageHandler(messageHandler: ((String, String) -> Void)) {
|
||||
// this ViewModel does need to send back messages to the viewcontroller asynchronously
|
||||
}
|
||||
|
||||
func completeSettingsViewRefreshNeeded(index: Int) -> Bool {
|
||||
|
||||
// changing follower to master or master to follower requires changing ui for nightscout settings and transmitter type settings
|
||||
|
|
|
@ -12,6 +12,10 @@ class SettingsViewHealthKitSettingsViewModel:SettingsViewModelProtocol {
|
|||
|
||||
// MARK: - functions in protocol SettingsViewModelProtocol
|
||||
|
||||
func storeMessageHandler(messageHandler: ((String, String) -> Void)) {
|
||||
// this ViewModel does need to send back messages to the viewcontroller asynchronously
|
||||
}
|
||||
|
||||
func completeSettingsViewRefreshNeeded(index: Int) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -15,6 +15,10 @@ fileprivate enum Setting:Int, CaseIterable {
|
|||
|
||||
struct SettingsViewInfoViewModel:SettingsViewModelProtocol {
|
||||
|
||||
func storeMessageHandler(messageHandler: ((String, String) -> Void)) {
|
||||
// this ViewModel does need to send back messages to the viewcontroller asynchronously
|
||||
}
|
||||
|
||||
func sectionTitle() -> String? {
|
||||
return Texts_HomeView.info
|
||||
}
|
||||
|
|
|
@ -9,7 +9,11 @@ fileprivate enum Setting:Int, CaseIterable {
|
|||
|
||||
struct SettingsViewM5StackSettingsViewModel: SettingsViewModelProtocol {
|
||||
|
||||
func sectionTitle() -> String? {
|
||||
func storeMessageHandler(messageHandler: ((String, String) -> Void)) {
|
||||
// this ViewModel does need to send back messages to the viewcontroller asynchronously
|
||||
}
|
||||
|
||||
func sectionTitle() -> String? {
|
||||
return "M5Stack"
|
||||
}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import UIKit
|
||||
import os
|
||||
|
||||
fileprivate enum Setting:Int, CaseIterable {
|
||||
|
||||
|
@ -11,21 +12,111 @@ fileprivate enum Setting:Int, CaseIterable {
|
|||
/// nightscout api key
|
||||
case nightScoutAPIKey = 2
|
||||
|
||||
/// to allow testing explicitly
|
||||
case testUrlAndAPIKey = 3
|
||||
|
||||
/// should sensor start time be uploaded to NS yes or no
|
||||
case uploadSensorStartTime = 3
|
||||
case uploadSensorStartTime = 4
|
||||
|
||||
/// use nightscout schedule or not
|
||||
case useSchedule = 4
|
||||
case useSchedule = 5
|
||||
|
||||
/// open uiviewcontroller to edit schedule
|
||||
case schedule = 5
|
||||
case schedule = 6
|
||||
|
||||
}
|
||||
|
||||
/// conforms to SettingsViewModelProtocol for all nightscout settings in the first sections screen
|
||||
class SettingsViewNightScoutSettingsViewModel:SettingsViewModelProtocol {
|
||||
class SettingsViewNightScoutSettingsViewModel {
|
||||
|
||||
func completeSettingsViewRefreshNeeded(index: Int) -> Bool {
|
||||
// MARK: - properties
|
||||
|
||||
/// in case info message or errors occur like credential check error, then this closure will be called with title and message
|
||||
/// - parameters:
|
||||
/// - first parameter is title
|
||||
/// - second parameter is the message
|
||||
///
|
||||
/// the viewcontroller sets it by calling storeMessageHandler
|
||||
private var messageHandler: ((String, String) -> Void)?
|
||||
|
||||
/// path to test API Secret
|
||||
private let nightScoutAuthTestPath = "/api/v1/experiments/test"
|
||||
|
||||
/// for trace
|
||||
private let log = OSLog(subsystem: ConstantsLog.subSystem, category: ConstantsLog.categoryCGMG5)
|
||||
|
||||
// MARK: - private functions
|
||||
|
||||
/// test the nightscout url and api key and send result to messageHandler
|
||||
private func testNightScoutCredentials() {
|
||||
|
||||
// unwrap siteUrl and apiKey
|
||||
guard let siteUrl = UserDefaults.standard.nightScoutUrl, let apiKey = UserDefaults.standard.nightScoutAPIKey else {return}
|
||||
|
||||
if let url = URL(string: siteUrl) {
|
||||
let testURL = url.appendingPathComponent(nightScoutAuthTestPath)
|
||||
|
||||
var request = URLRequest(url: testURL)
|
||||
request.setValue("application/json", forHTTPHeaderField:"Content-Type")
|
||||
request.setValue("application/json", forHTTPHeaderField:"Accept")
|
||||
request.setValue(apiKey.sha1(), forHTTPHeaderField:"api-secret")
|
||||
|
||||
let task = URLSession.shared.dataTask(with: request, completionHandler: { (data, response, error) in
|
||||
|
||||
if let error = error {
|
||||
|
||||
trace("in testNightScoutCredentials, error = %{public}@", log: self.log, category: ConstantsLog.nightScoutSettingsViewModel, type: .info, error.localizedDescription)
|
||||
|
||||
self.callMessageHandlerInMainThread(title: Texts_NightScoutTestResult.verificationErrorAlertTitle, message: error.localizedDescription)
|
||||
|
||||
return
|
||||
|
||||
}
|
||||
|
||||
if let httpResponse = response as? HTTPURLResponse ,
|
||||
httpResponse.statusCode != 200, let data = data {
|
||||
|
||||
let errorMessage = String(data: data, encoding: String.Encoding.utf8)!
|
||||
|
||||
trace("in testNightScoutCredentials, error = %{public}@", log: self.log, category: ConstantsLog.nightScoutSettingsViewModel, type: .info, errorMessage)
|
||||
|
||||
self.callMessageHandlerInMainThread(title: Texts_NightScoutTestResult.verificationErrorAlertTitle, message: errorMessage)
|
||||
|
||||
} else {
|
||||
|
||||
trace("in testNightScoutCredentials, successful", log: self.log, category: ConstantsLog.nightScoutSettingsViewModel, type: .info)
|
||||
|
||||
self.callMessageHandlerInMainThread(title: Texts_NightScoutTestResult.verificationSuccessFulAlertTitle, message: Texts_NightScoutTestResult.verificationSuccessFulAlertBody)
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
trace("in testNightScoutCredentials, url and apikey test started", log: log, category: ConstantsLog.nightScoutSettingsViewModel, type: .info)
|
||||
task.resume()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private func callMessageHandlerInMainThread(title: String, message: String) {
|
||||
|
||||
// unwrap messageHandler
|
||||
guard let messageHandler = messageHandler else {return}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
messageHandler(title, message)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// conforms to SettingsViewModelProtocol for all nightscout settings in the first sections screen
|
||||
extension SettingsViewNightScoutSettingsViewModel: SettingsViewModelProtocol {
|
||||
|
||||
func storeMessageHandler(messageHandler: @escaping ((String, String) -> Void)) {
|
||||
self.messageHandler = messageHandler
|
||||
}
|
||||
|
||||
func completeSettingsViewRefreshNeeded(index: Int) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -43,24 +134,37 @@ class SettingsViewNightScoutSettingsViewModel:SettingsViewModelProtocol {
|
|||
return SettingsSelectedRowAction.nothing
|
||||
|
||||
case .nightScoutUrl:
|
||||
return SettingsSelectedRowAction.askText(title: Texts_SettingsView.labelNightScoutUrl, message: Texts_SettingsView.giveNightScoutUrl, keyboardType: .URL, text: UserDefaults.standard.nightScoutUrl, placeHolder: "yoursitename", actionTitle: nil, cancelTitle: nil, actionHandler: {(nightscouturl:String) in
|
||||
return SettingsSelectedRowAction.askText(title: Texts_SettingsView.labelNightScoutUrl, message: Texts_SettingsView.giveNightScoutUrl, keyboardType: .URL, text: UserDefaults.standard.nightScoutUrl != nil ? UserDefaults.standard.nightScoutUrl : ConstantsNightScout.defaultNightScoutUrl, placeHolder: nil, actionTitle: nil, cancelTitle: nil, actionHandler: {(nightscouturl:String) in
|
||||
|
||||
// if user gave empty string then set to nil
|
||||
// if not nil, and if not starting with http, add https, and remove ending /
|
||||
UserDefaults.standard.nightScoutUrl = nightscouturl.toNilIfLength0().addHttpsIfNeeded()
|
||||
|
||||
if let url = UserDefaults.standard.nightScoutUrl {
|
||||
debuglogging("url = " + url)
|
||||
} else {
|
||||
debuglogging("url is nil")
|
||||
}
|
||||
|
||||
}, cancelHandler: nil, inputValidator: nil)
|
||||
|
||||
case .nightScoutAPIKey:
|
||||
return SettingsSelectedRowAction.askText(title: Texts_SettingsView.labelNightScoutAPIKey, message: Texts_SettingsView.giveNightScoutAPIKey, keyboardType: .default, text: UserDefaults.standard.nightScoutAPIKey, placeHolder: nil, actionTitle: nil, cancelTitle: nil, actionHandler: {(apiKey:String) in
|
||||
UserDefaults.standard.nightScoutAPIKey = apiKey.toNilIfLength0()}, cancelHandler: nil, inputValidator: nil)
|
||||
|
||||
|
||||
case .testUrlAndAPIKey:
|
||||
|
||||
if UserDefaults.standard.nightScoutAPIKey != nil && UserDefaults.standard.nightScoutUrl != nil {
|
||||
|
||||
// show info that test is started, through the messageHandler
|
||||
if let messageHandler = messageHandler {
|
||||
messageHandler(Texts_HomeView.info, Texts_NightScoutTestResult.nightScoutAPIKeyAndURLStarted)
|
||||
}
|
||||
|
||||
self.testNightScoutCredentials()
|
||||
|
||||
return .nothing
|
||||
|
||||
} else {
|
||||
|
||||
return .showInfoText(title: Texts_Common.warning, message: Texts_NightScoutTestResult.warningAPIKeyOrURLIsnil)
|
||||
|
||||
}
|
||||
|
||||
case .useSchedule:
|
||||
return .nothing
|
||||
|
||||
|
@ -116,7 +220,8 @@ class SettingsViewNightScoutSettingsViewModel:SettingsViewModelProtocol {
|
|||
return Texts_SettingsView.schedule
|
||||
case .uploadSensorStartTime:
|
||||
return Texts_SettingsView.uploadSensorStartTime
|
||||
|
||||
case .testUrlAndAPIKey:
|
||||
return Texts_SettingsView.testUrlAndAPIKey
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -136,6 +241,8 @@ class SettingsViewNightScoutSettingsViewModel:SettingsViewModelProtocol {
|
|||
return UITableViewCell.AccessoryType.disclosureIndicator
|
||||
case .uploadSensorStartTime:
|
||||
return UITableViewCell.AccessoryType.none
|
||||
case .testUrlAndAPIKey:
|
||||
return .none
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -155,7 +262,8 @@ class SettingsViewNightScoutSettingsViewModel:SettingsViewModelProtocol {
|
|||
return nil
|
||||
case .uploadSensorStartTime:
|
||||
return nil
|
||||
|
||||
case .testUrlAndAPIKey:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -182,6 +290,9 @@ class SettingsViewNightScoutSettingsViewModel:SettingsViewModelProtocol {
|
|||
case .uploadSensorStartTime:
|
||||
return UISwitch(isOn: UserDefaults.standard.uploadSensorStartTimeToNS, action: {(isOn:Bool) in UserDefaults.standard.uploadSensorStartTimeToNS = isOn})
|
||||
|
||||
case .testUrlAndAPIKey:
|
||||
return nil
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -22,7 +22,11 @@ fileprivate enum Setting:Int, CaseIterable {
|
|||
/// conforms to SettingsViewModelProtocol for all speak settings in the first sections screen
|
||||
class SettingsViewSpeakSettingsViewModel:SettingsViewModelProtocol {
|
||||
|
||||
func completeSettingsViewRefreshNeeded(index: Int) -> Bool {
|
||||
func storeMessageHandler(messageHandler: ((String, String) -> Void)) {
|
||||
// this ViewModel does need to send back messages to the viewcontroller asynchronously
|
||||
}
|
||||
|
||||
func completeSettingsViewRefreshNeeded(index: Int) -> Bool {
|
||||
return false
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue