Skip to content

Commit c0f96c7

Browse files
committed
Merge remote-tracking branch 'origin/dev' into feature/inline-documentation
2 parents 28479d6 + 25e9df1 commit c0f96c7

14 files changed

Lines changed: 195 additions & 133 deletions

File tree

.github/workflows/lint.yml

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
name: Lint
2+
run-name: Lint (${{ github.head_ref || github.ref_name }})
3+
4+
on:
5+
pull_request:
6+
workflow_dispatch:
7+
8+
concurrency:
9+
group: lint-${{ github.ref }}
10+
cancel-in-progress: true
11+
12+
permissions:
13+
contents: read
14+
15+
jobs:
16+
swiftformat:
17+
name: SwiftFormat
18+
runs-on: ubuntu-latest
19+
container: swift:6.0
20+
steps:
21+
- name: Checkout
22+
uses: actions/checkout@v5
23+
24+
- name: Cache SwiftFormat build
25+
uses: actions/cache@v4
26+
with:
27+
path: BuildTools/.build
28+
key: ${{ runner.os }}-swiftformat-${{ hashFiles('BuildTools/Package.resolved', 'BuildTools/Package.swift') }}
29+
restore-keys: |
30+
${{ runner.os }}-swiftformat-
31+
32+
- name: SwiftFormat --lint
33+
run: |
34+
swift run -c release --package-path BuildTools swiftformat . \
35+
--lint \
36+
--header "LoopFollow\n{file}" \
37+
--exclude Pods,Generated,R.generated.swift,fastlane/swift,Dependencies,dexcom-share-client-swift

Config.xcconfig

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,4 +6,4 @@
66
unique_id = ${DEVELOPMENT_TEAM}
77

88
//Version (DEFAULT)
9-
LOOP_FOLLOW_MARKETING_VERSION = 5.1.11
9+
LOOP_FOLLOW_MARKETING_VERSION = 6.0.5

LoopFollow/Application/SceneDelegate.swift

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,11 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
3939
// scene(_:openURLContexts:) fires after sceneDidBecomeActive when the app
4040
// foregrounds from background. Post on the next run loop so the view
4141
// hierarchy (including any presented modals) is fully settled.
42-
DispatchQueue.main.async {
43-
NotificationCenter.default.post(name: .liveActivityDidForeground, object: nil)
44-
}
42+
#if !targetEnvironment(macCatalyst)
43+
DispatchQueue.main.async {
44+
NotificationCenter.default.post(name: .liveActivityDidForeground, object: nil)
45+
}
46+
#endif
4547
}
4648

4749
func sceneWillResignActive(_: UIScene) {

LoopFollow/Controllers/Graphs.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -801,10 +801,14 @@ extension MainViewController {
801801

802802
topBG = Storage.shared.minBGScale.value
803803
for i in 0 ..< entries.count {
804-
if Double(entries[i].sgv) > topBG - maxBGOffset {
805-
topBG = Double(entries[i].sgv) + maxBGOffset
804+
// Clamp the plotted y-value to the same bounds the header text uses
805+
// (HIGH/LOW), so the graph stays consistent with the main display.
806+
// The pill tooltip still shows the raw reading.
807+
let plottedSgv = Double(min(max(entries[i].sgv, globalVariables.minDisplayGlucose), globalVariables.maxDisplayGlucose))
808+
if plottedSgv > topBG - maxBGOffset {
809+
topBG = plottedSgv + maxBGOffset
806810
}
807-
let value = ChartDataEntry(x: Double(entries[i].date), y: Double(entries[i].sgv), data: formatPillText(line1: Localizer.toDisplayUnits(String(entries[i].sgv)), time: entries[i].date))
811+
let value = ChartDataEntry(x: Double(entries[i].date), y: plottedSgv, data: formatPillText(line1: Localizer.toDisplayUnits(String(entries[i].sgv)), time: entries[i].date))
808812
mainChart.append(value)
809813
smallChart.append(value)
810814

LoopFollow/Controllers/VolumeButtonHandler.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ class VolumeButtonHandler: NSObject {
106106
}
107107

108108
private func alarmStopped() {
109-
LogManager.shared.log(category: .volumeButtonSnooze, message: "Alarm stop detected")
109+
guard isMonitoring else { return }
110110

111111
alarmStartTime = nil
112112
stopMonitoring()

LoopFollow/Helpers/Globals.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ enum globalVariables {
1515
// Glucose display range (mg/dL)
1616
// Values at or below the min are shown as "LOW" on the main display;
1717
// values at or above the max are shown as "HIGH". Also used to clamp
18-
// prediction values on the graph.
18+
// BG readings and prediction values on the graph.
1919
static let minDisplayGlucose: Int = 39
2020
static let maxDisplayGlucose: Int = 400
2121
}

LoopFollow/LiveActivity/LiveActivityManager.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -780,10 +780,10 @@ final class LiveActivityManager {
780780
}
781781
}
782782

783-
#endif
784-
785783
extension Notification.Name {
786784
/// Posted when the user taps the Live Activity or Dynamic Island.
787785
/// Observers navigate to the Home or Snoozer tab as appropriate.
788786
static let liveActivityDidForeground = Notification.Name("liveActivityDidForeground")
789787
}
788+
789+
#endif

LoopFollow/LiveActivity/StorageCurrentGlucoseStateProvider.swift

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -131,8 +131,12 @@ struct StorageCurrentGlucoseStateProvider: CurrentGlucoseStateProviding {
131131
// MARK: - Renewal
132132

133133
var showRenewalOverlay: Bool {
134-
let renewBy = Storage.shared.laRenewBy.value
135-
let now = Date().timeIntervalSince1970
136-
return renewBy > 0 && now >= renewBy - LiveActivityManager.renewalWarning
134+
#if targetEnvironment(macCatalyst)
135+
return false
136+
#else
137+
let renewBy = Storage.shared.laRenewBy.value
138+
let now = Date().timeIntervalSince1970
139+
return renewBy > 0 && now >= renewBy - LiveActivityManager.renewalWarning
140+
#endif
137141
}
138142
}
Lines changed: 67 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -1,89 +1,92 @@
11
// LoopFollow
22
// LiveActivitySettingsView.swift
33

4-
import SwiftUI
4+
#if !targetEnvironment(macCatalyst)
55

6-
struct LiveActivitySettingsView: View {
7-
@State private var laEnabled: Bool = Storage.shared.laEnabled.value
8-
@State private var restartConfirmed = false
9-
@State private var slots: [LiveActivitySlotOption] = LAAppGroupSettings.slots()
10-
@State private var smallWidgetSlot: LiveActivitySlotOption = LAAppGroupSettings.smallWidgetSlot()
6+
import SwiftUI
117

12-
private let slotLabels = ["Top left", "Top right", "Bottom left", "Bottom right"]
8+
struct LiveActivitySettingsView: View {
9+
@State private var laEnabled: Bool = Storage.shared.laEnabled.value
10+
@State private var restartConfirmed = false
11+
@State private var slots: [LiveActivitySlotOption] = LAAppGroupSettings.slots()
12+
@State private var smallWidgetSlot: LiveActivitySlotOption = LAAppGroupSettings.smallWidgetSlot()
1313

14-
var body: some View {
15-
Form {
16-
Section(header: Text("Live Activity")) {
17-
Toggle("Enable Live Activity", isOn: $laEnabled)
18-
}
14+
private let slotLabels = ["Top left", "Top right", "Bottom left", "Bottom right"]
15+
16+
var body: some View {
17+
Form {
18+
Section(header: Text("Live Activity")) {
19+
Toggle("Enable Live Activity", isOn: $laEnabled)
20+
}
1921

20-
if laEnabled {
21-
Section {
22-
Button(restartConfirmed ? "Live Activity Restarted" : "Restart Live Activity") {
23-
LiveActivityManager.shared.forceRestart()
24-
restartConfirmed = true
25-
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
26-
restartConfirmed = false
22+
if laEnabled {
23+
Section {
24+
Button(restartConfirmed ? "Live Activity Restarted" : "Restart Live Activity") {
25+
LiveActivityManager.shared.forceRestart()
26+
restartConfirmed = true
27+
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
28+
restartConfirmed = false
29+
}
2730
}
31+
.disabled(restartConfirmed)
2832
}
29-
.disabled(restartConfirmed)
3033
}
31-
}
3234

33-
Section(header: Text("Grid Slots - Live Activity")) {
34-
ForEach(0 ..< 4, id: \.self) { index in
35-
Picker(slotLabels[index], selection: Binding(
36-
get: { slots[index] },
37-
set: { selectSlot($0, at: index) }
35+
Section(header: Text("Grid Slots - Live Activity")) {
36+
ForEach(0 ..< 4, id: \.self) { index in
37+
Picker(slotLabels[index], selection: Binding(
38+
get: { slots[index] },
39+
set: { selectSlot($0, at: index) }
40+
)) {
41+
ForEach(LiveActivitySlotOption.allCases, id: \.self) { option in
42+
Text(option.displayName).tag(option)
43+
}
44+
}
45+
}
46+
}
47+
48+
Section(header: Text("Grid Slot - CarPlay / Watch")) {
49+
Picker("Right slot", selection: Binding(
50+
get: { smallWidgetSlot },
51+
set: { newValue in
52+
smallWidgetSlot = newValue
53+
LAAppGroupSettings.setSmallWidgetSlot(newValue)
54+
LiveActivityManager.shared.refreshFromCurrentState(reason: "small widget slot changed")
55+
}
3856
)) {
3957
ForEach(LiveActivitySlotOption.allCases, id: \.self) { option in
4058
Text(option.displayName).tag(option)
4159
}
4260
}
4361
}
4462
}
45-
46-
Section(header: Text("Grid Slot - CarPlay / Watch")) {
47-
Picker("Right slot", selection: Binding(
48-
get: { smallWidgetSlot },
49-
set: { newValue in
50-
smallWidgetSlot = newValue
51-
LAAppGroupSettings.setSmallWidgetSlot(newValue)
52-
LiveActivityManager.shared.refreshFromCurrentState(reason: "small widget slot changed")
53-
}
54-
)) {
55-
ForEach(LiveActivitySlotOption.allCases, id: \.self) { option in
56-
Text(option.displayName).tag(option)
57-
}
58-
}
63+
.onReceive(Storage.shared.laEnabled.$value) { newValue in
64+
if newValue != laEnabled { laEnabled = newValue }
5965
}
60-
}
61-
.onReceive(Storage.shared.laEnabled.$value) { newValue in
62-
if newValue != laEnabled { laEnabled = newValue }
63-
}
64-
.onChange(of: laEnabled) { newValue in
65-
Storage.shared.laEnabled.value = newValue
66-
if newValue {
67-
LiveActivityManager.shared.forceRestart()
68-
} else {
69-
LiveActivityManager.shared.end(dismissalPolicy: .immediate)
66+
.onChange(of: laEnabled) { newValue in
67+
Storage.shared.laEnabled.value = newValue
68+
if newValue {
69+
LiveActivityManager.shared.forceRestart()
70+
} else {
71+
LiveActivityManager.shared.end(dismissalPolicy: .immediate)
72+
}
7073
}
74+
.preferredColorScheme(Storage.shared.appearanceMode.value.colorScheme)
75+
.navigationTitle("Live Activity")
76+
.navigationBarTitleDisplayMode(.inline)
7177
}
72-
.preferredColorScheme(Storage.shared.appearanceMode.value.colorScheme)
73-
.navigationTitle("Live Activity")
74-
.navigationBarTitleDisplayMode(.inline)
75-
}
7678

77-
/// Selects an option for the given slot index, enforcing uniqueness:
78-
/// if the chosen option is already in another slot, that slot is cleared to `.none`.
79-
private func selectSlot(_ option: LiveActivitySlotOption, at index: Int) {
80-
if option != .none {
81-
for i in 0 ..< slots.count where i != index && slots[i] == option {
82-
slots[i] = .none
79+
/// Selects an option for the given slot index, enforcing uniqueness:
80+
/// if the chosen option is already in another slot, that slot is cleared to `.none`.
81+
private func selectSlot(_ option: LiveActivitySlotOption, at index: Int) {
82+
if option != .none {
83+
for i in 0 ..< slots.count where i != index && slots[i] == option {
84+
slots[i] = .none
85+
}
8386
}
87+
slots[index] = option
88+
LAAppGroupSettings.setSlots(slots)
89+
LiveActivityManager.shared.refreshFromCurrentState(reason: "slot config changed")
8490
}
85-
slots[index] = option
86-
LAAppGroupSettings.setSlots(slots)
87-
LiveActivityManager.shared.refreshFromCurrentState(reason: "slot config changed")
8891
}
89-
}
92+
#endif

LoopFollow/Settings/SettingsMenuView.swift

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,13 @@ struct SettingsMenuView: View {
7171
settingsPath.value.append(Sheet.apn)
7272
}
7373

74-
NavigationRow(title: "Live Activity",
75-
icon: "dot.radiowaves.left.and.right")
76-
{
77-
settingsPath.value.append(Sheet.liveActivity)
78-
}
74+
#if !targetEnvironment(macCatalyst)
75+
NavigationRow(title: "Live Activity",
76+
icon: "dot.radiowaves.left.and.right")
77+
{
78+
settingsPath.value.append(Sheet.liveActivity)
79+
}
80+
#endif
7981

8082
if !nightscoutURL.value.isEmpty {
8183
NavigationRow(title: "Remote",
@@ -171,7 +173,9 @@ private enum Sheet: Hashable, Identifiable {
171173
case infoDisplay
172174
case alarmSettings
173175
case apn
174-
case liveActivity
176+
#if !targetEnvironment(macCatalyst)
177+
case liveActivity
178+
#endif
175179
case remote
176180
case importExport
177181
case calendar, contact
@@ -192,7 +196,9 @@ private enum Sheet: Hashable, Identifiable {
192196
case .infoDisplay: InfoDisplaySettingsView(viewModel: .init())
193197
case .alarmSettings: AlarmSettingsView()
194198
case .apn: APNSettingsView()
195-
case .liveActivity: LiveActivitySettingsView()
199+
#if !targetEnvironment(macCatalyst)
200+
case .liveActivity: LiveActivitySettingsView()
201+
#endif
196202
case .remote: RemoteSettingsView(viewModel: .init())
197203
case .importExport: ImportExportSettingsView()
198204
case .calendar: CalendarSettingsView()

0 commit comments

Comments
 (0)