watch app improvements, follower mode data source, iOS widget improvements
- also widget data is stored in a new userDefaults variable based upon the app bundle to avoid cross-contamination if various versions are installed on the same device
This commit is contained in:
parent
48beccc216
commit
3229a0e532
|
@ -16,11 +16,13 @@ import WidgetKit
|
|||
/// also used to update the ComplicationSharedUserDefaultsModel in the app group so that the complication can access the data
|
||||
class WatchStateModel: NSObject, ObservableObject {
|
||||
|
||||
/// shared UserDefaults to publish data
|
||||
private let sharedUserDefaults = UserDefaults(suiteName: Bundle.main.appGroupSuiteName)
|
||||
|
||||
/// the Watch Connectivity session
|
||||
private var session: WCSession
|
||||
var session: WCSession
|
||||
|
||||
// set timer to automatically refresh the view
|
||||
// https://www.hackingwithswift.com/quick-start/swiftui/how-to-use-a-timer-with-swiftui
|
||||
let timer = Timer.publish(every: 1, tolerance: 0.5, on: .main, in: .common).autoconnect()
|
||||
@Published var timerControlDate = Date()
|
||||
|
||||
var bgReadingValues: [Double] = []
|
||||
var bgReadingDates: [Date] = []
|
||||
|
@ -37,14 +39,16 @@ class WatchStateModel: NSObject, ObservableObject {
|
|||
@Published var sensorAgeInMinutes: Double = 0
|
||||
@Published var sensorMaxAgeInMinutes: Double = 14400
|
||||
@Published var showAppleWatchDebug: Bool = false
|
||||
@Published var followerConnectionIsStale: Bool = false
|
||||
@Published var timeStampOfLastFollowerConnection: Date = Date()
|
||||
@Published var secondsUntilFollowerDisconnectWarning: Int = 90
|
||||
@Published var isMaster: Bool = true
|
||||
@Published var followerDataSourceType: FollowerDataSourceType = .nightscout
|
||||
@Published var followerBackgroundKeepAliveType: FollowerBackgroundKeepAliveType = .normal
|
||||
@Published var disableComplications: Bool = false
|
||||
|
||||
@Published var lastUpdatedTextString: String = "Updating..."
|
||||
@Published var lastUpdatedTimeString: String = "12:34"
|
||||
@Published var lastUpdatedTextString: String = "Requesting data..."
|
||||
@Published var lastUpdatedTimeString: String = ""
|
||||
@Published var debugString: String = "Debug info..."
|
||||
@Published var chartHoursIndex: Int = 1
|
||||
|
||||
|
@ -142,26 +146,29 @@ class WatchStateModel: NSObject, ObservableObject {
|
|||
/// convert the optional delta change int (in mg/dL) to a formatted change value in the user chosen unit making sure all zero values are shown as a positive change to follow Nightscout convention
|
||||
/// - Returns: a string holding the formatted delta change value (i.e. +0.4 or -6)
|
||||
func deltaChangeStringInUserChosenUnit() -> String {
|
||||
|
||||
let valueAsString = deltaChangeInMgDl.mgdlToMmolAndToString(mgdl: isMgDl)
|
||||
|
||||
var deltaSign: String = ""
|
||||
if (deltaChangeInMgDl > 0) { deltaSign = "+"; }
|
||||
|
||||
// quickly check "value" and prevent "-0mg/dl" or "-0.0mmol/l" being displayed
|
||||
// show unitized zero deltas as +0 or +0.0 as per Nightscout format
|
||||
if (isMgDl) {
|
||||
if (deltaChangeInMgDl > -1) && (deltaChangeInMgDl < 1) {
|
||||
return "+0"
|
||||
if let bgReadingDate = bgReadingDate(), bgReadingDate > Date().addingTimeInterval(-60 * 20) {
|
||||
let valueAsString = deltaChangeInMgDl.mgdlToMmolAndToString(mgdl: isMgDl)
|
||||
|
||||
var deltaSign: String = ""
|
||||
if (deltaChangeInMgDl > 0) { deltaSign = "+"; }
|
||||
|
||||
// quickly check "value" and prevent "-0mg/dl" or "-0.0mmol/l" being displayed
|
||||
// show unitized zero deltas as +0 or +0.0 as per Nightscout format
|
||||
if (isMgDl) {
|
||||
if (deltaChangeInMgDl > -1) && (deltaChangeInMgDl < 1) {
|
||||
return "+0"
|
||||
} else {
|
||||
return deltaSign + valueAsString
|
||||
}
|
||||
} else {
|
||||
return deltaSign + valueAsString
|
||||
if (deltaChangeInMgDl > -0.1) && (deltaChangeInMgDl < 0.1) {
|
||||
return "+0.0"
|
||||
} else {
|
||||
return deltaSign + valueAsString
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (deltaChangeInMgDl > -0.1) && (deltaChangeInMgDl < 0.1) {
|
||||
return "+0.0"
|
||||
} else {
|
||||
return deltaSign + valueAsString
|
||||
}
|
||||
return "-"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -202,6 +209,18 @@ class WatchStateModel: NSObject, ObservableObject {
|
|||
return (networkImage, tintColor)
|
||||
}
|
||||
|
||||
func getFollowerConnectionStatusImage() -> (networkImage: Image, tintColor: Color) {
|
||||
var networkImage: Image = Image(systemName: "network")
|
||||
var tintColor: Color = .green
|
||||
|
||||
if followerConnectionIsStale {
|
||||
networkImage = Image(systemName: "network.slash")
|
||||
tintColor = .gray
|
||||
}
|
||||
|
||||
return (networkImage, tintColor)
|
||||
}
|
||||
|
||||
/// request a state update from the iOS companion app
|
||||
func requestWatchStateUpdate() {
|
||||
guard session.activationState == .activated else {
|
||||
|
@ -211,8 +230,9 @@ class WatchStateModel: NSObject, ObservableObject {
|
|||
|
||||
// change the text, this must be done in the main thread
|
||||
DispatchQueue.main.async {
|
||||
self.lastUpdatedTextString = "Waiting for data..."
|
||||
self.lastUpdatedTimeString = ""
|
||||
self.debugString += "\nRequesting data..."
|
||||
// self.lastUpdatedTextString = "Updating..."
|
||||
// self.lastUpdatedTimeString = ""
|
||||
}
|
||||
|
||||
print("Requesting watch state update from iOS")
|
||||
|
@ -243,12 +263,14 @@ class WatchStateModel: NSObject, ObservableObject {
|
|||
isMaster = watchState.isMaster ?? true
|
||||
followerDataSourceType = FollowerDataSourceType(rawValue: watchState.followerDataSourceTypeRawValue ?? 0) ?? .nightscout
|
||||
followerBackgroundKeepAliveType = FollowerBackgroundKeepAliveType(rawValue: watchState.followerBackgroundKeepAliveTypeRawValue ?? 0) ?? .normal
|
||||
disableComplications = watchState.disableComplications ?? false
|
||||
followerConnectionIsStale = watchState.followerConnectionIsStale ?? false
|
||||
|
||||
// check if there is any BG data available before updating the strings accordingly
|
||||
if let bgReadingDate = bgReadingDate() {
|
||||
lastUpdatedTextString = "Last reading "
|
||||
lastUpdatedTimeString = bgReadingDate.formatted(date: .omitted, time: .shortened)
|
||||
debugString = "State updated: \(Date().formatted(date: .omitted, time: .shortened))\nBG updated: \(bgReadingDate.formatted(date: .omitted, time: .shortened))\nBG values: \(bgReadingValues.count)"
|
||||
debugString = "State updated: \(Date().formatted(date: .omitted, time: .standard))\nBG updated: \(bgReadingDate.formatted(date: .omitted, time: .standard))\nBG values: \(bgReadingValues.count)"
|
||||
} else {
|
||||
lastUpdatedTextString = "No sensor data"
|
||||
lastUpdatedTimeString = ""
|
||||
|
@ -260,17 +282,19 @@ class WatchStateModel: NSObject, ObservableObject {
|
|||
}
|
||||
|
||||
/// once we've process the state update, then save this data to the shared app group so that the complication can read it
|
||||
private func updateWatchSharedUserDefaults() {
|
||||
guard let sharedUserDefaults = sharedUserDefaults else { return }
|
||||
private func updateWatchSharedUserDefaults() {
|
||||
guard let sharedUserDefaults = UserDefaults(suiteName: Bundle.main.appGroupSuiteName) else { return }
|
||||
|
||||
let bgReadingDatesAsDouble = bgReadingDates.map { date in
|
||||
date.timeIntervalSince1970
|
||||
}
|
||||
|
||||
let complicationSharedUserDefaultsModel = ComplicationSharedUserDefaultsModel(bgReadingValues: bgReadingValues, bgReadingDatesAsDouble: bgReadingDatesAsDouble, isMgDl: isMgDl, slopeOrdinal: slopeOrdinal, deltaChangeInMgDl: deltaChangeInMgDl, urgentLowLimitInMgDl: urgentLowLimitInMgDl, lowLimitInMgDl: lowLimitInMgDl, highLimitInMgDl: highLimitInMgDl, urgentHighLimitInMgDl: urgentHighLimitInMgDl)
|
||||
let complicationSharedUserDefaultsModel = ComplicationSharedUserDefaultsModel(bgReadingValues: bgReadingValues, bgReadingDatesAsDouble: bgReadingDatesAsDouble, isMgDl: isMgDl, slopeOrdinal: slopeOrdinal, deltaChangeInMgDl: deltaChangeInMgDl, urgentLowLimitInMgDl: urgentLowLimitInMgDl, lowLimitInMgDl: lowLimitInMgDl, highLimitInMgDl: highLimitInMgDl, urgentHighLimitInMgDl: urgentHighLimitInMgDl, disableComplications: disableComplications)
|
||||
|
||||
// store the model in the shared user defaults using a name that is uniquely specific to this copy of the app as installed on
|
||||
// the user's device - this allows several copies of the app to be installed without cross-contamination of widget/complication data
|
||||
if let stateData = try? JSONEncoder().encode(complicationSharedUserDefaultsModel) {
|
||||
sharedUserDefaults.set(stateData, forKey: "complicationSharedUserDefaults")
|
||||
sharedUserDefaults.set(stateData, forKey: "complicationSharedUserDefaults.\(Bundle.main.mainAppBundleIdentifier)")
|
||||
}
|
||||
|
||||
// now that the new data is stored in the app group, try to force the complications to reload
|
||||
|
|
|
@ -11,91 +11,69 @@ import SwiftUI
|
|||
struct MainView: View {
|
||||
@EnvironmentObject var watchState: WatchStateModel
|
||||
|
||||
// set timer to automatically refresh the view
|
||||
// https://www.hackingwithswift.com/quick-start/swiftui/how-to-use-a-timer-with-swiftui
|
||||
let timer = Timer.publish(every: 7, on: .main, in: .common).autoconnect()
|
||||
|
||||
// get the array of different hour ranges from the constants file
|
||||
// we'll move through this array as the user swipes left/right on the chart
|
||||
let hoursToShow: [Double] = ConstantsAppleWatch.hoursToShow
|
||||
|
||||
@State private var currentDate = Date.now
|
||||
@State private var hoursToShowIndex: Int = ConstantsAppleWatch.hoursToShowDefaultIndex
|
||||
|
||||
// MARK: - Body
|
||||
var body: some View {
|
||||
VStack {
|
||||
HeaderView()
|
||||
.padding([.leading, .trailing], 5)
|
||||
.padding([.top], 20)
|
||||
.padding([.bottom], -10)
|
||||
.onTapGesture(count: 2) {
|
||||
watchState.requestWatchStateUpdate()
|
||||
}
|
||||
ZStack(alignment: Alignment(horizontal: .center, vertical: .top), content: {
|
||||
|
||||
GlucoseChartView(glucoseChartType: .watchApp, bgReadingValues: watchState.bgReadingValues, bgReadingDates: watchState.bgReadingDates, isMgDl: watchState.isMgDl, urgentLowLimitInMgDl: watchState.urgentLowLimitInMgDl, lowLimitInMgDl: watchState.lowLimitInMgDl, highLimitInMgDl: watchState.highLimitInMgDl, urgentHighLimitInMgDl: watchState.urgentHighLimitInMgDl, liveActivitySize: nil, hoursToShowScalingHours: hoursToShow[hoursToShowIndex], glucoseCircleDiameterScalingHours: 4)
|
||||
.padding(.top, 3)
|
||||
.padding(.bottom, 3)
|
||||
.gesture(
|
||||
DragGesture(minimumDistance: 80, coordinateSpace: .local)
|
||||
.onEnded({ value in
|
||||
if (value.startLocation.x > value.location.x) {
|
||||
if hoursToShow[hoursToShowIndex] != hoursToShow.first {
|
||||
hoursToShowIndex -= 1
|
||||
}
|
||||
} else {
|
||||
if hoursToShow[hoursToShowIndex] != hoursToShow.last {
|
||||
hoursToShowIndex += 1
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
if !watchState.isMaster && watchState.followerBackgroundKeepAliveType == .disabled {
|
||||
VStack(spacing: 2) {
|
||||
Image(systemName: "exclamationmark.triangle")
|
||||
.foregroundStyle(.black)
|
||||
.font(.system(size: 20))
|
||||
.padding(2)
|
||||
Text("Watch App not available")
|
||||
.foregroundStyle(.black)
|
||||
.font(.footnote).bold()
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(2)
|
||||
Text("Follower keep-alive is disabled")
|
||||
.foregroundStyle(.black)
|
||||
.font(.footnote).bold()
|
||||
.multilineTextAlignment(.center)
|
||||
.padding(2)
|
||||
ZStack(alignment: .topLeading) {
|
||||
VStack {
|
||||
HeaderView()
|
||||
.padding([.leading, .trailing], 5)
|
||||
.padding([.top], 20)
|
||||
.padding([.bottom], -10)
|
||||
.onTapGesture(count: 2) {
|
||||
watchState.requestWatchStateUpdate()
|
||||
}
|
||||
.background(.red).opacity(0.9)
|
||||
.cornerRadius(6)
|
||||
}
|
||||
|
||||
if watchState.showAppleWatchDebug {
|
||||
Text(watchState.debugString)
|
||||
.foregroundStyle(.black)
|
||||
.font(.footnote).bold()
|
||||
.multilineTextAlignment(.leading)
|
||||
.padding(EdgeInsets(top: 2, leading: 6, bottom: 2, trailing: 6))
|
||||
.background(.teal).opacity(0.9)
|
||||
.cornerRadius(6)
|
||||
.padding(.top, 10)
|
||||
.padding(.leading, 2)
|
||||
}
|
||||
})
|
||||
|
||||
Spacer()
|
||||
|
||||
DataSourceView()
|
||||
|
||||
InfoView()
|
||||
ZStack(alignment: Alignment(horizontal: .center, vertical: .top), content: {
|
||||
|
||||
GlucoseChartView(glucoseChartType: .watchApp, bgReadingValues: watchState.bgReadingValues, bgReadingDates: watchState.bgReadingDates, isMgDl: watchState.isMgDl, urgentLowLimitInMgDl: watchState.urgentLowLimitInMgDl, lowLimitInMgDl: watchState.lowLimitInMgDl, highLimitInMgDl: watchState.highLimitInMgDl, urgentHighLimitInMgDl: watchState.urgentHighLimitInMgDl, liveActivitySize: nil, hoursToShowScalingHours: hoursToShow[hoursToShowIndex], glucoseCircleDiameterScalingHours: 4)
|
||||
.padding(.top, 3)
|
||||
.padding(.bottom, 3)
|
||||
.gesture(
|
||||
DragGesture(minimumDistance: 80, coordinateSpace: .local)
|
||||
.onEnded({ value in
|
||||
if (value.startLocation.x > value.location.x) {
|
||||
if hoursToShow[hoursToShowIndex] != hoursToShow.first {
|
||||
hoursToShowIndex -= 1
|
||||
}
|
||||
} else {
|
||||
if hoursToShow[hoursToShowIndex] != hoursToShow.last {
|
||||
hoursToShowIndex += 1
|
||||
}
|
||||
}
|
||||
})
|
||||
)
|
||||
|
||||
if watchState.showAppleWatchDebug {
|
||||
Text(watchState.debugString)
|
||||
.foregroundStyle(.black)
|
||||
.font(.footnote).bold()
|
||||
.multilineTextAlignment(.leading)
|
||||
.padding(EdgeInsets(top: 2, leading: 6, bottom: 2, trailing: 6))
|
||||
.background(.teal).opacity(0.8)
|
||||
.cornerRadius(6)
|
||||
.padding(.top, 10)
|
||||
.padding(.leading, 2)
|
||||
}
|
||||
})
|
||||
|
||||
DataSourceView()
|
||||
|
||||
InfoView()
|
||||
}
|
||||
.padding(.bottom, 20)
|
||||
}
|
||||
.padding(.bottom, 20)
|
||||
.onReceive(timer) { date in
|
||||
currentDate = date
|
||||
watchState.requestWatchStateUpdate()
|
||||
.frame(maxHeight: .infinity)
|
||||
.onReceive(watchState.timer) { date in
|
||||
if watchState.updatedDate.timeIntervalSinceNow < -5 {
|
||||
watchState.timerControlDate = date
|
||||
watchState.requestWatchStateUpdate()
|
||||
}
|
||||
}
|
||||
.onAppear {
|
||||
watchState.requestWatchStateUpdate()
|
||||
|
@ -152,7 +130,7 @@ struct ContentView_Previews: PreviewProvider {
|
|||
watchState.bgReadingDates = bgDateArray()
|
||||
watchState.isMgDl = true
|
||||
watchState.slopeOrdinal = 5
|
||||
watchState.deltaChangeInMgDl = -2
|
||||
watchState.deltaChangeInMgDl = 0
|
||||
watchState.urgentLowLimitInMgDl = 60
|
||||
watchState.lowLimitInMgDl = 80
|
||||
watchState.highLimitInMgDl = 140
|
||||
|
@ -167,6 +145,8 @@ struct ContentView_Previews: PreviewProvider {
|
|||
|
||||
return Group {
|
||||
MainView()
|
||||
MainView().previewDevice("Apple Watch Series 5 - 40mm")
|
||||
MainView().previewDevice("Apple Watch Series 3 - 38mm")
|
||||
}.environmentObject(watchState)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,12 +22,17 @@ struct DataSourceView: View {
|
|||
|
||||
HStack {
|
||||
if !watchState.isMaster {
|
||||
HStack(alignment: .center, spacing: 6) {
|
||||
watchState.getDataTimeStampOfLastFollowerConnection().networkImage
|
||||
HStack(alignment: .center, spacing: 4) {
|
||||
watchState.getFollowerConnectionStatusImage().networkImage
|
||||
.font(.system(size: 14))
|
||||
.foregroundStyle(watchState.getDataTimeStampOfLastFollowerConnection().tintColor)
|
||||
.foregroundStyle(watchState.getFollowerConnectionStatusImage().tintColor)
|
||||
.padding(.bottom, -2)
|
||||
|
||||
watchState.followerBackgroundKeepAliveType.keepAliveImage
|
||||
.font(.system(size: 14))
|
||||
.foregroundStyle(Color(white: 0.7))
|
||||
.padding(.bottom, -3)
|
||||
|
||||
Text(watchState.followerDataSourceType.fullDescription)
|
||||
.font(.system(size: 14)).fontWeight(.semibold)
|
||||
}
|
||||
|
|
|
@ -13,7 +13,7 @@ struct HeaderView: View {
|
|||
@EnvironmentObject var watchState: WatchStateModel
|
||||
|
||||
var body: some View {
|
||||
HStack {
|
||||
HStack(alignment: .lastTextBaseline) {
|
||||
Text("\(watchState.bgValueStringInUserChosenUnit())\(watchState.trendArrow())")
|
||||
.font(.system(size: 60)).fontWeight(.semibold)
|
||||
.foregroundStyle(watchState.bgTextColor())
|
||||
|
|
21
xDrip Watch Complication/Assets.xcassets/ComplicationIcon.imageset/Contents.json
vendored
Normal file
21
xDrip Watch Complication/Assets.xcassets/ComplicationIcon.imageset/Contents.json
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "Icon-122.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
xDrip Watch Complication/Assets.xcassets/ComplicationIcon.imageset/Icon-122.png
vendored
Normal file
BIN
xDrip Watch Complication/Assets.xcassets/ComplicationIcon.imageset/Icon-122.png
vendored
Normal file
Binary file not shown.
After Width: | Height: | Size: 14 KiB |
|
@ -8,7 +8,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
/// model of the data we'll store in the shared app group to pass from the watch app to the watch complication widget extension
|
||||
/// model of the data we'll store in the shared app group to pass from the watch app to widgetkit
|
||||
struct ComplicationSharedUserDefaultsModel: Codable {
|
||||
var bgReadingValues: [Double]
|
||||
var bgReadingDatesAsDouble: [Double]
|
||||
|
@ -19,4 +19,5 @@ struct ComplicationSharedUserDefaultsModel: Codable {
|
|||
var lowLimitInMgDl: Double
|
||||
var highLimitInMgDl: Double
|
||||
var urgentHighLimitInMgDl: Double
|
||||
var disableComplications: Bool
|
||||
}
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
<dict>
|
||||
<key>AppGroupIdentifier</key>
|
||||
<string>$(APP_GROUP_IDENTIFIER)</string>
|
||||
<key>MainAppBundleIdentifier</key>
|
||||
<string>$(MAIN_APP_BUNDLE_IDENTIFIER)</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
|
|
|
@ -12,26 +12,32 @@ import SwiftUI
|
|||
extension XDripWatchComplication.EntryView {
|
||||
@ViewBuilder
|
||||
var accessoryCircularView: some View {
|
||||
Gauge(value: entry.widgetState.bgValueInMgDl ?? 100, in: entry.widgetState.gaugeModel().minValue...entry.widgetState.gaugeModel().maxValue) {
|
||||
Text("Not shown")
|
||||
} currentValueLabel: {
|
||||
Text(entry.widgetState.bgValueStringInUserChosenUnit)
|
||||
.font(.system(size: 20)).bold()
|
||||
.minimumScaleFactor(0.2)
|
||||
.lineLimit(1)
|
||||
} minimumValueLabel: {
|
||||
Text(entry.widgetState.gaugeModel().minValue.mgdlToMmolAndToString(mgdl: entry.widgetState.isMgDl))
|
||||
.font(.system(size: 8))
|
||||
.foregroundStyle(Color(white: 0.7))
|
||||
.minimumScaleFactor(0.2)
|
||||
} maximumValueLabel: {
|
||||
Text(entry.widgetState.gaugeModel().maxValue.mgdlToMmolAndToString(mgdl: entry.widgetState.isMgDl))
|
||||
.font(.system(size: 8))
|
||||
.foregroundStyle(Color(white: 0.7))
|
||||
.minimumScaleFactor(0.2)
|
||||
if !entry.widgetState.disableComplications {
|
||||
Gauge(value: entry.widgetState.bgValueInMgDl ?? 100, in: entry.widgetState.gaugeModel().minValue...entry.widgetState.gaugeModel().maxValue) {
|
||||
Text("Not shown")
|
||||
} currentValueLabel: {
|
||||
Text(entry.widgetState.bgValueStringInUserChosenUnit)
|
||||
.font(.system(size: 20)).bold()
|
||||
.minimumScaleFactor(0.2)
|
||||
.lineLimit(1)
|
||||
} minimumValueLabel: {
|
||||
Text(entry.widgetState.gaugeModel().minValue.mgdlToMmolAndToString(mgdl: entry.widgetState.isMgDl))
|
||||
.font(.system(size: 8))
|
||||
.foregroundStyle(Color(white: 0.7))
|
||||
.minimumScaleFactor(0.2)
|
||||
} maximumValueLabel: {
|
||||
Text(entry.widgetState.gaugeModel().maxValue.mgdlToMmolAndToString(mgdl: entry.widgetState.isMgDl))
|
||||
.font(.system(size: 8))
|
||||
.foregroundStyle(Color(white: 0.7))
|
||||
.minimumScaleFactor(0.2)
|
||||
}
|
||||
.gaugeStyle(.accessoryCircular)
|
||||
.tint(entry.widgetState.gaugeModel().gaugeGradient)
|
||||
.widgetBackground(backgroundView: Color.clear)
|
||||
} else {
|
||||
Image("ComplicationIcon")
|
||||
.resizable()
|
||||
.widgetBackground(backgroundView: Color.clear)
|
||||
}
|
||||
.gaugeStyle(.accessoryCircular)
|
||||
.tint(entry.widgetState.gaugeModel().gaugeGradient)
|
||||
.widgetBackground(backgroundView: Color.clear)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,31 +12,40 @@ import SwiftUI
|
|||
extension XDripWatchComplication.EntryView {
|
||||
@ViewBuilder
|
||||
var accessoryCornerView: some View {
|
||||
Text("\(entry.widgetState.bgValueStringInUserChosenUnit)\(entry.widgetState.trendArrow())")
|
||||
.font(.system(size: 20))
|
||||
.foregroundColor(entry.widgetState.bgTextColor())
|
||||
.minimumScaleFactor(0.2)
|
||||
.widgetCurvesContent()
|
||||
.widgetLabel {
|
||||
Gauge(value: entry.widgetState.bgValueInMgDl ?? 100, in: entry.widgetState.gaugeModel().minValue...entry.widgetState.gaugeModel().maxValue) {
|
||||
Text("Not shown")
|
||||
} currentValueLabel: {
|
||||
Text("Not shown")
|
||||
} minimumValueLabel: {
|
||||
Text(entry.widgetState.gaugeModel().minValue.mgdlToMmolAndToString(mgdl: entry.widgetState.isMgDl))
|
||||
.font(.system(size: 8))
|
||||
.foregroundStyle(Color(white: 0.7))
|
||||
.minimumScaleFactor(0.2)
|
||||
} maximumValueLabel: {
|
||||
Text(entry.widgetState.gaugeModel().maxValue.mgdlToMmolAndToString(mgdl: entry.widgetState.isMgDl))
|
||||
.font(.system(size: 8))
|
||||
.foregroundStyle(Color(white: 0.7))
|
||||
.minimumScaleFactor(0.2)
|
||||
if !entry.widgetState.disableComplications {
|
||||
Text("\(entry.widgetState.bgValueStringInUserChosenUnit)\(entry.widgetState.trendArrow())")
|
||||
.font(.system(size: 20))
|
||||
.foregroundColor(entry.widgetState.bgTextColor())
|
||||
.minimumScaleFactor(0.2)
|
||||
.widgetCurvesContent()
|
||||
.widgetLabel {
|
||||
Gauge(value: entry.widgetState.bgValueInMgDl ?? 100, in: entry.widgetState.gaugeModel().minValue...entry.widgetState.gaugeModel().maxValue) {
|
||||
Text("Not shown")
|
||||
} currentValueLabel: {
|
||||
Text("Not shown")
|
||||
} minimumValueLabel: {
|
||||
Text(entry.widgetState.gaugeModel().minValue.mgdlToMmolAndToString(mgdl: entry.widgetState.isMgDl))
|
||||
.font(.system(size: 8))
|
||||
.foregroundStyle(Color(white: 0.7))
|
||||
.minimumScaleFactor(0.2)
|
||||
} maximumValueLabel: {
|
||||
Text(entry.widgetState.gaugeModel().maxValue.mgdlToMmolAndToString(mgdl: entry.widgetState.isMgDl))
|
||||
.font(.system(size: 8))
|
||||
.foregroundStyle(Color(white: 0.7))
|
||||
.minimumScaleFactor(0.2)
|
||||
}
|
||||
.tint(entry.widgetState.gaugeModel().gaugeGradient)
|
||||
.gaugeStyle(LinearCapacityGaugeStyle()) // Doesn't do anything
|
||||
}
|
||||
.tint(entry.widgetState.gaugeModel().gaugeGradient)
|
||||
.gaugeStyle(LinearCapacityGaugeStyle()) // Doesn't do anything
|
||||
}
|
||||
.widgetBackground(backgroundView: Color.clear)
|
||||
.widgetBackground(backgroundView: Color.clear)
|
||||
} else {
|
||||
Text(" ")
|
||||
.font(.system(size: 20))
|
||||
.minimumScaleFactor(0.2)
|
||||
.widgetCurvesContent()
|
||||
.widgetLabel("\(ConstantsHomeView.applicationName)")
|
||||
.widgetBackground(backgroundView: Color.clear)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,11 @@ import SwiftUI
|
|||
extension XDripWatchComplication.EntryView {
|
||||
@ViewBuilder
|
||||
var accessoryInlineView: some View {
|
||||
Text("\(entry.widgetState.bgValueStringInUserChosenUnit) \(entry.widgetState.trendArrow()) \(entry.widgetState.deltaChangeStringInUserChosenUnit())")
|
||||
if !entry.widgetState.disableComplications {
|
||||
Text("\(entry.widgetState.bgValueStringInUserChosenUnit) \(entry.widgetState.trendArrow()) \(entry.widgetState.deltaChangeStringInUserChosenUnit())")
|
||||
} else {
|
||||
Text("\(ConstantsHomeView.applicationName)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,31 +12,49 @@ import SwiftUI
|
|||
extension XDripWatchComplication.EntryView {
|
||||
@ViewBuilder
|
||||
var accessoryRectangularView: some View {
|
||||
VStack(spacing: 0) {
|
||||
HStack(alignment: .center) {
|
||||
HStack(alignment: .firstTextBaseline, spacing: 4) {
|
||||
Text("\(entry.widgetState.bgValueStringInUserChosenUnit)\(entry.widgetState.trendArrow()) ")
|
||||
.font(.system(size: 24)).bold()
|
||||
.foregroundStyle(entry.widgetState.bgTextColor())
|
||||
if !entry.widgetState.disableComplications {
|
||||
VStack(spacing: 0) {
|
||||
HStack(alignment: .center) {
|
||||
HStack(alignment: .firstTextBaseline, spacing: 4) {
|
||||
Text("\(entry.widgetState.bgValueStringInUserChosenUnit)\(entry.widgetState.trendArrow()) ")
|
||||
.font(.system(size: 24)).bold()
|
||||
.foregroundStyle(entry.widgetState.bgTextColor())
|
||||
|
||||
Text(entry.widgetState.deltaChangeStringInUserChosenUnit())
|
||||
.font(.system(size: 24)).bold()
|
||||
.foregroundStyle(Color(white: 0.9))
|
||||
.minimumScaleFactor(0.2)
|
||||
.lineLimit(1)
|
||||
}
|
||||
|
||||
Text(entry.widgetState.deltaChangeStringInUserChosenUnit())
|
||||
.font(.system(size: 24)).bold()
|
||||
.foregroundStyle(Color(white: 0.9))
|
||||
Spacer()
|
||||
|
||||
Text(entry.widgetState.bgReadingDate?.formatted(date: .omitted, time: .shortened) ?? "--:--")
|
||||
.font(.system(size: 18))
|
||||
.foregroundStyle(Color(white: 0.6))
|
||||
.minimumScaleFactor(0.2)
|
||||
.lineLimit(1)
|
||||
}
|
||||
.padding(0)
|
||||
|
||||
Spacer()
|
||||
|
||||
Text(entry.widgetState.bgReadingDate?.formatted(date: .omitted, time: .shortened) ?? "--:--")
|
||||
.font(.system(size: 18))
|
||||
.foregroundStyle(Color(white: 0.6))
|
||||
.minimumScaleFactor(0.2)
|
||||
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)
|
||||
}
|
||||
.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)
|
||||
.widgetBackground(backgroundView: Color.clear)
|
||||
} else {
|
||||
VStack(alignment: .leading, spacing: 0) {
|
||||
Text(ConstantsHomeView.applicationName)
|
||||
.fontWeight(/*@START_MENU_TOKEN@*/.bold/*@END_MENU_TOKEN@*/)
|
||||
|
||||
HStack(alignment: .center, spacing: 6) {
|
||||
Image(systemName: "exclamationmark.triangle")
|
||||
.font(.system(size: 24))
|
||||
|
||||
Text("Enable background keep-alive")
|
||||
.font(.system(size: 16))
|
||||
}
|
||||
.foregroundStyle(.yellow)
|
||||
.padding(2)
|
||||
}
|
||||
.widgetBackground(backgroundView: Color.clear)
|
||||
}
|
||||
.widgetBackground(backgroundView: Color.clear)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,13 +32,16 @@ extension XDripWatchComplication.Entry {
|
|||
var lowLimitInMgDl: Double
|
||||
var highLimitInMgDl: Double
|
||||
var urgentHighLimitInMgDl: Double
|
||||
// var isMaster: Bool = true
|
||||
// var followerBackgroundKeepAliveType: FollowerBackgroundKeepAliveType = .normal
|
||||
|
||||
var bgUnitString: String
|
||||
var bgValueInMgDl: Double?
|
||||
var bgReadingDate: Date?
|
||||
var bgValueStringInUserChosenUnit: String
|
||||
var disableComplications: Bool
|
||||
|
||||
init(bgReadingValues: [Double]? = nil, bgReadingDates: [Date]? = nil, isMgDl: Bool? = true, slopeOrdinal: Int? = 0, deltaChangeInMgDl: Double? = nil, urgentLowLimitInMgDl: Double? = 60, lowLimitInMgDl: Double? = 80, highLimitInMgDl: Double? = 180, urgentHighLimitInMgDl: Double? = 250) {
|
||||
init(bgReadingValues: [Double]? = nil, bgReadingDates: [Date]? = nil, isMgDl: Bool? = true, slopeOrdinal: Int? = 0, deltaChangeInMgDl: Double? = nil, urgentLowLimitInMgDl: Double? = 60, lowLimitInMgDl: Double? = 80, highLimitInMgDl: Double? = 180, urgentHighLimitInMgDl: Double? = 250, disableComplications: Bool? = false) {
|
||||
self.bgReadingValues = bgReadingValues
|
||||
self.bgReadingDates = bgReadingDates
|
||||
self.isMgDl = isMgDl ?? true
|
||||
|
@ -48,11 +51,14 @@ extension XDripWatchComplication.Entry {
|
|||
self.lowLimitInMgDl = lowLimitInMgDl ?? 80
|
||||
self.highLimitInMgDl = highLimitInMgDl ?? 180
|
||||
self.urgentHighLimitInMgDl = urgentHighLimitInMgDl ?? 250
|
||||
// self.isMaster = isMaster ?? true
|
||||
// self.followerBackgroundKeepAliveType = followerBackgroundKeepAliveType ?? .normal
|
||||
|
||||
self.bgValueInMgDl = (bgReadingValues?.count ?? 0) > 0 ? bgReadingValues?[0] : nil
|
||||
self.bgReadingDate = (bgReadingDates?.count ?? 0) > 0 ? bgReadingDates?[0] : nil
|
||||
self.bgUnitString = self.isMgDl ? Texts_Common.mgdl : Texts_Common.mmol
|
||||
self.bgValueStringInUserChosenUnit = (bgReadingValues?.count ?? 0) > 0 ? bgReadingValues?[0].mgdlToMmolAndToString(mgdl: self.isMgDl) ?? "" : ""
|
||||
self.disableComplications = disableComplications ?? false //(!self.isMaster && self.followerBackgroundKeepAliveType == .disabled) ? true : false
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ extension XDripWatchComplication {
|
|||
func getTimeline(in context: Context, completion: @escaping (Timeline<Entry>) -> ()) {
|
||||
let entry = Entry(date: .now, widgetState: getWidgetStateFromSharedUserDefaults() ?? sampleWidgetStateFromProvider)
|
||||
|
||||
completion(.init(entries: [entry], policy: .atEnd))
|
||||
completion(.init(entries: [entry], policy: .after(Date().addingTimeInterval(30*60))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,10 +34,9 @@ extension XDripWatchComplication {
|
|||
|
||||
extension XDripWatchComplication.Provider {
|
||||
func getWidgetStateFromSharedUserDefaults() -> XDripWatchComplication.Entry.WidgetState? {
|
||||
|
||||
guard let sharedUserDefaults = UserDefaults(suiteName: Bundle.main.appGroupSuiteName) else {return nil}
|
||||
|
||||
guard let encodedLatestReadings = sharedUserDefaults.data(forKey: "complicationSharedUserDefaults") else {
|
||||
guard let encodedLatestReadings = sharedUserDefaults.data(forKey: "complicationSharedUserDefaults.\(Bundle.main.mainAppBundleIdentifier)") else {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -52,7 +51,7 @@ extension XDripWatchComplication.Provider {
|
|||
Date(timeIntervalSince1970: date)
|
||||
}
|
||||
|
||||
return Entry.WidgetState(bgReadingValues: data.bgReadingValues, bgReadingDates: bgReadingDates, isMgDl: data.isMgDl, slopeOrdinal: data.slopeOrdinal, deltaChangeInMgDl: data.deltaChangeInMgDl, urgentLowLimitInMgDl: data.urgentLowLimitInMgDl, lowLimitInMgDl: data.lowLimitInMgDl, highLimitInMgDl: data.highLimitInMgDl, urgentHighLimitInMgDl: data.urgentHighLimitInMgDl)
|
||||
return Entry.WidgetState(bgReadingValues: data.bgReadingValues, bgReadingDates: bgReadingDates, isMgDl: data.isMgDl, slopeOrdinal: data.slopeOrdinal, deltaChangeInMgDl: data.deltaChangeInMgDl, urgentLowLimitInMgDl: data.urgentLowLimitInMgDl, lowLimitInMgDl: data.lowLimitInMgDl, highLimitInMgDl: data.highLimitInMgDl, urgentHighLimitInMgDl: data.urgentHighLimitInMgDl, disableComplications: data.disableComplications)
|
||||
} catch {
|
||||
print(error.localizedDescription)
|
||||
}
|
||||
|
@ -61,7 +60,6 @@ extension XDripWatchComplication.Provider {
|
|||
}
|
||||
|
||||
private var sampleWidgetStateFromProvider: XDripWatchComplication.Entry.WidgetState {
|
||||
//var widgetState = Entry.WidgetState()
|
||||
|
||||
func bgDateArray() -> [Date] {
|
||||
let endDate = Date()
|
||||
|
@ -100,8 +98,6 @@ extension XDripWatchComplication.Provider {
|
|||
return bgValueArray
|
||||
}
|
||||
|
||||
var widgetState = Entry.WidgetState(bgReadingValues: bgValueArray(), bgReadingDates: bgDateArray(), isMgDl: true, slopeOrdinal: 3, deltaChangeInMgDl: 0, urgentLowLimitInMgDl: ConstantsBGGraphBuilder.defaultUrgentLowMarkInMgdl, lowLimitInMgDl: ConstantsBGGraphBuilder.defaultLowMarkInMgdl, highLimitInMgDl: ConstantsBGGraphBuilder.defaultHighMarkInMgdl, urgentHighLimitInMgDl: ConstantsBGGraphBuilder.defaultUrgentHighMarkInMgdl)
|
||||
|
||||
return widgetState
|
||||
return Entry.WidgetState(bgReadingValues: bgValueArray(), bgReadingDates: bgDateArray(), isMgDl: true, slopeOrdinal: 3, deltaChangeInMgDl: 0, urgentLowLimitInMgDl: ConstantsBGGraphBuilder.defaultUrgentLowMarkInMgdl, lowLimitInMgDl: ConstantsBGGraphBuilder.defaultLowMarkInMgdl, highLimitInMgDl: ConstantsBGGraphBuilder.defaultHighMarkInMgdl, urgentHighLimitInMgDl: ConstantsBGGraphBuilder.defaultUrgentHighMarkInMgdl)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,7 +26,6 @@ struct XDripWidgetAttributes: ActivityAttributes {
|
|||
var urgentHighLimitInMgDl: Double
|
||||
var eventStartDate: Date = Date()
|
||||
var warnUserToOpenApp: Bool = true
|
||||
var showClockAtNight: Bool = false
|
||||
var liveActivitySize: LiveActivitySize
|
||||
|
||||
// computed properties
|
||||
|
@ -35,7 +34,7 @@ struct XDripWidgetAttributes: ActivityAttributes {
|
|||
var bgReadingDate: Date?
|
||||
var bgValueStringInUserChosenUnit: String
|
||||
|
||||
init(bgReadingValues: [Double], bgReadingDates: [Date], isMgDl: Bool, slopeOrdinal: Int, deltaChangeInMgDl: Double?, urgentLowLimitInMgDl: Double, lowLimitInMgDl: Double, highLimitInMgDl: Double, urgentHighLimitInMgDl: Double, showClockAtNight: Bool, liveActivitySize: LiveActivitySize) {
|
||||
init(bgReadingValues: [Double], bgReadingDates: [Date], isMgDl: Bool, slopeOrdinal: Int, deltaChangeInMgDl: Double?, urgentLowLimitInMgDl: Double, lowLimitInMgDl: Double, highLimitInMgDl: Double, urgentHighLimitInMgDl: Double, liveActivitySize: LiveActivitySize) {
|
||||
|
||||
// these are the "passed in" stateful values used to initialize
|
||||
self.bgReadingValues = bgReadingValues
|
||||
|
@ -46,12 +45,8 @@ struct XDripWidgetAttributes: ActivityAttributes {
|
|||
self.urgentLowLimitInMgDl = urgentLowLimitInMgDl
|
||||
self.lowLimitInMgDl = lowLimitInMgDl
|
||||
self.highLimitInMgDl = highLimitInMgDl
|
||||
self.urgentHighLimitInMgDl = urgentHighLimitInMgDl
|
||||
|
||||
let hour = Calendar.current.component(.hour, from: Date())
|
||||
self.showClockAtNight = (showClockAtNight && (hour >= ConstantsLiveActivity.showClockAtNightFromHour || hour < ConstantsLiveActivity.showClockAtNightToHour)) ? true : false
|
||||
|
||||
self.liveActivitySize = self.showClockAtNight ? .large : liveActivitySize
|
||||
self.urgentHighLimitInMgDl = urgentHighLimitInMgDl
|
||||
self.liveActivitySize = liveActivitySize
|
||||
|
||||
self.bgUnitString = isMgDl ? Texts_Common.mgdl : Texts_Common.mmol
|
||||
|
||||
|
|
|
@ -4,6 +4,8 @@
|
|||
<dict>
|
||||
<key>AppGroupIdentifier</key>
|
||||
<string>$(APP_GROUP_IDENTIFIER)</string>
|
||||
<key>MainAppBundleIdentifier</key>
|
||||
<string>$(MAIN_APP_BUNDLE_IDENTIFIER)</string>
|
||||
<key>NSExtension</key>
|
||||
<dict>
|
||||
<key>NSExtensionPointIdentifier</key>
|
||||
|
|
|
@ -15,18 +15,11 @@ extension XDripWidget.EntryView {
|
|||
ZStack {
|
||||
AccessoryWidgetBackground()
|
||||
.cornerRadius(8)
|
||||
|
||||
VStack(spacing: 0) {
|
||||
Text("\(entry.widgetState.bgValueStringInUserChosenUnit)\(entry.widgetState.trendArrow())")
|
||||
.font(.largeTitle).fontWeight(.semibold)
|
||||
.foregroundStyle(Color(white: 1))
|
||||
.lineLimit(1)
|
||||
|
||||
Text("Last reading \(entry.widgetState.bgReadingDate?.formatted(date: .omitted, time: .shortened) ?? "--:--")")
|
||||
.font(.system(size: 12))
|
||||
.foregroundStyle(Color(white: 0.6))
|
||||
}
|
||||
.padding(8)
|
||||
Text("\(entry.widgetState.bgValueStringInUserChosenUnit)\(entry.widgetState.trendArrow())")
|
||||
.font(.system(size: 50)).fontWeight(.semibold)
|
||||
.minimumScaleFactor(0.2)
|
||||
.foregroundStyle(Color(white: 1))
|
||||
.lineLimit(1)
|
||||
}
|
||||
.widgetBackground(backgroundView: Color.black)
|
||||
}
|
||||
|
|
|
@ -22,10 +22,10 @@ extension XDripWidget.EntryView {
|
|||
|
||||
Spacer()
|
||||
|
||||
HStack(alignment: .firstTextBaseline, spacing: 6) {
|
||||
HStack(alignment: .firstTextBaseline, spacing: 4) {
|
||||
Text(entry.widgetState.deltaChangeStringInUserChosenUnit())
|
||||
.font(.title).fontWeight(.bold)
|
||||
.foregroundStyle(Color(white: 0.9))
|
||||
.foregroundStyle(entry.widgetState.deltaChangeTextColor())
|
||||
.lineLimit(1)
|
||||
Text(entry.widgetState.bgUnitString)
|
||||
.font(.title)
|
||||
|
|
|
@ -22,10 +22,10 @@ extension XDripWidget.EntryView {
|
|||
|
||||
Spacer()
|
||||
|
||||
HStack(alignment: .firstTextBaseline, spacing: 6) {
|
||||
HStack(alignment: .firstTextBaseline, spacing: 4) {
|
||||
Text(entry.widgetState.deltaChangeStringInUserChosenUnit())
|
||||
.font(.title2).fontWeight(.bold)
|
||||
.foregroundStyle(Color(white: 0.9))
|
||||
.foregroundStyle(entry.widgetState.deltaChangeTextColor())
|
||||
.lineLimit(1)
|
||||
Text(entry.widgetState.bgUnitString)
|
||||
.font(.title2)
|
||||
|
|
|
@ -12,7 +12,7 @@ import SwiftUI
|
|||
extension XDripWidget.EntryView {
|
||||
var systemSmallView: some View {
|
||||
VStack(spacing: 0) {
|
||||
HStack {
|
||||
HStack(alignment: .center) {
|
||||
Text("\(entry.widgetState.bgValueStringInUserChosenUnit)\(entry.widgetState.trendArrow())")
|
||||
.font(.title).fontWeight(.semibold)
|
||||
.foregroundStyle(entry.widgetState.bgTextColor())
|
||||
|
@ -22,29 +22,21 @@ extension XDripWidget.EntryView {
|
|||
|
||||
Spacer()
|
||||
|
||||
VStack(alignment: .trailing, spacing: 0) {
|
||||
Text(entry.widgetState.deltaChangeStringInUserChosenUnit())
|
||||
.font(.headline).fontWeight(.bold)
|
||||
.foregroundStyle(Color(white: 0.9))
|
||||
.lineLimit(1)
|
||||
.padding(.bottom, -3)
|
||||
Text(entry.widgetState.bgUnitString)
|
||||
.font(.footnote)
|
||||
.foregroundStyle(.gray)
|
||||
.lineLimit(1)
|
||||
}
|
||||
Text(entry.widgetState.deltaChangeStringInUserChosenUnit())
|
||||
.font(.title).fontWeight(.semibold)
|
||||
.foregroundStyle(entry.widgetState.deltaChangeTextColor())
|
||||
.minimumScaleFactor(0.5)
|
||||
.lineLimit(1)
|
||||
}
|
||||
.padding(.top, -6)
|
||||
.padding(.bottom, 6)
|
||||
|
||||
GlucoseChartView(glucoseChartType: .widgetSystemSmall, 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)
|
||||
|
||||
HStack {
|
||||
Spacer()
|
||||
|
||||
Text("Last reading \(entry.widgetState.bgReadingDate?.formatted(date: .omitted, time: .shortened) ?? "--:--")")
|
||||
.font(.caption).bold()
|
||||
.minimumScaleFactor(0.2)
|
||||
.font(.system(size: 10)).bold()
|
||||
.foregroundStyle(Color(white: 0.6))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -72,6 +72,16 @@ extension XDripWidget.Entry {
|
|||
}
|
||||
}
|
||||
|
||||
/// Delta text color dependant on the time since the last reading
|
||||
/// - Returns: a Color either red, yellow or green
|
||||
func deltaChangeTextColor() -> Color {
|
||||
if let bgReadingDate = bgReadingDate, bgReadingDate > Date().addingTimeInterval(-60 * 7) {
|
||||
return Color(white: 0.8)
|
||||
} else {
|
||||
return Color(.gray)
|
||||
}
|
||||
}
|
||||
|
||||
/// used to return values and colors used by a SwiftUI gauge view
|
||||
/// - Returns: minValue/maxValue - used to define the limits of the gauge. gaugeColor/gaugeGradient - the gauge view will use one or the other
|
||||
func gaugeModel() -> (minValue: Double, maxValue: Double, gaugeColor: Color, gaugeGradient: Gradient) {
|
||||
|
|
|
@ -37,7 +37,7 @@ extension XDripWidget.Provider {
|
|||
|
||||
guard let sharedUserDefaults = UserDefaults(suiteName: Bundle.main.appGroupSuiteName) else {return nil}
|
||||
|
||||
guard let encodedLatestReadings = sharedUserDefaults.data(forKey: "widgetSharedUserDefaults") else {
|
||||
guard let encodedLatestReadings = sharedUserDefaults.data(forKey: "widgetSharedUserDefaults.\(Bundle.main.mainAppBundleIdentifier)") else {
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
|
@ -112,51 +112,31 @@ struct XDripWidgetLiveActivity: Widget {
|
|||
// 3 = large chart is final default option
|
||||
ZStack {
|
||||
VStack {
|
||||
if context.state.showClockAtNight {
|
||||
HStack(alignment: .firstTextBaseline) {
|
||||
Text(Date().formatted(date: .omitted, time: .shortened))
|
||||
.font(.system(size: 50)).bold()
|
||||
.foregroundStyle(Color(white: 0.7))
|
||||
.minimumScaleFactor(0.2)
|
||||
|
||||
Spacer()
|
||||
|
||||
|
||||
Text("\(context.state.bgValueStringInUserChosenUnit)\(context.state.trendArrow()) ")
|
||||
.font(.system(size: 50)).bold()
|
||||
.foregroundStyle(context.state.bgTextColor())
|
||||
}
|
||||
.padding(.top, 4)
|
||||
.padding(.bottom, -14)
|
||||
.padding(.leading, 6)
|
||||
.padding(.trailing, 6)
|
||||
} else {
|
||||
HStack(alignment: .center) {
|
||||
Text("\(context.state.bgValueStringInUserChosenUnit)\(context.state.trendArrow()) ")
|
||||
.font(.largeTitle).bold()
|
||||
.foregroundStyle(context.state.bgTextColor())
|
||||
|
||||
Spacer()
|
||||
|
||||
HStack(alignment: .center, spacing: 4) {
|
||||
Text(context.state.deltaChangeStringInUserChosenUnit())
|
||||
.font(.title2).bold()
|
||||
.foregroundStyle(Color(white: 0.9))
|
||||
.minimumScaleFactor(0.2)
|
||||
.lineLimit(1)
|
||||
|
||||
Text(context.state.bgUnitString)
|
||||
.font(.title2)
|
||||
.foregroundStyle(Color(white: 0.5))
|
||||
.minimumScaleFactor(0.2)
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
.padding(.top, 4)
|
||||
.padding(.bottom, -8)
|
||||
.padding(.leading, 10)
|
||||
.padding(.trailing, 10)
|
||||
HStack(alignment: .center) {
|
||||
Text("\(context.state.bgValueStringInUserChosenUnit)\(context.state.trendArrow()) ")
|
||||
.font(.largeTitle).bold()
|
||||
.foregroundStyle(context.state.bgTextColor())
|
||||
|
||||
Spacer()
|
||||
|
||||
HStack(alignment: .center, spacing: 4) {
|
||||
Text(context.state.deltaChangeStringInUserChosenUnit())
|
||||
.font(.title2).bold()
|
||||
.foregroundStyle(Color(white: 0.9))
|
||||
.minimumScaleFactor(0.2)
|
||||
.lineLimit(1)
|
||||
|
||||
Text(context.state.bgUnitString)
|
||||
.font(.title2)
|
||||
.foregroundStyle(Color(white: 0.5))
|
||||
.minimumScaleFactor(0.2)
|
||||
.lineLimit(1)
|
||||
}
|
||||
}
|
||||
.padding(.top, 4)
|
||||
.padding(.bottom, -8)
|
||||
.padding(.leading, 10)
|
||||
.padding(.trailing, 10)
|
||||
|
||||
GlucoseChartView(glucoseChartType: .liveActivity, bgReadingValues: context.state.bgReadingValues, bgReadingDates: context.state.bgReadingDates, isMgDl: context.state.isMgDl, urgentLowLimitInMgDl: context.state.urgentLowLimitInMgDl, lowLimitInMgDl: context.state.lowLimitInMgDl, highLimitInMgDl: context.state.highLimitInMgDl, urgentHighLimitInMgDl: context.state.urgentHighLimitInMgDl, liveActivitySize: .large, hoursToShowScalingHours: nil, glucoseCircleDiameterScalingHours: nil)
|
||||
}
|
||||
|
@ -267,7 +247,7 @@ struct XDripWidgetLiveActivity_Previews: PreviewProvider {
|
|||
|
||||
static let attributes = XDripWidgetAttributes()
|
||||
|
||||
static let contentState = XDripWidgetAttributes.ContentState(bgReadingValues: bgValueArray(), bgReadingDates: bgDateArray(), isMgDl: true, slopeOrdinal: 5, deltaChangeInMgDl: -2, urgentLowLimitInMgDl: 70, lowLimitInMgDl: 80, highLimitInMgDl: 140, urgentHighLimitInMgDl: 180, showClockAtNight: false, liveActivitySize: .minimal)
|
||||
static let contentState = XDripWidgetAttributes.ContentState(bgReadingValues: bgValueArray(), bgReadingDates: bgDateArray(), isMgDl: true, slopeOrdinal: 5, deltaChangeInMgDl: -2, urgentLowLimitInMgDl: 70, lowLimitInMgDl: 80, highLimitInMgDl: 140, urgentHighLimitInMgDl: 180, liveActivitySize: .minimal)
|
||||
|
||||
static var previews: some View {
|
||||
attributes
|
||||
|
|
|
@ -4,5 +4,7 @@
|
|||
<dict>
|
||||
<key>AppGroupIdentifier</key>
|
||||
<string>$(APP_GROUP_IDENTIFIER)</string>
|
||||
<key>MainAppBundleIdentifier</key>
|
||||
<string>$(MAIN_APP_BUNDLE_IDENTIFIER)</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
@ -151,6 +151,7 @@
|
|||
47DE41B32B8672F90041DA19 /* DataSourceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47DE41B22B8672F90041DA19 /* DataSourceView.swift */; };
|
||||
47DE41B52B8693CB0041DA19 /* HeaderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47DE41B42B8693CB0041DA19 /* HeaderView.swift */; };
|
||||
47DE41B92B87B2680041DA19 /* ConstantsAppleWatch.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47DE41B82B87B2680041DA19 /* ConstantsAppleWatch.swift */; };
|
||||
47E91BBA2B9A43F20063181B /* FollowerBackgroundKeepAliveType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47B7FC712B00CF4B004C872B /* FollowerBackgroundKeepAliveType.swift */; };
|
||||
47FB28082636B04200042FFB /* StatisticsManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 47FB28072636B04200042FFB /* StatisticsManager.swift */; };
|
||||
CE1B2FE025D0264B00F642F5 /* LaunchScreen.strings in Resources */ = {isa = PBXBuildFile; fileRef = CE1B2FD125D0264900F642F5 /* LaunchScreen.strings */; };
|
||||
CE1B2FE125D0264B00F642F5 /* Main.strings in Resources */ = {isa = PBXBuildFile; fileRef = CE1B2FD425D0264900F642F5 /* Main.strings */; };
|
||||
|
@ -3937,6 +3938,7 @@
|
|||
471C9BFF2B932952005E1326 /* LibreLinkUpModels.swift in Sources */,
|
||||
478A923E2B8B64DE0084C394 /* ConstantsHomeView.swift in Sources */,
|
||||
4796C6072B9516FD00DE2210 /* Bundle.swift in Sources */,
|
||||
47E91BBA2B9A43F20063181B /* FollowerBackgroundKeepAliveType.swift in Sources */,
|
||||
478A92582B8FA1F20084C394 /* Date.swift in Sources */,
|
||||
478A92552B8FA1D80084C394 /* ConstantsBGGraphBuilder.swift in Sources */,
|
||||
478A925F2B8FB5290084C394 /* XDripWatchComplication+Entry.swift in Sources */,
|
||||
|
@ -5010,7 +5012,7 @@
|
|||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "xDrip Widget/Info.plist";
|
||||
INFOPLIST_KEY_CFBundleDisplayName = xDrip4iO5;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "$(MAIN_APP_DISPLAY_NAME)";
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Johan Degraeve. All rights reserved.";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
|
@ -5046,7 +5048,7 @@
|
|||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "xDrip Widget/Info.plist";
|
||||
INFOPLIST_KEY_CFBundleDisplayName = xDrip4iO5;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "$(MAIN_APP_DISPLAY_NAME)";
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2023 Johan Degraeve. All rights reserved.";
|
||||
IPHONEOS_DEPLOYMENT_TARGET = 17.0;
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
|
@ -5080,7 +5082,7 @@
|
|||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "xDrip Watch Complication/Info.plist";
|
||||
INFOPLIST_KEY_CFBundleDisplayName = xDrip4iO5;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "$(MAIN_APP_DISPLAY_NAME)";
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 Johan Degraeve. All rights reserved.";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
|
@ -5117,7 +5119,7 @@
|
|||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "xDrip Watch Complication/Info.plist";
|
||||
INFOPLIST_KEY_CFBundleDisplayName = xDrip4iO5;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "$(MAIN_APP_DISPLAY_NAME)";
|
||||
INFOPLIST_KEY_NSHumanReadableCopyright = "Copyright © 2024 Johan Degraeve. All rights reserved.";
|
||||
LD_RUNPATH_SEARCH_PATHS = (
|
||||
"$(inherited)",
|
||||
|
@ -5156,7 +5158,7 @@
|
|||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "xDrip-Watch-App-Info.plist";
|
||||
INFOPLIST_KEY_CFBundleDisplayName = xDrip4iO5;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "$(MAIN_APP_DISPLAY_NAME)";
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||
INFOPLIST_KEY_WKCompanionAppBundleIdentifier = "$(MAIN_APP_BUNDLE_IDENTIFIER)";
|
||||
INFOPLIST_KEY_WKRunsIndependentlyOfCompanionApp = NO;
|
||||
|
@ -5197,7 +5199,7 @@
|
|||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
GENERATE_INFOPLIST_FILE = YES;
|
||||
INFOPLIST_FILE = "xDrip-Watch-App-Info.plist";
|
||||
INFOPLIST_KEY_CFBundleDisplayName = xDrip4iO5;
|
||||
INFOPLIST_KEY_CFBundleDisplayName = "$(MAIN_APP_DISPLAY_NAME)";
|
||||
INFOPLIST_KEY_UISupportedInterfaceOrientations = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown";
|
||||
INFOPLIST_KEY_WKCompanionAppBundleIdentifier = "$(MAIN_APP_BUNDLE_IDENTIFIER)";
|
||||
INFOPLIST_KEY_WKRunsIndependentlyOfCompanionApp = NO;
|
||||
|
|
|
@ -9,18 +9,10 @@
|
|||
import Foundation
|
||||
|
||||
enum ConstantsLiveActivity {
|
||||
|
||||
// warn that live activity will soon end (in minutes)
|
||||
static let warnLiveActivityAfterMinutes: Double = 7.25 * 60 * 60
|
||||
|
||||
// end live activity after time in (minutes) we give a bit of margin
|
||||
// in case there is a missed reading (and therefore no update cycle) towards the end
|
||||
static let endLiveActivityAfterMinutes: Double = 7.75 * 60 * 60
|
||||
|
||||
// what time should the automatic standBy live activity view be started from?
|
||||
static let showClockAtNightFromHour: Int = 23
|
||||
|
||||
// what time should the automatic standBy live activity view end at?
|
||||
static let showClockAtNightToHour: Int = 8
|
||||
|
||||
}
|
||||
|
|
|
@ -4,4 +4,8 @@ extension Bundle {
|
|||
var appGroupSuiteName: String {
|
||||
return object(forInfoDictionaryKey: "AppGroupIdentifier") as! String
|
||||
}
|
||||
|
||||
var mainAppBundleIdentifier: String {
|
||||
return object(forInfoDictionaryKey: "MainAppBundleIdentifier") as! String
|
||||
}
|
||||
}
|
||||
|
|
|
@ -75,8 +75,6 @@ extension UserDefaults {
|
|||
case liveActivityType = "liveActivityType"
|
||||
/// which size should the live activities be shown?
|
||||
case liveActivitySize = "liveActivitySize"
|
||||
/// should the live activity be automatically configured for stand-by mode at night?
|
||||
case liveActivityShowClockAtNight = "liveActivityShowClockAtNight"
|
||||
|
||||
// Home Screen and main chart settings
|
||||
|
||||
|
@ -652,20 +650,6 @@ extension UserDefaults {
|
|||
}
|
||||
}
|
||||
|
||||
/// should the live activity be configured for the best stand-by mode during night hours?
|
||||
/// if true (and if the live activity is started/updated after 22hrs and before 08hrs), the big chart view with a clock will be shown
|
||||
/// if false (and if the live activity is started/updated before 22hrs or after 08hrs) then just show the normal live activity type the user has selected
|
||||
@objc dynamic var liveActivityShowClockAtNight: Bool {
|
||||
// default value for bool in userdefaults is false, as default we want this to be disabled
|
||||
get {
|
||||
return !bool(forKey: Key.liveActivityShowClockAtNight.rawValue)
|
||||
}
|
||||
set {
|
||||
set(!newValue, forKey: Key.liveActivityShowClockAtNight.rawValue)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// MARK: Home Screen Settings
|
||||
|
||||
/// the amount of hours to show in the mini-chart. Usually 24 hours but can be set to 48 hours by the user
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import Foundation
|
||||
import UIKit
|
||||
import SwiftUI
|
||||
|
||||
/// types of background keep-alive
|
||||
public enum FollowerBackgroundKeepAliveType: Int, CaseIterable {
|
||||
|
@ -42,7 +43,8 @@ public enum FollowerBackgroundKeepAliveType: Int, CaseIterable {
|
|||
}
|
||||
}
|
||||
|
||||
var keepAliveImage: UIImage {
|
||||
// return the keep-alive image for UIKit views
|
||||
var keepAliveUIImage: UIImage {
|
||||
switch self {
|
||||
case .disabled:
|
||||
return UIImage(systemName: "d.circle") ?? UIImage()
|
||||
|
@ -53,4 +55,16 @@ public enum FollowerBackgroundKeepAliveType: Int, CaseIterable {
|
|||
}
|
||||
}
|
||||
|
||||
// return the keep-alive image for SwiftUI views
|
||||
var keepAliveImage: Image {
|
||||
switch self {
|
||||
case .disabled:
|
||||
return Image(systemName: "d.circle")
|
||||
case .normal:
|
||||
return Image(systemName: "n.circle")
|
||||
case .aggressive:
|
||||
return Image(systemName: "a.circle")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -163,9 +163,9 @@ class NightScoutFollowManager: NSObject {
|
|||
trace(" last reading is less than 30 seconds old, will not download now", log: self.log, category: ConstantsLog.categoryNightScoutFollowManager, type: .info)
|
||||
|
||||
// schedule new download, only if followerBackgroundKeepAliveType != disabled
|
||||
if UserDefaults.standard.followerBackgroundKeepAliveType != .disabled {
|
||||
//if UserDefaults.standard.followerBackgroundKeepAliveType != .disabled {
|
||||
self.scheduleNewDownload()
|
||||
}
|
||||
//}
|
||||
|
||||
return
|
||||
}
|
||||
|
@ -205,9 +205,9 @@ class NightScoutFollowManager: NSObject {
|
|||
}
|
||||
|
||||
// schedule new download, only if followerBackgroundKeepAliveType != disabled
|
||||
if UserDefaults.standard.followerBackgroundKeepAliveType != .disabled {
|
||||
//if UserDefaults.standard.followerBackgroundKeepAliveType != .disabled {
|
||||
self.scheduleNewDownload()
|
||||
}
|
||||
//}
|
||||
|
||||
}
|
||||
|
||||
|
@ -417,14 +417,14 @@ class NightScoutFollowManager: NSObject {
|
|||
if UserDefaults.standard.followerBackgroundKeepAliveType != .disabled {
|
||||
|
||||
enableSuspensionPrevention()
|
||||
|
||||
// do initial download, this will also schedule future downloads
|
||||
download()
|
||||
|
||||
} else {
|
||||
disableSuspensionPrevention()
|
||||
}
|
||||
|
||||
// do initial download, this will also schedule future downloads
|
||||
download()
|
||||
|
||||
} else {
|
||||
|
||||
// disable the suspension prevention
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
|
||||
import Foundation
|
||||
import WatchConnectivity
|
||||
import WidgetKit
|
||||
|
||||
public final class WatchManager: NSObject, ObservableObject {
|
||||
|
||||
|
@ -16,6 +17,9 @@ public final class WatchManager: NSObject, ObservableObject {
|
|||
/// a watch connectivity session instance
|
||||
private let session: WCSession
|
||||
|
||||
// dispatch queue for async processing operations
|
||||
private let processQueue = DispatchQueue(label: "WatchManager.processQueue")
|
||||
|
||||
/// a BgReadingsAccessor instance
|
||||
private var bgReadingsAccessor: BgReadingsAccessor
|
||||
|
||||
|
@ -46,20 +50,8 @@ public final class WatchManager: NSObject, ObservableObject {
|
|||
}
|
||||
|
||||
private func processWatchState() {
|
||||
// check if the watch is connected and active before doing anything
|
||||
guard session.isReachable else { return }
|
||||
|
||||
DispatchQueue.main.async {
|
||||
|
||||
// get 2 last Readings, with a calculatedValue
|
||||
//let lastReading = self.bgReadingsAccessor.get2LatestBgReadings(minimumTimeIntervalInMinutes: 0)
|
||||
|
||||
// there should be at least one reading
|
||||
// guard lastReading.count > 0 else {
|
||||
// print("exiting processWatchState(), no recent BG readings returned")
|
||||
// return
|
||||
// }
|
||||
|
||||
|
||||
// create two simple arrays to send to the live activiy. One with the bg values in mg/dL and another with the corresponding timestamps
|
||||
// this is needed due to the not being able to pass structs that are not codable/hashable
|
||||
let hoursOfBgReadingsToSend: Double = 12
|
||||
|
@ -96,16 +88,25 @@ public final class WatchManager: NSObject, ObservableObject {
|
|||
self.watchState.showAppleWatchDebug = UserDefaults.standard.showAppleWatchDebug
|
||||
self.watchState.activeSensorDescription = UserDefaults.standard.activeSensorDescription
|
||||
self.watchState.timeStampOfLastFollowerConnection = UserDefaults.standard.timeStampOfLastFollowerConnection ?? Date()
|
||||
self.watchState.secondsUntilFollowerDisconnectWarning = UserDefaults.standard.followerDataSourceType.secondsUntilFollowerDisconnectWarning ?? 90
|
||||
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
|
||||
|
||||
// check when the last follower connection was and compare that to the actual time
|
||||
if let timeStampOfLastFollowerConnection = UserDefaults.standard.timeStampOfLastFollowerConnection, Calendar.current.dateComponents([.second], from: timeStampOfLastFollowerConnection, to: Date()).second! >= UserDefaults.standard.followerDataSourceType.secondsUntilFollowerDisconnectWarning {
|
||||
self.watchState.followerConnectionIsStale = true
|
||||
} else {
|
||||
self.watchState.followerConnectionIsStale = false
|
||||
}
|
||||
|
||||
if let sensorStartDate = UserDefaults.standard.activeSensorStartDate {
|
||||
self.watchState.sensorAgeInMinutes = Double(Calendar.current.dateComponents([.minute], from: sensorStartDate, to: Date()).minute!)
|
||||
} else {
|
||||
self.watchState.sensorAgeInMinutes = 0
|
||||
}
|
||||
|
||||
self.watchState.sensorMaxAgeInMinutes = (UserDefaults.standard.activeSensorMaxSensorAgeInDays ?? 0) * 24 * 60
|
||||
|
||||
self.sendToWatch()
|
||||
|
@ -113,12 +114,14 @@ public final class WatchManager: NSObject, ObservableObject {
|
|||
}
|
||||
|
||||
|
||||
private func sendToWatch() {
|
||||
private func sendToWatch() {
|
||||
guard let data = try? JSONEncoder().encode(watchState) else {
|
||||
print("Watch state JSON encoding error")
|
||||
return
|
||||
}
|
||||
|
||||
guard session.isReachable else { return }
|
||||
|
||||
session.sendMessageData(data, replyHandler: nil) { error in
|
||||
print("Cannot send data message to watch")
|
||||
}
|
||||
|
@ -148,8 +151,8 @@ extension WatchManager: WCSessionDelegate {
|
|||
|
||||
// if the action: refreshBGData message is received, then force the app to send new data to the Watch App
|
||||
if let requestWatchStateUpdate = message["requestWatchStateUpdate"] as? Bool, requestWatchStateUpdate {
|
||||
DispatchQueue.main.async {
|
||||
self.processWatchState()
|
||||
processQueue.async {
|
||||
self.sendToWatch()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -158,8 +161,8 @@ extension WatchManager: WCSessionDelegate {
|
|||
|
||||
public func sessionReachabilityDidChange(_ session: WCSession) {
|
||||
if session.isReachable {
|
||||
DispatchQueue.main.async {
|
||||
self.processWatchState()
|
||||
processQueue.async {
|
||||
self.sendToWatch()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -26,6 +26,8 @@ struct WatchState: Codable {
|
|||
var isMaster: Bool?
|
||||
var followerDataSourceTypeRawValue: Int?
|
||||
var followerBackgroundKeepAliveTypeRawValue: Int?
|
||||
var followerConnectionIsStale: Bool?
|
||||
var timeStampOfLastFollowerConnection: Date?
|
||||
var secondsUntilFollowerDisconnectWarning: Int?
|
||||
var disableComplications: Bool?
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
/// model of the data we'll store in the shared app group to pass from the watch app to the watch complication widget extension
|
||||
/// model of the data we'll store in the shared app group to pass from the watch app to widgetkit
|
||||
struct WidgetSharedUserDefaultsModel: Codable {
|
||||
var bgReadingValues: [Double]
|
||||
var bgReadingDatesAsDouble: [Double]
|
||||
|
|
|
@ -35,6 +35,8 @@
|
|||
<false/>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>MainAppBundleIdentifier</key>
|
||||
<string>$(MAIN_APP_BUNDLE_IDENTIFIER)</string>
|
||||
<key>NFCReaderUsageDescription</key>
|
||||
<string>xDrip4iO5 uses NFC to scan Libre sensors.</string>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
|
@ -51,6 +53,8 @@
|
|||
<string>Store bloodglucose readings</string>
|
||||
<key>NSHealthUpdateUsageDescription</key>
|
||||
<string>Store bloodglucose readings</string>
|
||||
<key>NSSupportsLiveActivities</key>
|
||||
<true/>
|
||||
<key>UIBackgroundModes</key>
|
||||
<array>
|
||||
<string>audio</string>
|
||||
|
@ -96,7 +100,5 @@
|
|||
<string>Light</string>
|
||||
<key>view controller-based status bar</key>
|
||||
<false/>
|
||||
<key>NSSupportsLiveActivities</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
|
@ -93,10 +93,6 @@ class Texts_SettingsView {
|
|||
return NSLocalizedString("settingsviews_liveActivitySizeLarge", tableName: filename, bundle: Bundle.main, value: "Large", comment: "notification settings, live activity size large")
|
||||
}()
|
||||
|
||||
static let liveActivityShowClockAtNight: String = {
|
||||
return NSLocalizedString("settingsviews_liveActivityShowClockAtNight", tableName: filename, bundle: Bundle.main, value: "Automatically configure for StandBy", comment: "notification settings, live activities will be automatically switch for standby mode at night")
|
||||
}()
|
||||
|
||||
|
||||
// MARK: - Section Data Source
|
||||
|
||||
|
|
|
@ -3223,7 +3223,7 @@ final class RootViewController: UIViewController, ObservableObject {
|
|||
setFollowerConnectionStatus()
|
||||
|
||||
// set the keep-alive image, then make it visible if in follower mode
|
||||
dataSourceKeepAliveImageOutlet.image = UserDefaults.standard.followerBackgroundKeepAliveType.keepAliveImage
|
||||
dataSourceKeepAliveImageOutlet.image = UserDefaults.standard.followerBackgroundKeepAliveType.keepAliveUIImage
|
||||
dataSourceKeepAliveImageOutlet.isHidden = isMaster
|
||||
|
||||
// let's go through the specific cases for follower modes
|
||||
|
@ -3591,7 +3591,7 @@ final class RootViewController: UIViewController, ObservableObject {
|
|||
if UserDefaults.standard.isMaster && showLiveActivity {
|
||||
|
||||
// create the contentState that will update the dynamic attributes of the Live Activity Widget
|
||||
let contentState = XDripWidgetAttributes.ContentState( bgReadingValues: bgReadingValues, bgReadingDates: bgReadingDates, isMgDl: UserDefaults.standard.bloodGlucoseUnitIsMgDl, slopeOrdinal: slopeOrdinal, deltaChangeInMgDl: deltaChangeInMgDl, urgentLowLimitInMgDl: UserDefaults.standard.urgentLowMarkValue, lowLimitInMgDl: UserDefaults.standard.lowMarkValue, highLimitInMgDl: UserDefaults.standard.highMarkValue, urgentHighLimitInMgDl: UserDefaults.standard.urgentHighMarkValue, showClockAtNight: UserDefaults.standard.liveActivityShowClockAtNight, liveActivitySize: UserDefaults.standard.liveActivitySize)
|
||||
let contentState = XDripWidgetAttributes.ContentState( bgReadingValues: bgReadingValues, bgReadingDates: bgReadingDates, isMgDl: UserDefaults.standard.bloodGlucoseUnitIsMgDl, slopeOrdinal: slopeOrdinal, deltaChangeInMgDl: deltaChangeInMgDl, urgentLowLimitInMgDl: UserDefaults.standard.urgentLowMarkValue, lowLimitInMgDl: UserDefaults.standard.lowMarkValue, highLimitInMgDl: UserDefaults.standard.highMarkValue, urgentHighLimitInMgDl: UserDefaults.standard.urgentHighMarkValue, liveActivitySize: UserDefaults.standard.liveActivitySize)
|
||||
|
||||
LiveActivityManager.shared.runActivity(contentState: contentState, forceRestart: forceRestart)
|
||||
|
||||
|
@ -3603,9 +3603,11 @@ final class RootViewController: UIViewController, ObservableObject {
|
|||
}
|
||||
|
||||
let widgetSharedUserDefaultsModel = WidgetSharedUserDefaultsModel(bgReadingValues: bgReadingValues, bgReadingDatesAsDouble: bgReadingDatesAsDouble, isMgDl: UserDefaults.standard.bloodGlucoseUnitIsMgDl, slopeOrdinal: slopeOrdinal, deltaChangeInMgDl: deltaChangeInMgDl, urgentLowLimitInMgDl: UserDefaults.standard.urgentLowMarkValue, lowLimitInMgDl: UserDefaults.standard.lowMarkValue, highLimitInMgDl: UserDefaults.standard.highMarkValue, urgentHighLimitInMgDl: UserDefaults.standard.urgentHighMarkValue)
|
||||
|
||||
|
||||
// store the model in the shared user defaults using a name that is uniquely specific to this copy of the app as installed on
|
||||
// the user's device - this allows several copies of the app to be installed without cross-contamination of widget data
|
||||
if let widgetData = try? JSONEncoder().encode(widgetSharedUserDefaultsModel) {
|
||||
UserDefaults.storeInSharedUserDefaults(value: widgetData, forKey: "widgetSharedUserDefaults")
|
||||
UserDefaults.storeInSharedUserDefaults(value: widgetData, forKey: "widgetSharedUserDefaults.\(Bundle.main.mainAppBundleIdentifier)")
|
||||
}
|
||||
|
||||
WidgetCenter.shared.reloadAllTimelines()
|
||||
|
|
|
@ -17,14 +17,11 @@ fileprivate enum Setting:Int, CaseIterable {
|
|||
/// live activity size
|
||||
case liveActivitySize = 3
|
||||
|
||||
/// live activity will be automatically configured for night/stand-by use?
|
||||
case liveActivityShowClockAtNight = 4
|
||||
|
||||
/// show reading in app badge
|
||||
case showReadingInAppBadge = 5
|
||||
case showReadingInAppBadge = 4
|
||||
|
||||
/// if reading is shown in app badge, should value be multiplied with 10 yes or no
|
||||
case multipleAppBadgeValueWith10 = 6
|
||||
case multipleAppBadgeValueWith10 = 5
|
||||
|
||||
}
|
||||
|
||||
|
@ -56,7 +53,7 @@ class SettingsViewNotificationsSettingsViewModel: SettingsViewModelProtocol {
|
|||
|
||||
switch setting {
|
||||
|
||||
case .showReadingInNotification, .showReadingInAppBadge, .multipleAppBadgeValueWith10, .liveActivityShowClockAtNight:
|
||||
case .showReadingInNotification, .showReadingInAppBadge, .multipleAppBadgeValueWith10:
|
||||
return .nothing
|
||||
|
||||
case .notificationInterval:
|
||||
|
@ -207,9 +204,6 @@ class SettingsViewNotificationsSettingsViewModel: SettingsViewModelProtocol {
|
|||
case .liveActivitySize:
|
||||
return Texts_SettingsView.labelliveActivitySize
|
||||
|
||||
case .liveActivityShowClockAtNight:
|
||||
return Texts_SettingsView.liveActivityShowClockAtNight
|
||||
|
||||
case .showReadingInAppBadge:
|
||||
return Texts_SettingsView.labelShowReadingInAppBadge
|
||||
|
||||
|
@ -224,7 +218,7 @@ class SettingsViewNotificationsSettingsViewModel: SettingsViewModelProtocol {
|
|||
|
||||
switch setting {
|
||||
|
||||
case .showReadingInNotification, .showReadingInAppBadge, .multipleAppBadgeValueWith10, .liveActivityShowClockAtNight:
|
||||
case .showReadingInNotification, .showReadingInAppBadge, .multipleAppBadgeValueWith10:
|
||||
return .none
|
||||
|
||||
case .notificationInterval:
|
||||
|
@ -264,13 +258,6 @@ class SettingsViewNotificationsSettingsViewModel: SettingsViewModelProtocol {
|
|||
} else {
|
||||
return "iOS 16.2 needed"
|
||||
}
|
||||
|
||||
case .liveActivityShowClockAtNight:
|
||||
if #available(iOS 16.2, *) {
|
||||
return UserDefaults.standard.isMaster ? nil : Texts_SettingsView.liveActivityDisabledInFollowerMode
|
||||
} else {
|
||||
return "iOS 16.2 needed"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -290,9 +277,6 @@ class SettingsViewNotificationsSettingsViewModel: SettingsViewModelProtocol {
|
|||
|
||||
case .notificationInterval, .liveActivityType, .liveActivitySize:
|
||||
return nil
|
||||
|
||||
case .liveActivityShowClockAtNight:
|
||||
return UISwitch(isOn: UserDefaults.standard.liveActivityShowClockAtNight, action: {(isOn:Bool) in UserDefaults.standard.liveActivityShowClockAtNight = isOn})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue