improvements to Watch app in follower mode
This commit is contained in:
parent
b1773513e9
commit
9fc843a849
|
@ -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" //
|
||||
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue