OverrideProfilesStateModel.swift 28 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784
  1. import CoreData
  2. import SwiftUI
  3. extension OverrideProfilesConfig {
  4. final class StateModel: BaseStateModel<Provider> {
  5. @Injected() var storage: TempTargetsStorage!
  6. @Injected() var apsManager: APSManager!
  7. @Published var percentageProfiles: Double = 100
  8. @Published var isEnabled = false
  9. @Published var _indefinite = true
  10. @Published var durationProfile: Decimal = 0
  11. @Published var target: Decimal = 0
  12. @Published var override_target: Bool = false
  13. @Published var smbIsOff: Bool = false
  14. @Published var id: String = ""
  15. @Published var profileName: String = ""
  16. @Published var isPreset: Bool = false
  17. @Published var profilePresets: [OverrideStored] = []
  18. @Published var selection: OverrideStored?
  19. @Published var advancedSettings: Bool = false
  20. @Published var isfAndCr: Bool = true
  21. @Published var isf: Bool = true
  22. @Published var cr: Bool = true
  23. @Published var smbIsAlwaysOff: Bool = false
  24. @Published var start: Decimal = 0
  25. @Published var end: Decimal = 23
  26. @Published var smbMinutes: Decimal = 0
  27. @Published var uamMinutes: Decimal = 0
  28. @Published var defaultSmbMinutes: Decimal = 0
  29. @Published var defaultUamMinutes: Decimal = 0
  30. @Published var selectedTab: Tab = .profiles
  31. @Published var activeOverrideName: String = ""
  32. @Published var currentActiveOverride: OverrideStored?
  33. var units: GlucoseUnits = .mmolL
  34. // temp target stuff
  35. @Published var low: Decimal = 0
  36. // @Published var target: Decimal = 0
  37. @Published var high: Decimal = 0
  38. @Published var durationTT: Decimal = 0
  39. @Published var date = Date()
  40. @Published var newPresetName = ""
  41. @Published var presetsTT: [TempTarget] = []
  42. @Published var percentageTT = 100.0
  43. @Published var maxValue: Decimal = 1.2
  44. @Published var viewPercantage = false
  45. @Published var hbt: Double = 160
  46. @Published var didSaveSettings: Bool = false
  47. private var dateFormatter: DateFormatter {
  48. let df = DateFormatter()
  49. df.dateFormat = "dd.MM.yy HH:mm"
  50. return df
  51. }
  52. override func subscribe() {
  53. setupNotification()
  54. units = settingsManager.settings.units
  55. defaultSmbMinutes = settingsManager.preferences.maxSMBBasalMinutes
  56. defaultUamMinutes = settingsManager.preferences.maxUAMSMBBasalMinutes
  57. setupOverridePresetsArray()
  58. updateLatestOverrideConfiguration()
  59. presetsTT = storage.presets()
  60. maxValue = settingsManager.preferences.autosensMax
  61. }
  62. let coredataContext = CoreDataStack.shared.newTaskContext()
  63. let viewContext = CoreDataStack.shared.persistentContainer.viewContext
  64. }
  65. }
  66. // MARK: - Setup Notifications
  67. extension OverrideProfilesConfig.StateModel {
  68. /// listens for the notifications sent when the managedObjectContext has saved!
  69. func setupNotification() {
  70. Foundation.NotificationCenter.default.addObserver(
  71. self,
  72. selector: #selector(contextDidSave(_:)),
  73. name: Notification.Name.NSManagedObjectContextDidSave,
  74. object: nil
  75. )
  76. /// listens for notifications sent when a Preset was added
  77. Foundation.NotificationCenter.default.addObserver(
  78. self,
  79. selector: #selector(handlePresetsUpdate),
  80. name: .didUpdateOverridePresets,
  81. object: nil
  82. )
  83. }
  84. @objc private func handlePresetsUpdate() {
  85. setupOverridePresetsArray()
  86. }
  87. /// determine the actions when the context has changed
  88. ///
  89. /// its done on a background thread and after that the UI gets updated on the main thread
  90. @objc private func contextDidSave(_ notification: Notification) {
  91. guard let userInfo = notification.userInfo else { return }
  92. Task { [weak self] in
  93. await self?.processUpdates(userInfo: userInfo)
  94. }
  95. }
  96. private func processUpdates(userInfo: [AnyHashable: Any]) async {
  97. var objects = Set((userInfo[NSInsertedObjectsKey] as? Set<NSManagedObject>) ?? [])
  98. objects.formUnion((userInfo[NSUpdatedObjectsKey] as? Set<NSManagedObject>) ?? [])
  99. objects.formUnion((userInfo[NSDeletedObjectsKey] as? Set<NSManagedObject>) ?? [])
  100. let overrideUpdates = objects.filter { $0 is OverrideStored }
  101. DispatchQueue.global(qos: .background).async {
  102. if overrideUpdates.isNotEmpty {
  103. self.updateLatestOverrideConfiguration()
  104. }
  105. }
  106. }
  107. }
  108. // MARK: - Enact Overrides
  109. extension OverrideProfilesConfig.StateModel {
  110. func scheduleOverrideDisabling(for override: OverrideStored) {
  111. let now = Date()
  112. guard let toCancelDuration = override.duration,
  113. let endTime = override.date?
  114. .addingTimeInterval(
  115. TimeInterval(truncating: toCancelDuration) *
  116. 60
  117. ) // ensuring duration is minutes, not seconds!
  118. else {
  119. debugPrint("\(DebuggingIdentifiers.failed) \(#file) \(#function) End time calculation failed")
  120. return
  121. }
  122. debugPrint(
  123. "\(DebuggingIdentifiers.inProgress) \(#file) \(#function) Scheduling cancellation at \(endTime) (in \(endTime.timeIntervalSince(now)) seconds)"
  124. )
  125. guard endTime > now else {
  126. debugPrint("\(DebuggingIdentifiers.failed) \(#file) \(#function) End time is in the past or now")
  127. return
  128. }
  129. let timeInterval = endTime.timeIntervalSince(now)
  130. DispatchQueue.main.asyncAfter(deadline: .now() + timeInterval) { [weak self] in
  131. debugPrint("\(DebuggingIdentifiers.inProgress) \(#file) \(#function) Executing scheduled cancelActiveProfile")
  132. self?.cancelActiveProfile()
  133. }
  134. }
  135. // Enact Preset
  136. /// here we only have to update the Boolean Flag 'enabled'
  137. @MainActor func enactProfilePreset(withID id: NSManagedObjectID) async {
  138. do {
  139. /// get the underlying NSManagedObject of the Profile that should be enabled
  140. let profileToEnact = try viewContext.existingObject(with: id) as? OverrideStored
  141. profileToEnact?.enabled = true
  142. profileToEnact?.date = Date()
  143. /// Update the 'Cancel Profile' button state
  144. isEnabled = true
  145. /// disable all active Profiles and reset state variables
  146. await disableAllActiveProfiles(except: id)
  147. await resetStateVariables()
  148. if let toSchedule = profileToEnact {
  149. scheduleOverrideDisabling(for: toSchedule)
  150. }
  151. guard viewContext.hasChanges else { return }
  152. try viewContext.save()
  153. } catch {
  154. debugPrint("\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to enact Profile Preset")
  155. }
  156. }
  157. }
  158. // MARK: - Profile (presets) save operations
  159. extension OverrideProfilesConfig.StateModel {
  160. // Saves Profile in a background context
  161. /// not a Preset
  162. func saveAsProfile() async {
  163. await coredataContext.perform { [self] in
  164. let newProfile = OverrideStored(context: self.coredataContext)
  165. if self.profileName.isNotEmpty {
  166. newProfile.name = self.profileName
  167. } else {
  168. let formattedDate = dateFormatter.string(from: Date())
  169. newProfile.name = "Preset <\(formattedDate)>"
  170. }
  171. newProfile.duration = self.durationProfile as NSDecimalNumber
  172. newProfile.indefinite = self._indefinite
  173. newProfile.percentage = self.percentageProfiles
  174. newProfile.enabled = true
  175. newProfile.smbIsOff = self.smbIsOff
  176. if self.isPreset {
  177. newProfile.isPreset = true
  178. newProfile.id = id
  179. } else { newProfile.isPreset = false }
  180. newProfile.date = Date()
  181. if override_target {
  182. if units == .mmolL {
  183. target = target.asMgdL
  184. }
  185. newProfile.target = target as NSDecimalNumber
  186. } else { newProfile.target = 0 }
  187. if advancedSettings {
  188. newProfile.advancedSettings = true
  189. if !isfAndCr {
  190. newProfile.isfAndCr = false
  191. newProfile.isf = isf
  192. newProfile.cr = cr
  193. } else { newProfile.isfAndCr = true }
  194. if smbIsAlwaysOff {
  195. newProfile.smbIsAlwaysOff = true
  196. newProfile.start = start as NSDecimalNumber
  197. newProfile.end = end as NSDecimalNumber
  198. } else { newProfile.smbIsAlwaysOff = false }
  199. newProfile.smbMinutes = smbMinutes as NSDecimalNumber
  200. newProfile.uamMinutes = uamMinutes as NSDecimalNumber
  201. }
  202. do {
  203. guard coredataContext.hasChanges else { return }
  204. try coredataContext.save()
  205. self.scheduleOverrideDisabling(for: newProfile)
  206. } catch {
  207. print(error.localizedDescription)
  208. }
  209. }
  210. }
  211. // Save Presets
  212. /// enabled has to be false, isPreset has to be true
  213. func savePreset() async {
  214. await coredataContext.perform { [self] in
  215. let newOverride = OverrideStored(context: self.coredataContext)
  216. newOverride.duration = self.durationProfile as NSDecimalNumber
  217. newOverride.indefinite = self._indefinite
  218. newOverride.percentage = self.percentageProfiles
  219. newOverride.smbIsOff = self.smbIsOff
  220. if self.profileName.isNotEmpty {
  221. newOverride.name = self.profileName
  222. } else {
  223. let formattedDate = dateFormatter.string(from: Date())
  224. newOverride.name = "Profile \(formattedDate)"
  225. }
  226. newOverride.isPreset = true
  227. newOverride.date = Date()
  228. newOverride.enabled = false
  229. if override_target {
  230. newOverride.target = (
  231. units == .mmolL
  232. ? target.asMgdL
  233. : target
  234. ) as NSDecimalNumber
  235. }
  236. if advancedSettings {
  237. newOverride.advancedSettings = true
  238. if !isfAndCr {
  239. newOverride.isfAndCr = false
  240. newOverride.isf = isf
  241. newOverride.cr = cr
  242. } else { newOverride.isfAndCr = true }
  243. if smbIsAlwaysOff {
  244. newOverride.smbIsAlwaysOff = true
  245. newOverride.start = start as NSDecimalNumber
  246. newOverride.end = end as NSDecimalNumber
  247. } else { newOverride.smbIsAlwaysOff = false }
  248. newOverride.smbMinutes = smbMinutes as NSDecimalNumber
  249. newOverride.uamMinutes = uamMinutes as NSDecimalNumber
  250. }
  251. do {
  252. guard coredataContext.hasChanges else { return }
  253. try coredataContext.save()
  254. /// Custom Notification to update Presets View
  255. Foundation.NotificationCenter.default.post(name: .didUpdateOverridePresets, object: nil)
  256. /// prevent showing the current config of the recently added Preset
  257. Task {
  258. await resetStateVariables()
  259. }
  260. } catch let error as NSError {
  261. debugPrint(
  262. "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to save Override Preset to Core Data with error: \(error.userInfo)"
  263. )
  264. }
  265. }
  266. }
  267. }
  268. // MARK: - Setup Override Presets Array
  269. extension OverrideProfilesConfig.StateModel {
  270. // Fill the array of the Profile Presets to display them in the UI
  271. private func setupOverridePresetsArray() {
  272. Task {
  273. let ids = await self.fetchForProfilePresets()
  274. await updateOverridePresetsArray(with: ids)
  275. }
  276. }
  277. /// Returns the NSManagedObjectID of the Override Presets
  278. private func fetchForProfilePresets() async -> [NSManagedObjectID] {
  279. let result = await CoreDataStack.shared.fetchEntitiesAsync(
  280. ofType: OverrideStored.self,
  281. onContext: coredataContext,
  282. predicate: NSPredicate.allOverridePresets,
  283. key: "name",
  284. ascending: true
  285. )
  286. return await coredataContext.perform {
  287. return result.map(\.objectID)
  288. }
  289. }
  290. @MainActor private func updateOverridePresetsArray(with IDs: [NSManagedObjectID]) async {
  291. do {
  292. let overrideObjects = try IDs.compactMap { id in
  293. try viewContext.existingObject(with: id) as? OverrideStored
  294. }
  295. profilePresets = overrideObjects
  296. } catch {
  297. debugPrint(
  298. "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to extract Overrides as NSManagedObjects from the NSManagedObjectIDs with error: \(error.localizedDescription)"
  299. )
  300. }
  301. }
  302. }
  303. // MARK: - Profile Cancelling
  304. extension OverrideProfilesConfig.StateModel {
  305. /// Gets the corresponding NSManagedObjectID of the current active Profile and cancels it
  306. func cancelActiveProfile() {
  307. Task {
  308. let id = await getActiveProfile()
  309. await cancelActiveProfile(withID: id)
  310. }
  311. }
  312. func getActiveProfile() async -> NSManagedObjectID? {
  313. let results = await CoreDataStack.shared.fetchEntitiesAsync(
  314. ofType: OverrideStored.self,
  315. onContext: coredataContext,
  316. predicate: NSPredicate.lastActiveOverride,
  317. key: "date",
  318. ascending: false,
  319. fetchLimit: 1
  320. )
  321. return await coredataContext.perform {
  322. return results.first.map(\.objectID)
  323. }
  324. }
  325. @MainActor func cancelActiveProfile(withID id: NSManagedObjectID?) async {
  326. guard let id = id else { return }
  327. return await viewContext.perform {
  328. do {
  329. let profileToCancel = try self.viewContext.existingObject(with: id) as? OverrideStored
  330. profileToCancel?.enabled = false
  331. /// Update the 'Cancel Profile' button state
  332. self.isEnabled = false
  333. guard self.viewContext.hasChanges else { return }
  334. try self.viewContext.save()
  335. } catch {
  336. debugPrint(
  337. "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to cancel Profile with error: \(error.localizedDescription)"
  338. )
  339. }
  340. }
  341. }
  342. /// Gets the corresponding NSManagedObjectIDs of all active Profiles and cancels them
  343. @MainActor func disableAllActiveProfiles(except profileID: NSManagedObjectID? = nil) async {
  344. /// get all NSManagedObject IDs of all active Profiles
  345. let ids = await loadLatestOverrideConfigurations(fetchLimit: 0) /// 0 = no fetch limit
  346. /// end all active profiles
  347. do {
  348. let results = try ids.compactMap { id in
  349. try viewContext.existingObject(with: id) as? OverrideStored
  350. }
  351. for profile in results {
  352. if profile.objectID != profileID {
  353. profile.enabled = false
  354. }
  355. }
  356. try await viewContext.perform {
  357. guard self.viewContext.hasChanges else { return }
  358. try self.viewContext.save()
  359. }
  360. } catch {
  361. debugPrint(
  362. "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to disable active Profiles with error: \(error.localizedDescription)"
  363. )
  364. }
  365. }
  366. }
  367. // MARK: - Setup the State variables with the last Override configuration
  368. extension OverrideProfilesConfig.StateModel {
  369. /// First get the latest Overrides corresponding NSManagedObjectID with a background fetch
  370. /// Then unpack it on the view context and update the State variables which can be used on in the View for some Logic
  371. /// This also needs to be called when we cancel a Profile via the Home View to update the State of the Button for this case
  372. func updateLatestOverrideConfiguration() {
  373. Task {
  374. let id = await loadLatestOverrideConfigurations(fetchLimit: 1)
  375. await updateLatestOverrideConfigurationOfState(from: id)
  376. await setCurrentOverrideName(from: id)
  377. }
  378. }
  379. func loadLatestOverrideConfigurations(fetchLimit: Int) async -> [NSManagedObjectID] {
  380. let results = await CoreDataStack.shared.fetchEntitiesAsync(
  381. ofType: OverrideStored.self,
  382. onContext: coredataContext,
  383. predicate: NSPredicate.lastActiveOverride,
  384. key: "date",
  385. ascending: false,
  386. fetchLimit: fetchLimit
  387. )
  388. return await coredataContext.perform {
  389. return results.map(\.objectID)
  390. }
  391. }
  392. @MainActor func updateLatestOverrideConfigurationOfState(from IDs: [NSManagedObjectID]) async {
  393. do {
  394. let result = try IDs.compactMap { id in
  395. try viewContext.existingObject(with: id) as? OverrideStored
  396. }
  397. isEnabled = result.first?.enabled ?? false
  398. if !isEnabled {
  399. await resetStateVariables()
  400. }
  401. } catch {
  402. debugPrint(
  403. "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to updateLatestOverrideConfiguration"
  404. )
  405. }
  406. }
  407. /// Sets the current active Preset name to show in the UI
  408. @MainActor func setCurrentOverrideName(from IDs: [NSManagedObjectID]) async {
  409. do {
  410. guard let firstID = IDs.first else {
  411. activeOverrideName = "Custom Override"
  412. currentActiveOverride = nil
  413. return
  414. }
  415. if let overrideToEdit = try viewContext.existingObject(with: firstID) as? OverrideStored {
  416. if overrideToEdit.isPreset {
  417. await handlePresetOverride(overrideToEdit)
  418. } else {
  419. currentActiveOverride = overrideToEdit
  420. activeOverrideName = overrideToEdit.name ?? "Custom Override"
  421. }
  422. }
  423. } catch {
  424. debugPrint(
  425. "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to set active preset name with error: \(error.localizedDescription)"
  426. )
  427. }
  428. }
  429. @MainActor private func handlePresetOverride(_ overrideToEdit: OverrideStored) async {
  430. do {
  431. await copyOverride(overrideToEdit)
  432. await cancelActiveProfile(withID: overrideToEdit.objectID)
  433. let ids = await loadLatestOverrideConfigurations(fetchLimit: 1)
  434. if let copiedID = ids.first,
  435. let copiedOverride = try viewContext.existingObject(with: copiedID) as? OverrideStored
  436. {
  437. currentActiveOverride = copiedOverride
  438. activeOverrideName = copiedOverride.name ?? "Custom Override"
  439. }
  440. } catch {
  441. debugPrint(
  442. "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to handle preset override with error: \(error.localizedDescription)"
  443. )
  444. }
  445. }
  446. }
  447. // MARK: - Profile Preset Deletion
  448. extension OverrideProfilesConfig.StateModel {
  449. /// marked as MainActor to be able to publish changes from the background
  450. /// - Parameter: NSManagedObjectID to be able to transfer the object safely from one thread to another thread
  451. @MainActor func invokeProfilePresetDeletion(_ objectID: NSManagedObjectID) {
  452. Task {
  453. await deleteProfile(objectID)
  454. }
  455. }
  456. private func deleteProfile(_ objectID: NSManagedObjectID) async {
  457. CoreDataStack.shared.deleteObject(identifiedBy: objectID)
  458. }
  459. }
  460. // MARK: - Helper functions for Overrides
  461. extension OverrideProfilesConfig.StateModel {
  462. @MainActor func resetStateVariables() async {
  463. durationProfile = 0
  464. _indefinite = true
  465. percentageProfiles = 100
  466. advancedSettings = false
  467. smbIsOff = false
  468. profileName = ""
  469. override_target = false
  470. isf = true
  471. cr = true
  472. isfAndCr = true
  473. smbIsAlwaysOff = false
  474. start = 0
  475. end = 23
  476. smbMinutes = defaultSmbMinutes
  477. uamMinutes = defaultUamMinutes
  478. target = 0
  479. }
  480. // Copy the current Override if it is a running Preset
  481. /// otherwise we would edit the current running Preset
  482. @MainActor private func copyOverride(_ override: OverrideStored) async {
  483. let newOverride = OverrideStored(context: viewContext)
  484. newOverride.duration = override.duration
  485. newOverride.indefinite = override.indefinite
  486. newOverride.percentage = override.percentage
  487. newOverride.smbIsOff = override.smbIsOff
  488. newOverride.name = override.name
  489. newOverride.isPreset = false // no Preset
  490. newOverride.date = Date()
  491. newOverride.enabled = override.enabled
  492. newOverride.target = override.target
  493. newOverride.advancedSettings = override.advancedSettings
  494. newOverride.isfAndCr = override.isfAndCr
  495. newOverride.isf = override.isf
  496. newOverride.cr = override.cr
  497. newOverride.smbIsAlwaysOff = override.smbIsAlwaysOff
  498. newOverride.start = override.start
  499. newOverride.end = override.end
  500. newOverride.smbMinutes = override.smbMinutes
  501. newOverride.uamMinutes = override.uamMinutes
  502. await viewContext.perform {
  503. do {
  504. guard self.viewContext.hasChanges else { return }
  505. try self.viewContext.save()
  506. } catch let error as NSError {
  507. debugPrint(
  508. "\(DebuggingIdentifiers.failed) \(#file) \(#function) Failed to copy Override with error: \(error.userInfo)"
  509. )
  510. }
  511. }
  512. }
  513. }
  514. // MARK: - TEMP TARGET
  515. extension OverrideProfilesConfig.StateModel {
  516. func enact() {
  517. guard durationTT > 0 else {
  518. return
  519. }
  520. var lowTarget = low
  521. if viewPercantage {
  522. lowTarget = Decimal(round(Double(computeTarget())))
  523. coredataContext.performAndWait {
  524. let saveToCoreData = TempTargets(context: self.coredataContext)
  525. saveToCoreData.id = UUID().uuidString
  526. saveToCoreData.active = true
  527. saveToCoreData.hbt = hbt
  528. saveToCoreData.date = Date()
  529. saveToCoreData.duration = durationTT as NSDecimalNumber
  530. saveToCoreData.startDate = Date()
  531. try? self.coredataContext.save()
  532. }
  533. didSaveSettings = true
  534. } else {
  535. coredataContext.performAndWait {
  536. let saveToCoreData = TempTargets(context: coredataContext)
  537. saveToCoreData.active = false
  538. saveToCoreData.date = Date()
  539. do {
  540. guard coredataContext.hasChanges else { return }
  541. try coredataContext.save()
  542. } catch {
  543. print(error.localizedDescription)
  544. }
  545. }
  546. }
  547. var highTarget = lowTarget
  548. if units == .mmolL, !viewPercantage {
  549. lowTarget = Decimal(round(Double(lowTarget.asMgdL)))
  550. highTarget = lowTarget
  551. }
  552. let entry = TempTarget(
  553. name: TempTarget.custom,
  554. createdAt: date,
  555. targetTop: highTarget,
  556. targetBottom: lowTarget,
  557. duration: durationTT,
  558. enteredBy: TempTarget.manual,
  559. reason: TempTarget.custom
  560. )
  561. storage.storeTempTargets([entry])
  562. showModal(for: nil)
  563. }
  564. func cancel() {
  565. storage.storeTempTargets([TempTarget.cancel(at: Date())])
  566. showModal(for: nil)
  567. coredataContext.performAndWait {
  568. let saveToCoreData = TempTargets(context: self.coredataContext)
  569. saveToCoreData.active = false
  570. saveToCoreData.date = Date()
  571. do {
  572. guard coredataContext.hasChanges else { return }
  573. try coredataContext.save()
  574. } catch {
  575. print(error.localizedDescription)
  576. }
  577. let setHBT = TempTargetsSlider(context: self.coredataContext)
  578. setHBT.enabled = false
  579. setHBT.date = Date()
  580. do {
  581. guard coredataContext.hasChanges else { return }
  582. try coredataContext.save()
  583. } catch {
  584. print(error.localizedDescription)
  585. }
  586. }
  587. }
  588. func save() {
  589. guard durationTT > 0 else {
  590. return
  591. }
  592. var lowTarget = low
  593. if viewPercantage {
  594. lowTarget = Decimal(round(Double(computeTarget())))
  595. didSaveSettings = true
  596. }
  597. var highTarget = lowTarget
  598. if units == .mmolL, !viewPercantage {
  599. lowTarget = Decimal(round(Double(lowTarget.asMgdL)))
  600. highTarget = lowTarget
  601. }
  602. let entry = TempTarget(
  603. name: newPresetName.isEmpty ? TempTarget.custom : newPresetName,
  604. createdAt: Date(),
  605. targetTop: highTarget,
  606. targetBottom: lowTarget,
  607. duration: durationTT,
  608. enteredBy: TempTarget.manual,
  609. reason: newPresetName.isEmpty ? TempTarget.custom : newPresetName
  610. )
  611. presetsTT.append(entry)
  612. storage.storePresets(presetsTT)
  613. if viewPercantage {
  614. let id = entry.id
  615. coredataContext.performAndWait {
  616. let saveToCoreData = TempTargetsSlider(context: self.coredataContext)
  617. saveToCoreData.id = id
  618. saveToCoreData.isPreset = true
  619. saveToCoreData.enabled = true
  620. saveToCoreData.hbt = hbt
  621. saveToCoreData.date = Date()
  622. saveToCoreData.duration = durationTT as NSDecimalNumber
  623. do {
  624. guard coredataContext.hasChanges else { return }
  625. try coredataContext.save()
  626. } catch {
  627. print(error.localizedDescription)
  628. }
  629. }
  630. }
  631. }
  632. func enactPreset(id: String) {
  633. if var preset = presetsTT.first(where: { $0.id == id }) {
  634. preset.createdAt = Date()
  635. storage.storeTempTargets([preset])
  636. showModal(for: nil)
  637. coredataContext.performAndWait {
  638. var tempTargetsArray = [TempTargetsSlider]()
  639. let requestTempTargets = TempTargetsSlider.fetchRequest() as NSFetchRequest<TempTargetsSlider>
  640. let sortTT = NSSortDescriptor(key: "date", ascending: false)
  641. requestTempTargets.sortDescriptors = [sortTT]
  642. try? tempTargetsArray = coredataContext.fetch(requestTempTargets)
  643. let whichID = tempTargetsArray.first(where: { $0.id == id })
  644. if whichID != nil {
  645. let saveToCoreData = TempTargets(context: self.coredataContext)
  646. saveToCoreData.active = true
  647. saveToCoreData.date = Date()
  648. saveToCoreData.hbt = whichID?.hbt ?? 160
  649. // saveToCoreData.id = id
  650. saveToCoreData.startDate = Date()
  651. saveToCoreData.duration = whichID?.duration ?? 0
  652. do {
  653. guard coredataContext.hasChanges else { return }
  654. try coredataContext.save()
  655. } catch {
  656. print(error.localizedDescription)
  657. }
  658. } else {
  659. let saveToCoreData = TempTargets(context: self.coredataContext)
  660. saveToCoreData.active = false
  661. saveToCoreData.date = Date()
  662. do {
  663. guard coredataContext.hasChanges else { return }
  664. try coredataContext.save()
  665. } catch {
  666. print(error.localizedDescription)
  667. }
  668. }
  669. }
  670. }
  671. }
  672. func removePreset(id: String) {
  673. presetsTT = presetsTT.filter { $0.id != id }
  674. storage.storePresets(presetsTT)
  675. }
  676. func computeTarget() -> Decimal {
  677. var ratio = Decimal(percentageTT / 100)
  678. let c = Decimal(hbt - 100)
  679. var target = (c / ratio) - c + 100
  680. if c * (c + target - 100) <= 0 {
  681. ratio = maxValue
  682. target = (c / ratio) - c + 100
  683. }
  684. return Decimal(Double(target))
  685. }
  686. }