adding option to send trace file
This commit is contained in:
parent
e367154b0a
commit
139cbefdcf
|
@ -161,6 +161,8 @@
|
|||
F8AC426721ADEBD70078C348 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = F8AC426621ADEBD70078C348 /* Assets.xcassets */; };
|
||||
F8AC426A21ADEBD70078C348 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F8AC426821ADEBD70078C348 /* LaunchScreen.storyboard */; };
|
||||
F8AC42A121B31F170078C348 /* xdrip.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = F8AC429F21B31F160078C348 /* xdrip.xcdatamodeld */; };
|
||||
F8AF36152455C6F700B5977B /* ConstantsTrace.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8AF36142455C6F700B5977B /* ConstantsTrace.swift */; };
|
||||
F8AF361B245D93EE00B5977B /* Int16.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8AF361A245D93ED00B5977B /* Int16.swift */; };
|
||||
F8B3A783225D37F2004BA588 /* TextsNightScoutTestResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8B3A782225D37F2004BA588 /* TextsNightScoutTestResult.swift */; };
|
||||
F8B3A786225D4473004BA588 /* NightScoutTestResult.strings in Resources */ = {isa = PBXBuildFile; fileRef = F8B3A788225D4473004BA588 /* NightScoutTestResult.strings */; };
|
||||
F8B3A78B225D473D004BA588 /* UIAlertController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8B3A78A225D473D004BA588 /* UIAlertController.swift */; };
|
||||
|
@ -254,6 +256,7 @@
|
|||
F8E51D63244B3386001C9E5A /* MiaoMiaoResponseType.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E51D62244B3386001C9E5A /* MiaoMiaoResponseType.swift */; };
|
||||
F8E51D65244BA790001C9E5A /* WatlaaBluetoothPeripheralViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E51D64244BA790001C9E5A /* WatlaaBluetoothPeripheralViewModel.swift */; };
|
||||
F8E51D67244BAE0E001C9E5A /* WatlaaBluetoothTransmitterMaster+CGMTransmitter.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E51D66244BAE0E001C9E5A /* WatlaaBluetoothTransmitterMaster+CGMTransmitter.swift */; };
|
||||
F8E51D6924549E2C001C9E5A /* SettingsViewTraceSettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8E51D6824549E2C001C9E5A /* SettingsViewTraceSettingsViewModel.swift */; };
|
||||
F8EA6C8221B723BC0082976B /* Date.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8EA6C8121B723BC0082976B /* Date.swift */; };
|
||||
F8EA6CA921BBE3010082976B /* UniqueId.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8EA6CA821BBE3010082976B /* UniqueId.swift */; };
|
||||
F8EEDD5422FF685400D2D610 /* NSMutableURLRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = F8EEDD5322FF685400D2D610 /* NSMutableURLRequest.swift */; };
|
||||
|
@ -494,6 +497,8 @@
|
|||
F8AC426921ADEBD70078C348 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||
F8AC426B21ADEBD70078C348 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
F8AC42A021B31F160078C348 /* xdrip.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = xdrip.xcdatamodel; sourceTree = "<group>"; };
|
||||
F8AF36142455C6F700B5977B /* ConstantsTrace.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConstantsTrace.swift; sourceTree = "<group>"; };
|
||||
F8AF361A245D93ED00B5977B /* Int16.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Int16.swift; sourceTree = "<group>"; };
|
||||
F8B3A782225D37F2004BA588 /* TextsNightScoutTestResult.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextsNightScoutTestResult.swift; sourceTree = "<group>"; };
|
||||
F8B3A787225D4473004BA588 /* en */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = en; path = en.lproj/NightScoutTestResult.strings; sourceTree = "<group>"; };
|
||||
F8B3A78A225D473D004BA588 /* UIAlertController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIAlertController.swift; sourceTree = "<group>"; };
|
||||
|
@ -656,6 +661,7 @@
|
|||
F8E51D62244B3386001C9E5A /* MiaoMiaoResponseType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MiaoMiaoResponseType.swift; sourceTree = "<group>"; };
|
||||
F8E51D64244BA790001C9E5A /* WatlaaBluetoothPeripheralViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WatlaaBluetoothPeripheralViewModel.swift; sourceTree = "<group>"; };
|
||||
F8E51D66244BAE0E001C9E5A /* WatlaaBluetoothTransmitterMaster+CGMTransmitter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "WatlaaBluetoothTransmitterMaster+CGMTransmitter.swift"; sourceTree = "<group>"; };
|
||||
F8E51D6824549E2C001C9E5A /* SettingsViewTraceSettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewTraceSettingsViewModel.swift; sourceTree = "<group>"; };
|
||||
F8EA6C8121B723BC0082976B /* Date.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Date.swift; sourceTree = "<group>"; };
|
||||
F8EA6CA821BBE3010082976B /* UniqueId.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UniqueId.swift; sourceTree = "<group>"; };
|
||||
F8EBB030239701DA0058B0D4 /* xdrip v6.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "xdrip v6.xcdatamodel"; sourceTree = "<group>"; };
|
||||
|
@ -788,6 +794,7 @@
|
|||
F808592C23677D6A00F3829D /* ChartPoint.swift */,
|
||||
F83098FD23AD3F84005741DF /* UITabBarController.swift */,
|
||||
F8E51D602448E695001C9E5A /* Bundle.swift */,
|
||||
F8AF361A245D93ED00B5977B /* Int16.swift */,
|
||||
);
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1131,17 +1138,17 @@
|
|||
F85DC2F621D25E3A00B9F74A /* Managers */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F8E51D5B2448D8A3001C9E5A /* Loop */,
|
||||
F8E3A2A723D906B600E5E98A /* Watch */,
|
||||
F8297F42238DC4AC00D74D66 /* BluetoothPeripheral */,
|
||||
F8BECB03235CE5970060DAE1 /* Charts */,
|
||||
F81D6D4622BD5F43005EFAE2 /* DexcomShare */,
|
||||
F821CF9922AEF2DF005C1E43 /* Speak */,
|
||||
F821CF9122ADB064005C1E43 /* HealthKit */,
|
||||
F821CF48229BF43A005C1E43 /* Alerts */,
|
||||
F821CF54229BF43A005C1E43 /* Application */,
|
||||
F8297F42238DC4AC00D74D66 /* BluetoothPeripheral */,
|
||||
F8BECB03235CE5970060DAE1 /* Charts */,
|
||||
F821CF4D229BF43A005C1E43 /* CoreData */,
|
||||
F81D6D4622BD5F43005EFAE2 /* DexcomShare */,
|
||||
F821CF9122ADB064005C1E43 /* HealthKit */,
|
||||
F8E51D5B2448D8A3001C9E5A /* Loop */,
|
||||
F821CF4F229BF43A005C1E43 /* NightScout */,
|
||||
F821CF9922AEF2DF005C1E43 /* Speak */,
|
||||
F8E3A2A723D906B600E5E98A /* Watch */,
|
||||
);
|
||||
path = Managers;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1363,6 +1370,7 @@
|
|||
F8A389EA233175A10010F405 /* SettingsViewM5StackSettingsViewModel.swift */,
|
||||
F8B3A83B227F090D004BA588 /* SettingsViewNightScoutSettingsViewModel.swift */,
|
||||
F8B3A83D227F090D004BA588 /* SettingsViewSpeakSettingsViewModel.swift */,
|
||||
F8E51D6824549E2C001C9E5A /* SettingsViewTraceSettingsViewModel.swift */,
|
||||
);
|
||||
path = SettingsViewModels;
|
||||
sourceTree = "<group>";
|
||||
|
@ -1542,6 +1550,7 @@
|
|||
F8A1586E22EDC7EE007F5B5D /* ConstantsSuspensionPrevention.swift */,
|
||||
F8E3A2AA23DA520B00E5E98A /* ConstantsWatch.swift */,
|
||||
F8E51D5E2448E2E8001C9E5A /* ConstantsShareWithLoop.swift */,
|
||||
F8AF36142455C6F700B5977B /* ConstantsTrace.swift */,
|
||||
);
|
||||
name = Constants;
|
||||
path = xdrip/Constants;
|
||||
|
@ -2196,6 +2205,7 @@
|
|||
F830992823C32A13005741DF /* TextsWatlaaView.swift in Sources */,
|
||||
F8B3A79722635A25004BA588 /* AlertEntry+CoreDataProperties.swift in Sources */,
|
||||
F830992023C291E2005741DF /* WatlaaBluetoothTransmitter.swift in Sources */,
|
||||
F8AF36152455C6F700B5977B /* ConstantsTrace.swift in Sources */,
|
||||
F8F971B723A5914D00C3F17D /* BluetoothPeripheralType.swift in Sources */,
|
||||
F80610C4222D4E4D00D8F236 /* ActionClosureable-extension.swift in Sources */,
|
||||
F8B3A835227F08AC004BA588 /* PickerViewController.swift in Sources */,
|
||||
|
@ -2281,7 +2291,9 @@
|
|||
F8B3A78E22622954004BA588 /* AlertType+CoreDataClass.swift in Sources */,
|
||||
F8F9721D23A5915900C3F17D /* LibreDerivedAlgorithmRunner.swift in Sources */,
|
||||
F821CF5A229BF43A005C1E43 /* CoreDataManager.swift in Sources */,
|
||||
F8E51D6924549E2C001C9E5A /* SettingsViewTraceSettingsViewModel.swift in Sources */,
|
||||
F85DC2F321CFE3D400B9F74A /* Calibration+CoreDataClass.swift in Sources */,
|
||||
F8AF361B245D93EE00B5977B /* Int16.swift in Sources */,
|
||||
F8E3A2AB23DA520B00E5E98A /* ConstantsWatch.swift in Sources */,
|
||||
F821CF7B22A1D359005C1E43 /* NightScoutFollowerDelegate.swift in Sources */,
|
||||
F8F9721523A5915900C3F17D /* PairRequestTxMessage.swift in Sources */,
|
||||
|
@ -2706,7 +2718,7 @@
|
|||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 3009;
|
||||
DEVELOPMENT_TEAM = 92C77A2942;
|
||||
DEVELOPMENT_TEAM = RNX44PP998;
|
||||
INFOPLIST_FILE = "$(SRCROOT)/xdrip/Supporting Files/Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
|
@ -2714,7 +2726,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.10.6;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.paulplant.xdriptest;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = net.johandegraeve.xdripswift;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "xdrip/xdrip-Bridging-Header.h";
|
||||
|
@ -2735,7 +2747,7 @@
|
|||
CODE_SIGN_IDENTITY = "Apple Development";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 3009;
|
||||
DEVELOPMENT_TEAM = 92C77A2942;
|
||||
DEVELOPMENT_TEAM = RNX44PP998;
|
||||
INFOPLIST_FILE = "$(SRCROOT)/xdrip/Supporting Files/Info.plist";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
|
@ -2743,7 +2755,7 @@
|
|||
"@executable_path/Frameworks",
|
||||
);
|
||||
MARKETING_VERSION = 3.10.6;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = com.paulplant.xdriptest;
|
||||
PRODUCT_BUNDLE_IDENTIFIER = net.johandegraeve.xdripswift;
|
||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||
PROVISIONING_PROFILE_SPECIFIER = "";
|
||||
SWIFT_OBJC_BRIDGING_HEADER = "xdrip/xdrip-Bridging-Header.h";
|
||||
|
|
|
@ -122,9 +122,10 @@ enum ConstantsLog {
|
|||
static let categoryWatchManager = "WatchManager "
|
||||
|
||||
/// bluetoothPeripheralViewController
|
||||
static let bluetoothPeripheralViewController = "blePeripheralViewController "
|
||||
static let categoryBluetoothPeripheralViewController = "blePeripheralViewController "
|
||||
|
||||
static let nightScoutSettingsViewModel = "nightScoutSettingsViewModel "
|
||||
static let categoryNightScoutSettingsViewModel = "nightScoutSettingsViewModel "
|
||||
|
||||
static let categoryTraceSettingsViewModel = "TraceSettingsViewModel"
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,14 @@
|
|||
import Foundation
|
||||
|
||||
enum ConstantsTrace {
|
||||
|
||||
/// email address to which to send trace file
|
||||
static let traceFileDestinationAddress = "xdrip@proximus.be"
|
||||
|
||||
/// will be used as filename to store traces on disk, and attachment file name when sending trace via e-mail
|
||||
static let traceFileName = "xdriptrace.txt"
|
||||
|
||||
/// will be used as filename to app info (configured transmitters etc ...)
|
||||
static let appInfoFileName = "appinfo.txt"
|
||||
|
||||
}
|
|
@ -45,4 +45,8 @@ public class AlertType: NSManagedObject {
|
|||
super.init(entity: entity, insertInto: context)
|
||||
}
|
||||
|
||||
public override var description: String {
|
||||
return name
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
import Foundation
|
||||
|
||||
extension Int16 {
|
||||
|
||||
/// example value 320 minutes is 5 hours and 20 minutes, would be converted to 05:20
|
||||
func convertMinutesToTimeAsString() -> String {
|
||||
return Int(self).convertMinutesToTimeAsString()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -224,3 +224,13 @@ extension Optional where Wrapped == String {
|
|||
|
||||
}
|
||||
}
|
||||
|
||||
extension String {
|
||||
|
||||
mutating func appendStringAndNewLine(_ stringToAdd: String) {
|
||||
|
||||
self = self + stringToAdd + "\n"
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -6,4 +6,10 @@ extension UInt16 {
|
|||
var int = self
|
||||
return Data(bytes: &int, count: MemoryLayout<UInt16>.size)
|
||||
}
|
||||
|
||||
/// example value 320 minutes is 5 hours and 20 minutes, would be converted to 05:20
|
||||
func convertMinutesToTimeAsString() -> String {
|
||||
return Int(self).convertMinutesToTimeAsString()
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -139,6 +139,9 @@ extension UserDefaults {
|
|||
/// - alertmanager will observe that value and when changed, verify if missed reading alert needs to be changed
|
||||
case missedReadingAlertChanged = "missedReadingAlertChanged"
|
||||
|
||||
/// when was the app launched, used in trace info that is sent via email. Just to be able to see afterwards if the app ever crashed. Because sometimes users say it crashed, but maybe it just stopped receiving readings and restarted by opening the app, but didn't really crash
|
||||
case timeStampAppLaunch = "timeStampAppLaunch"
|
||||
|
||||
// Nightscout
|
||||
/// timestamp lastest reading uploaded to NightScout
|
||||
case timeStampLatestNSUploadedBgReadingToNightScout = "timeStampLatestUploadedBgReading"
|
||||
|
@ -172,6 +175,9 @@ extension UserDefaults {
|
|||
/// OSLogEnabled enabled or not
|
||||
case OSLogEnabled = "OSLogEnabled"
|
||||
|
||||
/// write trace to file enabled or not
|
||||
case writeTraceToFile = "writeTraceToFile"
|
||||
|
||||
/// should libre oop value smoothing be done , default is on, means default libreValueSmoothingIsOff = false
|
||||
case libreValueSmoothingIsOff = "libreValueSmoothingIsOff"
|
||||
|
||||
|
@ -714,6 +720,16 @@ extension UserDefaults {
|
|||
}
|
||||
}
|
||||
|
||||
/// when was the app launched, used in trace info that is sent via email. Just to be able to see afterwards if the app ever crashed. Because sometimes users say it crashed, but maybe it just stopped receiving readings and restarted by opening the app, but didn't really crash
|
||||
var timeStampAppLaunch:Date? {
|
||||
get {
|
||||
return object(forKey: Key.timeStampAppLaunch.rawValue) as? Date
|
||||
}
|
||||
set {
|
||||
set(newValue, forKey: Key.timeStampAppLaunch.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
/// timestamp lastest reading uploaded to NightScout
|
||||
var timeStampLatestNightScoutUploadedBgReading:Date? {
|
||||
get {
|
||||
|
@ -814,6 +830,16 @@ extension UserDefaults {
|
|||
set(newValue, forKey: Key.OSLogEnabled.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
/// write trace to file enabled or not
|
||||
var writeTraceToFile: Bool {
|
||||
get {
|
||||
return bool(forKey: Key.writeTraceToFile.rawValue)
|
||||
}
|
||||
set {
|
||||
set(newValue, forKey: Key.writeTraceToFile.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
/// should libre oop value smoothing be done , default is on, means default libreValueSmoothingIsOff = false
|
||||
var libreValueSmoothingIsOff: Bool {
|
||||
|
|
|
@ -71,3 +71,9 @@
|
|||
"displayDeltaInCalendarEvent" = "Display Delta?";
|
||||
"infoCalendarAccessDeniedByUser" = "You previously denied access to your Calendar.\n\nTo enable it go to your device settings, privacy, calendars and enable it.";
|
||||
"infoCalendarAccessRestricted" = "You cannot give authorization to xDrip4iOS to access your calendar. This is possibly due to active restrictions such as parental controls being in place.";
|
||||
"writeTraceToFile" = "Write trace to file";
|
||||
"sectionTitleTrace" = "Trace";
|
||||
"sendTraceFile" = "Send trace file";
|
||||
"describeProblem" = "Explain why you send the trace file as detailed as possible. If you have reported your problem on facebook group 'Xdrip for IOS', then mention your facebook name in the e-mail";
|
||||
"emailbodyText" = "Problem description: ";
|
||||
"failedToSendEmail" = "In case user tries to send trace file via email but error occurs.";
|
||||
|
|
|
@ -21,7 +21,7 @@
|
|||
<key>CFBundleShortVersionString</key>
|
||||
<string>$(MARKETING_VERSION)</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>3806</string>
|
||||
<string>3861</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
|
|
|
@ -322,5 +322,37 @@ class Texts_SettingsView {
|
|||
return NSLocalizedString("infoCalendarAccessRestricted", tableName: filename, bundle: Bundle.main, value: "You cannot give authorization to xDrip4iOS to access your calendar. This is possibly due to active restrictions such as parental controls being in place.", comment: "If user is not allowed to give any app access to the Calendar, due to restrictions. And then tries to activate creation of events in calendar, this message will be shown")
|
||||
}()
|
||||
|
||||
static let writeTraceToFile: String = {
|
||||
return NSLocalizedString("writeTraceToFile", tableName: filename, bundle: Bundle.main, value: "Write trace to file", comment: "Should write trace to file be enabled or not")
|
||||
}()
|
||||
|
||||
static let sectionTitleTrace: String = {
|
||||
return NSLocalizedString("sectionTitleTrace", tableName: filename, bundle: Bundle.main, value: "Trace", comment: "in Settings, section title for Trace")
|
||||
}()
|
||||
|
||||
static let sendTraceFile: String = {
|
||||
return NSLocalizedString("sendTraceFile", tableName: filename, bundle: Bundle.main, value: "Send trace file", comment: "in Settings, row title to send settings")
|
||||
}()
|
||||
|
||||
static let warningWriteTraceToFile: String = {
|
||||
return NSLocalizedString("warningWriteTraceToFile", tableName: filename, bundle: Bundle.main, value: "Write trace to file is not enabled. Enable this first, then do your test, then send trace file", comment: "in Settings, when user clicks send trace to file, but write trace to file is not enabled")
|
||||
}()
|
||||
|
||||
static let describeProblem: String = {
|
||||
return NSLocalizedString("describeProblem", tableName: filename, bundle: Bundle.main, value: "Explain why you send the trace file as detailed as possible. If you have reported your problem on facebook group 'Xdrip for IOS', then mention your facebook name in the e-mail", comment: "Text in pop up shown when user wants to send the trace file")
|
||||
}()
|
||||
|
||||
static let emailNotConfigured: String = {
|
||||
return NSLocalizedString("emailNotConfigured", tableName: filename, bundle: Bundle.main, value: "You must configure an e-mail account on your iOS device.", comment: "user tries to send trace file but there's no native email account configured")
|
||||
}()
|
||||
|
||||
static let emailbodyText: String = {
|
||||
return NSLocalizedString("emailbodyText", tableName: filename, bundle: Bundle.main, value: "Problem description: ", comment: "default text in email body, when user wants to send trace file.")
|
||||
}()
|
||||
|
||||
static let failedToSendEmail: String = {
|
||||
return NSLocalizedString("failedToSendEmail", tableName: filename, bundle: Bundle.main, value: "Failed to send email", comment: "In case user tries to send trace file via email but error occurs.")
|
||||
}()
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,22 @@ func debuglogging(_ logtext:String) {
|
|||
os_log("%{public}@", log: log, type: .debug, logtext)
|
||||
}
|
||||
|
||||
/// finds the path to where xdrip can save files
|
||||
fileprivate func getDocumentsDirectory() -> URL {
|
||||
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
|
||||
return paths[0]
|
||||
}
|
||||
|
||||
/// trace file, in case tracing needs to be stored on file
|
||||
var traceFileName:URL = {
|
||||
getDocumentsDirectory().appendingPathComponent(ConstantsTrace.traceFileName)
|
||||
}()
|
||||
|
||||
// appinfo filename
|
||||
var appInfoFileName:URL = {
|
||||
getDocumentsDirectory().appendingPathComponent(ConstantsTrace.appInfoFileName)
|
||||
}()
|
||||
|
||||
/// function to be used for logging, takes same parameters as os_log but in a next phase also NSLog can be added, or writing to disk to send later via e-mail ..
|
||||
/// - message : the text, same format as in os_log with %{private} and %{public} to either keep variables private or public , for NSLog, only 3 String formatters are suppored "@" for String, "d" for Int, "f" for double.
|
||||
/// - log : is the name of the category that will be used in OSLog
|
||||
|
@ -63,8 +79,8 @@ func trace(_ message: StaticString, log:OSLog, category: String, type: OSLogType
|
|||
|
||||
}
|
||||
|
||||
// if nslog is enabled in settings, then do nslog
|
||||
if UserDefaults.standard.NSLogEnabled {
|
||||
// if nslog or write trace to file is enabled in settings, then calculate first string to log
|
||||
if UserDefaults.standard.NSLogEnabled || UserDefaults.standard.writeTraceToFile {
|
||||
|
||||
var argumentsCounter: Int = 0
|
||||
|
||||
|
@ -129,8 +145,288 @@ func trace(_ message: StaticString, log:OSLog, category: String, type: OSLogType
|
|||
|
||||
}
|
||||
|
||||
NSLog("%@", ConstantsLog.tracePrefix + " " + dateFormatNSLog.string(from: Date()) + " " + category + " " + actualMessage)
|
||||
// create timeStamp to use in NSLog and tracefile
|
||||
let timeStamp = dateFormatNSLog.string(from: Date())
|
||||
|
||||
// nslog if enabled
|
||||
if UserDefaults.standard.NSLogEnabled {
|
||||
|
||||
NSLog("%@", ConstantsLog.tracePrefix + " " + timeStamp + " " + category + " " + actualMessage)
|
||||
|
||||
}
|
||||
|
||||
// if trace to file enabled, then write trace to file
|
||||
if UserDefaults.standard.writeTraceToFile {
|
||||
|
||||
do {
|
||||
|
||||
let textToWrite = timeStamp + " " + category + " " + actualMessage + "\n"
|
||||
|
||||
if let fileHandle = FileHandle(forWritingAtPath: traceFileName.path) {
|
||||
|
||||
// file already exists, go to end of file and append text
|
||||
fileHandle.seekToEndOfFile()
|
||||
fileHandle.write(textToWrite.data(using: .utf8)!)
|
||||
|
||||
} else {
|
||||
|
||||
// file doesn't exist yet
|
||||
try textToWrite.write(to: traceFileName, atomically: true, encoding: String.Encoding.utf8)
|
||||
|
||||
}
|
||||
|
||||
} catch {
|
||||
NSLog("%@", ConstantsLog.tracePrefix + " " + dateFormatNSLog.string(from: Date()) + " write trace to file failed")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/// deletes the tracefile from disk
|
||||
func deleteTraceFile() {
|
||||
|
||||
do {
|
||||
|
||||
let fileManager = FileManager.default
|
||||
try fileManager.removeItem(at: traceFileName)
|
||||
|
||||
} catch {
|
||||
//
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class Trace {
|
||||
|
||||
// MARK: - private properties
|
||||
|
||||
/// CoreDataManager to use
|
||||
private static var coreDataManager:CoreDataManager?
|
||||
|
||||
/// BluetoothPeripheralManager to use
|
||||
private static var bluetoothPeripheralManager: BluetoothPeripheralManager?
|
||||
|
||||
private static let paragraphSeperator = "\n\n===================================================\n\n"
|
||||
|
||||
// MARK: - initializer
|
||||
|
||||
static func initialize(coreDataManager: CoreDataManager?) {
|
||||
|
||||
self.coreDataManager = coreDataManager
|
||||
|
||||
}
|
||||
|
||||
// MARK: - public static functions
|
||||
|
||||
/// creates file on disk with app info
|
||||
static func createAppInfoFile() {
|
||||
|
||||
var traceInfo = ""
|
||||
|
||||
// Info from User Defaults
|
||||
|
||||
// timestamp app launch
|
||||
// unwrap timeStampAppLaunch
|
||||
if let timeStampAppLaunch = UserDefaults.standard.timeStampAppLaunch {
|
||||
traceInfo.appendStringAndNewLine("App launched at " + timeStampAppLaunch.toShortString() + " local time.")
|
||||
}
|
||||
|
||||
// cgm transmitter type from UserDefaults
|
||||
if let cgmTransmitterTypeAsString = UserDefaults.standard.cgmTransmitterTypeAsString {
|
||||
traceInfo.appendStringAndNewLine("Transmitter type = " + cgmTransmitterTypeAsString + "\n")
|
||||
traceInfo.appendStringAndNewLine(paragraphSeperator)
|
||||
}
|
||||
|
||||
// Info from coredata
|
||||
|
||||
if let coreDataManager = coreDataManager {
|
||||
|
||||
// accessors
|
||||
let bLEPeripheralAccessor = BLEPeripheralAccessor(coreDataManager: coreDataManager)
|
||||
let alertEntriesAccessor = AlertEntriesAccessor(coreDataManager: coreDataManager)
|
||||
let alertTypesAccessor = AlertTypesAccessor(coreDataManager: coreDataManager)
|
||||
|
||||
// all bluetooth transmitters
|
||||
traceInfo.appendStringAndNewLine("List of bluetooth peripherals:\n")
|
||||
|
||||
for blePeripheral in bLEPeripheralAccessor.getBLEPeripherals() {
|
||||
traceInfo.appendStringAndNewLine(" Name : " + blePeripheral.name)
|
||||
traceInfo.appendStringAndNewLine(" Address : " + blePeripheral.address)
|
||||
if let alias = blePeripheral.alias {
|
||||
traceInfo.appendStringAndNewLine(" Alias : " + alias)
|
||||
}
|
||||
traceInfo.appendStringAndNewLine(" xDrip will " + (blePeripheral.shouldconnect ? "try ":"not try") + " to connect to this peripheral")
|
||||
|
||||
for bluetoothPeripheralType in BluetoothPeripheralType.allCases {
|
||||
|
||||
switch bluetoothPeripheralType {
|
||||
|
||||
case .M5StackType:
|
||||
if let m5Stack = blePeripheral.m5Stack, !m5Stack.isM5StickC {
|
||||
|
||||
traceInfo.appendStringAndNewLine(" type = " + bluetoothPeripheralType.rawValue)
|
||||
traceInfo.appendStringAndNewLine(" battery level = " + m5Stack.batteryLevel.description)
|
||||
|
||||
// if needed additional specific info can be added
|
||||
|
||||
}
|
||||
|
||||
case .M5StickCType:
|
||||
if let m5Stack = blePeripheral.m5Stack, m5Stack.isM5StickC {
|
||||
|
||||
traceInfo.appendStringAndNewLine(" type = " + bluetoothPeripheralType.rawValue)
|
||||
traceInfo.appendStringAndNewLine(" battery level = " + m5Stack.batteryLevel.description)
|
||||
|
||||
}
|
||||
|
||||
case .DexcomG4Type:
|
||||
if let dexcomG4 = blePeripheral.dexcomG4 {
|
||||
|
||||
traceInfo.appendStringAndNewLine(" type = " + bluetoothPeripheralType.rawValue)
|
||||
|
||||
// if needed additional specific info can be added
|
||||
traceInfo.appendStringAndNewLine(" batterylevel : " + dexcomG4.batteryLevel.description)
|
||||
|
||||
}
|
||||
|
||||
case .DexcomG5Type:
|
||||
if let dexcomG5 = blePeripheral.dexcomG5, !dexcomG5.isDexcomG6 {
|
||||
|
||||
traceInfo.appendStringAndNewLine(" type = " + bluetoothPeripheralType.rawValue)
|
||||
|
||||
// if needed additional specific info can be added
|
||||
traceInfo.appendStringAndNewLine(" voltageA : " + dexcomG5.voltageA.description)
|
||||
traceInfo.appendStringAndNewLine(" voltageB : " + dexcomG5.voltageB.description)
|
||||
|
||||
}
|
||||
|
||||
case .DexcomG6Type:
|
||||
if let dexcomG6 = blePeripheral.dexcomG5, dexcomG6.isDexcomG6 {
|
||||
|
||||
traceInfo.appendStringAndNewLine(" type = " + bluetoothPeripheralType.rawValue)
|
||||
|
||||
// if needed additional specific info can be added
|
||||
traceInfo.appendStringAndNewLine(" voltageA : " + dexcomG6.voltageA.description)
|
||||
traceInfo.appendStringAndNewLine(" voltageB : " + dexcomG6.voltageB.description)
|
||||
|
||||
}
|
||||
|
||||
case .BluconType:
|
||||
if let blucon = blePeripheral.blucon {
|
||||
|
||||
traceInfo.appendStringAndNewLine(" type = " + bluetoothPeripheralType.rawValue)
|
||||
|
||||
// if needed additional specific info can be added
|
||||
traceInfo.appendStringAndNewLine(" batteryLevel : " + blucon.batteryLevel.description)
|
||||
|
||||
}
|
||||
|
||||
case .BlueReaderType:
|
||||
if blePeripheral.blueReader != nil {
|
||||
|
||||
traceInfo.appendStringAndNewLine(" type = " + bluetoothPeripheralType.rawValue)
|
||||
|
||||
}
|
||||
|
||||
case .BubbleType:
|
||||
if let bubble = blePeripheral.bubble {
|
||||
|
||||
traceInfo.appendStringAndNewLine(" type = " + bluetoothPeripheralType.rawValue)
|
||||
traceInfo.appendStringAndNewLine(" batteryLevel : " + bubble.batteryLevel.description)
|
||||
|
||||
}
|
||||
|
||||
case .DropletType:
|
||||
if let droplet = blePeripheral.droplet {
|
||||
|
||||
traceInfo.appendStringAndNewLine(" type = " + bluetoothPeripheralType.rawValue)
|
||||
traceInfo.appendStringAndNewLine(" batteryLevel : " + droplet.batteryLevel.description)
|
||||
|
||||
}
|
||||
|
||||
case .GNSentryType:
|
||||
if let gNSEntry = blePeripheral.gNSEntry {
|
||||
|
||||
traceInfo.appendStringAndNewLine(" type = " + bluetoothPeripheralType.rawValue)
|
||||
traceInfo.appendStringAndNewLine(" batteryLevel : " + gNSEntry.batteryLevel.description)
|
||||
|
||||
}
|
||||
|
||||
case .MiaoMiaoType:
|
||||
if let miaoMiao = blePeripheral.miaoMiao {
|
||||
|
||||
traceInfo.appendStringAndNewLine(" type = " + bluetoothPeripheralType.rawValue)
|
||||
traceInfo.appendStringAndNewLine(" batteryLevel : " + miaoMiao.batteryLevel.description)
|
||||
|
||||
}
|
||||
|
||||
case .WatlaaType:
|
||||
if let watlaa = blePeripheral.watlaa {
|
||||
|
||||
traceInfo.appendStringAndNewLine(" type = " + bluetoothPeripheralType.rawValue)
|
||||
traceInfo.appendStringAndNewLine(" batteryLevel : " + watlaa.watlaaBatteryLevel.description)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
traceInfo.appendStringAndNewLine("")
|
||||
|
||||
}
|
||||
|
||||
// all alertentries
|
||||
traceInfo.appendStringAndNewLine("List of alerts:\n")
|
||||
|
||||
for alertKind in AlertKind.allCases {
|
||||
|
||||
traceInfo.appendStringAndNewLine(" alert kind : " + alertKind.descriptionForLogging())
|
||||
|
||||
let alertEntries = alertEntriesAccessor.getAllEntries(forAlertKind: alertKind, alertTypesAccessor: alertTypesAccessor)
|
||||
|
||||
for alertEntry in alertEntries {
|
||||
traceInfo.appendStringAndNewLine(" start " + alertEntry.start.convertMinutesToTimeAsString() + " - value " + alertEntry.value.description + " - alert type : " + alertEntry.alertType.description)
|
||||
|
||||
}
|
||||
|
||||
traceInfo.appendStringAndNewLine("")
|
||||
|
||||
}
|
||||
|
||||
// all alert types
|
||||
traceInfo.appendStringAndNewLine("List of alert types:\n")
|
||||
|
||||
for alertType in alertTypesAccessor.getAllAlertTypes() {
|
||||
|
||||
traceInfo.appendStringAndNewLine(" alert type : " + alertType.description)
|
||||
traceInfo.appendStringAndNewLine(" name : " + alertType.name)
|
||||
traceInfo.appendStringAndNewLine(" enabled : " + alertType.enabled.description)
|
||||
traceInfo.appendStringAndNewLine(" overridemute : " + alertType.overridemute.description)
|
||||
traceInfo.appendStringAndNewLine(" snooze via notification : " + alertType.snooze.description)
|
||||
traceInfo.appendStringAndNewLine(" default snooze period : " + alertType.snoozeperiod.description)
|
||||
if let soundname = alertType.soundname {
|
||||
traceInfo.appendStringAndNewLine(" sound : " + soundname)
|
||||
} else {
|
||||
traceInfo.appendStringAndNewLine(" sound : " + "default iOS sound")
|
||||
}
|
||||
|
||||
|
||||
traceInfo.appendStringAndNewLine("")
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
do {
|
||||
|
||||
try traceInfo.write(to: appInfoFileName, atomically: true, encoding: String.Encoding.utf8)
|
||||
|
||||
} catch {
|
||||
NSLog("%@", ConstantsLog.tracePrefix + " " + dateFormatNSLog.string(from: Date()) + " write appinfo to file failed")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -102,7 +102,7 @@ class BluetoothPeripheralViewController: UIViewController {
|
|||
private var infoAlertWhenScanningStarts: UIAlertController?
|
||||
|
||||
/// for trace
|
||||
private let log = OSLog(subsystem: ConstantsLog.subSystem, category: ConstantsLog.bluetoothPeripheralViewController)
|
||||
private let log = OSLog(subsystem: ConstantsLog.subSystem, category: ConstantsLog.categoryBluetoothPeripheralViewController)
|
||||
|
||||
/// to keep track of scanning result
|
||||
private var previousScanningResult: BluetoothTransmitter.startScanningResult?
|
||||
|
@ -450,7 +450,7 @@ class BluetoothPeripheralViewController: UIViewController {
|
|||
|
||||
case .alreadyScanning, .alreadyConnected, .connecting :
|
||||
|
||||
trace("in handleScanningResult, scanning not started. Scanning result = %{public}@", log: log, category: ConstantsLog.bluetoothPeripheralViewController, type: .error, startScanningResult.description())
|
||||
trace("in handleScanningResult, scanning not started. Scanning result = %{public}@", log: log, category: ConstantsLog.categoryBluetoothPeripheralViewController, type: .error, startScanningResult.description())
|
||||
// no further processing, should normally not happen,
|
||||
|
||||
// set isScanning false, although it should already be false
|
||||
|
@ -458,7 +458,7 @@ class BluetoothPeripheralViewController: UIViewController {
|
|||
|
||||
case .poweredOff:
|
||||
|
||||
trace("in handleScanningResult, scanning not started. Bluetooth is not on", log: log, category: ConstantsLog.bluetoothPeripheralViewController, type: .error)
|
||||
trace("in handleScanningResult, scanning not started. Bluetooth is not on", log: log, category: ConstantsLog.categoryBluetoothPeripheralViewController, type: .error)
|
||||
|
||||
// show info that user should switch on bluetooth
|
||||
self.infoAlertWhenScanningStarts = UIAlertController(title: Texts_Common.warning, message: Texts_HomeView.bluetoothIsNotOn, actionHandler: nil)
|
||||
|
@ -466,12 +466,12 @@ class BluetoothPeripheralViewController: UIViewController {
|
|||
|
||||
case .other(let reason):
|
||||
|
||||
trace("in handleScanningResult, scanning not started. Scanning result = %{public}@", log: log, category: ConstantsLog.bluetoothPeripheralViewController, type: .error, reason)
|
||||
trace("in handleScanningResult, scanning not started. Scanning result = %{public}@", log: log, category: ConstantsLog.categoryBluetoothPeripheralViewController, type: .error, reason)
|
||||
// no further processing, should normally not happen,
|
||||
|
||||
case .unauthorized:
|
||||
|
||||
trace("in handleScanningResult, scanning not started. Scanning result = unauthorized", log: log, category: ConstantsLog.bluetoothPeripheralViewController, type: .error)
|
||||
trace("in handleScanningResult, scanning not started. Scanning result = unauthorized", log: log, category: ConstantsLog.categoryBluetoothPeripheralViewController, type: .error)
|
||||
|
||||
// show info that user should switch on bluetooth
|
||||
self.infoAlertWhenScanningStarts = UIAlertController(title: Texts_Common.warning, message: Texts_HomeView.bluetoothIsNotAuthorized, actionHandler: nil)
|
||||
|
@ -479,7 +479,7 @@ class BluetoothPeripheralViewController: UIViewController {
|
|||
|
||||
case .unknown:
|
||||
|
||||
trace("in handleScanningResult, scanning not started. Scanning result = unknown - this is always occuring when a BluetoothTransmitter starts scanning the first time. You should see now a new call to handleScanningResult", log: log, category: ConstantsLog.bluetoothPeripheralViewController, type: .info)
|
||||
trace("in handleScanningResult, scanning not started. Scanning result = unknown - this is always occuring when a BluetoothTransmitter starts scanning the first time. You should see now a new call to handleScanningResult", log: log, category: ConstantsLog.categoryBluetoothPeripheralViewController, type: .info)
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -64,6 +64,14 @@ protocol SettingsViewModelProtocol {
|
|||
/// - two strings, a title and a message.
|
||||
func storeMessageHandler(messageHandler : @escaping ((String, String) -> Void))
|
||||
|
||||
/// store uiviewcontroller that created the model, in case it's needed
|
||||
func storeUIViewController(uIViewController: UIViewController)
|
||||
|
||||
/// closure to call to reload a row specified by index in the section that the viewmodel is implementing (ie not in another section)
|
||||
///
|
||||
/// just an additional method to force row reloads, (there's also the method completeSettingsViewRefreshNeeded which may return true or false depending on row number and which will be called from within the SettingsViewController. The rowReloadClosure is useful when the reload needs to be handled asynchronously
|
||||
func storeRowReloadClosure(rowReloadClosure: @escaping ((Int) -> Void))
|
||||
|
||||
}
|
||||
|
||||
/// to make the coding a bit easier, just one function defined for now, which is to get the viewModel for a specific setting
|
||||
|
|
|
@ -350,6 +350,9 @@ final class RootViewController: UIViewController {
|
|||
// creates activeSensor, bgreadingsAccessor, calibrationsAccessor, NightScoutUploadManager, soundPlayer, dexcomShareUploadManager, nightScoutFollowManager, alertManager, healthKitManager, bgReadingSpeaker, bluetoothPeripheralManager, watchManager
|
||||
private func setupApplicationData() {
|
||||
|
||||
// setup Trace
|
||||
Trace.initialize(coreDataManager: coreDataManager)
|
||||
|
||||
// if coreDataManager is nil then there's no reason to continue
|
||||
guard let coreDataManager = coreDataManager else {
|
||||
fatalError("In setupApplicationData but coreDataManager == nil")
|
||||
|
@ -654,6 +657,9 @@ final class RootViewController: UIViewController {
|
|||
/// should be called only once immediately after app start, ie in viewdidload
|
||||
private func setupUpdateLabelsAndChartTimer() {
|
||||
|
||||
// set timeStampAppLaunch to now
|
||||
UserDefaults.standard.timeStampAppLaunch = Date()
|
||||
|
||||
// this is the actual timer
|
||||
var updateLabelsAndChartTimer:Timer?
|
||||
|
||||
|
|
|
@ -9,10 +9,14 @@ fileprivate enum Setting:Int, CaseIterable {
|
|||
|
||||
struct SettingsViewM5StackBluetoothSettingsViewModel: SettingsViewModelProtocol {
|
||||
|
||||
func storeUIViewController(uIViewController: UIViewController) {}
|
||||
|
||||
func storeMessageHandler(messageHandler: ((String, String) -> Void)) {
|
||||
// this ViewModel does need to send back messages to the viewcontroller asynchronously
|
||||
}
|
||||
|
||||
func storeRowReloadClosure(rowReloadClosure: ((Int) -> Void)) {}
|
||||
|
||||
func sectionTitle() -> String? {
|
||||
return Texts_SettingsView.m5StackSectionTitleBluetooth
|
||||
}
|
||||
|
|
|
@ -9,6 +9,10 @@ fileprivate enum Setting:Int, CaseIterable {
|
|||
|
||||
struct SettingsViewM5StackGeneralSettingsViewModel: SettingsViewModelProtocol {
|
||||
|
||||
func storeRowReloadClosure(rowReloadClosure: ((Int) -> Void)) {}
|
||||
|
||||
func storeUIViewController(uIViewController: UIViewController) {}
|
||||
|
||||
func storeMessageHandler(messageHandler: ((String, String) -> Void)) {
|
||||
// this ViewModel does need to send back messages to the viewcontroller asynchronously
|
||||
}
|
||||
|
|
|
@ -24,6 +24,10 @@ fileprivate enum Setting:Int, CaseIterable {
|
|||
|
||||
struct SettingsViewM5StackWiFiSettingsViewModel: SettingsViewModelProtocol {
|
||||
|
||||
func storeRowReloadClosure(rowReloadClosure: ((Int) -> Void)) {}
|
||||
|
||||
func storeUIViewController(uIViewController: UIViewController) {}
|
||||
|
||||
func storeMessageHandler(messageHandler: ((String, String) -> Void)) {
|
||||
// this ViewModel does need to send back messages to the viewcontroller asynchronously
|
||||
}
|
||||
|
|
|
@ -21,6 +21,76 @@ final class SettingsViewController: UIViewController {
|
|||
/// UIAlertController used by messageHandler
|
||||
private var messageHandlerUiAlertController: UIAlertController?
|
||||
|
||||
/// array of viewmodels, one per section
|
||||
private var viewModels = [SettingsViewModelProtocol]()
|
||||
|
||||
private enum Section: Int, CaseIterable, SettingsProtocol {
|
||||
|
||||
///General settings - language, glucose unit, high and low value
|
||||
case general
|
||||
|
||||
/// alarms
|
||||
case alarms
|
||||
|
||||
///nightscout settings
|
||||
case nightscout
|
||||
|
||||
///dexcom share settings
|
||||
case dexcom
|
||||
|
||||
/// healthkit
|
||||
case healthkit
|
||||
|
||||
/// store bg values in healthkit
|
||||
case speak
|
||||
|
||||
/// M5 stack settings
|
||||
case M5stack
|
||||
|
||||
/// Apple Watch settings
|
||||
case AppleWatch
|
||||
|
||||
/// tracing
|
||||
case trace
|
||||
|
||||
/// info
|
||||
case info
|
||||
|
||||
/// developper settings
|
||||
case developer
|
||||
|
||||
func viewModel() -> SettingsViewModelProtocol {
|
||||
switch self {
|
||||
|
||||
case .general:
|
||||
return SettingsViewGeneralSettingsViewModel()
|
||||
case .alarms:
|
||||
return SettingsViewAlertSettingsViewModel()
|
||||
case .nightscout:
|
||||
return SettingsViewNightScoutSettingsViewModel()
|
||||
case .dexcom:
|
||||
return SettingsViewDexcomSettingsViewModel()
|
||||
case .healthkit:
|
||||
return SettingsViewHealthKitSettingsViewModel()
|
||||
case .speak:
|
||||
return SettingsViewSpeakSettingsViewModel()
|
||||
case .M5stack:
|
||||
return SettingsViewM5StackSettingsViewModel()
|
||||
case .info:
|
||||
return SettingsViewInfoViewModel()
|
||||
case .developer:
|
||||
return SettingsViewDevelopmentSettingsViewModel()
|
||||
case .AppleWatch:
|
||||
return SettingsViewAppleWatchSettingsViewModel()
|
||||
case .trace:
|
||||
return SettingsViewTraceSettingsViewModel()
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
// MARK:- public functions
|
||||
|
||||
/// configure
|
||||
|
@ -29,6 +99,32 @@ final class SettingsViewController: UIViewController {
|
|||
self.coreDataManager = coreDataManager
|
||||
self.soundPlayer = soundPlayer
|
||||
|
||||
// initialize viewModels
|
||||
for section in Section.allCases {
|
||||
|
||||
// get a viewModel for the section
|
||||
let viewModel = section.viewModel()
|
||||
|
||||
// unwrap messageHandler and store in the viewModel
|
||||
if let messageHandler = messageHandler {
|
||||
viewModel.storeMessageHandler(messageHandler: messageHandler)
|
||||
}
|
||||
|
||||
// store self as uiViewController in the viewModel
|
||||
viewModel.storeUIViewController(uIViewController: self)
|
||||
|
||||
// store reload closure in the viewModel
|
||||
viewModel.storeRowReloadClosure(rowReloadClosure: {row in
|
||||
|
||||
self.tableView.reloadRows(at: [IndexPath(row: row, section: section.rawValue)], with: .none)
|
||||
|
||||
})
|
||||
|
||||
// store the viewModel
|
||||
self.viewModels.append(viewModel)
|
||||
|
||||
}
|
||||
|
||||
// create messageHandler
|
||||
messageHandler = {
|
||||
(title, message) in
|
||||
|
@ -128,74 +224,11 @@ final class SettingsViewController: UIViewController {
|
|||
|
||||
extension SettingsViewController:UITableViewDataSource, UITableViewDelegate {
|
||||
|
||||
private enum Section: Int, CaseIterable, SettingsProtocol {
|
||||
|
||||
///General settings - language, glucose unit, high and low value
|
||||
case general
|
||||
|
||||
/// alarms
|
||||
case alarms
|
||||
|
||||
///nightscout settings
|
||||
case nightscout
|
||||
|
||||
///dexcom share settings
|
||||
case dexcom
|
||||
|
||||
/// healthkit
|
||||
case healthkit
|
||||
|
||||
/// store bg values in healthkit
|
||||
case speak
|
||||
|
||||
/// M5 stack settings
|
||||
case M5stack
|
||||
|
||||
|
||||
/// Apple Watch settings
|
||||
case AppleWatch
|
||||
|
||||
/// info
|
||||
case info
|
||||
|
||||
/// developper settings
|
||||
case developer
|
||||
|
||||
func viewModel() -> SettingsViewModelProtocol {
|
||||
switch self {
|
||||
|
||||
case .general:
|
||||
return SettingsViewGeneralSettingsViewModel()
|
||||
case .alarms:
|
||||
return SettingsViewAlertSettingsViewModel()
|
||||
case .nightscout:
|
||||
return SettingsViewNightScoutSettingsViewModel()
|
||||
case .dexcom:
|
||||
return SettingsViewDexcomSettingsViewModel()
|
||||
case .healthkit:
|
||||
return SettingsViewHealthKitSettingsViewModel()
|
||||
case .speak:
|
||||
return SettingsViewSpeakSettingsViewModel()
|
||||
case .M5stack:
|
||||
return SettingsViewM5StackSettingsViewModel()
|
||||
case .info:
|
||||
return SettingsViewInfoViewModel()
|
||||
case .developer:
|
||||
return SettingsViewDevelopmentSettingsViewModel()
|
||||
case .AppleWatch:
|
||||
return SettingsViewAppleWatchSettingsViewModel()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - UITableViewDataSource protocol Methods
|
||||
|
||||
func tableView(_ tableView: UITableView, titleForHeaderInSection section: Int) -> String? {
|
||||
|
||||
guard let section = Section(rawValue: section) else { fatalError("Unexpected Section") }
|
||||
|
||||
return section.viewModel().sectionTitle()
|
||||
return viewModels[section].sectionTitle()
|
||||
|
||||
}
|
||||
|
||||
|
@ -205,21 +238,17 @@ extension SettingsViewController:UITableViewDataSource, UITableViewDelegate {
|
|||
|
||||
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||||
|
||||
guard let section = Section(rawValue: section) else { fatalError("Unexpected Section") }
|
||||
|
||||
return section.viewModel().numberOfRows()
|
||||
return viewModels[section].numberOfRows()
|
||||
|
||||
}
|
||||
|
||||
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||||
|
||||
guard let section = Section(rawValue: indexPath.section) else { fatalError("Unexpected Section") }
|
||||
|
||||
guard var cell = tableView.dequeueReusableCell(withIdentifier: SettingsTableViewCell.reuseIdentifier, for: indexPath) as? SettingsTableViewCell else { fatalError("Unexpected Table View Cell") }
|
||||
|
||||
// Configure Cell
|
||||
|
||||
SettingsViewUtilities.configureSettingsCell(cell: &cell, forRowWithIndex: indexPath.row, forSectionWithIndex: indexPath.section, withViewModel: section.viewModel(), tableView: tableView)
|
||||
SettingsViewUtilities.configureSettingsCell(cell: &cell, forRowWithIndex: indexPath.row, forSectionWithIndex: indexPath.section, withViewModel: viewModels[indexPath.section], tableView: tableView)
|
||||
|
||||
return cell
|
||||
}
|
||||
|
@ -230,13 +259,7 @@ extension SettingsViewController:UITableViewDataSource, UITableViewDelegate {
|
|||
|
||||
tableView.deselectRow(at: indexPath, animated: true)
|
||||
|
||||
guard let section = Section(rawValue: indexPath.section) else { fatalError("Unexpected Section") }
|
||||
|
||||
let viewModel = section.viewModel()
|
||||
|
||||
if let messageHandler = messageHandler {
|
||||
viewModel.storeMessageHandler(messageHandler: messageHandler)
|
||||
}
|
||||
let viewModel = viewModels[indexPath.section]
|
||||
|
||||
if viewModel.isEnabled(index: indexPath.row) {
|
||||
|
||||
|
|
|
@ -10,6 +10,8 @@ fileprivate enum Setting:Int, CaseIterable {
|
|||
/// conforms to SettingsViewModelProtocol for all alert settings in the first sections screen
|
||||
struct SettingsViewAlertSettingsViewModel:SettingsViewModelProtocol {
|
||||
|
||||
func storeUIViewController(uIViewController: UIViewController) {}
|
||||
|
||||
func storeMessageHandler(messageHandler: ((String, String) -> Void)) {
|
||||
// this ViewModel does need to send back messages to the viewcontroller asynchronously
|
||||
}
|
||||
|
@ -33,6 +35,8 @@ struct SettingsViewAlertSettingsViewModel:SettingsViewModelProtocol {
|
|||
}
|
||||
}
|
||||
|
||||
func storeRowReloadClosure(rowReloadClosure: ((Int) -> Void)) {}
|
||||
|
||||
func sectionTitle() -> String? {
|
||||
return Texts_SettingsView.sectionTitleAlerting
|
||||
}
|
||||
|
|
|
@ -30,10 +30,14 @@ class SettingsViewAppleWatchSettingsViewModel: SettingsViewModelProtocol {
|
|||
/// used for requesting authorization to access calendar
|
||||
let eventStore = EKEventStore()
|
||||
|
||||
func storeUIViewController(uIViewController: UIViewController) {}
|
||||
|
||||
func storeMessageHandler(messageHandler: ((String, String) -> Void)) {
|
||||
// this ViewModel does need to send back messages to the viewcontroller asynchronously
|
||||
}
|
||||
|
||||
func storeRowReloadClosure(rowReloadClosure: ((Int) -> Void)) {}
|
||||
|
||||
func sectionTitle() -> String? {
|
||||
return Texts_SettingsView.appleWatchSectionTitle
|
||||
}
|
||||
|
|
|
@ -15,6 +15,10 @@ fileprivate enum Setting:Int, CaseIterable {
|
|||
|
||||
struct SettingsViewDevelopmentSettingsViewModel:SettingsViewModelProtocol {
|
||||
|
||||
func storeRowReloadClosure(rowReloadClosure: @escaping ((Int) -> Void)) {}
|
||||
|
||||
func storeUIViewController(uIViewController: UIViewController) {}
|
||||
|
||||
func storeMessageHandler(messageHandler: ((String, String) -> Void)) {
|
||||
// this ViewModel does need to send back messages to the viewcontroller asynchronously
|
||||
}
|
||||
|
|
|
@ -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 storeRowReloadClosure(rowReloadClosure: ((Int) -> Void)) {}
|
||||
|
||||
func storeUIViewController(uIViewController: UIViewController) {}
|
||||
|
||||
func storeMessageHandler(messageHandler: ((String, String) -> Void)) {
|
||||
// this ViewModel does need to send back messages to the viewcontroller asynchronously
|
||||
}
|
||||
|
|
|
@ -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 storeRowReloadClosure(rowReloadClosure: ((Int) -> Void)) {}
|
||||
|
||||
func storeUIViewController(uIViewController: UIViewController) {}
|
||||
|
||||
func storeMessageHandler(messageHandler: ((String, String) -> Void)) {
|
||||
// this ViewModel does need to send back messages to the viewcontroller asynchronously
|
||||
}
|
||||
|
|
|
@ -12,6 +12,10 @@ class SettingsViewHealthKitSettingsViewModel:SettingsViewModelProtocol {
|
|||
|
||||
// MARK: - functions in protocol SettingsViewModelProtocol
|
||||
|
||||
func storeRowReloadClosure(rowReloadClosure: ((Int) -> Void)) {}
|
||||
|
||||
func storeUIViewController(uIViewController: UIViewController) {}
|
||||
|
||||
func storeMessageHandler(messageHandler: ((String, String) -> Void)) {
|
||||
// this ViewModel does need to send back messages to the viewcontroller asynchronously
|
||||
}
|
||||
|
|
|
@ -18,6 +18,10 @@ fileprivate enum Setting:Int, CaseIterable {
|
|||
|
||||
struct SettingsViewInfoViewModel:SettingsViewModelProtocol {
|
||||
|
||||
func storeRowReloadClosure(rowReloadClosure: @escaping ((Int) -> Void)) {}
|
||||
|
||||
func storeUIViewController(uIViewController: UIViewController) {}
|
||||
|
||||
func storeMessageHandler(messageHandler: ((String, String) -> Void)) {
|
||||
// this ViewModel does need to send back messages to the viewcontroller asynchronously
|
||||
}
|
||||
|
|
|
@ -9,6 +9,10 @@ fileprivate enum Setting:Int, CaseIterable {
|
|||
|
||||
struct SettingsViewM5StackSettingsViewModel: SettingsViewModelProtocol {
|
||||
|
||||
func storeRowReloadClosure(rowReloadClosure: ((Int) -> Void)) {}
|
||||
|
||||
func storeUIViewController(uIViewController: UIViewController) {}
|
||||
|
||||
func storeMessageHandler(messageHandler: ((String, String) -> Void)) {
|
||||
// this ViewModel does need to send back messages to the viewcontroller asynchronously
|
||||
}
|
||||
|
|
|
@ -64,7 +64,7 @@ class SettingsViewNightScoutSettingsViewModel {
|
|||
|
||||
if let error = error {
|
||||
|
||||
trace("in testNightScoutCredentials, error = %{public}@", log: self.log, category: ConstantsLog.nightScoutSettingsViewModel, type: .info, error.localizedDescription)
|
||||
trace("in testNightScoutCredentials, error = %{public}@", log: self.log, category: ConstantsLog.categoryNightScoutSettingsViewModel, type: .info, error.localizedDescription)
|
||||
|
||||
self.callMessageHandlerInMainThread(title: Texts_NightScoutTestResult.verificationErrorAlertTitle, message: error.localizedDescription)
|
||||
|
||||
|
@ -77,20 +77,20 @@ class SettingsViewNightScoutSettingsViewModel {
|
|||
|
||||
let errorMessage = String(data: data, encoding: String.Encoding.utf8)!
|
||||
|
||||
trace("in testNightScoutCredentials, error = %{public}@", log: self.log, category: ConstantsLog.nightScoutSettingsViewModel, type: .info, errorMessage)
|
||||
trace("in testNightScoutCredentials, error = %{public}@", log: self.log, category: ConstantsLog.categoryNightScoutSettingsViewModel, type: .info, errorMessage)
|
||||
|
||||
self.callMessageHandlerInMainThread(title: Texts_NightScoutTestResult.verificationErrorAlertTitle, message: errorMessage)
|
||||
|
||||
} else {
|
||||
|
||||
trace("in testNightScoutCredentials, successful", log: self.log, category: ConstantsLog.nightScoutSettingsViewModel, type: .info)
|
||||
trace("in testNightScoutCredentials, successful", log: self.log, category: ConstantsLog.categoryNightScoutSettingsViewModel, 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)
|
||||
trace("in testNightScoutCredentials, url and apikey test started", log: log, category: ConstantsLog.categoryNightScoutSettingsViewModel, type: .info)
|
||||
task.resume()
|
||||
|
||||
}
|
||||
|
@ -112,6 +112,10 @@ class SettingsViewNightScoutSettingsViewModel {
|
|||
/// conforms to SettingsViewModelProtocol for all nightscout settings in the first sections screen
|
||||
extension SettingsViewNightScoutSettingsViewModel: SettingsViewModelProtocol {
|
||||
|
||||
func storeRowReloadClosure(rowReloadClosure: ((Int) -> Void)) {}
|
||||
|
||||
func storeUIViewController(uIViewController: UIViewController) {}
|
||||
|
||||
func storeMessageHandler(messageHandler: @escaping ((String, String) -> Void)) {
|
||||
self.messageHandler = messageHandler
|
||||
}
|
||||
|
|
|
@ -22,6 +22,10 @@ fileprivate enum Setting:Int, CaseIterable {
|
|||
/// conforms to SettingsViewModelProtocol for all speak settings in the first sections screen
|
||||
class SettingsViewSpeakSettingsViewModel:SettingsViewModelProtocol {
|
||||
|
||||
func storeRowReloadClosure(rowReloadClosure: ((Int) -> Void)) {}
|
||||
|
||||
func storeUIViewController(uIViewController: UIViewController) {}
|
||||
|
||||
func storeMessageHandler(messageHandler: ((String, String) -> Void)) {
|
||||
// this ViewModel does need to send back messages to the viewcontroller asynchronously
|
||||
}
|
||||
|
|
|
@ -0,0 +1,253 @@
|
|||
import UIKit
|
||||
import MessageUI
|
||||
import os
|
||||
|
||||
fileprivate enum Setting:Int, CaseIterable {
|
||||
|
||||
/// write trace to file enabled or not
|
||||
case writeTraceToFile = 0
|
||||
|
||||
/// to send trace file
|
||||
case sendTraceFile = 1
|
||||
|
||||
}
|
||||
|
||||
class SettingsViewTraceSettingsViewModel: NSObject {
|
||||
|
||||
/// need to present MFMailComposeViewController
|
||||
private var uIViewController: UIViewController?
|
||||
|
||||
/// to force a row reload
|
||||
private var rowReloadClosure: ((Int) -> Void)?
|
||||
|
||||
/// for logging
|
||||
private var log = OSLog(subsystem: ConstantsLog.subSystem, category: ConstantsLog.categoryTraceSettingsViewModel)
|
||||
|
||||
/// 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)?
|
||||
|
||||
}
|
||||
|
||||
extension SettingsViewTraceSettingsViewModel: SettingsViewModelProtocol {
|
||||
|
||||
func storeRowReloadClosure(rowReloadClosure: @escaping ((Int) -> Void)) {
|
||||
self.rowReloadClosure = rowReloadClosure
|
||||
}
|
||||
|
||||
func storeUIViewController(uIViewController: UIViewController) {
|
||||
self.uIViewController = uIViewController
|
||||
}
|
||||
|
||||
func storeMessageHandler(messageHandler: @escaping ((String, String) -> Void)) {
|
||||
self.messageHandler = messageHandler
|
||||
}
|
||||
|
||||
func sectionTitle() -> String? {
|
||||
return Texts_SettingsView.sectionTitleTrace
|
||||
}
|
||||
|
||||
func settingsRowText(index: Int) -> String {
|
||||
|
||||
guard let setting = Setting(rawValue: index) else { fatalError("Unexpected Section") }
|
||||
|
||||
switch setting {
|
||||
|
||||
case .writeTraceToFile:
|
||||
return Texts_SettingsView.writeTraceToFile
|
||||
|
||||
case .sendTraceFile:
|
||||
return Texts_SettingsView.sendTraceFile
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func accessoryType(index: Int) -> UITableViewCell.AccessoryType {
|
||||
|
||||
guard let setting = Setting(rawValue: index) else { fatalError("Unexpected Section") }
|
||||
|
||||
switch setting {
|
||||
|
||||
case .writeTraceToFile:
|
||||
return .none
|
||||
|
||||
case .sendTraceFile:
|
||||
return .disclosureIndicator
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func detailedText(index: Int) -> String? {
|
||||
|
||||
guard let setting = Setting(rawValue: index) else { fatalError("Unexpected Section") }
|
||||
|
||||
switch setting {
|
||||
|
||||
case .writeTraceToFile:
|
||||
return nil
|
||||
|
||||
case .sendTraceFile:
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func uiView(index: Int) -> UIView? {
|
||||
|
||||
guard let setting = Setting(rawValue: index) else { fatalError("Unexpected Section") }
|
||||
|
||||
switch setting {
|
||||
|
||||
case .writeTraceToFile:
|
||||
return UISwitch(isOn: UserDefaults.standard.writeTraceToFile, action: {
|
||||
|
||||
(isOn:Bool) in
|
||||
|
||||
if isOn {
|
||||
|
||||
// set writeTraceToFile to true before logging in trace that it is enabled
|
||||
UserDefaults.standard.writeTraceToFile = true
|
||||
|
||||
trace("Trace to file enabled", log: self.log, category: ConstantsLog.categoryTraceSettingsViewModel, type: .info)
|
||||
|
||||
} else {
|
||||
|
||||
trace("Trace to file disabled", log: self.log, category: ConstantsLog.categoryTraceSettingsViewModel, type: .info)
|
||||
|
||||
// set writeTraceToFile to false after logging in trace that it is enabled
|
||||
UserDefaults.standard.writeTraceToFile = false
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
})
|
||||
|
||||
case .sendTraceFile:
|
||||
return nil
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func numberOfRows() -> Int {
|
||||
return Setting.allCases.count
|
||||
}
|
||||
|
||||
func onRowSelect(index: Int) -> SettingsSelectedRowAction {
|
||||
|
||||
guard let setting = Setting(rawValue: index) else { fatalError("Unexpected Section") }
|
||||
|
||||
switch setting {
|
||||
|
||||
case .writeTraceToFile:
|
||||
return .nothing
|
||||
|
||||
case .sendTraceFile:
|
||||
|
||||
if !UserDefaults.standard.writeTraceToFile {
|
||||
|
||||
return .showInfoText(title: Texts_Common.warning, message: Texts_SettingsView.warningWriteTraceToFile)
|
||||
|
||||
} else {
|
||||
|
||||
guard let uIViewController = uIViewController else {fatalError("in SettingsViewTraceSettingsViewModel, onRowSelect, uIViewController is nil")}
|
||||
|
||||
do {
|
||||
|
||||
// create traceFile info as data
|
||||
let fileData = try Data(contentsOf: traceFileName)
|
||||
|
||||
// create app info as data
|
||||
Trace.createAppInfoFile()
|
||||
let appInfoData = try Data(contentsOf: appInfoFileName)
|
||||
|
||||
if MFMailComposeViewController.canSendMail() {
|
||||
|
||||
return .askConfirmation(title: Texts_HomeView.info, message: Texts_SettingsView.describeProblem, actionHandler: {
|
||||
|
||||
let mail = MFMailComposeViewController()
|
||||
mail.mailComposeDelegate = self
|
||||
mail.setToRecipients([ConstantsTrace.traceFileDestinationAddress])
|
||||
mail.setMessageBody(Texts_SettingsView.emailbodyText, isHTML: true)
|
||||
|
||||
mail.addAttachmentData(fileData as Data, mimeType: "text/txt", fileName: ConstantsTrace.traceFileName)
|
||||
|
||||
mail.addAttachmentData(appInfoData as Data, mimeType: "text/txt", fileName: ConstantsTrace.appInfoFileName)
|
||||
|
||||
uIViewController.present(mail, animated: true)
|
||||
|
||||
}, cancelHandler: nil)
|
||||
|
||||
|
||||
} else {
|
||||
|
||||
return .showInfoText(title: Texts_Common.warning, message: Texts_SettingsView.emailNotConfigured)
|
||||
|
||||
}
|
||||
|
||||
} catch {
|
||||
// should never get here ?
|
||||
return .showInfoText(title: Texts_Common.warning, message: "Failed to create attachment")
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
func isEnabled(index: Int) -> Bool {
|
||||
return true
|
||||
}
|
||||
|
||||
func completeSettingsViewRefreshNeeded(index: Int) -> Bool {
|
||||
|
||||
return false
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension SettingsViewTraceSettingsViewModel: MFMailComposeViewControllerDelegate {
|
||||
|
||||
func mailComposeController(_ controller: MFMailComposeViewController, didFinishWith result: MFMailComposeResult, error: Error?) {
|
||||
|
||||
controller.dismiss(animated: true, completion: nil)
|
||||
|
||||
switch result {
|
||||
|
||||
case .cancelled:
|
||||
break
|
||||
|
||||
case .sent, .saved:
|
||||
|
||||
// delete the file as it's been sent successfully
|
||||
deleteTraceFile()
|
||||
|
||||
// disable tracing, to avoid user forgets turning it off
|
||||
UserDefaults.standard.writeTraceToFile = false
|
||||
|
||||
// as setting writeTraceToFile has been changed to false, the row with that setting needs to be reloaded
|
||||
if let rowReloadClosure = rowReloadClosure {
|
||||
rowReloadClosure(Setting.writeTraceToFile.rawValue)
|
||||
}
|
||||
|
||||
case .failed:
|
||||
if let messageHandler = messageHandler {
|
||||
messageHandler(Texts_Common.warning, Texts_SettingsView.failedToSendEmail)
|
||||
}
|
||||
|
||||
@unknown default:
|
||||
break
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -172,6 +172,4 @@ class SettingsViewUtilities {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue