Skip to content

Commit

Permalink
Merge pull request loopandlearn#208 from loopandlearn/ios17-calendar
Browse files Browse the repository at this point in the history
Preparation for ios17 calendar access
  • Loading branch information
marionbarker authored Jul 29, 2023
2 parents 5b2dbdc + bd20e5c commit 1136c55
Show file tree
Hide file tree
Showing 6 changed files with 184 additions and 118 deletions.
4 changes: 4 additions & 0 deletions LoopFollow.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
3F1335F351590E573D8E6962 /* Pods_LoopFollow.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A7D55B42A22051DAD69E89D0 /* Pods_LoopFollow.framework */; };
DD07B5C929E2F9C400C6A635 /* NightscoutUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD07B5C829E2F9C400C6A635 /* NightscoutUtils.swift */; };
DD152D3B24C01B2300361FA2 /* InfoDisplaySettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD152D3A24C01B2300361FA2 /* InfoDisplaySettingsViewController.swift */; };
DD7FFAFD2A72953000C3A304 /* EKEventStore+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD7FFAFC2A72953000C3A304 /* EKEventStore+Extensions.swift */; };
DD98F54424BCEFEE0007425A /* ShareClientExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD98F54324BCEFEE0007425A /* ShareClientExtension.swift */; };
DDCF979424C0D380002C9752 /* UIViewExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCF979324C0D380002C9752 /* UIViewExtension.swift */; };
DDCF979624C1443C002C9752 /* GeneralSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCF979524C1443C002C9752 /* GeneralSettingsViewController.swift */; };
Expand Down Expand Up @@ -177,6 +178,7 @@
A7D55B42A22051DAD69E89D0 /* Pods_LoopFollow.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_LoopFollow.framework; sourceTree = BUILT_PRODUCTS_DIR; };
DD07B5C829E2F9C400C6A635 /* NightscoutUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NightscoutUtils.swift; sourceTree = "<group>"; };
DD152D3A24C01B2300361FA2 /* InfoDisplaySettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = InfoDisplaySettingsViewController.swift; sourceTree = "<group>"; };
DD7FFAFC2A72953000C3A304 /* EKEventStore+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "EKEventStore+Extensions.swift"; sourceTree = "<group>"; };
DD98F54324BCEFEE0007425A /* ShareClientExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareClientExtension.swift; sourceTree = "<group>"; };
DDCF979324C0D380002C9752 /* UIViewExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewExtension.swift; sourceTree = "<group>"; };
DDCF979524C1443C002C9752 /* GeneralSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralSettingsViewController.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -382,6 +384,7 @@
DD98F54324BCEFEE0007425A /* ShareClientExtension.swift */,
DDCF979324C0D380002C9752 /* UIViewExtension.swift */,
FC1BB3A7252CA4C5003839C2 /* UIImageExtension.swift */,
DD7FFAFC2A72953000C3A304 /* EKEventStore+Extensions.swift */,
);
path = Extensions;
sourceTree = "<group>";
Expand Down Expand Up @@ -862,6 +865,7 @@
files = (
FCC68850248935D800A0279D /* AlarmViewController.swift in Sources */,
FC7CE59F248D8D23001F83B8 /* SnoozeViewController.swift in Sources */,
DD7FFAFD2A72953000C3A304 /* EKEventStore+Extensions.swift in Sources */,
FCC6886724898F8000A0279D /* UserDefaultsValue.swift in Sources */,
DDCF979E24C2382A002C9752 /* AppStateController.swift in Sources */,
FC97881E2485969B00A7906C /* NightScoutViewController.swift in Sources */,
Expand Down
8 changes: 6 additions & 2 deletions LoopFollow/Application/AppDelegate.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
print("User has declined notifications")
}
}

let store = EKEventStore()
store.requestAccess(to: .event) {(granted, error) in
if !granted { return }
store.requestCalendarAccess { (granted, error) in
if !granted {
print("Failed to get calendar access: \(String(describing: error))")
return
}
}

UNUserNotificationCenter.current().delegate = self
Expand Down
34 changes: 34 additions & 0 deletions LoopFollow/Extensions/EKEventStore+Extensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
//
// EKEventStore+Extensions.swift
// LoopFollow
//
// Created by Jonas Björkert on 2023-07-27.
// Copyright © 2023 Jon Fawcett. All rights reserved.
//

import Foundation
import EventKit

#if swift(>=5.9)
extension EKEventStore {
func requestCalendarAccess(completion: @escaping (Bool, Error?) -> Void) {
if #available(iOS 17, *) {
requestFullAccessToEvents { (granted, error) in
completion(granted, error)
}
} else {
requestAccess(to: .event) { (granted, error) in
completion(granted, error)
}
}
}
}
#else
extension EKEventStore {
func requestCalendarAccess(completion: @escaping (Bool, Error?) -> Void) {
requestAccess(to: .event) { (granted, error) in
completion(granted, error)
}
}
}
#endif
4 changes: 3 additions & 1 deletion LoopFollow/Info.plist
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,9 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>NSCalendarsFullAccessUsageDescription</key>
<string>Loop Follow would like to access your calendar to update BG readings</string>
<key>ITSAppUsesNonExemptEncryption</key>
<false/>
<false/>
</dict>
</plist>
229 changes: 118 additions & 111 deletions LoopFollow/ViewControllers/MainViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -641,126 +641,133 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
}
})
}

// Write calendar

func writeCalendar() {
if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Write calendar start") }
store.requestAccess(to: .event) {(granted, error) in
if !granted { return }

if UserDefaultsRepository.calendarIdentifier.value == "" { return }

if self.bgData.count < 1 { return }

// This lets us fire the method to write Min Ago entries only once a minute starting after 6 minutes but allows new readings through
if self.lastCalDate == self.bgData[self.bgData.count - 1].date
&& (self.calTimer.isValid || (dateTimeUtils.getNowTimeIntervalUTC() - self.lastCalDate) < 360) {
if UserDefaultsRepository.debugLog.value {
self.writeDebugLog(value: "Write calendar start")
}

self.store.requestCalendarAccess { (granted, error) in
if !granted {
print("Failed to get calendar access: \(String(describing: error))")
return
}
self.processCalendarUpdates()
}
}

func processCalendarUpdates() {
if UserDefaultsRepository.calendarIdentifier.value == "" { return }

if self.bgData.count < 1 { return }

// This lets us fire the method to write Min Ago entries only once a minute starting after 6 minutes but allows new readings through
if self.lastCalDate == self.bgData[self.bgData.count - 1].date
&& (self.calTimer.isValid || (dateTimeUtils.getNowTimeIntervalUTC() - self.lastCalDate) < 360) {
return
}

// Create Event info
let deltaBG = self.bgData[self.bgData.count - 1].sgv - self.bgData[self.bgData.count - 2].sgv as Int
let deltaTime = (TimeInterval(Date().timeIntervalSince1970) - self.bgData[self.bgData.count - 1].date) / 60
var deltaString = ""
if deltaBG < 0 {
deltaString = bgUnits.toDisplayUnits(String(deltaBG))
}
else
{
deltaString = "+" + bgUnits.toDisplayUnits(String(deltaBG))
}
let direction = self.bgDirectionGraphic(self.bgData[self.bgData.count - 1].direction ?? "")
var eventStartDate = Date(timeIntervalSince1970: self.bgData[self.bgData.count - 1].date)
// Create Event info
let deltaBG = self.bgData[self.bgData.count - 1].sgv - self.bgData[self.bgData.count - 2].sgv as Int
let deltaTime = (TimeInterval(Date().timeIntervalSince1970) - self.bgData[self.bgData.count - 1].date) / 60
var deltaString = ""
if deltaBG < 0 {
deltaString = bgUnits.toDisplayUnits(String(deltaBG))
}
else
{
deltaString = "+" + bgUnits.toDisplayUnits(String(deltaBG))
}
let direction = self.bgDirectionGraphic(self.bgData[self.bgData.count - 1].direction ?? "")

var eventStartDate = Date(timeIntervalSince1970: self.bgData[self.bgData.count - 1].date)
// if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Calendar start date") }
var eventEndDate = eventStartDate.addingTimeInterval(60 * 10)
var eventTitle = UserDefaultsRepository.watchLine1.value
if (UserDefaultsRepository.watchLine2.value.count > 1) {
eventTitle += "\n" + UserDefaultsRepository.watchLine2.value
}
eventTitle = eventTitle.replacingOccurrences(of: "%BG%", with: bgUnits.toDisplayUnits(String(self.bgData[self.bgData.count - 1].sgv)))
eventTitle = eventTitle.replacingOccurrences(of: "%DIRECTION%", with: direction)
eventTitle = eventTitle.replacingOccurrences(of: "%DELTA%", with: deltaString)
if self.currentOverride != 1.0 {
let val = Int( self.currentOverride*100)
// let overrideText = String(format:"%f1", self.currentOverride*100)
let text = String(val) + "%"
eventTitle = eventTitle.replacingOccurrences(of: "%OVERRIDE%", with: text)
} else {
eventTitle = eventTitle.replacingOccurrences(of: "%OVERRIDE%", with: "")
}
eventTitle = eventTitle.replacingOccurrences(of: "%LOOP%", with: self.latestLoopStatusString)
var minAgo = ""
if deltaTime > 9 {
// write old BG reading and continue pushing out end date to show last entry
minAgo = String(Int(deltaTime)) + " min"
eventEndDate = eventStartDate.addingTimeInterval((60 * 10) + (deltaTime * 60))
}
var cob = "0"
if self.latestCOB != "" {
cob = self.latestCOB
}
var basal = "~"
if self.latestBasal != "" {
basal = self.latestBasal
}
var iob = "0"
if self.latestIOB != "" {
iob = self.latestIOB
}
eventTitle = eventTitle.replacingOccurrences(of: "%MINAGO%", with: minAgo)
eventTitle = eventTitle.replacingOccurrences(of: "%IOB%", with: iob)
eventTitle = eventTitle.replacingOccurrences(of: "%COB%", with: cob)
eventTitle = eventTitle.replacingOccurrences(of: "%BASAL%", with: basal)



// Delete Events from last 2 hours and 2 hours in future
var deleteStartDate = Date().addingTimeInterval(-60*60*2)
var deleteEndDate = Date().addingTimeInterval(60*60*2)
// guard solves for some ios upgrades removing the calendar
guard let deleteCalendar = self.store.calendar(withIdentifier: UserDefaultsRepository.calendarIdentifier.value) as? EKCalendar else { return }
var predicate2 = self.store.predicateForEvents(withStart: deleteStartDate, end: deleteEndDate, calendars: [deleteCalendar])
var eVDelete = self.store.events(matching: predicate2) as [EKEvent]?
if eVDelete != nil {
for i in eVDelete! {
do {
try self.store.remove(i, span: EKSpan.thisEvent, commit: true)
//if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Calendar Delete") }
} catch let error {
//if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Error - Calendar Delete") }
print(error)
}
}
}

// Write New Event
var event = EKEvent(eventStore: self.store)
event.title = eventTitle
event.startDate = eventStartDate
event.endDate = eventEndDate
event.calendar = self.store.calendar(withIdentifier: UserDefaultsRepository.calendarIdentifier.value)
do {
try self.store.save(event, span: .thisEvent, commit: true)
self.calTimer.invalidate()
self.startCalTimer(time: (60 * 1))

self.lastCalDate = self.bgData[self.bgData.count - 1].date
//if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Calendar Write: " + eventTitle) }
//UserDefaultsRepository.savedEventID.value = event.eventIdentifier //save event id to access this particular event later
} catch {
// Display error to user
//if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Error: Calendar Write") }
}
var eventEndDate = eventStartDate.addingTimeInterval(60 * 10)
var eventTitle = UserDefaultsRepository.watchLine1.value
if (UserDefaultsRepository.watchLine2.value.count > 1) {
eventTitle += "\n" + UserDefaultsRepository.watchLine2.value
}
eventTitle = eventTitle.replacingOccurrences(of: "%BG%", with: bgUnits.toDisplayUnits(String(self.bgData[self.bgData.count - 1].sgv)))
eventTitle = eventTitle.replacingOccurrences(of: "%DIRECTION%", with: direction)
eventTitle = eventTitle.replacingOccurrences(of: "%DELTA%", with: deltaString)
if self.currentOverride != 1.0 {
let val = Int( self.currentOverride*100)
// let overrideText = String(format:"%f1", self.currentOverride*100)
let text = String(val) + "%"
eventTitle = eventTitle.replacingOccurrences(of: "%OVERRIDE%", with: text)
} else {
eventTitle = eventTitle.replacingOccurrences(of: "%OVERRIDE%", with: "")
}
eventTitle = eventTitle.replacingOccurrences(of: "%LOOP%", with: self.latestLoopStatusString)
var minAgo = ""
if deltaTime > 9 {
// write old BG reading and continue pushing out end date to show last entry
minAgo = String(Int(deltaTime)) + " min"
eventEndDate = eventStartDate.addingTimeInterval((60 * 10) + (deltaTime * 60))
}
var cob = "0"
if self.latestCOB != "" {
cob = self.latestCOB
}
var basal = "~"
if self.latestBasal != "" {
basal = self.latestBasal
}
var iob = "0"
if self.latestIOB != "" {
iob = self.latestIOB
}
eventTitle = eventTitle.replacingOccurrences(of: "%MINAGO%", with: minAgo)
eventTitle = eventTitle.replacingOccurrences(of: "%IOB%", with: iob)
eventTitle = eventTitle.replacingOccurrences(of: "%COB%", with: cob)
eventTitle = eventTitle.replacingOccurrences(of: "%BASAL%", with: basal)

if UserDefaultsRepository.saveImage.value {
DispatchQueue.main.async {
self.saveChartImage()


// Delete Events from last 2 hours and 2 hours in future
var deleteStartDate = Date().addingTimeInterval(-60*60*2)
var deleteEndDate = Date().addingTimeInterval(60*60*2)
// guard solves for some ios upgrades removing the calendar
guard let deleteCalendar = self.store.calendar(withIdentifier: UserDefaultsRepository.calendarIdentifier.value) as? EKCalendar else { return }
var predicate2 = self.store.predicateForEvents(withStart: deleteStartDate, end: deleteEndDate, calendars: [deleteCalendar])
var eVDelete = self.store.events(matching: predicate2) as [EKEvent]?
if eVDelete != nil {
for i in eVDelete! {
do {
try self.store.remove(i, span: EKSpan.thisEvent, commit: true)
//if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Calendar Delete") }
} catch let error {
//if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Error - Calendar Delete") }
print(error)
}
}

}


// Write New Event
var event = EKEvent(eventStore: self.store)
event.title = eventTitle
event.startDate = eventStartDate
event.endDate = eventEndDate
event.calendar = self.store.calendar(withIdentifier: UserDefaultsRepository.calendarIdentifier.value)
do {
try self.store.save(event, span: .thisEvent, commit: true)
self.calTimer.invalidate()
self.startCalTimer(time: (60 * 1))

self.lastCalDate = self.bgData[self.bgData.count - 1].date
//if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Calendar Write: " + eventTitle) }
//UserDefaultsRepository.savedEventID.value = event.eventIdentifier //save event id to access this particular event later
} catch {
print("*** Error storing to the calendar")
// Display error to user
//if UserDefaultsRepository.debugLog.value { self.writeDebugLog(value: "Error: Calendar Write") }
}

if UserDefaultsRepository.saveImage.value {
DispatchQueue.main.async {
self.saveChartImage()
}

}
}

Expand Down
Loading

0 comments on commit 1136c55

Please sign in to comment.