Skip to content

Commit 6fc8e26

Browse files
authored
Modify live activity (#2397)
* improve large font display; * add section headings; * use short labels for display, long labels for description * Enable autoscaling in Live Activity widget to limit truncation * bug fix for plot using glucose color; author: bastiaanv
1 parent 5ea835d commit 6fc8e26

File tree

7 files changed

+185
-77
lines changed

7 files changed

+185
-77
lines changed

Loop Widget Extension/Live Activity/BasalViewActivity.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,10 +25,14 @@ struct BasalViewActivity: View {
2525
if let rateString = decimalFormatter.string(from: NSNumber(value: rate)) {
2626
Text("\(rateString)U")
2727
.font(.subheadline)
28+
.minimumScaleFactor(0.5)
29+
.lineLimit(2)
2830
}
2931
else {
3032
Text("-U")
3133
.font(.subheadline)
34+
.minimumScaleFactor(0.5)
35+
.lineLimit(2)
3236
}
3337
}
3438
}

Loop Widget Extension/Live Activity/ChartView.swift

Lines changed: 26 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ struct ChartView: View {
1717
private let glucoseRanges: [GlucoseRangeValue]
1818
private let preset: Preset?
1919
private let yAxisMarks: [Double]
20+
private let colorGradient: LinearGradient
2021

2122
init(glucoseSamples: [GlucoseSampleAttributes], predicatedGlucose: [Double], predicatedStartDate: Date?, predicatedInterval: TimeInterval?, useLimits: Bool, lowerLimit: Double, upperLimit: Double, glucoseRanges: [GlucoseRangeValue], preset: Preset?, yAxisMarks: [Double]) {
2223
self.glucoseSampleData = ChartValues.convert(data: glucoseSamples, useLimits: useLimits, lowerLimit: lowerLimit, upperLimit: upperLimit)
@@ -28,6 +29,7 @@ struct ChartView: View {
2829
lowerLimit: lowerLimit,
2930
upperLimit: upperLimit
3031
)
32+
self.colorGradient = ChartView.getGradient(useLimits: useLimits, lowerLimit: lowerLimit, upperLimit: upperLimit, highestValue: yAxisMarks.max() ?? 1)
3133
self.preset = preset
3234
self.glucoseRanges = glucoseRanges
3335
self.yAxisMarks = yAxisMarks
@@ -39,6 +41,28 @@ struct ChartView: View {
3941
self.preset = preset
4042
self.glucoseRanges = glucoseRanges
4143
self.yAxisMarks = yAxisMarks
44+
self.colorGradient = ChartView.getGradient(useLimits: useLimits, lowerLimit: lowerLimit, upperLimit: upperLimit, highestValue: yAxisMarks.max() ?? 1)
45+
}
46+
47+
private static func getGradient(useLimits: Bool, lowerLimit: Double, upperLimit: Double, highestValue: Double) -> LinearGradient {
48+
var stops: [Gradient.Stop] = [Gradient.Stop(color: Color("glucose"), location: 0)]
49+
if useLimits {
50+
let lowerStop = lowerLimit / highestValue
51+
let upperStop = upperLimit / highestValue
52+
stops = [
53+
Gradient.Stop(color: .red, location: 0),
54+
Gradient.Stop(color: .red, location: lowerStop - 0.01),
55+
Gradient.Stop(color: .green, location: lowerStop),
56+
Gradient.Stop(color: .green, location: upperStop),
57+
Gradient.Stop(color: .orange, location: upperStop + 0.01),
58+
Gradient.Stop(color: .orange, location: 600), // Just use the mg/dl limit for the most upper value
59+
]
60+
}
61+
return LinearGradient(
62+
gradient: Gradient(stops: stops),
63+
startPoint: .bottom,
64+
endPoint: .top
65+
)
4266
}
4367

4468
var body: some View {
@@ -79,7 +103,7 @@ struct ChartView: View {
79103
y: .value("Glucose level", item.y)
80104
)
81105
.lineStyle(StrokeStyle(lineWidth: 2, dash: [6, 5]))
82-
.foregroundStyle(by: .value("Color", item.color))
106+
.foregroundStyle(colorGradient)
83107
}
84108
}
85109
.chartForegroundStyleScale([
@@ -144,7 +168,7 @@ struct ChartValues: Identifiable {
144168
return ChartValues(
145169
x: startDate.addingTimeInterval(interval * Double(index)),
146170
y: item,
147-
color: !useLimits ? "Default" : item < lowerLimit ? "Low" : item > upperLimit ? "High" : "Good"
171+
color: "Default" // Color is handled by the gradient
148172
)
149173
}
150174
}

Loop/Localizable.xcstrings

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5323,6 +5323,7 @@
53235323
},
53245324
"Add item to bottom row" : {
53255325
"comment" : "Title for Add item",
5326+
"extractionState" : "stale",
53265327
"localizations" : {
53275328
"de" : {
53285329
"stringUnit" : {
@@ -5332,6 +5333,9 @@
53325333
}
53335334
}
53345335
},
5336+
"Add item to Lock Screen / CarPlay display" : {
5337+
"comment" : "Title for Add item"
5338+
},
53355339
"Add Meal" : {
53365340
"comment" : "The label of the carb entry button",
53375341
"localizations" : {
@@ -5459,6 +5463,7 @@
54595463
},
54605464
"Add predictive line" : {
54615465
"comment" : "Title for predictive line toggle",
5466+
"extractionState" : "stale",
54625467
"localizations" : {
54635468
"de" : {
54645469
"stringUnit" : {
@@ -9855,6 +9860,7 @@
98559860
},
98569861
"Bottom row" : {
98579862
"comment" : "Live activity Bottom row configuration title",
9863+
"extractionState" : "stale",
98589864
"localizations" : {
98599865
"de" : {
98609866
"stringUnit" : {
@@ -9872,6 +9878,7 @@
98729878
},
98739879
"Bottom row configuration" : {
98749880
"comment" : "Title for Bottom row configuration",
9881+
"extractionState" : "stale",
98759882
"localizations" : {
98769883
"de" : {
98779884
"stringUnit" : {
@@ -13152,6 +13159,13 @@
1315213159
}
1315313160
}
1315413161
},
13162+
"Configure Display" : {
13163+
"comment" : "Title for the view to configure the lock screen display"
13164+
},
13165+
"Configure Lock Screen / Carplay Row" : {
13166+
"comment" : "A link that takes the user to a view where they can configure the display of the live activity screen on their lock screen and in CarPlay.",
13167+
"isCommentAutoGenerated" : true
13168+
},
1315513169
"Continue" : {
1315613170
"comment" : "Button label for continue\nDefault alert dismissal",
1315713171
"localizations" : {
@@ -16210,6 +16224,19 @@
1621016224
}
1621116225
}
1621216226
},
16227+
"Display colors for glucose" : {
16228+
"comment" : "Title for glucose coloring"
16229+
},
16230+
"Display Control Options" : {
16231+
"comment" : "A section header in the live activity settings view, describing the control options for displaying glucose levels on the live activity screen.",
16232+
"isCommentAutoGenerated" : true
16233+
},
16234+
"Display prediction in plot" : {
16235+
"comment" : "Title for prediction line toggle"
16236+
},
16237+
"Display up to 4 items. Display label is in parentheses." : {
16238+
"comment" : "Indicates the maximum number of items that can be displayed and how the label for each item is shortened."
16239+
},
1621316240
"Done" : {
1621416241
"localizations" : {
1621516242
"cs" : {
@@ -23681,6 +23708,10 @@
2368123708
}
2368223709
}
2368323710
},
23711+
"Lock Screen / Dynamic Island / CarPlay" : {
23712+
"comment" : "The title of a section in the live activity settings view, related to lock screen, dynamic island, or carplay.",
23713+
"isCommentAutoGenerated" : true
23714+
},
2368423715
"Log Dose" : {
2368523716
"comment" : "Button text to log a dose\nTitle for dose logging screen",
2368623717
"localizations" : {
@@ -33105,6 +33136,10 @@
3310533136
}
3310633137
}
3310733138
},
33139+
"Select Lock Screen Display Options" : {
33140+
"comment" : "A section header for the lock screen display options.",
33141+
"isCommentAutoGenerated" : true
33142+
},
3310833143
"Selecting a favorite food in the carb entry screen automatically fills in the carb quantity, food type, and absorption time fields! Tap the add button below to create your first favorite food!" : {
3310933144
"localizations" : {
3311033145
"da" : {
@@ -38598,6 +38633,7 @@
3859838633
},
3859938634
"Use BG coloring" : {
3860038635
"comment" : "Title for BG coloring",
38636+
"extractionState" : "stale",
3860138637
"localizations" : {
3860238638
"de" : {
3860338639
"stringUnit" : {
@@ -41093,5 +41129,5 @@
4109341129
}
4109441130
}
4109541131
},
41096-
"version" : "1.0"
41132+
"version" : "1.1"
4109741133
}

Loop/Views/LiveActivityBottomRowManagerView.swift

Lines changed: 16 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -36,26 +36,27 @@ struct LiveActivityBottomRowManagerView: View {
3636
}
3737
buttons.append(.cancel(Text(NSLocalizedString("Cancel", comment: "Button text to cancel"))))
3838

39-
return ActionSheet(title: Text(NSLocalizedString("Add item to bottom row", comment: "Title for Add item")), buttons: buttons)
39+
return ActionSheet(title: Text(NSLocalizedString("Add item to Lock Screen / CarPlay display", comment: "Title for Add item")), buttons: buttons)
4040
}
4141

4242
var body: some View {
4343
List {
44-
ForEach($configuration, id: \.self) { item in
45-
HStack {
46-
deleteButton
47-
.onTapGesture {
48-
onDelete(item.wrappedValue)
49-
isDirty = configuration != previousConfiguration
50-
}
51-
Text(item.wrappedValue.description())
52-
53-
Spacer()
54-
editBars
44+
Section(header: Text("Display up to 4 items. Display label is in parentheses.", comment: "Indicates the maximum number of items that can be displayed and how the label for each item is shortened.")) {
45+
ForEach($configuration, id: \.self) { item in
46+
HStack {
47+
deleteButton
48+
.onTapGesture {
49+
onDelete(item.wrappedValue)
50+
isDirty = configuration != previousConfiguration
51+
}
52+
Text(item.wrappedValue.description())
53+
Spacer()
54+
editBars
55+
}
5556
}
57+
.onMove(perform: onReorder)
58+
.deleteDisabled(true)
5659
}
57-
.onMove(perform: onReorder)
58-
.deleteDisabled(true)
5960

6061
Section {
6162
Button(action: onSave) {
@@ -81,7 +82,7 @@ struct LiveActivityBottomRowManagerView: View {
8182
}
8283
.actionSheet(isPresented: $showAdd, content: { addItem })
8384
.insetGroupedListStyle()
84-
.navigationBarTitle(Text(NSLocalizedString("Bottom row", comment: "Live activity Bottom row configuration title")))
85+
.navigationBarTitle(Text(NSLocalizedString("Configure Display", comment: "Title for the view to configure the lock screen display")))
8586
}
8687

8788
@ViewBuilder

Loop/Views/LiveActivityManagementView.swift

Lines changed: 37 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -21,12 +21,14 @@ struct LiveActivityManagementView: View {
2121
var body: some View {
2222
VStack {
2323
List {
24-
Section {
24+
Section(header: Text("Lock Screen / Dynamic Island / CarPlay")) {
2525
Toggle(NSLocalizedString("Enabled", comment: "Title for enable live activity toggle"), isOn: $viewModel.enabled)
2626
.onChange(of: viewModel.enabled) { _ in
2727
self.isDirty = previousViewModel.enabled != viewModel.enabled
2828
}
29-
29+
}
30+
31+
Section(header: Text("Select Lock Screen Display Options")){
3032
ExpandableSetting(
3133
isEditing: $viewModel.isEditingMode,
3234
leadingValueContent: {
@@ -48,50 +50,50 @@ struct LiveActivityManagementView: View {
4850
}
4951
}
5052

51-
Section {
52-
if viewModel.mode == .large {
53-
Toggle(NSLocalizedString("Add predictive line", comment: "Title for predictive line toggle"), isOn: $viewModel.addPredictiveLine)
54-
.transition(.move(edge: viewModel.mode == .large ? .top : .bottom))
55-
.onChange(of: viewModel.addPredictiveLine) { _ in
56-
self.isDirty = previousViewModel.addPredictiveLine != viewModel.addPredictiveLine
57-
}
58-
}
53+
Section(header: Text("Display Control Options")) {
54+
Toggle(NSLocalizedString("Display prediction in plot", comment: "Title for prediction line toggle"), isOn: $viewModel.addPredictiveLine)
55+
.transition(.move(edge: viewModel.mode == .large ? .top : .bottom))
56+
.onChange(of: viewModel.addPredictiveLine) { _ in
57+
self.isDirty = previousViewModel.addPredictiveLine != viewModel.addPredictiveLine
58+
}
5959

60-
Toggle(NSLocalizedString("Use BG coloring", comment: "Title for BG coloring"), isOn: $viewModel.useLimits)
60+
Toggle(NSLocalizedString("Display colors for glucose", comment: "Title for glucose coloring"), isOn: $viewModel.useLimits)
6161
.transition(.move(edge: viewModel.mode == .large ? .top : .bottom))
6262
.onChange(of: viewModel.useLimits) { _ in
6363
self.isDirty = previousViewModel.useLimits != viewModel.useLimits
6464
}
6565

66-
if self.displayGlucosePreference.unit == .millimolesPerLiter {
67-
TextInput(label: "Upper limit", value: $viewModel.upperLimitChartMmol)
68-
.transition(.move(edge: viewModel.useLimits ? .top : .bottom))
69-
.onChange(of: viewModel.upperLimitChartMmol) { _ in
70-
self.isDirty = previousViewModel.upperLimitChartMmol != viewModel.upperLimitChartMmol
71-
}
72-
TextInput(label: "Lower limit", value: $viewModel.lowerLimitChartMmol)
73-
.transition(.move(edge: viewModel.useLimits ? .top : .bottom))
74-
.onChange(of: viewModel.lowerLimitChartMmol) { _ in
75-
self.isDirty = previousViewModel.lowerLimitChartMmol != viewModel.lowerLimitChartMmol
76-
}
77-
} else {
78-
TextInput(label: "Upper limit", value: $viewModel.upperLimitChartMg)
79-
.transition(.move(edge: viewModel.useLimits ? .top : .bottom))
80-
.onChange(of: viewModel.upperLimitChartMg) { _ in
81-
self.isDirty = previousViewModel.upperLimitChartMg != viewModel.upperLimitChartMg
82-
}
83-
TextInput(label: "Lower limit", value: $viewModel.lowerLimitChartMg)
84-
.transition(.move(edge: viewModel.useLimits ? .top : .bottom))
85-
.onChange(of: viewModel.lowerLimitChartMg) { _ in
86-
self.isDirty = previousViewModel.lowerLimitChartMg != viewModel.lowerLimitChartMg
87-
}
66+
if self.viewModel.useLimits {
67+
if self.displayGlucosePreference.unit == .millimolesPerLiter {
68+
TextInput(label: "Upper limit", value: $viewModel.upperLimitChartMmol)
69+
.transition(.move(edge: viewModel.useLimits ? .top : .bottom))
70+
.onChange(of: viewModel.upperLimitChartMmol) { _ in
71+
self.isDirty = previousViewModel.upperLimitChartMmol != viewModel.upperLimitChartMmol
72+
}
73+
TextInput(label: "Lower limit", value: $viewModel.lowerLimitChartMmol)
74+
.transition(.move(edge: viewModel.useLimits ? .top : .bottom))
75+
.onChange(of: viewModel.lowerLimitChartMmol) { _ in
76+
self.isDirty = previousViewModel.lowerLimitChartMmol != viewModel.lowerLimitChartMmol
77+
}
78+
} else {
79+
TextInput(label: "Upper limit", value: $viewModel.upperLimitChartMg)
80+
.transition(.move(edge: viewModel.useLimits ? .top : .bottom))
81+
.onChange(of: viewModel.upperLimitChartMg) { _ in
82+
self.isDirty = previousViewModel.upperLimitChartMg != viewModel.upperLimitChartMg
83+
}
84+
TextInput(label: "Lower limit", value: $viewModel.lowerLimitChartMg)
85+
.transition(.move(edge: viewModel.useLimits ? .top : .bottom))
86+
.onChange(of: viewModel.lowerLimitChartMg) { _ in
87+
self.isDirty = previousViewModel.lowerLimitChartMg != viewModel.lowerLimitChartMg
88+
}
89+
}
8890
}
8991
}
9092

91-
Section {
93+
Section(header: Text("Configure Lock Screen / Carplay Row")) {
9294
NavigationLink(
9395
destination: LiveActivityBottomRowManagerView(),
94-
label: { Text(NSLocalizedString("Bottom row configuration", comment: "Title for Bottom row configuration")) }
96+
label: { Text(NSLocalizedString("Configure Display", comment: "")) }
9597
)
9698
}
9799
}

0 commit comments

Comments
 (0)