xdripswift/xDrip Widget/XDripWidgetLiveActivity.swift

267 lines
13 KiB
Swift

//
// XDripWidgetLiveActivity.swift
// XDripWidget
//
// Created by Paul Plant on 29/12/23.
// Copyright © 2023 Johan Degraeve. All rights reserved.
//
import ActivityKit
import WidgetKit
import SwiftUI
@available(iOSApplicationExtension 16.2, *)
struct XDripWidgetLiveActivity: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: XDripWidgetAttributes.self) { context in
if context.state.liveActivitySize == .minimal {
// 1 = minimal widget with no chart
HStack(alignment: .center) {
Text("\(context.state.bgValueStringInUserChosenUnit) \(context.state.trendArrow())")
.font(.system(size: 35)).fontWeight(.semibold)
.foregroundStyle(context.state.bgTextColor())
.minimumScaleFactor(0.1)
.lineLimit(1)
Spacer()
if context.state.warnUserToOpenApp {
Text("Ending soon...")
.font(.footnote).bold()
.foregroundStyle(.black)
.multilineTextAlignment(.center)
.padding(EdgeInsets(top: 6, leading: 10, bottom: 6, trailing: 10))
.background(.cyan).opacity(0.9)
.cornerRadius(10)
Spacer()
}
HStack(alignment: .firstTextBaseline, spacing: 4) {
Text(context.state.deltaChangeStringInUserChosenUnit())
.font(.title).bold()
.foregroundStyle(context.state.deltaChangeTextColor())
.minimumScaleFactor(0.2)
.lineLimit(1)
Text(context.state.bgUnitString)
.font(.title)
.foregroundStyle(Color(white: 0.5))
.minimumScaleFactor(0.2)
.lineLimit(1)
}
}
.activityBackgroundTint(.black)
.padding([.top, .bottom], 0)
.padding([.leading, .trailing], 20)
} else if context.state.liveActivitySize == .normal {
// 0 = normal size chart
HStack(spacing: 30) {
VStack(spacing: -2) {
Text("\(context.state.bgValueStringInUserChosenUnit)\(context.state.trendArrow())")
.font(.system(size: 44)).fontWeight(.semibold)
.foregroundStyle(context.state.bgTextColor())
.minimumScaleFactor(0.1)
.lineLimit(1)
HStack(alignment: .firstTextBaseline, spacing: 4) {
Text(context.state.deltaChangeStringInUserChosenUnit())
.font(.system(size: 20)).bold()
.foregroundStyle(context.state.deltaChangeTextColor())
.minimumScaleFactor(0.2)
.lineLimit(1)
Text(context.state.bgUnitString)
.font(.system(size: 20))
.foregroundStyle(Color(white: 0.5))
.minimumScaleFactor(0.2)
.lineLimit(1)
}
}
.padding(2)
ZStack {
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: .normal, hoursToShowScalingHours: nil, glucoseCircleDiameterScalingHours: nil)
if context.state.warnUserToOpenApp {
VStack(alignment: .center) {
Spacer()
Text("Activity ending soon")
.font(.footnote).bold()
.foregroundStyle(.black)
.multilineTextAlignment(.center)
.padding(EdgeInsets(top: 6, leading: 10, bottom: 6, trailing: 10))
.background(.cyan).opacity(0.9)
.cornerRadius(10)
Spacer()
}
.padding(8)
}
}
}
.activityBackgroundTint(.black)
.padding(4)
} else {
// 3 = large chart is final default option
ZStack {
VStack {
HStack(alignment: .firstTextBaseline) {
Text("\(context.state.bgValueStringInUserChosenUnit) \(context.state.trendArrow()) ")
.font(.title).bold()
.foregroundStyle(context.state.bgTextColor())
.lineLimit(1)
Spacer()
HStack(alignment: .firstTextBaseline, spacing: 4) {
Text(context.state.deltaChangeStringInUserChosenUnit())
.font(.title).bold()
.foregroundStyle(context.state.deltaChangeTextColor())
.lineLimit(1)
Text(context.state.bgUnitString)
.font(.title2)
.foregroundStyle(Color(white: 0.5))
.lineLimit(1)
}
}
.padding(.top, 4)
.padding(.bottom, -8)
.padding(.leading, 15)
.padding(.trailing, 15)
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)
}
.frame(maxWidth: .infinity, alignment: .leading)
.padding(0)
if context.state.warnUserToOpenApp {
VStack(alignment: .center) {
Text("Live activity ending soon")
.font(.footnote).bold()
.foregroundStyle(.black)
.multilineTextAlignment(.center)
.padding(EdgeInsets(top: 6, leading: 10, bottom: 6, trailing: 10))
.background(.cyan).opacity(0.9)
.cornerRadius(10)
}
}
}
.activityBackgroundTint(.black)
}
} dynamicIsland: { context in
DynamicIsland {
DynamicIslandExpandedRegion(.leading) { Text("\(context.state.bgValueStringInUserChosenUnit)\(context.state.trendArrow())")
.font(.largeTitle).bold()
.foregroundStyle(context.state.bgTextColor())
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
.minimumScaleFactor(0.1)
.lineLimit(1)
}
DynamicIslandExpandedRegion(.trailing) {
HStack(alignment: .firstTextBaseline, spacing: 4) {
Text(context.state.deltaChangeStringInUserChosenUnit())
.font(.title).bold()
.foregroundStyle(context.state.deltaChangeTextColor())
.minimumScaleFactor(0.2)
.lineLimit(1)
Text(context.state.bgUnitString)
.font(.title)
.foregroundStyle(Color(white: 0.5))
.minimumScaleFactor(0.2)
.lineLimit(1)
}
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .center)
}
DynamicIslandExpandedRegion(.bottom) {
GlucoseChartView(glucoseChartType: .dynamicIsland, 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: nil, hoursToShowScalingHours: nil, glucoseCircleDiameterScalingHours: nil)
}
} compactLeading: {
Text("\(context.state.bgValueStringInUserChosenUnit)\(context.state.trendArrow())")
.foregroundStyle(context.state.bgTextColor())
.minimumScaleFactor(0.1)
} compactTrailing: {
Text(context.state.deltaChangeStringInUserChosenUnit())
.minimumScaleFactor(0.1)
} minimal: {
Text("\(context.state.bgValueStringInUserChosenUnit)")
.foregroundStyle(context.state.bgTextColor())
.minimumScaleFactor(0.1)
}
.widgetURL(URL(string: "xdripswift"))
.keylineTint(context.state.bgTextColor())
}
}
}
@available(iOS 16.2, *)
struct XDripWidgetLiveActivity_Previews: PreviewProvider {
// generate some random dates for the preview
static func bgDateArray() -> [Date] {
let endDate = Date()
let startDate = endDate.addingTimeInterval(-3600 * 12)
var currentDate = startDate
var dateArray: [Date] = []
while currentDate < endDate {
dateArray.append(currentDate)
currentDate = currentDate.addingTimeInterval(60 * 5)
}
return dateArray
}
// generate some random bg values for the preview
static func bgValueArray() -> [Double] {
var bgValueArray:[Double] = Array(repeating: 0, count: 144)
var currentValue: Double = 100
var increaseValues: Bool = true
for index in bgValueArray.indices {
let randomValue = Double(Int.random(in: -10..<10))
if currentValue < 80 {
increaseValues = true
bgValueArray[index] = currentValue + abs(randomValue)
} else if currentValue > 160 {
increaseValues = false
bgValueArray[index] = currentValue - abs(randomValue)
} else {
bgValueArray[index] = currentValue + (increaseValues ? randomValue : -randomValue)
}
currentValue = bgValueArray[index]
}
return bgValueArray
}
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, liveActivitySize: .minimal)
static var previews: some View {
attributes
.previewContext(contentState, viewKind: .content)
.previewDisplayName("Notification")
attributes
.previewContext(contentState, viewKind: .dynamicIsland(.compact))
.previewDisplayName("Compact")
attributes
.previewContext(contentState, viewKind: .dynamicIsland(.expanded))
.previewDisplayName("Expanded")
attributes
.previewContext(contentState, viewKind: .dynamicIsland(.minimal))
.previewDisplayName("Minimal")
}
}