Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions LoopFollow/Watch/WatchSessionManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ final class WatchSessionManager: NSObject {
Storage.shared.watchRemoteCommands.$value.map { _ in () }.eraseToAnyPublisher(),
Storage.shared.remoteType.$value.map { _ in () }.eraseToAnyPublisher(),
Storage.shared.colorBGText.$value.map { _ in () }.eraseToAnyPublisher(),
Storage.shared.showDisplayName.$value.map { _ in () }.eraseToAnyPublisher(),
Storage.shared.units.$value.map { _ in () }.eraseToAnyPublisher(),
Storage.shared.mealWithBolus.$value.map { _ in () }.eraseToAnyPublisher(),
Storage.shared.mealWithFatProtein.$value.map { _ in () }.eraseToAnyPublisher(),
Expand Down
5 changes: 4 additions & 1 deletion LoopFollow/Watch/WatchStateBuilder.swift
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,10 @@ enum WatchStateBuilder {
stats: vc.map { stats($0.statsDisplayModel) },
device: Storage.shared.device.value,
alarm: currentAlarm(),
remote: remoteConfig()
remote: remoteConfig(),
displayName: Storage.shared.showDisplayName.value
? Bundle.main.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String
: nil
)
}

Expand Down
19 changes: 19 additions & 0 deletions LoopFollowWatch/GlucosePage.swift
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ struct GlucosePage: View {
}
}
.toolbar {
if let name = state.displayName {
ToolbarItem(placement: .topBarLeading) {
DisplayNameLabel(name: name)
}
}
ToolbarItem(placement: .topBarTrailing) {
age(now: context.date)
}
Expand Down Expand Up @@ -261,3 +266,17 @@ struct AlarmBanner: View {
}
}
}

/// The app's display name. Names built from the default `LoopFollow_<suffix>` pattern
/// truncate at the head so the suffix stays visible.
struct DisplayNameLabel: View {
let name: String

var body: some View {
Text(name)
.font(.footnote.weight(.semibold))
.foregroundStyle(.secondary)
.lineLimit(1)
.truncationMode(name.hasPrefix("LoopFollow") ? .head : .tail)
}
}
3 changes: 2 additions & 1 deletion LoopFollowWatch/StandaloneFetcher.swift
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,8 @@ enum StandaloneFetcher {
stats: base?.stats,
device: base?.device ?? "",
alarm: nil,
remote: remote
remote: remote,
displayName: base?.displayName
)
}

Expand Down
2 changes: 1 addition & 1 deletion LoopFollowWatch/WatchSession.swift
Original file line number Diff line number Diff line change
Expand Up @@ -267,7 +267,7 @@ final class WatchSession: NSObject, ObservableObject {

private static func complicationKey(_ state: WatchState) -> String {
let s = state.snapshot
return "\(s?.updatedAt.timeIntervalSince1970 ?? 0)|\(s?.unit.rawValue ?? "")|\(state.lowMgdl)|\(state.highMgdl)|\(state.colorBGText)"
return "\(s?.updatedAt.timeIntervalSince1970 ?? 0)|\(s?.unit.rawValue ?? "")|\(state.lowMgdl)|\(state.highMgdl)|\(state.colorBGText)|\(state.displayName ?? "")"
}

private func receiveCredentials(_ data: Data) {
Expand Down
47 changes: 32 additions & 15 deletions LoopFollowWatchWidgets/GlucoseComplicationView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,15 @@ struct GlucoseComplicationView: View {
snapshot.map { WatchFormat.minutesAgo($0.updatedAt, now: entry.date) } ?? ""
}

private var displayName: String? { entry.state?.displayName }

/// Names built from the default `LoopFollow_<suffix>` pattern truncate at the head so the suffix stays visible.
private func nameText(_ name: String) -> some View {
Text(name)
.lineLimit(1)
.truncationMode(name.hasPrefix("LoopFollow") ? .head : .tail)
}

var body: some View {
switch family {
case .accessoryCircular:
Expand Down Expand Up @@ -71,21 +80,28 @@ struct GlucoseComplicationView: View {
private var rectangular: some View {
HStack(spacing: 6) {
VStack(alignment: .leading, spacing: 0) {
HStack(alignment: .firstTextBaseline, spacing: 2) {
Text(bgText)
.font(.system(size: 26, weight: .bold))
.foregroundStyle(bgColor)
.strikethrough(isStale, color: .red)
Text(trendText)
.font(.system(size: 14, weight: .bold))
if let displayName {
nameText(displayName)
.font(.system(size: 11, weight: .semibold))
.foregroundStyle(.secondary)
}
VStack(alignment: .leading, spacing: 0) {
HStack(alignment: .firstTextBaseline, spacing: 2) {
Text(bgText)
.font(.system(size: 26, weight: .bold))
.foregroundStyle(bgColor)
.strikethrough(isStale, color: .red)
Text(trendText)
.font(.system(size: 14, weight: .bold))
}
Text("\(deltaText) \(ageText)")
.font(.system(size: 12))
.foregroundStyle(.secondary)
}
Text("\(deltaText) \(ageText)")
.font(.system(size: 12))
.foregroundStyle(.secondary)
.lineLimit(1)
.minimumScaleFactor(0.6)
.widgetAccentable()
}
.lineLimit(1)
.minimumScaleFactor(0.6)
.widgetAccentable()
sparkline
}
}
Expand Down Expand Up @@ -122,11 +138,12 @@ struct GlucoseComplicationView: View {
.foregroundStyle(bgColor)
.widgetCurvesContent()
.widgetLabel {
Text("\(trendText) \(deltaText) \(ageText)")
Text([displayName, trendText, deltaText, ageText].compactMap { $0 }.joined(separator: " "))
}
}

private var inline: some View {
Text(snapshot == nil ? "LoopFollow --" : "\(bgText) \(trendText) \(deltaText) · \(ageText)")
let reading = snapshot == nil ? "--" : "\(bgText) \(trendText) \(deltaText) · \(ageText)"
return Text(snapshot == nil && displayName == nil ? "LoopFollow --" : [displayName, reading].compactMap { $0 }.joined(separator: " "))
}
}
13 changes: 12 additions & 1 deletion Tests/WatchProtocolTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@ struct WatchProtocolTests {
tempTargetPresets: [],
quickPickBoluses: [1, 2.5],
quickPickMeals: [WatchMealPick(carbs: 45, fat: 0, protein: 0, bolus: 0)]
)
),
displayName: "Emma"
)
}

Expand All @@ -56,6 +57,16 @@ struct WatchProtocolTests {
#expect(WatchState.decode(data) == original)
}

@Test("A state without a display name decodes with none")
func stateWithoutDisplayName() throws {
let data = try #require(state().encoded())
var json = try #require(JSONSerialization.jsonObject(with: data) as? [String: Any])
json["displayName"] = nil
let decoded = try #require(WatchState.decode(JSONSerialization.data(withJSONObject: json)))
#expect(decoded.displayName == nil)
#expect(decoded.snapshot == state().snapshot)
}

@Test("The active override is the one whose period contains the time")
func activeOverride() {
let state = state()
Expand Down
2 changes: 2 additions & 0 deletions WatchShared/WatchState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ struct WatchState: Codable, Equatable {
var alarm: WatchAlarm?
/// Present when remote commands from the watch are enabled on the phone.
var remote: WatchRemoteConfig?
/// The app's display name, present while the phone's "Show Display Name" setting is on.
var displayName: String?

func encoded() -> Data? {
try? JSONEncoder().encode(self)
Expand Down