improvements to Watch app in follower mode

This commit is contained in:
Paul Plant 2024-03-23 12:26:23 +01:00
parent b1773513e9
commit 9fc843a849
6 changed files with 93 additions and 50 deletions

View File

@ -7,6 +7,7 @@
//
import Foundation
import SwiftUI
enum ConstantsAppleWatch {
@ -19,4 +20,10 @@ enum ConstantsAppleWatch {
/// less than how many pixels wide should we consider the screen size to the "small"
static let pixelWidthLimitForSmallScreen: Double = 185
/// colour for the "requesting data" symbol
static let requestingDataIconColorActive = Color(.green) //.opacity(0.9)
static let requestingDataIconColorInactive = Color(.white).opacity(0.3)
static let requestingDataIconFontSize: CGFloat = 6
static let requestingDataIconSFSymbolName: String = "circle.fill" // "iphone.gen3.radiowaves.left.and.right" //
}

View File

@ -53,8 +53,7 @@ class WatchStateModel: NSObject, ObservableObject {
@Published var lastUpdatedTimeString: String = ""
@Published var debugString: String = "Debug..."
@Published var chartHoursIndex: Int = 1
var lastComplicationUpdateDate: Date = .distantPast
@Published var requestingDataIconColor: Color = ConstantsAppleWatch.requestingDataIconColorInactive
init(session: WCSession = .default) {
self.session = session
@ -200,11 +199,17 @@ class WatchStateModel: NSObject, ObservableObject {
/// check when the last follower connection was and compare that to the actual time
func getFollowerConnectionNetworkStatus() -> (image: Image, color: Color) {
if let timeDifferenceInSeconds = Calendar.current.dateComponents([.second], from: timeStampOfLastFollowerConnection, to: Date()).second, timeDifferenceInSeconds > secondsUntilFollowerDisconnectWarning {
return(Image(systemName: "network.slash"), Color(.red))
} else {
return(Image(systemName: "network"), Color(.green))
}
if timeStampOfLastFollowerConnection > Date().addingTimeInterval(-Double(secondsUntilFollowerDisconnectWarning)) {
return(Image(systemName: "network"), Color(.green))
} else {
if followerBackgroundKeepAliveType != .disabled {
return(Image(systemName: "network.slash"), Color(.red))
} else {
// if keep-alive is disabled, then this will never show a constant server connection so just "disable"
// the icon when not recent. It would be incorrect to show a red error.
return(Image(systemName: "network.slash"), Color(.gray))
}
}
}
/// check when the last heartbeat connection was and compare that to the actual time
@ -226,10 +231,11 @@ class WatchStateModel: NSObject, ObservableObject {
guard session.activationState == .activated else {
session.activate()
return
}
}
// change the text, this must be done in the main thread but only do it if the watch app is reachable
if session.isReachable {
DispatchQueue.main.async {
self.requestingDataIconColor = ConstantsAppleWatch.requestingDataIconColorActive
self.debugString.removeLast(4)
self.debugString += "Fetching"
}
@ -259,7 +265,7 @@ class WatchStateModel: NSObject, ObservableObject {
sensorAgeInMinutes = watchState.sensorAgeInMinutes ?? 0
sensorMaxAgeInMinutes = watchState.sensorMaxAgeInMinutes ?? 0
timeStampOfLastFollowerConnection = watchState.timeStampOfLastFollowerConnection ?? .distantPast
secondsUntilFollowerDisconnectWarning = watchState.secondsUntilFollowerDisconnectWarning ?? 5
secondsUntilFollowerDisconnectWarning = watchState.secondsUntilFollowerDisconnectWarning ?? 70// give it some more time compared to the iOS app
lastHeartBeatTimeStamp = watchState.lastHeartBeatTimeStamp ?? .distantPast
heartbeatShowDisconnectedTimeInSeconds = watchState.heartbeatShowDisconnectedTimeInSeconds ?? 5
isMaster = watchState.isMaster ?? true
@ -267,36 +273,25 @@ class WatchStateModel: NSObject, ObservableObject {
followerBackgroundKeepAliveType = FollowerBackgroundKeepAliveType(rawValue: watchState.followerBackgroundKeepAliveTypeRawValue ?? 0) ?? .normal
disableComplications = watchState.disableComplications ?? false
debugString = "🩸 WATCH DEBUG"
debugString += "\nLast state: \(Date().formatted(date: .omitted, time: .standard))"
// check if there is any BG data available before updating the strings accordingly
// check if there is any BG data available before updating the data source info strings accordingly
if let bgReadingDate = bgReadingDate() {
lastUpdatedTextString = "Last reading "
lastUpdatedTimeString = bgReadingDate.formatted(date: .omitted, time: .shortened)
debugString += "\nBG updated: \(bgReadingDate.formatted(date: .omitted, time: .standard))"
} else {
lastUpdatedTextString = "No sensor data"
lastUpdatedTimeString = ""
debugString += "\nBG updated: ---"
}
debugString += "\nBG values: \(bgReadingValues.count)"
if !isMaster {
debugString += "\nServer connection: \(timeStampOfLastFollowerConnection.formatted(date: .omitted, time: .standard))"
if followerBackgroundKeepAliveType == .heartbeat {
debugString += "\nLast hearbeat: \(lastHeartBeatTimeStamp.formatted(date: .omitted, time: .standard))"
}
}
debugString += "\nScreen width: \(Int(WKInterfaceDevice.current().screenBounds.size.width))"
debugString += "\n\(ConstantsHomeView.applicationName): Idle"
debugString = generateDebugString()
// now process the shared user defaults to get data for the WidgetKit complications
updateWatchSharedUserDefaults()
// change the requesting icon color back after a small delay to prevent it
// flashing on/off too quickly
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
self.requestingDataIconColor = ConstantsAppleWatch.requestingDataIconColorInactive
}
}
/// once we've process the state update, then save this data to the shared app group so that the complication can read it
@ -319,6 +314,34 @@ class WatchStateModel: NSObject, ObservableObject {
// now that the new data is stored in the app group, try to force the complications to reload
WidgetCenter.shared.reloadAllTimelines()
}
// generate a debugString
private func generateDebugString() -> String {
var debugString = "Last state: \(Date().formatted(date: .omitted, time: .standard))"
// check if there is any BG data available before updating the strings accordingly
if let bgReadingDate = bgReadingDate() {
debugString += "\nBG updated: \(bgReadingDate.formatted(date: .omitted, time: .standard))"
} else {
debugString += "\nBG updated: ---"
}
debugString += "\nBG values: \(bgReadingValues.count)"
if !isMaster {
debugString += "\nFollower conn.: \(timeStampOfLastFollowerConnection.formatted(date: .omitted, time: .standard))"
if followerBackgroundKeepAliveType == .heartbeat {
debugString += "\nLast hearbeat: \(lastHeartBeatTimeStamp.formatted(date: .omitted, time: .standard))"
}
}
debugString += "\nScreen width: \(Int(WKInterfaceDevice.current().screenBounds.size.width))"
debugString += "\niOS app: Idle"
return debugString
}
}
extension WatchStateModel: WCSessionDelegate {

View File

@ -63,7 +63,7 @@ struct MainView: View {
if showDebug {
Text(watchState.debugString)
.foregroundStyle(.black)
.font(.system(size: 15)).bold()
.font(.system(size: isSmallScreen ? 12 : 14))
.multilineTextAlignment(.leading)
.padding(EdgeInsets(top: 5, leading: 5, bottom: 5, trailing: 5))
.background(.teal).opacity(0.85)
@ -145,6 +145,7 @@ struct ContentView_Previews: PreviewProvider {
watchState.sensorAgeInMinutes = Double(Int.random(in: 1..<14400))
watchState.sensorMaxAgeInMinutes = 14400
watchState.isMaster = false
watchState.followerDataSourceType = .libreLinkUp
watchState.followerBackgroundKeepAliveType = .heartbeat
return Group {

View File

@ -47,13 +47,19 @@ struct DataSourceView: View {
Spacer()
Image(systemName: ConstantsAppleWatch.requestingDataIconSFSymbolName)
.font(.system(size: ConstantsAppleWatch.requestingDataIconFontSize, weight: .heavy))
.foregroundStyle(watchState.requestingDataIconColor)
.padding(.bottom, -2)
.padding(.trailing, -3)
if watchState.sensorAgeInMinutes > 0 {
Text(watchState.sensorAgeInMinutes.minutesToDaysAndHours())
.font(.system(size: textSize))
.foregroundStyle(watchState.activeSensorProgress().textColor)
}
}
.padding([.leading, .trailing], isSmallScreen ? 6 : 10)
.padding([.leading, .trailing], isSmallScreen ? 6 : 8)
} else {
ProgressView(value: 0)
.tint(ConstantsHomeView.sensorProgressViewNormalColorSwiftUI)
@ -64,6 +70,11 @@ struct DataSourceView: View {
.font(.system(size: textSize)).bold()
Spacer()
Image(systemName: ConstantsAppleWatch.requestingDataIconSFSymbolName)
.font(.system(size: ConstantsAppleWatch.requestingDataIconFontSize, weight: .heavy))
.foregroundStyle(watchState.requestingDataIconColor)
.padding(.bottom, -2)
}
.padding([.leading, .trailing], 10)
}

View File

@ -12,7 +12,7 @@ import SwiftUI
extension XDripWatchComplication.EntryView {
@ViewBuilder
var accessoryRectangularView: some View {
if !entry.widgetState.disableComplications {
ZStack {
VStack(spacing: 0) {
HStack(alignment: .center) {
HStack(alignment: .center, spacing: 4) {
@ -35,25 +35,22 @@ extension XDripWatchComplication.EntryView {
}
.padding(0)
GlucoseChartView(glucoseChartType: .watchAccessoryRectangular, bgReadingValues: entry.widgetState.bgReadingValues, bgReadingDates: entry.widgetState.bgReadingDates, isMgDl: entry.widgetState.isMgDl, urgentLowLimitInMgDl: entry.widgetState.urgentLowLimitInMgDl, lowLimitInMgDl: entry.widgetState.lowLimitInMgDl, highLimitInMgDl: entry.widgetState.highLimitInMgDl, urgentHighLimitInMgDl: entry.widgetState.urgentHighLimitInMgDl, liveActivitySize: nil, hoursToShowScalingHours: nil, glucoseCircleDiameterScalingHours: nil, overrideChartHeight: nil, overrideChartWidth: nil)
}
.widgetBackground(backgroundView: Color.clear)
} else {
VStack(alignment: .leading, spacing: 0) {
Text(ConstantsHomeView.applicationName)
.fontWeight(/*@START_MENU_TOKEN@*/.bold/*@END_MENU_TOKEN@*/)
GlucoseChartView(glucoseChartType: .watchAccessoryRectangular, bgReadingValues: entry.widgetState.bgReadingValues, bgReadingDates: entry.widgetState.bgReadingDates, isMgDl: entry.widgetState.isMgDl, urgentLowLimitInMgDl: entry.widgetState.urgentLowLimitInMgDl, lowLimitInMgDl: entry.widgetState.lowLimitInMgDl, highLimitInMgDl: entry.widgetState.highLimitInMgDl, urgentHighLimitInMgDl: entry.widgetState.urgentHighLimitInMgDl, liveActivitySize: nil, hoursToShowScalingHours: nil, glucoseCircleDiameterScalingHours: nil, overrideChartHeight: entry.widgetState.disableComplications ? ConstantsGlucoseChartSwiftUI.viewHeightWatchAccessoryRectangular - 15 : nil, overrideChartWidth: nil)
HStack(alignment: .center, spacing: 6) {
Image(systemName: "exclamationmark.triangle")
.font(.system(size: 24))
Text("Enable background keep-alive")
.font(.system(size: 16))
if entry.widgetState.disableComplications {
HStack(alignment: .center, spacing: 4) {
Image(systemName: "exclamationmark.triangle")
.font(.system(size: 14))
Text("Keep-alive disabled")
.font(.system(size: 14))
.multilineTextAlignment(.leading)
}
.foregroundStyle(.teal)
.padding(0)
}
.foregroundStyle(.yellow)
.padding(2)
}
.widgetBackground(backgroundView: Color.clear)
}
.widgetBackground(backgroundView: Color.clear)
}
}

View File

@ -83,13 +83,11 @@ public final class WatchManager: NSObject, ObservableObject {
self.watchState.highLimitInMgDl = UserDefaults.standard.highMarkValue
self.watchState.urgentHighLimitInMgDl = UserDefaults.standard.urgentHighMarkValue
self.watchState.activeSensorDescription = UserDefaults.standard.activeSensorDescription
self.watchState.timeStampOfLastFollowerConnection = UserDefaults.standard.timeStampOfLastFollowerConnection ?? Date()
self.watchState.secondsUntilFollowerDisconnectWarning = UserDefaults.standard.followerDataSourceType.secondsUntilFollowerDisconnectWarning
self.watchState.isMaster = UserDefaults.standard.isMaster
self.watchState.followerDataSourceTypeRawValue = UserDefaults.standard.followerDataSourceType.rawValue
self.watchState.followerBackgroundKeepAliveTypeRawValue = UserDefaults.standard.followerBackgroundKeepAliveType.rawValue
self.watchState.disableComplications = !UserDefaults.standard.isMaster && UserDefaults.standard.followerBackgroundKeepAliveType == .disabled
if let sensorStartDate = UserDefaults.standard.activeSensorStartDate {
self.watchState.sensorAgeInMinutes = Double(Calendar.current.dateComponents([.minute], from: sensorStartDate, to: Date()).minute!)
} else {
@ -104,6 +102,12 @@ public final class WatchManager: NSObject, ObservableObject {
self.watchState.lastHeartBeatTimeStamp = lastHeartBeatTimeStamp
}
// let's set the follower server connection values if we're using follower mode
if let timeStampOfLastFollowerConnection = UserDefaults.standard.timeStampOfLastFollowerConnection {
self.watchState.secondsUntilFollowerDisconnectWarning = UserDefaults.standard.followerDataSourceType.secondsUntilFollowerDisconnectWarning
self.watchState.timeStampOfLastFollowerConnection = timeStampOfLastFollowerConnection
}
self.sendToWatch()
}
}