From 174a6df07ea7ad3daf085e3c0ab24c56f95a63e1 Mon Sep 17 00:00:00 2001 From: marionbarker Date: Tue, 11 Jun 2024 09:13:42 -0700 Subject: [PATCH 01/33] update the testflight.md file --- fastlane/testflight.md | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/fastlane/testflight.md b/fastlane/testflight.md index 2068a88fc..94f809d68 100644 --- a/fastlane/testflight.md +++ b/fastlane/testflight.md @@ -27,6 +27,8 @@ This method for building without a Mac was ported from Loop. If you have used th There are more detailed instructions in LoopDocs for doing Browser Builds of Loop and other apps, including troubleshooting and build errors. Please refer to [LoopDocs](https://loopkit.github.io/loopdocs/gh-actions/gh-other-apps/) for more details. +If you build multiple apps, you may want to use a free *GitHub* organization. Please refer to [LoopDocs: Use a *GitHub* Organization Account](https://loopkit.github.io/loopdocs/gh-actions/gh-other-apps/#use-a-github-organization-account). + ## Prerequisites * A [github account](https://github.com/signup). The free level comes with plenty of storage and free compute time to build Trio, multiple times a day, if you wanted to. @@ -121,12 +123,18 @@ _Please note that in default builds of Trio, the app group is actually identical ## Add App Group to Bundle Identifiers +> Each Identifier **NAME** listed below is the same as the **NAME** used by iAPS, they are distinguised by the **IDENTIFIER** itself, which includes the new BundleID for your Trio app + +> If you built Trio with Xcode before, the **NAME** will start with XC + +> Use the **IDENTIFIER** string rather than the **NAME** to select the correct identifier + 1. Go to [Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/identifiers/list) on the Apple developer site. -1. For each of the following identifier names: - * FreeAPS - * FreeAPS watchkitapp - * FreeAPS watchkitapp watchkitextension -1. Click on the identifier's name. +1. Repeat this step for each **NAME** with associated **IDENTIFIER**: + * FreeAPS: `org.nightscout.TEAMID.trio` + * FreeAPS watchkitapp: `org.nightscout.TEAMID.trio.watchkitapp` + * FreeAPS watchkitapp watchkitextension: `org.nightscout.TEAMID.trio.watchkitapp.watchkitextension` +1. Click on the **IDENTIFIER** row. 1. On the "App Groups" capabilies, click on the "Configure" button. 1. Select the "Loop App Group" _(yes, "Loop App Group" is correct)_ 1. Click "Continue". @@ -134,6 +142,9 @@ _Please note that in default builds of Trio, the app group is actually identical 1. Click "Confirm". 1. Remember to do this for each of the identifiers above. +There is an additional identifier, but it does not need the App Group added to it: +* LiveActivityExtension: `org.nightscout.TEAMID.trio.LiveActivity` + ## Create Trio App in App Store Connect If you have created a Trio app in App Store Connect before, you can skip this section as well. From 9122243f3ddef8d4c0ab9ece2ceb1eace6c2c46b Mon Sep 17 00:00:00 2001 From: 10nas <151583878+10nas@users.noreply.github.com> Date: Sat, 15 Jun 2024 14:42:21 -0700 Subject: [PATCH 02/33] fix simple live activity text clipping, add expired disclaimer, add back unit tests --- .../LiveActivity/LiveActitiyShared.swift | 9 +- .../LiveActivity/LiveActivityBridge.swift | 61 ++-- LiveActivity/LiveActivity.swift | 301 ++++++++++++------ 3 files changed, 246 insertions(+), 125 deletions(-) diff --git a/FreeAPS/Sources/Services/LiveActivity/LiveActitiyShared.swift b/FreeAPS/Sources/Services/LiveActivity/LiveActitiyShared.swift index ad65a4804..36d27237f 100644 --- a/FreeAPS/Sources/Services/LiveActivity/LiveActitiyShared.swift +++ b/FreeAPS/Sources/Services/LiveActivity/LiveActitiyShared.swift @@ -7,6 +7,14 @@ struct LiveActivityAttributes: ActivityAttributes { let direction: String? let change: String let date: Date + + let detailedViewState: ContentAdditionalState? + + /// true for the first state that is set on the activity + let isInitialState: Bool + } + + public struct ContentAdditionalState: Codable, Hashable { let chart: [Double] let chartDate: [Date?] let rotationDegrees: Double @@ -14,7 +22,6 @@ struct LiveActivityAttributes: ActivityAttributes { let lowGlucose: Double let cob: Decimal let iob: Decimal - let lockScreenView: String } let startDate: Date diff --git a/FreeAPS/Sources/Services/LiveActivity/LiveActivityBridge.swift b/FreeAPS/Sources/Services/LiveActivity/LiveActivityBridge.swift index dfe355056..8e658e232 100644 --- a/FreeAPS/Sources/Services/LiveActivity/LiveActivityBridge.swift +++ b/FreeAPS/Sources/Services/LiveActivity/LiveActivityBridge.swift @@ -65,35 +65,44 @@ extension LiveActivityAttributes.ContentState { Self.formatGlucose(glucose - $0, mmol: mmol, forceSign: true) }) ?? "" - let chartBG = chart.map(\.glucose) + let detailedState: LiveActivityAttributes.ContentAdditionalState? - let conversionFactor: Double = settings.units == .mmolL ? 18.0 : 1.0 - let convertedChartBG = chartBG.map { Double($0) / conversionFactor } + switch settings.lockScreenView { + case .detailed: + let chartBG = chart.map(\.glucose) - let chartDate = chart.map(\.date) + let conversionFactor: Double = settings.units == .mmolL ? 18.0 : 1.0 + let convertedChartBG = chartBG.map { Double($0) / conversionFactor } - /// glucose limits from UI settings - let highGlucose = settings.high / Decimal(conversionFactor) - let lowGlucose = settings.low / Decimal(conversionFactor) + let chartDate = chart.map(\.date) - let cob = suggestion.cob ?? 0 - let iob = suggestion.iob ?? 0 + /// glucose limits from UI settings + let highGlucose = settings.high / Decimal(conversionFactor) + let lowGlucose = settings.low / Decimal(conversionFactor) - let lockScreenView = settings.lockScreenView.displayName + let cob = suggestion.cob ?? 0 + let iob = suggestion.iob ?? 0 + + detailedState = LiveActivityAttributes.ContentAdditionalState( + chart: convertedChartBG, + chartDate: chartDate, + rotationDegrees: rotationDegrees, + highGlucose: Double(highGlucose), + lowGlucose: Double(lowGlucose), + cob: cob, + iob: iob + ) + case .simple: + detailedState = nil + } self.init( bg: formattedBG, direction: trendString, change: change, date: bg.dateString, - chart: convertedChartBG, - chartDate: chartDate, - rotationDegrees: rotationDegrees, - highGlucose: Double(highGlucose), - lowGlucose: Double(lowGlucose), - cob: cob, - iob: iob, - lockScreenView: lockScreenView + detailedViewState: detailedState, + isInitialState: false ) } } @@ -216,28 +225,22 @@ extension LiveActivityAttributes.ContentState { do { // always push a non-stale content as the first update // pushing a stale content as the frst content results in the activity not being shown at all - // we want it shown though even if it is iniially stale, as we expect new BG readings to become available soon, which should then be displayed - let nonStale = ActivityContent( + // apparently this initial state is also what is shown after the live activity expires (after 8h) + let expired = ActivityContent( state: LiveActivityAttributes.ContentState( bg: "--", direction: nil, change: "--", date: Date.now, - chart: [], - chartDate: [], - rotationDegrees: 0, - highGlucose: Double(180), - lowGlucose: Double(70), - cob: 0, - iob: 0, - lockScreenView: "Simple" + detailedViewState: nil, + isInitialState: true ), staleDate: Date.now.addingTimeInterval(60) ) let activity = try Activity.request( attributes: LiveActivityAttributes(startDate: Date.now), - content: nonStale, + content: expired, pushType: nil ) currentActivity = ActiveActivity(activity: activity, startDate: Date.now) diff --git a/LiveActivity/LiveActivity.swift b/LiveActivity/LiveActivity.swift index 44904283b..b6421e68c 100644 --- a/LiveActivity/LiveActivity.swift +++ b/LiveActivity/LiveActivity.swift @@ -45,19 +45,22 @@ struct LiveActivity: Widget { } } - @ViewBuilder func mealLabel(context: ActivityViewContext) -> some View { + @ViewBuilder func mealLabel( + context _: ActivityViewContext, + additionalState: LiveActivityAttributes.ContentAdditionalState + ) -> some View { VStack(alignment: .leading, spacing: 1, content: { HStack { Text("COB: ").font(.caption) Text( - (carbsFormatter.string(from: context.state.cob as NSNumber) ?? "--") + + (carbsFormatter.string(from: additionalState.cob as NSNumber) ?? "--") + NSLocalizedString(" g", comment: "grams of carbs") ).font(.caption).fontWeight(.bold) } HStack { Text("IOB: ").font(.caption) Text( - (bolusFormatter.string(from: context.state.iob as NSNumber) ?? "--") + + (bolusFormatter.string(from: additionalState.iob as NSNumber) ?? "--") + NSLocalizedString(" U", comment: "Unit in number of units delivered (keep the space character!)") ).font(.caption).fontWeight(.bold) } @@ -74,6 +77,11 @@ struct LiveActivity: Widget { } } + private func expiredLabel() -> some View { + Text("Live Activity Expired. Open Trio to Refresh") + .minimumScaleFactor(0.01) + } + private func updatedLabel(context: ActivityViewContext) -> Text { let text = Text("Updated: \(dateFormatter.string(from: context.state.date))") if context.isStale { @@ -106,7 +114,7 @@ struct LiveActivity: Widget { var directionText: String? var warnColor: Color? if let direction = context.state.direction { - if size == .compact { + if size == .compact || size == .minimal { directionText = String(direction[direction.startIndex ... direction.startIndex]) if direction.count > 1 { @@ -148,33 +156,36 @@ struct LiveActivity: Widget { } } .foregroundStyle( - context.state.lockScreenView == "Simple" ? (context.isStale ? Color.primary.opacity(0.5) : Color.primary) : + context.state.detailedViewState == nil ? (context.isStale ? Color.primary.opacity(0.5) : Color.primary) : (context.isStale ? Color.white.opacity(0.5) : Color.white) ) return (stack, characters) } - @ViewBuilder func chart(context: ActivityViewContext) -> some View { + @ViewBuilder func chart( + context: ActivityViewContext, + additionalState: LiveActivityAttributes.ContentAdditionalState + ) -> some View { if context.isStale { Text("No data available") } else { Chart { - ForEach(context.state.chart.indices, id: \.self) { index in - let currentValue = context.state.chart[index] - if currentValue > context.state.highGlucose { + ForEach(additionalState.chart.indices, id: \.self) { index in + let currentValue = additionalState.chart[index] + if currentValue > additionalState.highGlucose { PointMark( - x: .value("Time", context.state.chartDate[index] ?? Date()), + x: .value("Time", additionalState.chartDate[index] ?? Date()), y: .value("Value", currentValue) ).foregroundStyle(Color.orange.gradient).symbolSize(12) - } else if currentValue < context.state.lowGlucose { + } else if currentValue < additionalState.lowGlucose { PointMark( - x: .value("Time", context.state.chartDate[index] ?? Date()), + x: .value("Time", additionalState.chartDate[index] ?? Date()), y: .value("Value", currentValue) ).foregroundStyle(Color.red.gradient).symbolSize(12) } else { PointMark( - x: .value("Time", context.state.chartDate[index] ?? Date()), + x: .value("Time", additionalState.chartDate[index] ?? Date()), y: .value("Value", currentValue) ).foregroundStyle(Color.green.gradient).symbolSize(12) } @@ -198,11 +209,37 @@ struct LiveActivity: Widget { } } - var body: some WidgetConfiguration { - ActivityConfiguration(for: LiveActivityAttributes.self) { context in - // Lock screen/banner UI goes here - if context.state.lockScreenView == "Simple" { - HStack(spacing: 3) { + @ViewBuilder func content(context: ActivityViewContext) -> some View { + // Lock screen/banner UI goes here + if let detailedViewState = context.state.detailedViewState { + HStack(spacing: 2) { + VStack { + chart(context: context, additionalState: detailedViewState).frame(width: UIScreen.main.bounds.width / 1.8) + }.padding(.all, 15) + Divider().foregroundStyle(Color.white) + VStack(alignment: .center) { + Spacer() + ZStack { + VStack { + bgAndTrend(context: context, size: .expanded).0.font(.largeTitle) + changeLabel(context: context).font(.callout) + }.frame(width: 130, height: 130) + }.scaleEffect(0.85).offset(y: 30) + mealLabel(context: context, additionalState: detailedViewState).padding(.bottom, 8) + updatedLabel(context: context).font(.caption).padding(.bottom, 70) + } + } + .privacySensitive() + .imageScale(.small) + .background(Color.white.opacity(0.2)) + .foregroundColor(Color.white) + .activityBackgroundTint(Color.black.opacity(0.7)) + .activitySystemActionForegroundColor(Color.white) + } else { + HStack(spacing: 3) { + if context.state.isInitialState { + expiredLabel() + } else { bgAndTrend(context: context, size: .expanded).0.font(.title) Spacer() VStack(alignment: .trailing, spacing: 5) { @@ -210,89 +247,163 @@ struct LiveActivity: Widget { updatedLabel(context: context).font(.caption).foregroundStyle(.primary.opacity(0.7)) } } - .privacySensitive() - .padding(.all, 15) - // Semantic BackgroundStyle and Color values work here. They adapt to the given interface style (light mode, dark mode) - // Semantic UIColors do NOT (as of iOS 17.1.1). Like UIColor.systemBackgroundColor (it does not adapt to changes of the interface style) - // The colorScheme environment varaible that is usually used to detect dark mode does NOT work here (it reports false values) - .foregroundStyle(Color.primary) - .background(BackgroundStyle.background.opacity(0.4)) - .activityBackgroundTint(Color.clear) - } else { - HStack(spacing: 2) { - VStack { - chart(context: context).frame(width: UIScreen.main.bounds.width / 1.8) - }.padding(.all, 15) - Divider().foregroundStyle(Color.white) - VStack(alignment: .center) { - Spacer() - ZStack { - VStack { - bgAndTrend(context: context, size: .expanded).0.font(.largeTitle) - changeLabel(context: context).font(.callout) - }.frame(width: 130, height: 130) - }.scaleEffect(0.85).offset(y: 30) - mealLabel(context: context).padding(.bottom, 8) - updatedLabel(context: context).font(.caption).padding(.bottom, 70) - } - } - .privacySensitive() - .imageScale(.small) - .background(Color.white.opacity(0.2)) - .foregroundColor(Color.white) - .activityBackgroundTint(Color.black.opacity(0.7)) - .activitySystemActionForegroundColor(Color.white) } - } dynamicIsland: { context in - DynamicIsland { - // Expanded UI goes here. Compose the expanded UI through - // various regions, like leading/trailing/center/bottom - DynamicIslandExpandedRegion(.leading) { - bgAndTrend(context: context, size: .expanded).0.font(.title2).padding(.leading, 5) - } - DynamicIslandExpandedRegion(.trailing) { - changeLabel(context: context).font(.title2).padding(.trailing, 5) - } - DynamicIslandExpandedRegion(.bottom) { - if context.state.lockScreenView == "Simple" { - Group { - updatedLabel(context: context).font(.caption).foregroundStyle(Color.secondary) - } - .frame( - maxHeight: .infinity, - alignment: .bottom - ) - } else { - chart(context: context) - } - } - DynamicIslandExpandedRegion(.center) { - if context.state.lockScreenView == "Detailed" { + .privacySensitive() + .padding(.all, 15) + // Semantic BackgroundStyle and Color values work here. They adapt to the given interface style (light mode, dark mode) + // Semantic UIColors do NOT (as of iOS 17.1.1). Like UIColor.systemBackgroundColor (it does not adapt to changes of the interface style) + // The colorScheme environment varaible that is usually used to detect dark mode does NOT work here (it reports false values) + .foregroundStyle(Color.primary) + .background(BackgroundStyle.background.opacity(0.4)) + .activityBackgroundTint(Color.clear) + } + } + + func dynamicIsland(context: ActivityViewContext) -> DynamicIsland { + DynamicIsland { + // Expanded UI goes here. Compose the expanded UI through + // various regions, like leading/trailing/center/bottom + DynamicIslandExpandedRegion(.leading) { + bgAndTrend(context: context, size: .expanded).0.font(.title2).padding(.leading, 5) + } + DynamicIslandExpandedRegion(.trailing) { + changeLabel(context: context).font(.title2).padding(.trailing, 5) + } + DynamicIslandExpandedRegion(.bottom) { + if context.state.isInitialState { + expiredLabel() + } else if let detailedViewState = context.state.detailedViewState { + chart(context: context, additionalState: detailedViewState) + } else { + Group { updatedLabel(context: context).font(.caption).foregroundStyle(Color.secondary) } + .frame( + maxHeight: .infinity, + alignment: .bottom + ) } - } compactLeading: { - bgAndTrend(context: context, size: .compact).0.padding(.leading, 4) - } compactTrailing: { - changeLabel(context: context).padding(.trailing, 4) - } minimal: { - let (_label, characterCount) = bgAndTrend(context: context, size: .minimal) - - let label = _label.padding(.leading, 7).padding(.trailing, 3) - - if characterCount < 4 { - label - } else if characterCount < 5 { - label.fontWidth(.condensed) - } else { - label.fontWidth(.compressed) + } + DynamicIslandExpandedRegion(.center) { + if context.state.detailedViewState != nil { + updatedLabel(context: context).font(.caption).foregroundStyle(Color.secondary) } } - .widgetURL(URL(string: "Trio://")) - .keylineTint(Color.purple) - .contentMargins(.horizontal, 0, for: .minimal) - .contentMargins(.trailing, 0, for: .compactLeading) - .contentMargins(.leading, 0, for: .compactTrailing) + } compactLeading: { + bgAndTrend(context: context, size: .compact).0.padding(.leading, 4) + } compactTrailing: { + changeLabel(context: context).padding(.trailing, 4) + } minimal: { + let (_label, characterCount) = bgAndTrend(context: context, size: .minimal) + + let label = _label.padding(.leading, 7).padding(.trailing, 3) + + if characterCount < 4 { + label + } else if characterCount < 5 { + label.fontWidth(.condensed) + } else { + label.fontWidth(.compressed) + } } + .widgetURL(URL(string: "Trio://")) + .keylineTint(Color.purple) + .contentMargins(.horizontal, 0, for: .minimal) + .contentMargins(.trailing, 0, for: .compactLeading) + .contentMargins(.leading, 0, for: .compactTrailing) } + + var body: some WidgetConfiguration { + ActivityConfiguration(for: LiveActivityAttributes.self, content: self.content, dynamicIsland: self.dynamicIsland) + } +} + +private extension LiveActivityAttributes { + static var preview: LiveActivityAttributes { + LiveActivityAttributes(startDate: Date()) + } +} + +private extension LiveActivityAttributes.ContentState { + // 0 is the widest digit. Use this to get an upper bound on text width. + + // Use mmol/l notation with decimal point as well for the same reason, it uses up to 4 characters, while mg/dl uses up to 3 + static var testWide: LiveActivityAttributes.ContentState { + LiveActivityAttributes.ContentState( + bg: "00.0", + direction: "→", + change: "+0.0", + date: Date(), + detailedViewState: nil, + isInitialState: false + ) + } + + static var testVeryWide: LiveActivityAttributes.ContentState { + LiveActivityAttributes.ContentState( + bg: "00.0", + direction: "↑↑", + change: "+0.0", + date: Date(), + detailedViewState: nil, + isInitialState: false + ) + } + + static var testSuperWide: LiveActivityAttributes.ContentState { + LiveActivityAttributes.ContentState( + bg: "00.0", + direction: "↑↑↑", + change: "+0.0", + date: Date(), + detailedViewState: nil, + isInitialState: false + ) + } + + // 2 characters for BG, 1 character for change is the minimum that will be shown + static var testNarrow: LiveActivityAttributes.ContentState { + LiveActivityAttributes.ContentState( + bg: "00", + direction: "↑", + change: "+0", + date: Date(), + detailedViewState: nil, + isInitialState: false + ) + } + + static var testMedium: LiveActivityAttributes.ContentState { + LiveActivityAttributes.ContentState( + bg: "000", + direction: "↗︎", + change: "+00", + date: Date(), + detailedViewState: nil, + isInitialState: false + ) + } + + static var testExpired: LiveActivityAttributes.ContentState { + LiveActivityAttributes.ContentState( + bg: "--", + direction: nil, + change: "--", + date: Date().addingTimeInterval(-60 * 60), + detailedViewState: nil, + isInitialState: true + ) + } +} + +@available(iOS 17.0, iOSApplicationExtension 17.0, *) +#Preview("Notification", as: .content, using: LiveActivityAttributes.preview) { + LiveActivity() +} contentStates: { + LiveActivityAttributes.ContentState.testSuperWide + LiveActivityAttributes.ContentState.testVeryWide + LiveActivityAttributes.ContentState.testWide + LiveActivityAttributes.ContentState.testMedium + LiveActivityAttributes.ContentState.testNarrow + LiveActivityAttributes.ContentState.testExpired } From 73afe846da67bd898cbdeafabd722fed0e6d4947 Mon Sep 17 00:00:00 2001 From: 10nas <151583878+10nas@users.noreply.github.com> Date: Sat, 15 Jun 2024 14:42:24 -0700 Subject: [PATCH 03/33] delete unused file --- LiveActivity/WidgetBobble 2.swift | 66 ------------------------------- 1 file changed, 66 deletions(-) delete mode 100644 LiveActivity/WidgetBobble 2.swift diff --git a/LiveActivity/WidgetBobble 2.swift b/LiveActivity/WidgetBobble 2.swift deleted file mode 100644 index 17e5fefe5..000000000 --- a/LiveActivity/WidgetBobble 2.swift +++ /dev/null @@ -1,66 +0,0 @@ -import SwiftUI - -struct WidgetBobble: View { - @Environment(\.colorScheme) var colorScheme - - let gradient: AngularGradient - let color: Color - - var body: some View { - HStack(alignment: .center) { - ZStack { - Group { - CircleShapeWidget(gradient: gradient) - TriangleShapeWidget(color: color) - } - CircleShapeWidget(gradient: gradient) - } - } - } -} - -struct CircleShapeWidget: View { - @Environment(\.colorScheme) var colorScheme - - let gradient: AngularGradient - - var body: some View { -// let colorBackground: Color = colorScheme == .dark ? Color( -// red: 0.05490196078, -// green: 0.05490196078, -// blue: 0.05490196078 -// ) : .white - - Circle() - .stroke(gradient, lineWidth: 10) - .background(Circle().fill(.clear)) - .frame(width: 130, height: 130) - } -} - -struct TriangleShapeWidget: View { - let color: Color - - var body: some View { - TriangleWidget() - .fill(color) - .frame(width: 35, height: 35) - .rotationEffect(.degrees(90)) - .offset(x: 78) - } -} - -struct TriangleWidget: Shape { - func path(in rect: CGRect) -> Path { - var path = Path() - - let cornerRadius: CGFloat = 2 - - path.move(to: CGPoint(x: rect.midX, y: rect.minY)) - path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - cornerRadius)) - path.addQuadCurve(to: CGPoint(x: rect.minX, y: rect.maxY - cornerRadius), control: CGPoint(x: rect.midX, y: rect.maxY)) - path.closeSubpath() - - return path - } -} From 1d1a3322abbfbad4ea4d56593b3e3967a463d7d0 Mon Sep 17 00:00:00 2001 From: Sjoerd-Bo3 Date: Mon, 17 Jun 2024 18:33:48 +0200 Subject: [PATCH 04/33] fix(bgtargets): change variable from double to decimal Signed-off-by: Sjoerd-Bo3 --- .../Modules/TargetsEditor/TargetsEditorStateModel.swift | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/FreeAPS/Sources/Modules/TargetsEditor/TargetsEditorStateModel.swift b/FreeAPS/Sources/Modules/TargetsEditor/TargetsEditorStateModel.swift index 723af7d0e..971d22242 100644 --- a/FreeAPS/Sources/Modules/TargetsEditor/TargetsEditorStateModel.swift +++ b/FreeAPS/Sources/Modules/TargetsEditor/TargetsEditorStateModel.swift @@ -6,7 +6,7 @@ extension TargetsEditor { let timeValues = stride(from: 0.0, to: 1.days.timeInterval, by: 30.minutes.timeInterval).map { $0 } - var rateValues: [Double] { + var rateValues: [Decimal] { switch units { case .mgdL: return stride(from: 72, to: 180.01, by: 1.0).map { $0 } @@ -27,8 +27,8 @@ extension TargetsEditor { units = profile.units items = profile.targets.map { value in let timeIndex = timeValues.firstIndex(of: Double(value.offset * 60)) ?? 0 - let lowIndex = rateValues.firstIndex(of: Double(value.low)) ?? 0 let highIndex = lowIndex + let lowIndex = rateValues.firstIndex(of: value.low) ?? 0 return Item(lowIndex: lowIndex, highIndex: highIndex, timeIndex: timeIndex) } } @@ -55,7 +55,7 @@ extension TargetsEditor { fotmatter.dateFormat = "HH:mm:ss" let date = Date(timeIntervalSince1970: self.timeValues[item.timeIndex]) let minutes = Int(date.timeIntervalSince1970 / 60) - let low = Decimal(self.rateValues[item.lowIndex]) + let low = self.rateValues[item.lowIndex] let high = low return BGTargetEntry(low: low, high: high, start: fotmatter.string(from: date), offset: minutes) } @@ -68,8 +68,7 @@ extension TargetsEditor { let uniq = Array(Set(self.items)) let sorted = uniq.sorted { $0.timeIndex < $1.timeIndex } .map { item -> Item in - guard item.highIndex < item.lowIndex else { return item } - return Item(lowIndex: item.lowIndex, highIndex: item.lowIndex, timeIndex: item.timeIndex) + Item(lowIndex: item.lowIndex, highIndex: item.lowIndex, timeIndex: item.timeIndex) } sorted.first?.timeIndex = 0 self.items = sorted From 9be108be01ef02c260a16ef5cd160a623f0d9268 Mon Sep 17 00:00:00 2001 From: Sjoerd-Bo3 Date: Mon, 17 Jun 2024 18:34:23 +0200 Subject: [PATCH 05/33] refactor(bgtargets): typo fix Signed-off-by: Sjoerd-Bo3 --- .../Modules/TargetsEditor/TargetsEditorStateModel.swift | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/FreeAPS/Sources/Modules/TargetsEditor/TargetsEditorStateModel.swift b/FreeAPS/Sources/Modules/TargetsEditor/TargetsEditorStateModel.swift index 971d22242..69fe1dd40 100644 --- a/FreeAPS/Sources/Modules/TargetsEditor/TargetsEditorStateModel.swift +++ b/FreeAPS/Sources/Modules/TargetsEditor/TargetsEditorStateModel.swift @@ -50,14 +50,14 @@ extension TargetsEditor { func save() { let targets = items.map { item -> BGTargetEntry in - let fotmatter = DateFormatter() - fotmatter.timeZone = TimeZone(secondsFromGMT: 0) - fotmatter.dateFormat = "HH:mm:ss" + let formatter = DateFormatter() + formatter.timeZone = TimeZone(secondsFromGMT: 0) + formatter.dateFormat = "HH:mm:ss" let date = Date(timeIntervalSince1970: self.timeValues[item.timeIndex]) let minutes = Int(date.timeIntervalSince1970 / 60) let low = self.rateValues[item.lowIndex] let high = low - return BGTargetEntry(low: low, high: high, start: fotmatter.string(from: date), offset: minutes) + return BGTargetEntry(low: low, high: high, start: formatter.string(from: date), offset: minutes) } let profile = BGTargets(units: units, userPrefferedUnits: settingsManager.settings.units, targets: targets) provider.saveProfile(profile) From 45d1ede5e92343d68fdfbcc8bea20ebe573acb80 Mon Sep 17 00:00:00 2001 From: Sjoerd-Bo3 Date: Mon, 17 Jun 2024 18:36:36 +0200 Subject: [PATCH 06/33] fix(bgtargets): correctly assigning highindex Signed-off-by: Sjoerd-Bo3 --- .../Modules/TargetsEditor/TargetsEditorStateModel.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FreeAPS/Sources/Modules/TargetsEditor/TargetsEditorStateModel.swift b/FreeAPS/Sources/Modules/TargetsEditor/TargetsEditorStateModel.swift index 69fe1dd40..f4c02877b 100644 --- a/FreeAPS/Sources/Modules/TargetsEditor/TargetsEditorStateModel.swift +++ b/FreeAPS/Sources/Modules/TargetsEditor/TargetsEditorStateModel.swift @@ -27,8 +27,8 @@ extension TargetsEditor { units = profile.units items = profile.targets.map { value in let timeIndex = timeValues.firstIndex(of: Double(value.offset * 60)) ?? 0 - let highIndex = lowIndex let lowIndex = rateValues.firstIndex(of: value.low) ?? 0 + let highIndex = rateValues.firstIndex(of: value.high) ?? 0 return Item(lowIndex: lowIndex, highIndex: highIndex, timeIndex: timeIndex) } } @@ -68,7 +68,7 @@ extension TargetsEditor { let uniq = Array(Set(self.items)) let sorted = uniq.sorted { $0.timeIndex < $1.timeIndex } .map { item -> Item in - Item(lowIndex: item.lowIndex, highIndex: item.lowIndex, timeIndex: item.timeIndex) + Item(lowIndex: item.lowIndex, highIndex: item.highIndex, timeIndex: item.timeIndex) } sorted.first?.timeIndex = 0 self.items = sorted From 20ba334b2c3b62ba296e67695bc015c4e5c868d4 Mon Sep 17 00:00:00 2001 From: bjornoleh Date: Mon, 17 Jun 2024 22:26:57 +0200 Subject: [PATCH 07/33] Eventual BG with 1 decimal in the app and watch app - Use `targetFormatter` instead of `numberFormater` for `eventualBG` in the app, since this has the appropriate `maximumFractionDigits = 1` - Change eventualFormatter to use `maximumFractionDigits = 1` instead of `2` on the watch --- FreeAPS/Sources/Modules/Home/View/HomeRootView.swift | 2 +- FreeAPS/Sources/Services/WatchManager/WatchManager.swift | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/FreeAPS/Sources/Modules/Home/View/HomeRootView.swift b/FreeAPS/Sources/Modules/Home/View/HomeRootView.swift index 37af36839..96125ed2d 100644 --- a/FreeAPS/Sources/Modules/Home/View/HomeRootView.swift +++ b/FreeAPS/Sources/Modules/Home/View/HomeRootView.swift @@ -344,7 +344,7 @@ extension Home { if let eventualBG = state.eventualBG { Text( - "⇢ " + numberFormatter.string( + "⇢ " + targetFormatter.string( from: (state.units == .mmolL ? eventualBG.asMmolL : Decimal(eventualBG)) as NSNumber )! ) diff --git a/FreeAPS/Sources/Services/WatchManager/WatchManager.swift b/FreeAPS/Sources/Services/WatchManager/WatchManager.swift index f28c44f4c..3757cd74f 100644 --- a/FreeAPS/Sources/Services/WatchManager/WatchManager.swift +++ b/FreeAPS/Sources/Services/WatchManager/WatchManager.swift @@ -216,7 +216,7 @@ final class BaseWatchManager: NSObject, WatchManager, Injectable { private var eventualFormatter: NumberFormatter { let formatter = NumberFormatter() formatter.numberStyle = .decimal - formatter.maximumFractionDigits = 2 + formatter.maximumFractionDigits = 1 return formatter } From f7b39cb81d7d1097fbe6b23413496a48e83faac1 Mon Sep 17 00:00:00 2001 From: Sjoerd-Bo3 Date: Tue, 18 Jun 2024 11:08:01 +0200 Subject: [PATCH 08/33] feat(issues): add to project for bug needs-triage Signed-off-by: Sjoerd-Bo3 --- .github/workflows/add_to_project.yml | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) create mode 100644 .github/workflows/add_to_project.yml diff --git a/.github/workflows/add_to_project.yml b/.github/workflows/add_to_project.yml new file mode 100644 index 000000000..7c66e6a14 --- /dev/null +++ b/.github/workflows/add_to_project.yml @@ -0,0 +1,20 @@ +name: Add bugs to bugs project + +on: + issues: + types: + - opened + +jobs: + add-to-project: + name: Add issue to project + runs-on: ubuntu-latest + steps: + - uses: actions/add-to-project@RELEASE_VERSION + with: + # You can target a project in a different organization + # to the issue + project-url: https://github.com/orgs/nightscout/projects/2 + github-token: ${{ secrets.ADD_TO_PROJECT_PAT }} + labeled: bug, needs-triage + label-operator: OR \ No newline at end of file From 5e087276c42700818c73f66cb358e50676b9a11b Mon Sep 17 00:00:00 2001 From: Sjoerd-Bo3 Date: Tue, 18 Jun 2024 11:18:13 +0200 Subject: [PATCH 09/33] feat(issuemanagement): stale issues Signed-off-by: Sjoerd-Bo3 --- .github/workflows/stale_issues.yml | 23 +++++++++++++++++++++++ .github/workflows/stale_triage_issues.yml | 0 2 files changed, 23 insertions(+) create mode 100644 .github/workflows/stale_issues.yml create mode 100644 .github/workflows/stale_triage_issues.yml diff --git a/.github/workflows/stale_issues.yml b/.github/workflows/stale_issues.yml new file mode 100644 index 000000000..15d5f9ebe --- /dev/null +++ b/.github/workflows/stale_issues.yml @@ -0,0 +1,23 @@ + name: close inactive issues +on: + schedule: + - cron: "30 1 * * *" + +jobs: + close-issues: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - uses: actions/stale@v4 + with: + days-before-issue-stale: 30 + days-before-issue-close: 14 + stale-issue-label: "stale" + stale-issue-message: "hey 👋 - silence for 30 days 🤐 ... anybody? triage is required!" + close-issue-message: "closed 📴 because silencio 🤫 since an additional 14 days after staleness 📠" + exempt-issue-labels: "needs-triage" + days-before-pr-stale: -1 + days-before-pr-close: -1 + repo-token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file diff --git a/.github/workflows/stale_triage_issues.yml b/.github/workflows/stale_triage_issues.yml new file mode 100644 index 000000000..e69de29bb From b6db9605d1d73f32b5b79386ce8fb469b78605ee Mon Sep 17 00:00:00 2001 From: Marion Barker Date: Tue, 18 Jun 2024 07:05:43 -0700 Subject: [PATCH 10/33] use Trio specific names; remove APP_GROUPS from LiveActivity --- fastlane/Fastfile | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 7af9c6a1e..543fa6129 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -184,24 +184,22 @@ platform :ios do } end - configure_bundle_id("FreeAPS", "#{BUNDLE_ID}", [ + configure_bundle_id("Trio", "#{BUNDLE_ID}", [ Spaceship::ConnectAPI::BundleIdCapability::Type::APP_GROUPS, Spaceship::ConnectAPI::BundleIdCapability::Type::HEALTHKIT, Spaceship::ConnectAPI::BundleIdCapability::Type::NFC_TAG_READING ]) - configure_bundle_id("FreeAPSWatch WatchKit Extension", "#{BUNDLE_ID}.watchkitapp.watchkitextension", [ + configure_bundle_id("Trio WatchKit Extension", "#{BUNDLE_ID}.watchkitapp.watchkitextension", [ Spaceship::ConnectAPI::BundleIdCapability::Type::APP_GROUPS, Spaceship::ConnectAPI::BundleIdCapability::Type::HEALTHKIT ]) - configure_bundle_id("FreeAPSWatch", "#{BUNDLE_ID}.watchkitapp", [ + configure_bundle_id("Trio Watch", "#{BUNDLE_ID}.watchkitapp", [ Spaceship::ConnectAPI::BundleIdCapability::Type::APP_GROUPS ]) - configure_bundle_id("LiveActivityExtension", "#{BUNDLE_ID}.LiveActivity", [ - Spaceship::ConnectAPI::BundleIdCapability::Type::APP_GROUPS - ]) + configure_bundle_id("Trio LiveActivity", "#{BUNDLE_ID}.LiveActivity", []) end From 14b5a8622b2b8ca802ed8c339e3fb600a5edc4f8 Mon Sep 17 00:00:00 2001 From: marionbarker Date: Tue, 18 Jun 2024 21:48:29 -0700 Subject: [PATCH 11/33] update instructions in testflight.md, match Fastfile improvements --- fastlane/testflight.md | 83 +++++++++++++++++++++++++++++++----------- 1 file changed, 61 insertions(+), 22 deletions(-) diff --git a/fastlane/testflight.md b/fastlane/testflight.md index 7ec665fe4..958880217 100644 --- a/fastlane/testflight.md +++ b/fastlane/testflight.md @@ -2,7 +2,7 @@ These instructions allow you to build Trio without having access to a Mac. -* You can install Trio on phones via TestFlight that are not connected to your computer +* You can install Trio on phones using TestFlight that are not connected to your computer * You can send builds and updates to those you care for * You can install Trio on your phone using only the TestFlight app if a phone was lost or the app is accidentally deleted * You do not need to worry about specific Xcode/Mac versions for a given iOS @@ -22,7 +22,7 @@ These instructions allow you to build Trio without having access to a Mac. The setup steps are somewhat involved, but nearly all are one time steps. Subsequent builds are trivial. Your app must be updated once every 90 days, but it's a simple click to make a new build and can be done from anywhere. -Note that TestFlight requires apple id accounts 13 years or older. This can be circumvented by logging into Media & Purchase on the child's phone with an adult's account. More details on this can be found in [LoopDocs](https://loopkit.github.io/loopdocs/gh-actions/gh-deploy/#install-testflight-loop-for-child). +Note that installing with TestFlight requires the Apple ID account holder for the phone be 13 years or older (age varies with country). This can be circumvented by logging into Media & Purchase on the child's phone with an adult's account. More details on this can be found in [LoopDocs](https://loopkit.github.io/loopdocs/gh-actions/gh-deploy/#install-testflight-loop-for-child). This method for building without a Mac was ported from Loop. If you have used this method for Loop or one of the other DIY apps (Loop Caregiver, Loop Follow, Xdrip4iOS), some of the steps can be re-used and the full set of instructions does not need to be repeated. This will be mentioned in relevant sections below. @@ -46,6 +46,8 @@ You require 6 Secrets (alphanumeric items) to use the GitHub build method and if * Be sure to save the 6 Secrets in a text file using a text editor - Do **NOT** use a smart editor, which might auto-correct and change case, because these Secrets are case sensitive +Refer to [LoopDocs: Make a Secrets Reference File](https://loopkit.github.io/loopdocs/gh-actions/gh-first-time/#make-a-secrets-reference-file) for a handy template to use when saving your Secrets. + ## Generate App Store Connect API Key This step is common for all GitHub Browser Builds; do this step only once. You will be saving 4 Secrets from your Apple Account in this step. @@ -59,7 +61,7 @@ This step is common for all GitHub Browser Builds; do this step only once. You w ## Create GitHub Personal Access Token -If you have previously built another app using the "browser build" method, you can can re-use your previous personal access token (`GH_PAT`) and skip this step. +If you have previously built another app using the "browser build" method, you use the same personal access token (`GH_PAT`), so skip this step. Log into your GitHub account to create a personal access token; this is one of two GitHub secrets needed for your build. @@ -74,15 +76,13 @@ Log into your GitHub account to create a personal access token; this is one of t This is the second one of two GitHub secrets needed for your build. -The first time you build with the GitHub Browser Build method for any DIY app, you will make up a password and record it as `MATCH_PASSWORD`. Note, if you later lose `MATCH_PASSWORD`, you will need to delete and make a new Match-Secrets repository (next step). +The first time you build with the GitHub Browser Build method for any DIY app, you will make up a password and record it as `MATCH_PASSWORD`. Note, if you later lose `MATCH_PASSWORD`, you will need to delete and make a new Match-Secrets repository (next step). You use the same password for all DIY apps. ## Setup GitHub Match-Secrets Repository -The creation of the Match-Secrets repository is a common step for all GitHub Browser Builds; do this step only once. You must be logged into your GitHub account. - -1. Create a [new empty repository](https://github.com/new) titled `Match-Secrets`. It should be private. +> This step is no longer required, your Match-Secrets repository is automatically created the first time you run a GitHub Action. You can skip ahead to [Setup Github Trio repository](#setup-github-trio-repository). -Once created, you will not take any direct actions with this repository; it needs to be there for the GitHub to use as you progress through the steps. +> Because it is private - only you can see your Match-Secrets repository. You will not take any direct actions with this repository; it needs to be there for GitHub to use as you progress through the steps. ## Setup Github Trio repository 1. Fork https://github.com/nightscout/Trio into your account. If you already have a fork of Trio in GitHub, you can't make another one. You can continue to work with your existing fork, or delete that from GitHub and then and fork https://github.com/nightscout/Trio. @@ -97,7 +97,7 @@ Once created, you will not take any direct actions with this repository; it need ## Validate repository secrets -This step validates most of your six Secrets and provides error messages if it detects an issue with one or more. +This step validates most of your six Secrets and provides error messages if it detects an issue with one or more. In addition, if you do not have a private Match-Secrets repository it creates one for you. 1. Click on the "Actions" tab of your Trio repository and enable workflows if needed 1. On the left side, select "1. Validate Secrets". @@ -105,6 +105,8 @@ This step validates most of your six Secrets and provides error messages if it d 1. Wait, and within a minute or two you should see a green checkmark indicating the workflow succeeded. 1. The workflow will check if the required secrets are added and that they are correctly formatted. If errors are detected, please check the run log for details. +> There can be a delay after you start the workflow before the screen changes. Refresh your browser to see if it started. And if it seems to take a long time to finish - refresh your browser to see if it is done. + ## Add Identifiers for Trio App 1. Click on the "Actions" tab of your Trio repository. @@ -114,29 +116,66 @@ This step validates most of your six Secrets and provides error messages if it d ## Create App Group -If you have already built Trio via Xcode using this Apple ID, you can skip on to [Create Trio App in App Store Connect](#create-trio-app-in-app-store-connect). -_Please note that in default builds of Trio, the app group is actually identical to the one used with Loop, so please enter these details exactly as described below. This is to ease the setup of apps such as Xdrip4iOS. It may require some caution if transfering between Trio and Loop._ +If you previously built Trio using Mac with Xcode with this Apple ID, skip ahead to [Optional: App Group Description Modification](#optional-app-group-description-modification). + +_Please note that Trio uses the same app group as Loop. This enables other apps such as Xdrip4iOS to share data with Trio. It may require some caution if transfering between Trio and Loop._ 1. Go to [Register an App Group](https://developer.apple.com/account/resources/identifiers/applicationGroup/add/) on the apple developer site. 1. For Description, use "Loop App Group". 1. For Identifier, enter "group.com.TEAMID.loopkit.LoopGroup", substituting your team id for `TEAMID`. + * If you are told that this group already exists, skip ahead to [Optional: App Group Description Modification](#optional-app-group-description-modification) 1. Click "Continue" and then "Register". -## Add App Group to Bundle Identifiers +### Optional: App Group Description Modification + +> This step is not required, but if you previously built using a Mac with Xcode, it is a good idea to update the **NAME** associated with the **IDENTIFIER** for the Loop App Group. Notice in the table below that the XCode version of the **NAME** is the same as the **IDENTIFIER** but with the `.` replaced with a space. + +_Referring to the link and table below, tap on the **IDENTIFIER** for the `Loop App Group`, edit the Description to match the **NAME**, then Save the change._ + +* [App Group List](https://developer.apple.com/account/resources/identifiers/list/applicationGroup) + +| NAME | XCode version | IDENTIFIER | +|:--|:--|:--| +| Loop App Group | group com TEAMID loopkit LoopGroup| group.com.TEAMID.loopkit.LoopGroup | + +## Bundle Identifiers -> Each Identifier **NAME** listed below is the same as the **NAME** used by iAPS, they are distinguised by the **IDENTIFIER** itself, which includes the new BundleID for your Trio app +Open this link in a separate browser window: + +* [Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/identifiers/list) on the Apple developer site +* You will select each of the Identifiers as instructed below, modify it if needed and then save it. + +### Optional: Identifier Description Modification + +> This step is not required, but if you previously built using a Mac with Xcode or during Beta testing for Trio, it is a good idea to update the **NAME** associated with each **IDENTIFIER** to match the table below. + +_Referring to the table below, tap on each **IDENTIFIER** that has a different **NAME**, edit the Description to match the **NAME**, then Save the change for that identifier._ + +#### Table of Identifiers + +* If you built previously using a Mac with Xcode, you may see the XCode version in your **NAME** column - it starts with XC and then the **IDENTIFIER** is appended where the `.` is replaced with a space, the example for Trio is shown in detail +* If you built during beta testing, you might not have `Trio` at the beginning of each **IDENTIFIER** and the full **NAME** may be slightly different + +| NAME | XCode version | IDENTIFIER | +|:--|:--|:--| +| Trio | XC org nightscout TEAMID trio | org.nightscout.TEAMID.trio | +| Trio LiveActivity | - | org.nightscout.TEAMID.trio.LiveActivity | +| Trio Watch | XC IDENTIFIER | org.nightscout.TEAMID.trio.watchkitapp | +| Trio WatchKit Extension | XC IDENTIFIER | org.nightscout.TEAMID.trio.watchkitapp.watchkitextension | + +## Add App Group to Bundle Identifiers -> If you built Trio with Xcode before, the **NAME** will start with XC +> This step is required for first-time builders using GitHub Actions (Browser Build). -> Use the **IDENTIFIER** string rather than the **NAME** to select the correct identifier +> If you previously built using a Mac with Xcode you can skip ahead to [Create Trio App in App Store Connect](#create-trio-app-in-app-store-connect). 1. Go to [Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/identifiers/list) on the Apple developer site. -1. Repeat this step for each **NAME** with associated **IDENTIFIER**: - * FreeAPS: `org.nightscout.TEAMID.trio` - * FreeAPS watchkitapp: `org.nightscout.TEAMID.trio.watchkitapp` - * FreeAPS watchkitapp watchkitextension: `org.nightscout.TEAMID.trio.watchkitapp.watchkitextension` +1. Repeat this step for these three Identifiers - refer to the Table above if your Names look different + * Trio + * Trio Watch + * Trio WatchKit Extension 1. Click on the **IDENTIFIER** row. -1. On the "App Groups" capabilies, click on the "Configure" button. +1. Scroll down to the "App Groups" capabilies, click on the "Configure" button. 1. Select the "Loop App Group" _(yes, "Loop App Group" is correct)_ 1. Click "Continue". 1. Click "Save". @@ -144,11 +183,11 @@ _Please note that in default builds of Trio, the app group is actually identical 1. Remember to do this for each of the identifiers above. There is an additional identifier, but it does not need the App Group added to it: -* LiveActivityExtension: `org.nightscout.TEAMID.trio.LiveActivity` +* Trio LiveActivity ## Create Trio App in App Store Connect -If you have created a Trio app in App Store Connect before, you can skip this section as well. +If you created a Trio app in App Store Connect before, skip ahead to [Create Building Certficates](#create-building-certficates). 1. Go to the [apps list](https://appstoreconnect.apple.com/apps) on App Store Connect and click the blue "plus" icon to create a New App. * Select "iOS". From 59479e2a2dd085ee05eccaf9566191e528078226 Mon Sep 17 00:00:00 2001 From: marionbarker Date: Wed, 19 Jun 2024 12:20:24 -0700 Subject: [PATCH 12/33] clarify some wording for testflight.md --- fastlane/testflight.md | 24 +++++++++++------------- 1 file changed, 11 insertions(+), 13 deletions(-) diff --git a/fastlane/testflight.md b/fastlane/testflight.md index 958880217..59fabf080 100644 --- a/fastlane/testflight.md +++ b/fastlane/testflight.md @@ -76,16 +76,14 @@ Log into your GitHub account to create a personal access token; this is one of t This is the second one of two GitHub secrets needed for your build. -The first time you build with the GitHub Browser Build method for any DIY app, you will make up a password and record it as `MATCH_PASSWORD`. Note, if you later lose `MATCH_PASSWORD`, you will need to delete and make a new Match-Secrets repository (next step). You use the same password for all DIY apps. +The first time you build with the GitHub Browser Build method for any DIY app, you will make up a password and record it as `MATCH_PASSWORD`. You use the same password for all DIY apps. Note, if you later lose `MATCH_PASSWORD`, you will need to delete your Match-Secrets repository (automatically created), and go through the GitHub actions again. -## Setup GitHub Match-Secrets Repository +## GitHub Match-Secrets Repository -> This step is no longer required, your Match-Secrets repository is automatically created the first time you run a GitHub Action. You can skip ahead to [Setup Github Trio repository](#setup-github-trio-repository). - -> Because it is private - only you can see your Match-Secrets repository. You will not take any direct actions with this repository; it needs to be there for GitHub to use as you progress through the steps. +> A private Match-Secrets repository is automatically created under your GitHub username the first time you run a GitHub Action. Because it is a private repository - only you can see it. You will not take any direct actions with this repository; it needs to be there for GitHub to use as you progress through the steps. ## Setup Github Trio repository -1. Fork https://github.com/nightscout/Trio into your account. If you already have a fork of Trio in GitHub, you can't make another one. You can continue to work with your existing fork, or delete that from GitHub and then and fork https://github.com/nightscout/Trio. +1. Fork https://github.com/nightscout/Trio into your account. If you already have a fork of Trio in GitHub, you can't make another one. You can continue to work with your existing fork, or delete that from GitHub and then fork https://github.com/nightscout/Trio. 1. In the forked Trio repo, go to Settings -> Secrets and variables -> Actions. 1. For each of the following secrets, tap on "New repository secret", then add the name of the secret, along with the value you recorded for it: * `TEAMID` @@ -105,7 +103,7 @@ This step validates most of your six Secrets and provides error messages if it d 1. Wait, and within a minute or two you should see a green checkmark indicating the workflow succeeded. 1. The workflow will check if the required secrets are added and that they are correctly formatted. If errors are detected, please check the run log for details. -> There can be a delay after you start the workflow before the screen changes. Refresh your browser to see if it started. And if it seems to take a long time to finish - refresh your browser to see if it is done. +> There can be a delay after you start a workflow before the screen changes. Refresh your browser to see if it started. And if it seems to take a long time to finish - refresh your browser to see if it is done. ## Add Identifiers for Trio App @@ -147,14 +145,14 @@ Open this link in a separate browser window: ### Optional: Identifier Description Modification -> This step is not required, but if you previously built using a Mac with Xcode or during Beta testing for Trio, it is a good idea to update the **NAME** associated with each **IDENTIFIER** to match the table below. +> This step is not required, but if you previously built using a Mac with Xcode or during early Beta testing for Trio, it is a good idea to update the **NAME** associated with each **IDENTIFIER** to match the table below. _Referring to the table below, tap on each **IDENTIFIER** that has a different **NAME**, edit the Description to match the **NAME**, then Save the change for that identifier._ #### Table of Identifiers * If you built previously using a Mac with Xcode, you may see the XCode version in your **NAME** column - it starts with XC and then the **IDENTIFIER** is appended where the `.` is replaced with a space, the example for Trio is shown in detail -* If you built during beta testing, you might not have `Trio` at the beginning of each **IDENTIFIER** and the full **NAME** may be slightly different +* If you built during early beta testing, you might not have `Trio` at the beginning of each **IDENTIFIER** and the full **NAME** may be slightly different | NAME | XCode version | IDENTIFIER | |:--|:--|:--| @@ -170,17 +168,17 @@ _Referring to the table below, tap on each **IDENTIFIER** that has a different * > If you previously built using a Mac with Xcode you can skip ahead to [Create Trio App in App Store Connect](#create-trio-app-in-app-store-connect). 1. Go to [Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/identifiers/list) on the Apple developer site. -1. Repeat this step for these three Identifiers - refer to the Table above if your Names look different +1. Repeat this step for these three Identifier **NAMES** - refer to the [Table](#table-of-identifiers) above if your Names look different; if they do, see [Optional: Identifier Description Modification](#optional-identifier-description-modification) * Trio * Trio Watch * Trio WatchKit Extension 1. Click on the **IDENTIFIER** row. -1. Scroll down to the "App Groups" capabilies, click on the "Configure" button. -1. Select the "Loop App Group" _(yes, "Loop App Group" is correct)_ +1. Scroll down to the "App Groups" capabilies row, click on the "Configure" (or "Edit") button. +1. Select (or verify the selection for) the "Loop App Group" _(yes, "Loop App Group" is correct)_ 1. Click "Continue". 1. Click "Save". 1. Click "Confirm". -1. Remember to do this for each of the identifiers above. +1. Remember to do this for each of three identifiers listed under step 2. There is an additional identifier, but it does not need the App Group added to it: * Trio LiveActivity From 44e66547e6cd9179badb4352f0cc6be80d176a56 Mon Sep 17 00:00:00 2001 From: marionbarker Date: Wed, 19 Jun 2024 13:45:26 -0700 Subject: [PATCH 13/33] update submodule MinimedKit, update SHA, no change to code --- MinimedKit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MinimedKit b/MinimedKit index 4a6df3119..f11abde5e 160000 --- a/MinimedKit +++ b/MinimedKit @@ -1 +1 @@ -Subproject commit 4a6df31192cbbddf62ed36ad78b1ca29649bd851 +Subproject commit f11abde5e2eea2cbf7ac80f3f4bc4bc6e7f6de56 From 8c20819f4ab6ff554b734b3c49ed04e717113f78 Mon Sep 17 00:00:00 2001 From: marionbarker Date: Sat, 22 Jun 2024 13:46:42 -0700 Subject: [PATCH 14/33] update submodule CGMBLEKit: no change in function, add log to filter future events --- CGMBLEKit | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CGMBLEKit b/CGMBLEKit index a92e97529..15af9cf31 160000 --- a/CGMBLEKit +++ b/CGMBLEKit @@ -1 +1 @@ -Subproject commit a92e9752994e7b143cdb007d3c7bcba0c0cc9214 +Subproject commit 15af9cf319bff2ac49c361da254ad667461d4687 From 95463884e1af320683fe47566b9596a4220ebc68 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 24 Jun 2024 16:53:51 +0200 Subject: [PATCH 15/33] add Fingerprick visualization to chart and history --- .../DataTable/View/DataTableRootView.swift | 11 +++- .../Home/View/Chart/MainChartView.swift | 52 ++++++++++++++++++- 2 files changed, 60 insertions(+), 3 deletions(-) diff --git a/FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift b/FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift index 9739356c6..c4074d413 100644 --- a/FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift +++ b/FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift @@ -316,7 +316,16 @@ extension DataTable { state.units == .mmolL ? $0.asMmolL : Decimal($0) ) as NSNumber)! } ?? "--") - Text(item.glucose.direction?.symbol ?? "--") + if item.glucose.type == "Manual" { + ZStack { + Image(systemName: "drop.fill") + .foregroundColor(Color.loopRed) + Image(systemName: "drop") + .foregroundColor(Color.primary) + } + } else { + Text(item.glucose.direction?.symbol ?? "--") + } Spacer() Text(dateFormatter.string(from: item.glucose.dateString)) diff --git a/FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift b/FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift index dc70707ee..06b38bd61 100644 --- a/FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift +++ b/FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift @@ -57,6 +57,7 @@ struct MainChartView: View { @State var didAppearTrigger = false @State private var glucoseDots: [CGRect] = [] @State private var unSmoothedGlucoseDots: [CGRect] = [] + @State private var manualGlucoseDots: [CGRect] = [] @State private var predictionDots: [PredictionType: [CGRect]] = [:] @State private var bolusDots: [DotInfo] = [] @State private var bolusPath = Path() @@ -270,6 +271,7 @@ struct MainChartView: View { bolusView(fullSize: fullSize) if smooth { unSmoothedGlucoseView(fullSize: fullSize) } glucoseView(fullSize: fullSize) + manualGlucoseView(fullSize: fullSize) predictionsView(fullSize: fullSize) } timeLabelsView(fullSize: fullSize) @@ -363,6 +365,32 @@ struct MainChartView: View { } } + private func manualGlucoseView(fullSize: CGSize) -> some View { + ZStack { + Path { path in + for rect in manualGlucoseDots { + path.addEllipse(in: rect) + } + } + .fill(Color.loopRed) + Path { path in + for rect in manualGlucoseDots { + path.addEllipse(in: rect) + } + } + .stroke(Color.primary, lineWidth: 0.5) + } + .onChange(of: glucose) { _ in + update(fullSize: fullSize) + } + .onChange(of: didAppearTrigger) { _ in + update(fullSize: fullSize) + } + .onReceive(Foundation.NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)) { _ in + update(fullSize: fullSize) + } + } + private func bolusView(fullSize: CGSize) -> some View { ZStack { bolusPath @@ -489,6 +517,7 @@ extension MainChartView { calculatePredictionDots(fullSize: fullSize, type: .uam) calculateGlucoseDots(fullSize: fullSize) calculateUnSmoothedGlucoseDots(fullSize: fullSize) + calculateManualGlucoseDots(fullSize: fullSize) calculateBolusDots(fullSize: fullSize) calculateCarbsDots(fullSize: fullSize) calculateFPUsDots(fullSize: fullSize) @@ -499,7 +528,8 @@ extension MainChartView { private func calculateGlucoseDots(fullSize: CGSize) { calculationQueue.async { - let dots = glucose.concurrentMap { value -> CGRect in + let sgvs = glucose.filter { $0.type == "sgv" } + let dots = sgvs.concurrentMap { value -> CGRect in let position = glucoseToCoordinate(value, fullSize: fullSize) return CGRect(x: position.x - 2, y: position.y - 2, width: 4, height: 4) } @@ -515,7 +545,8 @@ extension MainChartView { private func calculateUnSmoothedGlucoseDots(fullSize: CGSize) { calculationQueue.async { - let dots = glucose.concurrentMap { value -> CGRect in + let sgvs = glucose.filter { $0.type == "sgv" } + let dots = sgvs.concurrentMap { value -> CGRect in let position = UnSmoothedGlucoseToCoordinate(value, fullSize: fullSize) return CGRect(x: position.x - 2, y: position.y - 2, width: 4, height: 4) } @@ -529,6 +560,23 @@ extension MainChartView { } } + private func calculateManualGlucoseDots(fullSize: CGSize) { + calculationQueue.async { + let manuals = glucose.filter { $0.type == "Manual" } + let dots = manuals.concurrentMap { value -> CGRect in + let position = glucoseToCoordinate(value, fullSize: fullSize) + return CGRect(x: position.x - 2, y: position.y - 2, width: 6, height: 6) + } + + let range = self.getGlucoseYRange(fullSize: fullSize) + + DispatchQueue.main.async { + glucoseYRange = range + manualGlucoseDots = dots + } + } + } + private func calculateBolusDots(fullSize: CGSize) { calculationQueue.async { let dots = boluses.map { value -> DotInfo in From 92ff5c2723309e62d57aaea570de6bc76471b024 Mon Sep 17 00:00:00 2001 From: Robert Date: Tue, 25 Jun 2024 07:53:31 +0200 Subject: [PATCH 16/33] remove border on drop icon --- .../Sources/Modules/DataTable/View/DataTableRootView.swift | 4 ---- 1 file changed, 4 deletions(-) diff --git a/FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift b/FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift index c4074d413..902bdf241 100644 --- a/FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift +++ b/FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift @@ -317,12 +317,8 @@ extension DataTable { ) as NSNumber)! } ?? "--") if item.glucose.type == "Manual" { - ZStack { Image(systemName: "drop.fill") .foregroundColor(Color.loopRed) - Image(systemName: "drop") - .foregroundColor(Color.primary) - } } else { Text(item.glucose.direction?.symbol ?? "--") } From 22890354c740c3eadde3743de7321540f6d62054 Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 26 Jun 2024 11:53:57 +0200 Subject: [PATCH 17/33] change filter for drawing glucoseDots --- FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift b/FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift index 06b38bd61..8a91e8878 100644 --- a/FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift +++ b/FreeAPS/Sources/Modules/Home/View/Chart/MainChartView.swift @@ -528,7 +528,9 @@ extension MainChartView { private func calculateGlucoseDots(fullSize: CGSize) { calculationQueue.async { - let sgvs = glucose.filter { $0.type == "sgv" } + let sgvs = glucose + .filter { $0.type != "Manual" + } // as fingerpricks will be drawn differently, slightly larger and red - so do not draw them here let dots = sgvs.concurrentMap { value -> CGRect in let position = glucoseToCoordinate(value, fullSize: fullSize) return CGRect(x: position.x - 2, y: position.y - 2, width: 4, height: 4) From ca4b0e545cfb8fe2996194ac0d027cded0e5602a Mon Sep 17 00:00:00 2001 From: Robert Date: Wed, 26 Jun 2024 17:52:49 +0200 Subject: [PATCH 18/33] linting --- .../Sources/Modules/DataTable/View/DataTableRootView.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift b/FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift index 902bdf241..19f42d67c 100644 --- a/FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift +++ b/FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift @@ -317,8 +317,8 @@ extension DataTable { ) as NSNumber)! } ?? "--") if item.glucose.type == "Manual" { - Image(systemName: "drop.fill") - .foregroundColor(Color.loopRed) + Image(systemName: "drop.fill") + .foregroundColor(Color.loopRed) } else { Text(item.glucose.direction?.symbol ?? "--") } From 3402f45dc7f1d2ad9cbd0627941b39e876d98ad5 Mon Sep 17 00:00:00 2001 From: Sjoerd Bozon Date: Wed, 26 Jun 2024 20:29:46 +0200 Subject: [PATCH 19/33] add versionnumber --- .github/workflows/add_to_project.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/add_to_project.yml b/.github/workflows/add_to_project.yml index 7c66e6a14..0cbd0eb73 100644 --- a/.github/workflows/add_to_project.yml +++ b/.github/workflows/add_to_project.yml @@ -10,11 +10,11 @@ jobs: name: Add issue to project runs-on: ubuntu-latest steps: - - uses: actions/add-to-project@RELEASE_VERSION + - uses: actions/add-to-project@v1.0.2 with: # You can target a project in a different organization # to the issue project-url: https://github.com/orgs/nightscout/projects/2 github-token: ${{ secrets.ADD_TO_PROJECT_PAT }} labeled: bug, needs-triage - label-operator: OR \ No newline at end of file + label-operator: OR From 5a5943915cf45db48cda40a9196e92034829461c Mon Sep 17 00:00:00 2001 From: Sjoerd Bozon Date: Wed, 26 Jun 2024 20:42:57 +0200 Subject: [PATCH 20/33] add longterm, changed token, add not planned --- .github/workflows/stale_issues.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/stale_issues.yml b/.github/workflows/stale_issues.yml index 15d5f9ebe..b5be10403 100644 --- a/.github/workflows/stale_issues.yml +++ b/.github/workflows/stale_issues.yml @@ -1,4 +1,4 @@ - name: close inactive issues +name: close inactive issues on: schedule: - cron: "30 1 * * *" @@ -10,14 +10,15 @@ jobs: issues: write pull-requests: write steps: - - uses: actions/stale@v4 + - uses: actions/stale@v9.0.0 with: days-before-issue-stale: 30 days-before-issue-close: 14 stale-issue-label: "stale" stale-issue-message: "hey 👋 - silence for 30 days 🤐 ... anybody? triage is required!" close-issue-message: "closed 📴 because silencio 🤫 since an additional 14 days after staleness 📠" - exempt-issue-labels: "needs-triage" + close-issue-label: "not-planned" + exempt-issue-labels: "needs-triage, long-term" days-before-pr-stale: -1 days-before-pr-close: -1 - repo-token: ${{ secrets.GITHUB_TOKEN }} \ No newline at end of file + repo-token: ${{ secrets.STALE_ISSUES_PAT }} From 59efe32d1b20de0c8c8c8ebabaf7fd130e1a4171 Mon Sep 17 00:00:00 2001 From: Sjoerd Bozon Date: Wed, 26 Jun 2024 20:57:28 +0200 Subject: [PATCH 21/33] add needs-triage stale job --- .github/workflows/stale_issues.yml | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/.github/workflows/stale_issues.yml b/.github/workflows/stale_issues.yml index b5be10403..b44f1718f 100644 --- a/.github/workflows/stale_issues.yml +++ b/.github/workflows/stale_issues.yml @@ -8,7 +8,6 @@ jobs: runs-on: ubuntu-latest permissions: issues: write - pull-requests: write steps: - uses: actions/stale@v9.0.0 with: @@ -18,7 +17,27 @@ jobs: stale-issue-message: "hey 👋 - silence for 30 days 🤐 ... anybody? triage is required!" close-issue-message: "closed 📴 because silencio 🤫 since an additional 14 days after staleness 📠" close-issue-label: "not-planned" - exempt-issue-labels: "needs-triage, long-term" + exempt-issue-labels: "needs-triage, long-term, in-progress" days-before-pr-stale: -1 days-before-pr-close: -1 repo-token: ${{ secrets.STALE_ISSUES_PAT }} + + close-issues-triage: + runs-on: ubuntu-latest + permissions: + issues: write + steps: + - uses: actions/stale@v9.0.0 + with: + days-before-issue-stale: 30 + days-before-issue-close: 30 + stale-issue-label: "stale" + stale-issue-message: "hey 👋 - no triage is done for 30 days 🤐 ... anybody? triage is required!" + close-issue-message: "closed 📴 because silencio 🤫 since an additional 30 days after staleness 📠" + close-issue-label: "not-planned" + exempt-issue-labels: "long-term, in-progress" + any-of-labels: "needs-triage" + days-before-pr-stale: -1 + days-before-pr-close: -1 + repo-token: ${{ secrets.STALE_ISSUES_PAT }} + From 99da4479d542243fa32e85591bac88e3f889fc7f Mon Sep 17 00:00:00 2001 From: polscm32 aka Marvout Date: Thu, 27 Jun 2024 01:40:00 +0200 Subject: [PATCH 22/33] Refactor current implementation of the DecimalTextField struct - fix constraint error - fix clear button not working as expected - make cursor right adjusted when tapping in the Textfield --- FreeAPS.xcodeproj/project.pbxproj | 8 +- .../AddCarbs/View/AddCarbsRootView.swift | 24 +-- .../View/AddTempTargetRootView.swift | 6 +- .../Modules/Bolus/View/BolusRootView.swift | 8 +- .../View/CalibrationsRootView.swift | 8 +- .../DataTable/View/DataTableRootView.swift | 16 +- .../FPUConfig/View/FPUConfigRootView.swift | 14 +- .../View/ManualTempBasalRootView.swift | 2 +- .../View/NightscoutConnectView.swift | 2 +- .../View/NotificationsConfigRootView.swift | 6 +- .../View/OverrideProfilesRootView.swift | 22 +-- .../View/PreferencesEditorRootView.swift | 8 +- .../View/PumpSettingsEditorRootView.swift | 6 +- .../StatConfig/View/StatConfigRootView.swift | 6 +- FreeAPS/Sources/Views/DecimalTextField.swift | 160 --------------- .../Sources/Views/TextFieldWithToolBar.swift | 187 ++++++++++++++++++ 16 files changed, 227 insertions(+), 256 deletions(-) delete mode 100644 FreeAPS/Sources/Views/DecimalTextField.swift create mode 100644 FreeAPS/Sources/Views/TextFieldWithToolBar.swift diff --git a/FreeAPS.xcodeproj/project.pbxproj b/FreeAPS.xcodeproj/project.pbxproj index e3c82b11c..e5723f794 100644 --- a/FreeAPS.xcodeproj/project.pbxproj +++ b/FreeAPS.xcodeproj/project.pbxproj @@ -122,7 +122,7 @@ 3871F38725ED661C0013ECB5 /* Suggestion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3871F38625ED661C0013ECB5 /* Suggestion.swift */; }; 3871F39C25ED892B0013ECB5 /* TempTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3871F39B25ED892B0013ECB5 /* TempTarget.swift */; }; 3871F39F25ED895A0013ECB5 /* Decimal+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3871F39E25ED895A0013ECB5 /* Decimal+Extensions.swift */; }; - 3883581C25EE79BB00E024B2 /* DecimalTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3883581B25EE79BB00E024B2 /* DecimalTextField.swift */; }; + 3883581C25EE79BB00E024B2 /* TextFieldWithToolBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3883581B25EE79BB00E024B2 /* TextFieldWithToolBar.swift */; }; 3883583425EEB38000E024B2 /* PumpSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3883583325EEB38000E024B2 /* PumpSettings.swift */; }; 388358C825EEF6D200E024B2 /* BasalProfileEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 388358C725EEF6D200E024B2 /* BasalProfileEntry.swift */; }; 38887CCE25F5725200944304 /* IOBEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = 38887CCD25F5725200944304 /* IOBEntry.swift */; }; @@ -641,7 +641,7 @@ 3871F38625ED661C0013ECB5 /* Suggestion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Suggestion.swift; sourceTree = ""; }; 3871F39B25ED892B0013ECB5 /* TempTarget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TempTarget.swift; sourceTree = ""; }; 3871F39E25ED895A0013ECB5 /* Decimal+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Decimal+Extensions.swift"; sourceTree = ""; }; - 3883581B25EE79BB00E024B2 /* DecimalTextField.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DecimalTextField.swift; sourceTree = ""; }; + 3883581B25EE79BB00E024B2 /* TextFieldWithToolBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextFieldWithToolBar.swift; sourceTree = ""; }; 3883583325EEB38000E024B2 /* PumpSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PumpSettings.swift; sourceTree = ""; }; 388358C725EEF6D200E024B2 /* BasalProfileEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BasalProfileEntry.swift; sourceTree = ""; }; 38887CCD25F5725200944304 /* IOBEntry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IOBEntry.swift; sourceTree = ""; }; @@ -1516,7 +1516,7 @@ isa = PBXGroup; children = ( 3811DE5925C9D4D500A708ED /* ViewModifiers.swift */, - 3883581B25EE79BB00E024B2 /* DecimalTextField.swift */, + 3883581B25EE79BB00E024B2 /* TextFieldWithToolBar.swift */, 383420D825FFEB3F002D46C1 /* Popup.swift */, 389ECDFD2601061500D86C4F /* View+Snapshot.swift */, 38EA05FF262091870064E39B /* BolusProgressViewStyle.swift */, @@ -2719,7 +2719,7 @@ 38A504A425DD9C4000C5B9E8 /* UserDefaultsExtensions.swift in Sources */, 38FE826A25CC82DB001FF17A /* NetworkService.swift in Sources */, FE66D16B291F74F8005D6F77 /* Bundle+Extensions.swift in Sources */, - 3883581C25EE79BB00E024B2 /* DecimalTextField.swift in Sources */, + 3883581C25EE79BB00E024B2 /* TextFieldWithToolBar.swift in Sources */, 6B1A8D2E2B156EEF00E76752 /* LiveActivityBridge.swift in Sources */, 38DAB28A260D349500F74C1A /* FetchGlucoseManager.swift in Sources */, 38F37828261260DC009DB701 /* Color+Extensions.swift in Sources */, diff --git a/FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift b/FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift index 2afe06961..d9c87458f 100644 --- a/FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift +++ b/FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift @@ -42,13 +42,7 @@ extension AddCarbs { HStack { Text("Carbs").fontWeight(.semibold) Spacer() - DecimalTextField( - "0", - value: $state.carbs, - formatter: formatter, - autofocus: true, - cleanInput: true - ) + TextFieldWithToolBar(text: $state.carbs, placeholder: "0", shouldBecomeFirstResponder: true, numberFormatter: formatter) Text(state.carbs > state.maxCarbs ? "⚠️" : "g").foregroundColor(.secondary) }.padding(.vertical) @@ -268,25 +262,13 @@ extension AddCarbs { HStack { Text("Fat").foregroundColor(.orange) // .fontWeight(.thin) Spacer() - DecimalTextField( - "0", - value: $state.fat, - formatter: formatter, - autofocus: false, - cleanInput: true - ) + TextFieldWithToolBar(text: $state.fat, placeholder: "0", numberFormatter: formatter) Text(state.fat > state.maxFat ? "⚠️" : "g").foregroundColor(.secondary) } HStack { Text("Protein").foregroundColor(.red) // .fontWeight(.thin) Spacer() - DecimalTextField( - "0", - value: $state.protein, - formatter: formatter, - autofocus: false, - cleanInput: true - ) + TextFieldWithToolBar(text: $state.protein, placeholder: "0", numberFormatter: formatter) Text(state.protein > state.maxProtein ? "⚠️" : "g").foregroundColor(.secondary) } } diff --git a/FreeAPS/Sources/Modules/AddTempTarget/View/AddTempTargetRootView.swift b/FreeAPS/Sources/Modules/AddTempTarget/View/AddTempTargetRootView.swift index 87a1772e9..a25bda924 100644 --- a/FreeAPS/Sources/Modules/AddTempTarget/View/AddTempTargetRootView.swift +++ b/FreeAPS/Sources/Modules/AddTempTarget/View/AddTempTargetRootView.swift @@ -187,13 +187,13 @@ extension AddTempTarget { HStack { Text("Target") Spacer() - DecimalTextField("0", value: $state.low, formatter: formatter, cleanInput: true) + TextFieldWithToolBar(text: $state.low, placeholder: "0", numberFormatter: formatter) Text(state.units.rawValue).foregroundColor(.secondary) } HStack { Text("Duration") Spacer() - DecimalTextField("0", value: $state.duration, formatter: formatter, cleanInput: true) + TextFieldWithToolBar(text: $state.duration, placeholder: "0", numberFormatter: formatter) Text("minutes").foregroundColor(.secondary) } } @@ -203,7 +203,7 @@ extension AddTempTarget { HStack { Text("Duration") Spacer() - DecimalTextField("0", value: $state.duration, formatter: formatter, cleanInput: true) + TextFieldWithToolBar(text: $state.duration, placeholder: "0", numberFormatter: formatter) Text("minutes").foregroundColor(.secondary) } } diff --git a/FreeAPS/Sources/Modules/Bolus/View/BolusRootView.swift b/FreeAPS/Sources/Modules/Bolus/View/BolusRootView.swift index 51817c5db..167268599 100644 --- a/FreeAPS/Sources/Modules/Bolus/View/BolusRootView.swift +++ b/FreeAPS/Sources/Modules/Bolus/View/BolusRootView.swift @@ -65,13 +65,7 @@ extension Bolus { HStack { Text("Amount") Spacer() - DecimalTextField( - "0", - value: $state.amount, - formatter: formatter, - autofocus: true, - cleanInput: true - ) + TextFieldWithToolBar(text: $state.amount, placeholder: "0", shouldBecomeFirstResponder: true, numberFormatter: formatter) Text(state.amount > state.maxBolus ? "⚠️" : "U").foregroundColor(.secondary) } } diff --git a/FreeAPS/Sources/Modules/Calibrations/View/CalibrationsRootView.swift b/FreeAPS/Sources/Modules/Calibrations/View/CalibrationsRootView.swift index 8bfaead27..fca7c4938 100644 --- a/FreeAPS/Sources/Modules/Calibrations/View/CalibrationsRootView.swift +++ b/FreeAPS/Sources/Modules/Calibrations/View/CalibrationsRootView.swift @@ -27,13 +27,7 @@ extension Calibrations { HStack { Text("Meter glucose") Spacer() - DecimalTextField( - "0", - value: $state.newCalibration, - formatter: formatter, - autofocus: false, - cleanInput: true - ) + TextFieldWithToolBar(text: $state.newCalibration, placeholder: "0", numberFormatter: formatter) Text(state.units.rawValue).foregroundColor(.secondary) } Button { diff --git a/FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift b/FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift index 9739356c6..1e737533b 100644 --- a/FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift +++ b/FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift @@ -140,13 +140,7 @@ extension DataTable { Section { HStack { Text("New Glucose") - DecimalTextField( - " ... ", - value: $state.manualGlucose, - formatter: glucoseFormatter, - autofocus: true, - cleanInput: true - ) + TextFieldWithToolBar(text: $state.manualGlucose, placeholder: " ... ", shouldBecomeFirstResponder: true, numberFormatter: glucoseFormatter) Text(state.units.rawValue).foregroundStyle(.secondary) } } @@ -253,13 +247,7 @@ extension DataTable { HStack { Text("Amount") Spacer() - DecimalTextField( - "0", - value: $state.externalInsulinAmount, - formatter: insulinFormatter, - autofocus: true, - cleanInput: true - ) + TextFieldWithToolBar(text: $state.externalInsulinAmount, placeholder: "0", shouldBecomeFirstResponder: true, numberFormatter: insulinFormatter) Text("U").foregroundColor(.secondary) } } diff --git a/FreeAPS/Sources/Modules/FPUConfig/View/FPUConfigRootView.swift b/FreeAPS/Sources/Modules/FPUConfig/View/FPUConfigRootView.swift index d8df7d878..fd60d4f5f 100644 --- a/FreeAPS/Sources/Modules/FPUConfig/View/FPUConfigRootView.swift +++ b/FreeAPS/Sources/Modules/FPUConfig/View/FPUConfigRootView.swift @@ -31,15 +31,15 @@ extension FPUConfig { Section(header: Text("Limit Per Entry")) { HStack { Text("Max Carbs") - DecimalTextField("g", value: $state.maxCarbs, formatter: formatter) + TextFieldWithToolBar(text: $state.maxCarbs, placeholder: "g", numberFormatter: formatter) } HStack { Text("Max Fat") - DecimalTextField("g", value: $state.maxFat, formatter: formatter) + TextFieldWithToolBar(text: $state.maxFat, placeholder: "g", numberFormatter: formatter) } HStack { Text("Max Protein") - DecimalTextField("g", value: $state.maxProtein, formatter: formatter) + TextFieldWithToolBar(text: $state.maxProtein, placeholder: "g", numberFormatter: formatter) } } @@ -47,22 +47,22 @@ extension FPUConfig { HStack { Text("Delay In Minutes") Spacer() - DecimalTextField("60", value: $state.delay, formatter: intFormater) + TextFieldWithToolBar(text: $state.delay, placeholder: "60", numberFormatter: intFormater) } HStack { Text("Maximum Duration In Hours") Spacer() - DecimalTextField("8", value: $state.timeCap, formatter: intFormater) + TextFieldWithToolBar(text: $state.timeCap, placeholder: "8", numberFormatter: intFormater) } HStack { Text("Interval In Minutes") Spacer() - DecimalTextField("30", value: $state.minuteInterval, formatter: intFormater) + TextFieldWithToolBar(text: $state.minuteInterval, placeholder: "30", numberFormatter: intFormater) } HStack { Text("Override With A Factor Of ") Spacer() - DecimalTextField("0.5", value: $state.individualAdjustmentFactor, formatter: conversionFormatter) + TextFieldWithToolBar(text: $state.individualAdjustmentFactor, placeholder: "0.5", numberFormatter: conversionFormatter) } } diff --git a/FreeAPS/Sources/Modules/ManualTempBasal/View/ManualTempBasalRootView.swift b/FreeAPS/Sources/Modules/ManualTempBasal/View/ManualTempBasalRootView.swift index 4b230ec9c..33652a183 100644 --- a/FreeAPS/Sources/Modules/ManualTempBasal/View/ManualTempBasalRootView.swift +++ b/FreeAPS/Sources/Modules/ManualTempBasal/View/ManualTempBasalRootView.swift @@ -19,7 +19,7 @@ extension ManualTempBasal { HStack { Text("Amount") Spacer() - DecimalTextField("0", value: $state.rate, formatter: formatter, autofocus: true, cleanInput: true) + TextFieldWithToolBar(text: $state.rate, placeholder: "0", shouldBecomeFirstResponder: true, numberFormatter: formatter) Text("U/hr").foregroundColor(.secondary) } Picker(selection: $state.durationIndex, label: Text("Duration")) { diff --git a/FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutConnectView.swift b/FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutConnectView.swift index 7ae671ecd..cff0365c1 100644 --- a/FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutConnectView.swift +++ b/FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutConnectView.swift @@ -49,7 +49,7 @@ struct NightscoutConnectView: View { Toggle("Use local glucose server", isOn: $state.useLocalSource) HStack { Text("Port") - DecimalTextField("", value: $state.localPort, formatter: portFormater) + TextFieldWithToolBar(text: $state.localPort, placeholder: "", numberFormatter: portFormater) } } header: { Text("Local glucose source") } } diff --git a/FreeAPS/Sources/Modules/NotificationsConfig/View/NotificationsConfigRootView.swift b/FreeAPS/Sources/Modules/NotificationsConfig/View/NotificationsConfigRootView.swift index 39b12dddc..e4d864e54 100644 --- a/FreeAPS/Sources/Modules/NotificationsConfig/View/NotificationsConfigRootView.swift +++ b/FreeAPS/Sources/Modules/NotificationsConfig/View/NotificationsConfigRootView.swift @@ -89,14 +89,14 @@ extension NotificationsConfig { HStack { Text("Low") Spacer() - DecimalTextField("0", value: $state.lowGlucose, formatter: glucoseFormatter) + TextFieldWithToolBar(text: $state.lowGlucose, placeholder: "0", numberFormatter: glucoseFormatter) Text(state.units.rawValue).foregroundColor(.secondary) } HStack { Text("High") Spacer() - DecimalTextField("0", value: $state.highGlucose, formatter: glucoseFormatter) + TextFieldWithToolBar(text: $state.highGlucose, placeholder: "0", numberFormatter: glucoseFormatter) Text(state.units.rawValue).foregroundColor(.secondary) } } @@ -105,7 +105,7 @@ extension NotificationsConfig { HStack { Text("Carbs Required Threshold") Spacer() - DecimalTextField("0", value: $state.carbsRequiredThreshold, formatter: carbsFormatter) + TextFieldWithToolBar(text: $state.carbsRequiredThreshold, placeholder: "0", numberFormatter: carbsFormatter) Text("g").foregroundColor(.secondary) } } diff --git a/FreeAPS/Sources/Modules/OverrideProfilesConfig/View/OverrideProfilesRootView.swift b/FreeAPS/Sources/Modules/OverrideProfilesConfig/View/OverrideProfilesRootView.swift index 7e8be9694..0ab35859e 100644 --- a/FreeAPS/Sources/Modules/OverrideProfilesConfig/View/OverrideProfilesRootView.swift +++ b/FreeAPS/Sources/Modules/OverrideProfilesConfig/View/OverrideProfilesRootView.swift @@ -135,7 +135,7 @@ extension OverrideProfilesConfig { if !state._indefinite { HStack { Text("Duration") - DecimalTextField("0", value: $state.duration, formatter: formatter, cleanInput: false) + TextFieldWithToolBar(text: $state.duration, placeholder: "0", numberFormatter: formatter) Text("minutes").foregroundColor(.secondary) } } @@ -148,7 +148,7 @@ extension OverrideProfilesConfig { if state.override_target { HStack { Text("Target Glucose") - DecimalTextField("0", value: $state.target, formatter: glucoseFormatter, cleanInput: false) + TextFieldWithToolBar(text: $state.target, placeholder: "0", numberFormatter: glucoseFormatter) Text(state.units.rawValue).foregroundColor(.secondary) } } @@ -172,12 +172,12 @@ extension OverrideProfilesConfig { if state.smbIsScheduledOff { HStack { Text("First Hour SMBs are Off (24 hours)") - DecimalTextField("0", value: $state.start, formatter: formatter, cleanInput: false) + TextFieldWithToolBar(text: $state.start, placeholder: "0", numberFormatter: formatter) Text("hour").foregroundColor(.secondary) } HStack { Text("First Hour SMBs are Resumed (24 hours)") - DecimalTextField("0", value: $state.end, formatter: formatter, cleanInput: false) + TextFieldWithToolBar(text: $state.end, placeholder: "0", numberFormatter: formatter) Text("hour").foregroundColor(.secondary) } } @@ -201,22 +201,12 @@ extension OverrideProfilesConfig { } HStack { Text("SMB Minutes") - DecimalTextField( - "0", - value: $state.smbMinutes, - formatter: formatter, - cleanInput: false - ) + TextFieldWithToolBar(text: $state.smbMinutes, placeholder: "0", numberFormatter: formatter) Text("minutes").foregroundColor(.secondary) } HStack { Text("UAM SMB Minutes") - DecimalTextField( - "0", - value: $state.uamMinutes, - formatter: formatter, - cleanInput: false - ) + TextFieldWithToolBar(text: $state.uamMinutes, placeholder: "0", numberFormatter: formatter) Text("minutes").foregroundColor(.secondary) } } diff --git a/FreeAPS/Sources/Modules/PreferencesEditor/View/PreferencesEditorRootView.swift b/FreeAPS/Sources/Modules/PreferencesEditor/View/PreferencesEditorRootView.swift index 7ef673171..43375d418 100644 --- a/FreeAPS/Sources/Modules/PreferencesEditor/View/PreferencesEditorRootView.swift +++ b/FreeAPS/Sources/Modules/PreferencesEditor/View/PreferencesEditorRootView.swift @@ -29,7 +29,7 @@ extension PreferencesEditor { } HStack { Text("Recommended Bolus Percentage") - DecimalTextField("", value: $state.insulinReqPercentage, formatter: formatter) + TextFieldWithToolBar(text: $state.insulinReqPercentage, placeholder: "", numberFormatter: formatter) } Toggle("Skip Bolus screen after carbs", isOn: $state.skipBolusScreenAfterCarbs) @@ -62,11 +62,7 @@ extension PreferencesEditor { }) Text(field.displayName) } - DecimalTextField( - "0", - value: self.$state.sections[sectionIndex].fields[fieldIndex].decimalValue, - formatter: formatter - ) + TextFieldWithToolBar(text: self.$state.sections[sectionIndex].fields[fieldIndex].decimalValue, placeholder: "0", numberFormatter: formatter) case .insulinCurve: Picker( selection: $state.sections[sectionIndex].fields[fieldIndex].insulinCurveValue, diff --git a/FreeAPS/Sources/Modules/PumpSettingsEditor/View/PumpSettingsEditorRootView.swift b/FreeAPS/Sources/Modules/PumpSettingsEditor/View/PumpSettingsEditorRootView.swift index fcbd7826d..5f518813f 100644 --- a/FreeAPS/Sources/Modules/PumpSettingsEditor/View/PumpSettingsEditorRootView.swift +++ b/FreeAPS/Sources/Modules/PumpSettingsEditor/View/PumpSettingsEditorRootView.swift @@ -17,18 +17,18 @@ extension PumpSettingsEditor { Section(header: Text("Delivery limits")) { HStack { Text("Max Basal") - DecimalTextField("U/hr", value: $state.maxBasal, formatter: formatter) + TextFieldWithToolBar(text: $state.maxBasal, placeholder: "U/hr", numberFormatter: formatter) } HStack { Text("Max Bolus") - DecimalTextField("U", value: $state.maxBolus, formatter: formatter) + TextFieldWithToolBar(text: $state.maxBolus, placeholder: "U", numberFormatter: formatter) } } Section(header: Text("Duration of Insulin Action")) { HStack { Text("DIA") - DecimalTextField("hours", value: $state.dia, formatter: formatter) + TextFieldWithToolBar(text: $state.dia, placeholder: "hours", numberFormatter: formatter) } } diff --git a/FreeAPS/Sources/Modules/StatConfig/View/StatConfigRootView.swift b/FreeAPS/Sources/Modules/StatConfig/View/StatConfigRootView.swift index fb12163d1..6d6938320 100644 --- a/FreeAPS/Sources/Modules/StatConfig/View/StatConfigRootView.swift +++ b/FreeAPS/Sources/Modules/StatConfig/View/StatConfigRootView.swift @@ -36,21 +36,21 @@ extension StatConfig { HStack { Text("Hours X-Axis (6 default)") Spacer() - DecimalTextField("6", value: $state.hours, formatter: carbsFormatter) + TextFieldWithToolBar(text: $state.hours, placeholder: "6", numberFormatter: carbsFormatter) Text("hours").foregroundColor(.secondary) } HStack { Text("Low") Spacer() - DecimalTextField("0", value: $state.low, formatter: glucoseFormatter) + TextFieldWithToolBar(text: $state.low, placeholder: "0", numberFormatter: glucoseFormatter) Text(state.units.rawValue).foregroundColor(.secondary) } HStack { Text("High") Spacer() - DecimalTextField("0", value: $state.high, formatter: glucoseFormatter) + TextFieldWithToolBar(text: $state.high, placeholder: "0", numberFormatter: glucoseFormatter) Text(state.units.rawValue).foregroundColor(.secondary) } } diff --git a/FreeAPS/Sources/Views/DecimalTextField.swift b/FreeAPS/Sources/Views/DecimalTextField.swift deleted file mode 100644 index 7b46a960f..000000000 --- a/FreeAPS/Sources/Views/DecimalTextField.swift +++ /dev/null @@ -1,160 +0,0 @@ -import Combine -import SwiftUI - -struct DecimalTextField: UIViewRepresentable { - private var placeholder: String - @Binding var value: Decimal - private var formatter: NumberFormatter - private var autofocus: Bool - private var cleanInput: Bool - - init( - _ placeholder: String, - value: Binding, - formatter: NumberFormatter, - autofocus: Bool = false, - cleanInput: Bool = false - ) { - self.placeholder = placeholder - _value = value - self.formatter = formatter - self.autofocus = autofocus - self.cleanInput = cleanInput - } - - func makeUIView(context: Context) -> UITextField { - let textfield = UITextField() - textfield.keyboardType = .decimalPad - textfield.delegate = context.coordinator - textfield.placeholder = placeholder - textfield.text = cleanInput ? "" : formatter.string(for: value) ?? placeholder - textfield.textAlignment = .right - - let toolBar = UIToolbar(frame: CGRect( - x: 0, - y: 0, - width: textfield.frame.size.width, - height: 44 - )) - let clearButton = UIBarButtonItem( - title: NSLocalizedString("Clear", comment: "Clear button"), - style: .plain, - target: self, - action: #selector(textfield.clearButtonTapped(button:)) - ) - let doneButton = UIBarButtonItem( - title: NSLocalizedString("Done", comment: "Done button"), - style: .done, - target: self, - action: #selector(textfield.doneButtonTapped(button:)) - ) - let space = UIBarButtonItem( - barButtonSystemItem: UIBarButtonItem.SystemItem.flexibleSpace, - target: nil, - action: nil - ) - toolBar.setItems([clearButton, space, doneButton], animated: true) - textfield.inputAccessoryView = toolBar - if autofocus { - DispatchQueue.main.async { - textfield.becomeFirstResponder() - } - } - return textfield - } - - func updateUIView(_ textField: UITextField, context: Context) { - let coordinator = context.coordinator - if coordinator.isEditing { - coordinator.resetEditing() - } else if value == 0 { - textField.text = "" - } else { - textField.text = formatter.string(for: value) - } - } - - func makeCoordinator() -> Coordinator { - Coordinator(self) - } - - class Coordinator: NSObject, UITextFieldDelegate { - var parent: DecimalTextField - - init(_ textField: DecimalTextField) { - parent = textField - } - - private(set) var isEditing = false - private var editingCancellable: AnyCancellable? - - func resetEditing() { - editingCancellable = Just(false) - .delay(for: 0.5, scheduler: DispatchQueue.main) - .weakAssign(to: \.isEditing, on: self) - } - - func textField( - _ textField: UITextField, - shouldChangeCharactersIn range: NSRange, - replacementString string: String - ) -> Bool { - // Allow only numbers and decimal characters - let isNumber = CharacterSet.decimalDigits.isSuperset(of: CharacterSet(charactersIn: string)) - let withDecimal = ( - string == NumberFormatter().decimalSeparator && - textField.text?.contains(string) == false - ) - - if isNumber || withDecimal, - let currentValue = textField.text as NSString? - { - // Update Value - let proposedValue = currentValue.replacingCharacters(in: range, with: string) as String - - let decimalFormatter = NumberFormatter() - decimalFormatter.locale = Locale.current - decimalFormatter.numberStyle = .decimal - - // Try currency formatter then Decimal formatrer - let number = parent.formatter.number(from: proposedValue) ?? decimalFormatter.number(from: proposedValue) ?? 0.0 - - // Set Value - let double = number.doubleValue - isEditing = true - parent.value = Decimal(double) - } - - return isNumber || withDecimal - } - - func textFieldDidEndEditing( - _ textField: UITextField, - reason _: UITextField.DidEndEditingReason - ) { - // Format value with formatter at End Editing - textField.text = parent.formatter.string(for: parent.value) - isEditing = false - } - } -} - -// MARK: extension for done button - -extension UITextField { - @objc func doneButtonTapped(button _: UIBarButtonItem) { - resignFirstResponder() - } - - @objc func clearButtonTapped(button _: UIBarButtonItem) { - text = "" - } -} - -// MARK: extension for keyboard to dismiss - -extension UIApplication { - func endEditing() { - sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) - } -} diff --git a/FreeAPS/Sources/Views/TextFieldWithToolBar.swift b/FreeAPS/Sources/Views/TextFieldWithToolBar.swift new file mode 100644 index 000000000..1f6da9030 --- /dev/null +++ b/FreeAPS/Sources/Views/TextFieldWithToolBar.swift @@ -0,0 +1,187 @@ +import SwiftUI +import UIKit + +public struct TextFieldWithToolBar: UIViewRepresentable { + @Binding var text: Decimal + var placeholder: String + var textColor: UIColor + var textAlignment: NSTextAlignment + var keyboardType: UIKeyboardType + var autocapitalizationType: UITextAutocapitalizationType + var autocorrectionType: UITextAutocorrectionType + var shouldBecomeFirstResponder: Bool + var maxLength: Int? + var isDismissible: Bool + var textFieldDidBeginEditing: (() -> Void)? + var numberFormatter: NumberFormatter + + public init( + text: Binding, + placeholder: String, + textColor: UIColor = .label, + textAlignment: NSTextAlignment = .right, + keyboardType: UIKeyboardType = .decimalPad, + autocapitalizationType: UITextAutocapitalizationType = .none, + autocorrectionType: UITextAutocorrectionType = .no, + shouldBecomeFirstResponder: Bool = false, + maxLength: Int? = nil, + isDismissible: Bool = true, + textFieldDidBeginEditing: (() -> Void)? = nil, + numberFormatter: NumberFormatter + ) { + _text = text + self.placeholder = placeholder + self.textColor = textColor + self.textAlignment = textAlignment + self.keyboardType = keyboardType + self.autocapitalizationType = autocapitalizationType + self.autocorrectionType = autocorrectionType + self.shouldBecomeFirstResponder = shouldBecomeFirstResponder + self.maxLength = maxLength + self.isDismissible = isDismissible + self.textFieldDidBeginEditing = textFieldDidBeginEditing + self.numberFormatter = numberFormatter + self.numberFormatter.numberStyle = .decimal + } + + public func makeUIView(context: Context) -> UITextField { + let textField = UITextField() + context.coordinator.textField = textField + textField.inputAccessoryView = isDismissible ? makeDoneToolbar(for: textField, context: context) : nil + textField.addTarget(context.coordinator, action: #selector(Coordinator.editingDidBegin), for: .editingDidBegin) + textField.delegate = context.coordinator + if text == 0 { /// show no value initially, i.e. empty String + textField.text = "" + } else { + textField.text = numberFormatter.string(for: text) + } + textField.placeholder = placeholder + return textField + } + + private func makeDoneToolbar(for textField: UITextField, context: Context) -> UIToolbar { + let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 50)) + let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) + let doneButton = UIBarButtonItem( + image: UIImage(systemName: "keyboard.chevron.compact.down"), + style: .done, + target: textField, + action: #selector(UITextField.resignFirstResponder) + ) + let clearButton = UIBarButtonItem( + image: UIImage(systemName: "trash"), + style: .plain, + target: context.coordinator, + action: #selector(Coordinator.clearText) + ) + + toolbar.items = [clearButton, flexibleSpace, doneButton] + toolbar.sizeToFit() + return toolbar + } + + public func updateUIView(_ textField: UITextField, context: Context) { + if text != 0 { + let newText = numberFormatter.string(for: text) ?? "" + if textField.text != newText { + textField.text = newText + } + } + + textField.textColor = textColor + textField.textAlignment = textAlignment + textField.keyboardType = keyboardType + textField.autocapitalizationType = autocapitalizationType + textField.autocorrectionType = autocorrectionType + + if shouldBecomeFirstResponder, !context.coordinator.didBecomeFirstResponder { + if textField.window != nil, textField.becomeFirstResponder() { + context.coordinator.didBecomeFirstResponder = true + } + } else if !shouldBecomeFirstResponder, context.coordinator.didBecomeFirstResponder { + context.coordinator.didBecomeFirstResponder = false + } + } + + public func makeCoordinator() -> Coordinator { + Coordinator(self, maxLength: maxLength) + } + + public final class Coordinator: NSObject { + var parent: TextFieldWithToolBar + var textField: UITextField? + let maxLength: Int? + var didBecomeFirstResponder = false + let decimalFormatter: NumberFormatter + + init(_ parent: TextFieldWithToolBar, maxLength: Int?) { + self.parent = parent + self.maxLength = maxLength + decimalFormatter = NumberFormatter() + decimalFormatter.locale = Locale.current + decimalFormatter.numberStyle = .decimal + } + + @objc fileprivate func clearText() { + parent.text = 0 + textField?.text = "" + } + + @objc fileprivate func editingDidBegin(_ textField: UITextField) { + DispatchQueue.main.async { + textField.moveCursorToEnd() + } + } + } +} + +extension TextFieldWithToolBar.Coordinator: UITextFieldDelegate { + public func textField( + _ textField: UITextField, + shouldChangeCharactersIn range: NSRange, + replacementString string: String + ) -> Bool { + // Check if the input is a number or the decimal separator + let isNumber = CharacterSet.decimalDigits.isSuperset(of: CharacterSet(charactersIn: string)) + let isDecimalSeparator = (string == decimalFormatter.decimalSeparator && textField.text?.contains(string) == false) + + // Only proceed if the input is a valid number or decimal separator + if isNumber || isDecimalSeparator, + let currentText = textField.text as NSString? + { + // Get the proposed new text + let proposedText = currentText.replacingCharacters(in: range, with: string) + + // Try to convert proposed text to number + let number = parent.numberFormatter.number(from: proposedText) ?? decimalFormatter.number(from: proposedText) + + // Update the binding value if conversion is successful + if let number = number { + parent.text = number.decimalValue + } else { + parent.text = 0 + } + } + + // Allow the change if it's a valid number or decimal separator + return isNumber || isDecimalSeparator + } + + public func textFieldDidBeginEditing(_: UITextField) { + parent.textFieldDidBeginEditing?() + } +} + +extension UITextField { + func moveCursorToEnd() { + dispatchPrecondition(condition: .onQueue(.main)) + let newPosition = endOfDocument + selectedTextRange = textRange(from: newPosition, to: newPosition) + } +} + +extension UIApplication { + func endEditing() { + sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) + } +} From 0299def9d591a04831b6183a9133bd5609e3de86 Mon Sep 17 00:00:00 2001 From: Sjoerd Bozon Date: Fri, 28 Jun 2024 13:44:38 +0200 Subject: [PATCH 23/33] Delete .github/workflows/stale_triage_issues.yml --- .github/workflows/stale_triage_issues.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 .github/workflows/stale_triage_issues.yml diff --git a/.github/workflows/stale_triage_issues.yml b/.github/workflows/stale_triage_issues.yml deleted file mode 100644 index e69de29bb..000000000 From 7cea3306f260593f497e29b7703ac99ba37aadb9 Mon Sep 17 00:00:00 2001 From: Mike Plante Date: Sat, 29 Jun 2024 13:15:39 -0400 Subject: [PATCH 24/33] direct user where to flip Upload Glucose toggle Only display if the selected CGM disables the toggle in Nightscout > Upload. --- .../Modules/NightscoutConfig/View/NightscoutUploadView.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutUploadView.swift b/FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutUploadView.swift index 235496d98..0ed56b904 100644 --- a/FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutUploadView.swift +++ b/FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutUploadView.swift @@ -13,6 +13,10 @@ struct NightscoutUploadView: View { "The Upload Treatments toggle enables uploading of carbs, temp targets, device status, preferences and settings." ) Text("\nThe Upload Glucose toggle enables uploading of CGM readings.") + + if !state.changeUploadGlucose { + Text("\nTo flip the Upload Glucose toggle, go to ⚙️ > CGM > CGM Configuration") + } } ) { From 49bc151a36a75cd5403b2e029f7146789e80358c Mon Sep 17 00:00:00 2001 From: polscm32 aka Marvout Date: Sun, 30 Jun 2024 21:32:59 +0200 Subject: [PATCH 25/33] Refactor notes Field as well --- .../AddCarbs/View/AddCarbsRootView.swift | 29 +++-- .../Modules/Bolus/View/BolusRootView.swift | 7 +- .../DataTable/View/DataTableRootView.swift | 14 +- .../FPUConfig/View/FPUConfigRootView.swift | 6 +- .../View/ManualTempBasalRootView.swift | 7 +- .../View/NotificationsConfigRootView.swift | 6 +- .../View/PreferencesEditorRootView.swift | 6 +- .../Sources/Views/TextFieldWithToolBar.swift | 121 ++++++++++++++++++ 8 files changed, 181 insertions(+), 15 deletions(-) diff --git a/FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift b/FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift index d9c87458f..f805e4ca3 100644 --- a/FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift +++ b/FreeAPS/Sources/Modules/AddCarbs/View/AddCarbsRootView.swift @@ -42,21 +42,34 @@ extension AddCarbs { HStack { Text("Carbs").fontWeight(.semibold) Spacer() - TextFieldWithToolBar(text: $state.carbs, placeholder: "0", shouldBecomeFirstResponder: true, numberFormatter: formatter) + TextFieldWithToolBar( + text: $state.carbs, + placeholder: "0", + shouldBecomeFirstResponder: true, + numberFormatter: formatter + ) Text(state.carbs > state.maxCarbs ? "⚠️" : "g").foregroundColor(.secondary) }.padding(.vertical) if state.useFPUconversion { proteinAndFat() } - HStack { - Text("Note").foregroundColor(.secondary) - TextField("", text: $state.note).multilineTextAlignment(.trailing) - if isFocused { - Button { isFocused = false } label: { Image(systemName: "keyboard.chevron.compact.down") } - .controlSize(.mini) + VStack { + HStack { + Text("Note").foregroundColor(.secondary) + TextFieldWithToolBarString(text: $state.note, placeholder: "", maxLength: 25) + if isFocused { + Button { isFocused = false } label: { Image(systemName: "keyboard.chevron.compact.down") } + .controlSize(.mini) + } + }.focused($isFocused) + + HStack { + Spacer() + Text("\(state.note.count) / 25") + .foregroundStyle(.secondary) } - }.focused($isFocused) + } HStack { Button { state.useFPUconversion.toggle() diff --git a/FreeAPS/Sources/Modules/Bolus/View/BolusRootView.swift b/FreeAPS/Sources/Modules/Bolus/View/BolusRootView.swift index 167268599..a112b0a4b 100644 --- a/FreeAPS/Sources/Modules/Bolus/View/BolusRootView.swift +++ b/FreeAPS/Sources/Modules/Bolus/View/BolusRootView.swift @@ -65,7 +65,12 @@ extension Bolus { HStack { Text("Amount") Spacer() - TextFieldWithToolBar(text: $state.amount, placeholder: "0", shouldBecomeFirstResponder: true, numberFormatter: formatter) + TextFieldWithToolBar( + text: $state.amount, + placeholder: "0", + shouldBecomeFirstResponder: true, + numberFormatter: formatter + ) Text(state.amount > state.maxBolus ? "⚠️" : "U").foregroundColor(.secondary) } } diff --git a/FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift b/FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift index 1e737533b..4b211c44f 100644 --- a/FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift +++ b/FreeAPS/Sources/Modules/DataTable/View/DataTableRootView.swift @@ -140,7 +140,12 @@ extension DataTable { Section { HStack { Text("New Glucose") - TextFieldWithToolBar(text: $state.manualGlucose, placeholder: " ... ", shouldBecomeFirstResponder: true, numberFormatter: glucoseFormatter) + TextFieldWithToolBar( + text: $state.manualGlucose, + placeholder: " ... ", + shouldBecomeFirstResponder: true, + numberFormatter: glucoseFormatter + ) Text(state.units.rawValue).foregroundStyle(.secondary) } } @@ -247,7 +252,12 @@ extension DataTable { HStack { Text("Amount") Spacer() - TextFieldWithToolBar(text: $state.externalInsulinAmount, placeholder: "0", shouldBecomeFirstResponder: true, numberFormatter: insulinFormatter) + TextFieldWithToolBar( + text: $state.externalInsulinAmount, + placeholder: "0", + shouldBecomeFirstResponder: true, + numberFormatter: insulinFormatter + ) Text("U").foregroundColor(.secondary) } } diff --git a/FreeAPS/Sources/Modules/FPUConfig/View/FPUConfigRootView.swift b/FreeAPS/Sources/Modules/FPUConfig/View/FPUConfigRootView.swift index fd60d4f5f..9272a7440 100644 --- a/FreeAPS/Sources/Modules/FPUConfig/View/FPUConfigRootView.swift +++ b/FreeAPS/Sources/Modules/FPUConfig/View/FPUConfigRootView.swift @@ -62,7 +62,11 @@ extension FPUConfig { HStack { Text("Override With A Factor Of ") Spacer() - TextFieldWithToolBar(text: $state.individualAdjustmentFactor, placeholder: "0.5", numberFormatter: conversionFormatter) + TextFieldWithToolBar( + text: $state.individualAdjustmentFactor, + placeholder: "0.5", + numberFormatter: conversionFormatter + ) } } diff --git a/FreeAPS/Sources/Modules/ManualTempBasal/View/ManualTempBasalRootView.swift b/FreeAPS/Sources/Modules/ManualTempBasal/View/ManualTempBasalRootView.swift index 33652a183..6679e4468 100644 --- a/FreeAPS/Sources/Modules/ManualTempBasal/View/ManualTempBasalRootView.swift +++ b/FreeAPS/Sources/Modules/ManualTempBasal/View/ManualTempBasalRootView.swift @@ -19,7 +19,12 @@ extension ManualTempBasal { HStack { Text("Amount") Spacer() - TextFieldWithToolBar(text: $state.rate, placeholder: "0", shouldBecomeFirstResponder: true, numberFormatter: formatter) + TextFieldWithToolBar( + text: $state.rate, + placeholder: "0", + shouldBecomeFirstResponder: true, + numberFormatter: formatter + ) Text("U/hr").foregroundColor(.secondary) } Picker(selection: $state.durationIndex, label: Text("Duration")) { diff --git a/FreeAPS/Sources/Modules/NotificationsConfig/View/NotificationsConfigRootView.swift b/FreeAPS/Sources/Modules/NotificationsConfig/View/NotificationsConfigRootView.swift index e4d864e54..cafd8acdc 100644 --- a/FreeAPS/Sources/Modules/NotificationsConfig/View/NotificationsConfigRootView.swift +++ b/FreeAPS/Sources/Modules/NotificationsConfig/View/NotificationsConfigRootView.swift @@ -105,7 +105,11 @@ extension NotificationsConfig { HStack { Text("Carbs Required Threshold") Spacer() - TextFieldWithToolBar(text: $state.carbsRequiredThreshold, placeholder: "0", numberFormatter: carbsFormatter) + TextFieldWithToolBar( + text: $state.carbsRequiredThreshold, + placeholder: "0", + numberFormatter: carbsFormatter + ) Text("g").foregroundColor(.secondary) } } diff --git a/FreeAPS/Sources/Modules/PreferencesEditor/View/PreferencesEditorRootView.swift b/FreeAPS/Sources/Modules/PreferencesEditor/View/PreferencesEditorRootView.swift index 43375d418..b8e9d3d3c 100644 --- a/FreeAPS/Sources/Modules/PreferencesEditor/View/PreferencesEditorRootView.swift +++ b/FreeAPS/Sources/Modules/PreferencesEditor/View/PreferencesEditorRootView.swift @@ -62,7 +62,11 @@ extension PreferencesEditor { }) Text(field.displayName) } - TextFieldWithToolBar(text: self.$state.sections[sectionIndex].fields[fieldIndex].decimalValue, placeholder: "0", numberFormatter: formatter) + TextFieldWithToolBar( + text: self.$state.sections[sectionIndex].fields[fieldIndex].decimalValue, + placeholder: "0", + numberFormatter: formatter + ) case .insulinCurve: Picker( selection: $state.sections[sectionIndex].fields[fieldIndex].insulinCurveValue, diff --git a/FreeAPS/Sources/Views/TextFieldWithToolBar.swift b/FreeAPS/Sources/Views/TextFieldWithToolBar.swift index 1f6da9030..ca80c1588 100644 --- a/FreeAPS/Sources/Views/TextFieldWithToolBar.swift +++ b/FreeAPS/Sources/Views/TextFieldWithToolBar.swift @@ -185,3 +185,124 @@ extension UIApplication { sendAction(#selector(UIResponder.resignFirstResponder), to: nil, from: nil, for: nil) } } + +public struct TextFieldWithToolBarString: UIViewRepresentable { + @Binding var text: String + var placeholder: String + var textAlignment: NSTextAlignment = .right + var keyboardType: UIKeyboardType = .default + var autocapitalizationType: UITextAutocapitalizationType = .none + var autocorrectionType: UITextAutocorrectionType = .no + var shouldBecomeFirstResponder: Bool = false + var maxLength: Int? = nil + var isDismissible: Bool = true + + public func makeUIView(context: Context) -> UITextField { + let textField = UITextField() + context.coordinator.textField = textField + textField.inputAccessoryView = isDismissible ? makeDoneToolbar(for: textField, context: context) : nil + textField.addTarget(context.coordinator, action: #selector(Coordinator.editingDidBegin), for: .editingDidBegin) + textField.delegate = context.coordinator + textField.text = text + textField.placeholder = placeholder + textField.textAlignment = textAlignment + textField.keyboardType = keyboardType + textField.autocapitalizationType = autocapitalizationType + textField.autocorrectionType = autocorrectionType + textField.adjustsFontSizeToFitWidth = true + return textField + } + + private func makeDoneToolbar(for textField: UITextField, context: Context) -> UIToolbar { + let toolbar = UIToolbar(frame: CGRect(x: 0, y: 0, width: UIScreen.main.bounds.width, height: 50)) + let flexibleSpace = UIBarButtonItem(barButtonSystemItem: .flexibleSpace, target: nil, action: nil) + let doneButton = UIBarButtonItem( + image: UIImage(systemName: "keyboard.chevron.compact.down"), + style: .done, + target: textField, + action: #selector(UITextField.resignFirstResponder) + ) + let clearButton = UIBarButtonItem( + image: UIImage(systemName: "trash"), + style: .plain, + target: context.coordinator, + action: #selector(Coordinator.clearText) + ) + + toolbar.items = [clearButton, flexibleSpace, doneButton] + toolbar.sizeToFit() + return toolbar + } + + public func updateUIView(_ textField: UITextField, context: Context) { + if textField.text != text { + textField.text = text + } + + textField.textAlignment = textAlignment + textField.keyboardType = keyboardType + textField.autocapitalizationType = autocapitalizationType + textField.autocorrectionType = autocorrectionType + + if shouldBecomeFirstResponder, !context.coordinator.didBecomeFirstResponder { + if textField.window != nil, textField.becomeFirstResponder() { + context.coordinator.didBecomeFirstResponder = true + } + } else if !shouldBecomeFirstResponder, context.coordinator.didBecomeFirstResponder { + context.coordinator.didBecomeFirstResponder = false + } + } + + public func makeCoordinator() -> Coordinator { + Coordinator(self, maxLength: maxLength) + } + + public final class Coordinator: NSObject { + var parent: TextFieldWithToolBarString + var textField: UITextField? + let maxLength: Int? + var didBecomeFirstResponder = false + + init(_ parent: TextFieldWithToolBarString, maxLength: Int?) { + self.parent = parent + self.maxLength = maxLength + } + + @objc fileprivate func clearText() { + parent.text = "" + textField?.text = "" + } + + @objc fileprivate func editingDidBegin(_ textField: UITextField) { + DispatchQueue.main.async { + textField.moveCursorToEnd() + } + } + } +} + +extension TextFieldWithToolBarString.Coordinator: UITextFieldDelegate { + public func textField( + _ textField: UITextField, + shouldChangeCharactersIn range: NSRange, + replacementString string: String + ) -> Bool { + if let maxLength = parent.maxLength { + // Get the current text, including the proposed change + let currentText = textField.text ?? "" + let newLength = currentText.count + string.count - range.length + if newLength > maxLength { + return false + } + } + + DispatchQueue.main.async { + if let textFieldText = textField.text as NSString? { + let newText = textFieldText.replacingCharacters(in: range, with: string) + self.parent.text = newText + } + } + + return true + } +} From f750e68cf330797f94ecc410ab0294e2423613e0 Mon Sep 17 00:00:00 2001 From: Robert Date: Mon, 1 Jul 2024 15:59:21 +0200 Subject: [PATCH 26/33] gemfile update --- Gemfile.lock | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/Gemfile.lock b/Gemfile.lock index 4f8a439b5..1ff12d548 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -5,22 +5,22 @@ GEM base64 nkf rexml - addressable (2.8.6) - public_suffix (>= 2.0.2, < 6.0) + addressable (2.8.7) + public_suffix (>= 2.0.2, < 7.0) artifactory (3.0.17) atomos (0.1.3) aws-eventstream (1.3.0) - aws-partitions (1.921.0) - aws-sdk-core (3.193.0) + aws-partitions (1.949.0) + aws-sdk-core (3.200.0) aws-eventstream (~> 1, >= 1.3.0) aws-partitions (~> 1, >= 1.651.0) aws-sigv4 (~> 1.8) jmespath (~> 1, >= 1.6.1) - aws-sdk-kms (1.80.0) - aws-sdk-core (~> 3, >= 3.193.0) + aws-sdk-kms (1.87.0) + aws-sdk-core (~> 3, >= 3.199.0) aws-sigv4 (~> 1.1) - aws-sdk-s3 (1.148.0) - aws-sdk-core (~> 3, >= 3.193.0) + aws-sdk-s3 (1.155.0) + aws-sdk-core (~> 3, >= 3.199.0) aws-sdk-kms (~> 1) aws-sigv4 (~> 1.8) aws-sigv4 (1.8.0) @@ -69,7 +69,7 @@ GEM faraday_middleware (1.2.0) faraday (~> 1.0) fastimage (2.3.1) - fastlane (2.220.0) + fastlane (2.221.1) CFPropertyList (>= 2.3, < 4.0.0) addressable (>= 2.8, < 3.0.0) artifactory (~> 3.0) @@ -148,31 +148,32 @@ GEM os (>= 0.9, < 2.0) signet (>= 0.16, < 2.a) highline (2.0.3) - http-cookie (1.0.5) + http-cookie (1.0.6) domain_name (~> 0.5) httpclient (2.8.3) jmespath (1.6.2) json (2.7.2) - jwt (2.8.1) + jwt (2.8.2) base64 - mini_magick (4.12.0) + mini_magick (4.13.1) mini_mime (1.1.5) multi_json (1.15.0) - multipart-post (2.4.0) + multipart-post (2.4.1) nanaimo (0.3.0) naturally (2.2.1) nkf (0.2.0) optparse (0.5.0) os (1.1.4) plist (3.7.1) - public_suffix (5.0.5) + public_suffix (5.1.1) rake (13.2.1) representable (3.2.0) declarative (< 0.1.0) trailblazer-option (>= 0.1.1, < 0.2.0) uber (< 0.2.0) retriable (3.1.2) - rexml (3.2.6) + rexml (3.2.9) + strscan rouge (2.0.7) ruby2_keywords (0.0.5) rubyzip (2.3.2) @@ -185,6 +186,7 @@ GEM simctl (1.6.10) CFPropertyList naturally + strscan (3.1.0) terminal-notifier (2.0.0) terminal-table (3.0.2) unicode-display_width (>= 1.1.1, < 3) @@ -222,4 +224,4 @@ DEPENDENCIES fastlane BUNDLED WITH - 2.4.19 \ No newline at end of file + 2.4.19 From 56c80e2c2ab6d8ff1f3900c7fec6b1a25a78cf98 Mon Sep 17 00:00:00 2001 From: Sjoerd Bozon Date: Thu, 4 Jul 2024 13:30:29 +0200 Subject: [PATCH 27/33] fix(staleissues): Only run in Nightscout Repo and use of general token --- .github/workflows/stale_issues.yml | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/.github/workflows/stale_issues.yml b/.github/workflows/stale_issues.yml index b44f1718f..85c11ddc9 100644 --- a/.github/workflows/stale_issues.yml +++ b/.github/workflows/stale_issues.yml @@ -1,4 +1,4 @@ -name: close inactive issues +name: 8. DONT RUN - close inactive issues on: schedule: - cron: "30 1 * * *" @@ -8,6 +8,8 @@ jobs: runs-on: ubuntu-latest permissions: issues: write + pull-requests: write + if: github.repository_owner == 'nightscout' steps: - uses: actions/stale@v9.0.0 with: @@ -20,12 +22,15 @@ jobs: exempt-issue-labels: "needs-triage, long-term, in-progress" days-before-pr-stale: -1 days-before-pr-close: -1 - repo-token: ${{ secrets.STALE_ISSUES_PAT }} + repo-token: ${{ secrets.GITHUB_TOKEN }} + close-issues-triage: runs-on: ubuntu-latest permissions: issues: write + pull-requests: write + if: github.repository_owner == 'nightscout' steps: - uses: actions/stale@v9.0.0 with: @@ -39,5 +44,5 @@ jobs: any-of-labels: "needs-triage" days-before-pr-stale: -1 days-before-pr-close: -1 - repo-token: ${{ secrets.STALE_ISSUES_PAT }} + repo-token: ${{ secrets.GITHUB_TOKEN }} From 81280770f4fa09b2982bd421ef9a49bfb54292ca Mon Sep 17 00:00:00 2001 From: Sjoerd Bozon Date: Thu, 4 Jul 2024 13:36:50 +0200 Subject: [PATCH 28/33] fix(addtoproject): only run in nightscout repo and rename --- .github/workflows/add_to_project.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/add_to_project.yml b/.github/workflows/add_to_project.yml index 0cbd0eb73..5d72eb0ed 100644 --- a/.github/workflows/add_to_project.yml +++ b/.github/workflows/add_to_project.yml @@ -1,4 +1,4 @@ -name: Add bugs to bugs project +name: 8. DONT RUN - Add bugs to bugs project on: issues: @@ -9,6 +9,7 @@ jobs: add-to-project: name: Add issue to project runs-on: ubuntu-latest + if: github.repository_owner == 'nightscout' steps: - uses: actions/add-to-project@v1.0.2 with: @@ -18,3 +19,4 @@ jobs: github-token: ${{ secrets.ADD_TO_PROJECT_PAT }} labeled: bug, needs-triage label-operator: OR + From 7587a2e49565aef821a66c9d27a8c951e6d33fb3 Mon Sep 17 00:00:00 2001 From: 10nas <151583878+10nas@users.noreply.github.com> Date: Thu, 4 Jul 2024 12:26:51 -0700 Subject: [PATCH 29/33] fix expired live activity view background --- LiveActivity/LiveActivity.swift | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/LiveActivity/LiveActivity.swift b/LiveActivity/LiveActivity.swift index b6421e68c..1d489b18f 100644 --- a/LiveActivity/LiveActivity.swift +++ b/LiveActivity/LiveActivity.swift @@ -236,15 +236,26 @@ struct LiveActivity: Widget { .activityBackgroundTint(Color.black.opacity(0.7)) .activitySystemActionForegroundColor(Color.white) } else { - HStack(spacing: 3) { + Group { if context.state.isInitialState { - expiredLabel() + // add vertical and horizontal spacers around the label to ensure that the live activity view gets filled completely + HStack { + Spacer() + VStack { + Spacer() + expiredLabel() + Spacer() + } + Spacer() + } } else { - bgAndTrend(context: context, size: .expanded).0.font(.title) - Spacer() - VStack(alignment: .trailing, spacing: 5) { - changeLabel(context: context).font(.title3) - updatedLabel(context: context).font(.caption).foregroundStyle(.primary.opacity(0.7)) + HStack(spacing: 3) { + bgAndTrend(context: context, size: .expanded).0.font(.title) + Spacer() + VStack(alignment: .trailing, spacing: 5) { + changeLabel(context: context).font(.title3) + updatedLabel(context: context).font(.caption).foregroundStyle(.primary.opacity(0.7)) + } } } } From f99c5b94062afd0a4787671641cf6752f2a9f6bf Mon Sep 17 00:00:00 2001 From: Sjoerd Bozon Date: Thu, 4 Jul 2024 23:55:34 +0200 Subject: [PATCH 30/33] remove hyphen --- .github/workflows/add_to_project.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/add_to_project.yml b/.github/workflows/add_to_project.yml index 5d72eb0ed..b7c59b394 100644 --- a/.github/workflows/add_to_project.yml +++ b/.github/workflows/add_to_project.yml @@ -1,4 +1,4 @@ -name: 8. DONT RUN - Add bugs to bugs project +name: 8. DONT RUN Add bugs to bugs project on: issues: From 41d76cf42cb11814a6471bfb2075b112b88f4e14 Mon Sep 17 00:00:00 2001 From: Sjoerd Bozon Date: Thu, 4 Jul 2024 23:56:09 +0200 Subject: [PATCH 31/33] remove hyphen --- .github/workflows/stale_issues.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/stale_issues.yml b/.github/workflows/stale_issues.yml index 85c11ddc9..2b5c5525c 100644 --- a/.github/workflows/stale_issues.yml +++ b/.github/workflows/stale_issues.yml @@ -1,4 +1,4 @@ -name: 8. DONT RUN - close inactive issues +name: 8. DONT RUN close inactive issues on: schedule: - cron: "30 1 * * *" From c24bd7bb007cf6cc520a28506a4c1976eaa298ac Mon Sep 17 00:00:00 2001 From: polscm32 aka Marvout Date: Sat, 6 Jul 2024 11:24:19 +0200 Subject: [PATCH 32/33] fix NS Port Textfield showing a thousands place separator --- .../View/NightscoutConnectView.swift | 15 +++++++++++---- FreeAPS/Sources/Views/TextFieldWithToolBar.swift | 9 ++++++--- 2 files changed, 17 insertions(+), 7 deletions(-) diff --git a/FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutConnectView.swift b/FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutConnectView.swift index cff0365c1..9f69b8821 100644 --- a/FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutConnectView.swift +++ b/FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutConnectView.swift @@ -2,12 +2,13 @@ import SwiftUI struct NightscoutConnectView: View { @ObservedObject var state: NightscoutConfig.StateModel - @State private var portFormater: NumberFormatter + @State private var portFormatter: NumberFormatter init(state: NightscoutConfig.StateModel) { self.state = state - portFormater = NumberFormatter() - portFormater.allowsFloats = false + portFormatter = NumberFormatter() + portFormatter.allowsFloats = false + portFormatter.usesGroupingSeparator = false } var body: some View { @@ -49,7 +50,13 @@ struct NightscoutConnectView: View { Toggle("Use local glucose server", isOn: $state.useLocalSource) HStack { Text("Port") - TextFieldWithToolBar(text: $state.localPort, placeholder: "", numberFormatter: portFormater) + TextFieldWithToolBar( + text: $state.localPort, + placeholder: "", + keyboardType: .numberPad, + numberFormatter: portFormatter, + allowDecimalSeparator: false + ) } } header: { Text("Local glucose source") } } diff --git a/FreeAPS/Sources/Views/TextFieldWithToolBar.swift b/FreeAPS/Sources/Views/TextFieldWithToolBar.swift index ca80c1588..2095d8537 100644 --- a/FreeAPS/Sources/Views/TextFieldWithToolBar.swift +++ b/FreeAPS/Sources/Views/TextFieldWithToolBar.swift @@ -14,6 +14,7 @@ public struct TextFieldWithToolBar: UIViewRepresentable { var isDismissible: Bool var textFieldDidBeginEditing: (() -> Void)? var numberFormatter: NumberFormatter + var allowDecimalSeparator: Bool public init( text: Binding, @@ -27,7 +28,8 @@ public struct TextFieldWithToolBar: UIViewRepresentable { maxLength: Int? = nil, isDismissible: Bool = true, textFieldDidBeginEditing: (() -> Void)? = nil, - numberFormatter: NumberFormatter + numberFormatter: NumberFormatter, + allowDecimalSeparator: Bool = true ) { _text = text self.placeholder = placeholder @@ -42,6 +44,7 @@ public struct TextFieldWithToolBar: UIViewRepresentable { self.textFieldDidBeginEditing = textFieldDidBeginEditing self.numberFormatter = numberFormatter self.numberFormatter.numberStyle = .decimal + self.allowDecimalSeparator = allowDecimalSeparator } public func makeUIView(context: Context) -> UITextField { @@ -146,7 +149,7 @@ extension TextFieldWithToolBar.Coordinator: UITextFieldDelegate { let isDecimalSeparator = (string == decimalFormatter.decimalSeparator && textField.text?.contains(string) == false) // Only proceed if the input is a valid number or decimal separator - if isNumber || isDecimalSeparator, + if isNumber || isDecimalSeparator && parent.allowDecimalSeparator, let currentText = textField.text as NSString? { // Get the proposed new text @@ -164,7 +167,7 @@ extension TextFieldWithToolBar.Coordinator: UITextFieldDelegate { } // Allow the change if it's a valid number or decimal separator - return isNumber || isDecimalSeparator + return isNumber || isDecimalSeparator && parent.allowDecimalSeparator } public func textFieldDidBeginEditing(_: UITextField) { From 9672da256c317a314acc76d6e4f6e82cc174d133 Mon Sep 17 00:00:00 2001 From: Deniz Cengiz <48965855+dnzxy@users.noreply.github.com> Date: Tue, 16 Jul 2024 19:32:02 +0200 Subject: [PATCH 33/33] Add Trio-Specific App Group for Isolated Data Storage (#314) * Add app group `group.$(BUNDLE_IDENTIIFER).trio-app-group` to Trio * Update testflight.md regarding Trio app group Co-Authored-By: Marion Barker * Fix wrong CGMBLEKit SHA checked out for submodule * Update with latest dev; update submodules; resolve merge conflict leftover * Update testflight.md * Address review feedback: fix small typos, add bullet point * Address review feedback: Remove sections; add beta tester hint for app group --------- Co-authored-by: Marion Barker --- Config.xcconfig | 1 - FreeAPS.xcodeproj/project.pbxproj | 2 + fastlane/testflight.md | 95 +++++++++++++++++-------------- 3 files changed, 53 insertions(+), 45 deletions(-) diff --git a/Config.xcconfig b/Config.xcconfig index 7bcdc3dde..b7384d183 100644 --- a/Config.xcconfig +++ b/Config.xcconfig @@ -4,7 +4,6 @@ APP_BUILD_NUMBER = 1 COPYRIGHT_NOTICE = DEVELOPER_TEAM = ##TEAM_ID## BUNDLE_IDENTIFIER = org.nightscout.$(DEVELOPMENT_TEAM).trio -APP_GROUP_ID = group.com.$(DEVELOPMENT_TEAM).loopkit.LoopGroup APP_ICON = trioBlack APP_URL_SCHEME = Trio diff --git a/FreeAPS.xcodeproj/project.pbxproj b/FreeAPS.xcodeproj/project.pbxproj index e5723f794..4792ba35e 100644 --- a/FreeAPS.xcodeproj/project.pbxproj +++ b/FreeAPS.xcodeproj/project.pbxproj @@ -3026,6 +3026,7 @@ baseConfigurationReference = 38F3783A2613555C009DB701 /* Config.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + APP_GROUP_ID = "group.$(BUNDLE_IDENTIFIER).trio-app-group"; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; @@ -3091,6 +3092,7 @@ baseConfigurationReference = 38F3783A2613555C009DB701 /* Config.xcconfig */; buildSettings = { ALWAYS_SEARCH_USER_PATHS = NO; + APP_GROUP_ID = "group.$(BUNDLE_IDENTIFIER).trio-app-group"; CLANG_ANALYZER_LOCALIZABILITY_NONLOCALIZED = YES; CLANG_ANALYZER_NONNULL = YES; CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; diff --git a/fastlane/testflight.md b/fastlane/testflight.md index 59fabf080..e0b99c973 100644 --- a/fastlane/testflight.md +++ b/fastlane/testflight.md @@ -8,15 +8,16 @@ These instructions allow you to build Trio without having access to a Mac. * You do not need to worry about specific Xcode/Mac versions for a given iOS ## **Automatic Builds** -> +> > The browser build defaults to automatically updating and building a new version of Trio according to this schedule: -> - automatically checks for updates weekly on Wednesdays and if updates are found, it will build a new version of the app -> - automatically builds once a month regardless of whether there are updates on the first of the month -> - with each scheduled run (weekly or monthly), a successful Build Trio log appears - if the time is very short, it did not need to build - only the long actions (>10 minutes) built a new Trio app -> +> +> * automatically checks for updates weekly on Wednesdays and if updates are found, it will build a new version of the app +> * automatically builds once a month regardless of whether there are updates on the first of the month +> * with each scheduled run (weekly or monthly), a successful Build Trio log appears - if the time is very short, it did not need to build - only the long actions (>10 minutes) built a new Trio app +> > It also creates an alive branch, if you don't already have one. See [Why do I have an alive branch?](#why-do-i-have-an-alive-branch). > -> The [**Optional**](#optional) section provides instructions to modify the default behavior if desired. > +> The [**Optional**](#optional) section provides instructions to modify the default behavior if desired. ## Introduction @@ -24,7 +25,7 @@ The setup steps are somewhat involved, but nearly all are one time steps. Subseq Note that installing with TestFlight requires the Apple ID account holder for the phone be 13 years or older (age varies with country). This can be circumvented by logging into Media & Purchase on the child's phone with an adult's account. More details on this can be found in [LoopDocs](https://loopkit.github.io/loopdocs/gh-actions/gh-deploy/#install-testflight-loop-for-child). -This method for building without a Mac was ported from Loop. If you have used this method for Loop or one of the other DIY apps (Loop Caregiver, Loop Follow, Xdrip4iOS), some of the steps can be re-used and the full set of instructions does not need to be repeated. This will be mentioned in relevant sections below. +This method for building without a Mac was ported from Loop. If you have used this method for Loop or one of the other DIY apps (Loop Caregiver, Loop Follow, xDrip4iOS), some of the steps can be re-used and the full set of instructions does not need to be repeated. This will be mentioned in relevant sections below. There are more detailed instructions in LoopDocs for doing Browser Builds of Loop and other apps, including troubleshooting and build errors. Please refer to [LoopDocs](https://loopkit.github.io/loopdocs/gh-actions/gh-other-apps/) for more details. @@ -34,17 +35,17 @@ If you build multiple apps, you may want to use a free *GitHub* organization. Pl * A [github account](https://github.com/signup). The free level comes with plenty of storage and free compute time to build Trio, multiple times a day, if you wanted to. * A paid [Apple Developer account](https://developer.apple.com). -* Some time. Set aside a couple of hours to perform the setup. +* Some time. Set aside a couple of hours to perform the setup. * Use the same GitHub account for all "Browser Builds" of the various DIY apps. ## Save 6 Secrets -You require 6 Secrets (alphanumeric items) to use the GitHub build method and if you use the GitHub method to build other apps, e.g., Loop Follow or Xdrip4iOS, you will use the same 6 Secrets for each app you build with this method. Each secret is indentified below by `ALL_CAPITAL_LETTER_NAMES`. +You require 6 Secrets (alphanumeric items) to use the GitHub build method and if you use the GitHub method to build other apps, e.g., Loop Follow or xDrip4iOS, you will use the same 6 Secrets for each app you build with this method. Each secret is indentified below by `ALL_CAPITAL_LETTER_NAMES`. * Four Secrets are from your Apple Account * Two Secrets are from your GitHub account * Be sure to save the 6 Secrets in a text file using a text editor - - Do **NOT** use a smart editor, which might auto-correct and change case, because these Secrets are case sensitive + * Do **NOT** use a smart editor, which might auto-correct and change case, because these Secrets are case sensitive Refer to [LoopDocs: Make a Secrets Reference File](https://loopkit.github.io/loopdocs/gh-actions/gh-first-time/#make-a-secrets-reference-file) for a handy template to use when saving your Secrets. @@ -68,7 +69,7 @@ Log into your GitHub account to create a personal access token; this is one of t 1. Create a [new personal access token](https://github.com/settings/tokens/new): * Enter a name for your token, use "FastLane Access Token". * Change the Expiration selection to `No expiration`. - * Select the `workflow` permission scope - this also selects `repo` scope. + * Select the `workflow` permission scope \* this also selects `repo` scope. * Click "Generate token". * Copy the token and record it. It will be used below as `GH_PAT`. @@ -83,6 +84,7 @@ The first time you build with the GitHub Browser Build method for any DIY app, y > A private Match-Secrets repository is automatically created under your GitHub username the first time you run a GitHub Action. Because it is a private repository - only you can see it. You will not take any direct actions with this repository; it needs to be there for GitHub to use as you progress through the steps. ## Setup Github Trio repository + 1. Fork https://github.com/nightscout/Trio into your account. If you already have a fork of Trio in GitHub, you can't make another one. You can continue to work with your existing fork, or delete that from GitHub and then fork https://github.com/nightscout/Trio. 1. In the forked Trio repo, go to Settings -> Secrets and variables -> Actions. 1. For each of the following secrets, tap on "New repository secret", then add the name of the secret, along with the value you recorded for it: @@ -116,25 +118,25 @@ This step validates most of your six Secrets and provides error messages if it d If you previously built Trio using Mac with Xcode with this Apple ID, skip ahead to [Optional: App Group Description Modification](#optional-app-group-description-modification). -_Please note that Trio uses the same app group as Loop. This enables other apps such as Xdrip4iOS to share data with Trio. It may require some caution if transfering between Trio and Loop._ +_Please note that Trio uses a Trio-specific app group, not the same as Loop. This enables other apps such as xDrip4iOS to share data with Trio. It may require some caution if transfering between Trio and Loop._ 1. Go to [Register an App Group](https://developer.apple.com/account/resources/identifiers/applicationGroup/add/) on the apple developer site. -1. For Description, use "Loop App Group". -1. For Identifier, enter "group.com.TEAMID.loopkit.LoopGroup", substituting your team id for `TEAMID`. +1. For Description, use "Trio App Group". +1. For Identifier, enter `group.org.nightscout.TEAMID.trio.trio-app-group`, substituting your team id for `TEAMID`. * If you are told that this group already exists, skip ahead to [Optional: App Group Description Modification](#optional-app-group-description-modification) 1. Click "Continue" and then "Register". ### Optional: App Group Description Modification -> This step is not required, but if you previously built using a Mac with Xcode, it is a good idea to update the **NAME** associated with the **IDENTIFIER** for the Loop App Group. Notice in the table below that the XCode version of the **NAME** is the same as the **IDENTIFIER** but with the `.` replaced with a space. +> This step is not required, but if you previously built using a Mac with Xcode, it is a good idea to update the **NAME** associated with the **IDENTIFIER** for the `Trio App Group`. Notice in the table below that the Xcode version of the **NAME** is the same as the **IDENTIFIER** but with the `.` replaced with a space. -_Referring to the link and table below, tap on the **IDENTIFIER** for the `Loop App Group`, edit the Description to match the **NAME**, then Save the change._ +_Referring to the link and table below, tap on the **IDENTIFIER** for the `Trio App Group`, edit the Description to match the **NAME**, then Save the change._ * [App Group List](https://developer.apple.com/account/resources/identifiers/list/applicationGroup) -| NAME | XCode version | IDENTIFIER | +| NAME | Xcode version | IDENTIFIER | |:--|:--|:--| -| Loop App Group | group com TEAMID loopkit LoopGroup| group.com.TEAMID.loopkit.LoopGroup | +| Trio App Group | group org nightscout TEAMID trio trio-app-group | group.org.nightscout.TEAMID.trio.trio-app-group | ## Bundle Identifiers @@ -149,12 +151,13 @@ Open this link in a separate browser window: _Referring to the table below, tap on each **IDENTIFIER** that has a different **NAME**, edit the Description to match the **NAME**, then Save the change for that identifier._ -#### Table of Identifiers +### Table of Identifiers -* If you built previously using a Mac with Xcode, you may see the XCode version in your **NAME** column - it starts with XC and then the **IDENTIFIER** is appended where the `.` is replaced with a space, the example for Trio is shown in detail +* If you built previously using a Mac with Xcode, you may see the Xcode version in your **NAME** column - it starts with XC and then the **IDENTIFIER** is appended where the `.` is replaced with a space, the example for Trio is shown in detail * If you built during early beta testing, you might not have `Trio` at the beginning of each **IDENTIFIER** and the full **NAME** may be slightly different +* If you built during early beta testing, you might have the Loop App Group associated with the Trio identifiers. If so, use instructions to [Create App Group](#create-app-group) for Trio. Subsequently, modify the App Group associated with the Trio Identifiers using [Add App Group to Bundle Identifiers](#add-app-group-to-bundle-identifiers). -| NAME | XCode version | IDENTIFIER | +| NAME | Xcode version | IDENTIFIER | |:--|:--|:--| | Trio | XC org nightscout TEAMID trio | org.nightscout.TEAMID.trio | | Trio LiveActivity | - | org.nightscout.TEAMID.trio.LiveActivity | @@ -167,6 +170,8 @@ _Referring to the table below, tap on each **IDENTIFIER** that has a different * > If you previously built using a Mac with Xcode you can skip ahead to [Create Trio App in App Store Connect](#create-trio-app-in-app-store-connect). +> If you have previously built Trio as a beta tester (between May 13th, 2024, and today), you will already have an app group (`Loop App Group`) created and configured for your bundle identifiers. In this case, please *do not* skip this section; you are required to create the `Trio App Group` and configure it for your identifiers, as described below. + 1. Go to [Certificates, Identifiers & Profiles](https://developer.apple.com/account/resources/identifiers/list) on the Apple developer site. 1. Repeat this step for these three Identifier **NAMES** - refer to the [Table](#table-of-identifiers) above if your Names look different; if they do, see [Optional: Identifier Description Modification](#optional-identifier-description-modification) * Trio @@ -174,13 +179,14 @@ _Referring to the table below, tap on each **IDENTIFIER** that has a different * * Trio WatchKit Extension 1. Click on the **IDENTIFIER** row. 1. Scroll down to the "App Groups" capabilies row, click on the "Configure" (or "Edit") button. -1. Select (or verify the selection for) the "Loop App Group" _(yes, "Loop App Group" is correct)_ +1. Select the "Trio App Group" _(yes, "Trio App Group" is correct)_ 1. Click "Continue". 1. Click "Save". 1. Click "Confirm". 1. Remember to do this for each of three identifiers listed under step 2. There is an additional identifier, but it does not need the App Group added to it: + * Trio LiveActivity ## Create Trio App in App Store Connect @@ -192,7 +198,7 @@ If you created a Trio app in App Store Connect before, skip ahead to [Create Bui * Select a name: this will have to be unique, so you may have to try a few different names here, but it will not be the name you see on your phone, so it's not that important. * Select your primary language. * Choose the bundle ID that matches the `BUNDLE_IDENTIFIER` in your `Config.xcconfig` file - * this is typically `org.nightscout.TEAMID.trio` with `TEAMID` matching your team id + * This is typically `org.nightscout.TEAMID.trio` with `TEAMID` matching your team id * SKU can be anything; e.g. "123". * Select "Full Access". 1. Click Create @@ -257,7 +263,7 @@ If you choose not to have automatic building enabled, be sure the `GH_PAT` has ` You can modify the automation by creating and using some variables. -To configure the automated build more granularly involves creating up to two environment variables: `SCHEDULED_BUILD` and/or `SCHEDULED_SYNC`. See [How to configure a variable](#how-to-configure-a-variable). +To configure the automated build more granularly involves creating up to two environment variables: `SCHEDULED_BUILD` and/or `SCHEDULED_SYNC`. See [How to configure a variable](#how-to-configure-a-variable). Note that the weekly and monthly Build Trio actions will continue, but the actions are modified if one or more of these variables is set to false. **A successful Action Log will still appear, even if no automatic activity happens**. @@ -267,10 +273,10 @@ Note that the weekly and monthly Build Trio actions will continue, but the actio |`SCHEDULED_SYNC`|`SCHEDULED_BUILD`|Automatic Actions| |---|---|---| -| `true` (or NA) | `true` (or NA) | keep-alive, weekly update check (auto update/build), monthly build with auto update| -| `true` (or NA) | `false` | keep-alive, weekly update check with auto update, only builds if update detected| +| `true` (or NA) | `true` (or NA) | keep-alive, weekly update check (auto update/build), monthly build with auto update | +| `true` (or NA) | `false` | keep-alive, weekly update check with auto update, only builds if update detected | | `false` | `true` (or NA) | keep-alive, monthly build, no auto update | -| `false` | `false` | no automatic activity, no keep-alive| +| `false` | `false` | no automatic activity, no keep-alive | ### How to configure a variable @@ -279,21 +285,22 @@ Note that the weekly and monthly Build Trio actions will continue, but the actio 3. Click on `Actions` 4. You will now see a page titled *Actions secrets and variables*. Click on the `Variables` tab 5. To disable ONLY scheduled building, do the following: - - Click on the green `New repository variable` button (upper right) - - Type `SCHEDULED_BUILD` in the "Name" field - - Type `false` in the "Value" field - - Click the green `Add variable` button to save. -7. To disable scheduled syncing, add a variable: - - Click on the green `New repository variable` button (upper right) - - - Type `SCHEDULED_SYNC` in the "Name" field - - Type `false` in the "Value" field - - Click the green `Add variable` button to save - + * Click on the green `New repository variable` button (upper right) + * Type `SCHEDULED_BUILD` in the "Name" field + * Type `false` in the "Value" field + * Click the green `Add variable` button to save. +6. To disable scheduled syncing, add a variable: + * Click on the green `New repository variable` button (upper right) + * Type `SCHEDULED_SYNC` in the "Name" field + * Type `false` in the "Value" field + * Click the green `Add variable` button to save + Your build will run on the following conditions: -- Default behaviour: - - Run weekly, every Wednesday at 08:00 UTC to check for changes; if there are changes, it will update your repository and build - - Run monthly, every first of the month at 06:00 UTC, if there are changes, it will update your repository; regardless of changes, it will build - - Each time the action runs, it makes a keep-alive commit to the `alive` branch if necessary -- If you disable any automation (both variables set to `false`), no updates, keep-alive or building happens when Build Trio runs -- If you disabled just scheduled synchronization (`SCHEDULED_SYNC` set to`false`), it will only run once a month, on the first of the month, no update will happen; keep-alive will run -- If you disabled just scheduled build (`SCHEDULED_BUILD` set to`false`), it will run once weekly, every Wednesday, to check for changes; if there are changes, it will update and build; keep-alive will run \ No newline at end of file + +* Default behaviour: + * Run weekly, every Wednesday at 08:00 UTC to check for changes; if there are changes, it will update your repository and build + * Run monthly, every first of the month at 06:00 UTC, if there are changes, it will update your repository; regardless of changes, it will build + * Each time the action runs, it makes a keep-alive commit to the `alive` branch if necessary +* If you disable any automation (both variables set to `false`), no updates, keep-alive or building happens when Build Trio runs +* If you disabled just scheduled synchronization (`SCHEDULED_SYNC` set to`false`), it will only run once a month, on the first of the month, no update will happen; keep-alive will run +* If you disabled just scheduled build (`SCHEDULED_BUILD` set to`false`), it will run once weekly, every Wednesday, to check for changes; if there are changes, it will update and build; keep-alive will run