Просмотр исходного кода

Add slider button for cannula insertion and pod deactivation from Loop (#458)

Joe Moran 2 лет назад
Родитель
Сommit
729f5985d4

+ 17 - 0
Dependencies/OmniBLE/OmniBLE.xcodeproj/project.pbxproj

@@ -181,6 +181,7 @@
 		D895BF5B275DE64000D51FC7 /* StringLengthPrefixEncoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = D895BF5A275DE64000D51FC7 /* StringLengthPrefixEncoding.swift */; };
 		D897B06B29347ED500FDB009 /* BolusDeliveryTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = D897B06A29347ED500FDB009 /* BolusDeliveryTable.swift */; };
 		D897B06D29347EE500FDB009 /* InsulinTableEntry.swift in Sources */ = {isa = PBXBuildFile; fileRef = D897B06C29347EE500FDB009 /* InsulinTableEntry.swift */; };
+		D8DA5CCE2B49EDE900C54E6C /* SlideButton in Frameworks */ = {isa = PBXBuildFile; productRef = D8DA5CCD2B49EDE900C54E6C /* SlideButton */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -449,6 +450,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				D8DA5CCE2B49EDE900C54E6C /* SlideButton in Frameworks */,
 				847530F626ED65DD009FD801 /* LoopKit.framework in Frameworks */,
 				8475306E26ED15DE009FD801 /* CryptoSwift in Frameworks */,
 				847530F826ED65DD009FD801 /* LoopKitUI.framework in Frameworks */,
@@ -911,6 +913,7 @@
 			name = OmniBLE;
 			packageProductDependencies = (
 				8475306D26ED15DE009FD801 /* CryptoSwift */,
+				D8DA5CCD2B49EDE900C54E6C /* SlideButton */,
 			);
 			productName = OmniBLE;
 			productReference = 84752E8226ED0FFE009FD801 /* OmniBLE.framework */;
@@ -1013,6 +1016,7 @@
 			mainGroup = 84752E7826ED0FFE009FD801;
 			packageReferences = (
 				8475306C26ED15DE009FD801 /* XCRemoteSwiftPackageReference "CryptoSwift" */,
+				D8DA5CCC2B49EDE900C54E6C /* XCRemoteSwiftPackageReference "SlideButton" */,
 			);
 			productRefGroup = 84752E8326ED0FFE009FD801 /* Products */;
 			projectDirPath = "";
@@ -1679,6 +1683,14 @@
 				minimumVersion = 1.4.1;
 			};
 		};
+		D8DA5CCC2B49EDE900C54E6C /* XCRemoteSwiftPackageReference "SlideButton" */ = {
+			isa = XCRemoteSwiftPackageReference;
+			repositoryURL = "https://github.com/no-comment/SlideButton";
+			requirement = {
+				branch = main;
+				kind = branch;
+			};
+		};
 /* End XCRemoteSwiftPackageReference section */
 
 /* Begin XCSwiftPackageProductDependency section */
@@ -1687,6 +1699,11 @@
 			package = 8475306C26ED15DE009FD801 /* XCRemoteSwiftPackageReference "CryptoSwift" */;
 			productName = CryptoSwift;
 		};
+		D8DA5CCD2B49EDE900C54E6C /* SlideButton */ = {
+			isa = XCSwiftPackageProductDependency;
+			package = D8DA5CCC2B49EDE900C54E6C /* XCRemoteSwiftPackageReference "SlideButton" */;
+			productName = SlideButton;
+		};
 /* End XCSwiftPackageProductDependency section */
 	};
 	rootObject = 84752E7926ED0FFE009FD801 /* Project object */;

+ 10 - 1
Dependencies/OmniBLE/OmniBLE/PumpManagerUI/ViewModels/DeactivatePodViewModel.swift

@@ -41,7 +41,7 @@ class DeactivatePodViewModel: ObservableObject, Identifiable {
         var actionButtonDescription: String {
             switch self {
             case .active:
-                return LocalizedString("Deactivate Pod", comment: "Action button description for deactivate while pod still active")
+                return LocalizedString("Slide to Deactivate Pod", comment: "Action button description for deactivate while pod still active")
             case .resultError:
                 return LocalizedString("Retry", comment: "Action button description for deactivate after failed attempt")
             case .deactivating:
@@ -101,6 +101,15 @@ class DeactivatePodViewModel: ObservableObject, Identifiable {
     
     @Published var state: DeactivatePodViewModelState = .active
 
+    public var stateNeedsDeliberateUserAcceptance : Bool {
+        switch state {
+        case .active:
+            true
+        default:
+            false
+        }
+    }
+
     var error: DeactivationError? {
         if case .resultError(let error) = self.state {
             return error

+ 11 - 2
Dependencies/OmniBLE/OmniBLE/PumpManagerUI/ViewModels/InsertCannulaViewModel.swift

@@ -29,7 +29,7 @@ class InsertCannulaViewModel: ObservableObject, Identifiable {
         var actionButtonAccessibilityLabel: String {
             switch self {
             case .ready, .startingInsertion:
-                return LocalizedString("Insert Cannula", comment: "Insert cannula action button accessibility label while ready to pair")
+                return LocalizedString("Slide Button to insert Cannula", comment: "Insert cannula slider button accessibility label while ready to pair")
             case .inserting:
                 return LocalizedString("Inserting. Please wait.", comment: "Insert cannula action button accessibility label while pairing")
             case .checkingInsertion:
@@ -53,7 +53,7 @@ class InsertCannulaViewModel: ObservableObject, Identifiable {
         var nextActionButtonDescription: String {
             switch self {
             case .ready:
-                return LocalizedString("Insert Cannula", comment: "Cannula insertion button text while ready to insert")
+                return LocalizedString("Slide to Insert Cannula", comment: "Cannula insertion button text while ready to insert")
             case .error:
                 return LocalizedString("Retry", comment: "Cannula insertion button text while showing error")
             case .inserting, .startingInsertion:
@@ -124,6 +124,15 @@ class InsertCannulaViewModel: ObservableObject, Identifiable {
     }
 
     @Published var state: InsertCannulaViewModelState = .ready
+
+    public var stateNeedsDeliberateUserAcceptance : Bool {
+        switch state {
+        case .ready:
+            true
+        default:
+            false
+        }
+    }
     
     var didFinish: (() -> Void)?
     

+ 28 - 9
Dependencies/OmniBLE/OmniBLE/PumpManagerUI/Views/DeactivatePodView.swift

@@ -8,6 +8,7 @@
 
 import SwiftUI
 import LoopKitUI
+import SlideButton
 
 struct DeactivatePodView: View {
     
@@ -65,15 +66,8 @@ struct DeactivatePodView: View {
                     }
                     .disabled(viewModel.state.isProcessing)
                 }
-                Button(action: {
-                    viewModel.continueButtonTapped()
-                }) {
-                    Text(viewModel.state.actionButtonDescription)
-                        .accessibility(identifier: "button_next_action")
-                        .accessibility(label: Text(viewModel.state.actionButtonAccessibilityLabel))
-                        .actionButtonStyle(viewModel.state.actionButtonStyle)
-                }
-                .disabled(viewModel.state.isProcessing)
+                actionButton
+                    .disabled(viewModel.state.isProcessing)
             }
             .padding()
         }
@@ -94,4 +88,29 @@ struct DeactivatePodView: View {
             secondaryButton: .default(FrameworkLocalText("Continue", comment: "Title of button to continue discard"), action: { viewModel.discardPod() })
         )
     }
+
+    var actionText: some View {
+        Text(self.viewModel.state.actionButtonDescription)
+            .accessibility(identifier: "button_next_action")
+            .accessibility(label: Text(self.viewModel.state.actionButtonAccessibilityLabel))
+            .font(.headline)
+    }
+
+    @ViewBuilder
+    var actionButton: some View {
+        if self.viewModel.stateNeedsDeliberateUserAcceptance {
+            SlideButton(styling: .init(indicatorSize: 60, indicatorColor: Color.red), action: {
+                self.viewModel.continueButtonTapped()
+            }) {
+                actionText
+            }
+        } else {
+            Button(action: {
+                self.viewModel.continueButtonTapped()
+            }) {
+                actionText
+                    .actionButtonStyle(.primary)
+            }
+        }
+    }
 }

+ 48 - 12
Dependencies/OmniBLE/OmniBLE/PumpManagerUI/Views/InsertCannulaView.swift

@@ -8,6 +8,7 @@
 
 import SwiftUI
 import LoopKitUI
+import SlideButton
 
 struct InsertCannulaView: View {
     
@@ -24,7 +25,7 @@ struct InsertCannulaView: View {
 
                 HStack {
                     InstructionList(instructions: [
-                        LocalizedString("Tap below to start cannula insertion.", comment: "Label text for step one of insert cannula instructions"),
+                        LocalizedString("Slide the switch below to start cannula insertion.", comment: "Label text for step one of insert cannula instructions"),
                         LocalizedString("Wait until insertion is completed.", comment: "Label text for step two of insert cannula instructions"),
                     ])
                     .disabled(viewModel.state.instructionsDisabled)
@@ -65,29 +66,45 @@ struct InsertCannulaView: View {
                 }
                 
                 if (self.viewModel.error == nil || self.viewModel.error?.recoverable == true) {
-                    Button(action: {
-                        self.viewModel.continueButtonTapped()
-                    }) {
-                        Text(self.viewModel.state.nextActionButtonDescription)
-                            .accessibility(identifier: "button_next_action")
-                            .accessibility(label: Text(self.viewModel.state.actionButtonAccessibilityLabel))
-                            .actionButtonStyle(.primary)
-                    }
+                    actionButton
                     .disabled(self.viewModel.state.isProcessing)
-                    .animation(nil)
                     .zIndex(1)
                 }
             }
             .transition(AnyTransition.opacity.combined(with: .move(edge: .bottom)))
             .padding()
         }
-        .animation(.default)
         .alert(isPresented: $cancelModalIsPresented) { cancelPairingModal }
         .navigationBarTitle(LocalizedString("Insert Cannula", comment: "navigation bar title for insert cannula"), displayMode: .automatic)
         .navigationBarBackButtonHidden(true)
         .navigationBarItems(trailing: cancelButton)
     }
-    
+
+    var actionText : some View {
+        Text(self.viewModel.state.nextActionButtonDescription)
+            .accessibility(identifier: "button_next_action")
+            .accessibility(label: Text(self.viewModel.state.actionButtonAccessibilityLabel))
+            .font(.headline)
+    }
+
+    @ViewBuilder
+    var actionButton: some View {
+        if self.viewModel.stateNeedsDeliberateUserAcceptance {
+            SlideButton(action: {
+                self.viewModel.continueButtonTapped()
+            }) {
+                actionText
+            }
+        } else {
+            Button(action: {
+                self.viewModel.continueButtonTapped()
+            }) {
+                actionText
+                    .actionButtonStyle(.primary)
+            }
+        }
+    }
+
     var cancelButton: some View {
         Button(LocalizedString("Cancel", comment: "Cancel button text in navigation bar on insert cannula screen")) {
             cancelModalIsPresented = true
@@ -103,5 +120,24 @@ struct InsertCannulaView: View {
             secondaryButton: .default(FrameworkLocalText("No, Continue With Pod", comment: "Continue pairing button title of in pairing cancel modal"))
         )
     }
+}
+
+class MockCannulaInserter: CannulaInserter {
+    public func insertCannula(completion: @escaping (Result<TimeInterval,OmniBLEPumpManagerError>) -> Void) {
+        let mockDelay = TimeInterval(seconds: 3)
+        let result :Result<TimeInterval, OmniBLEPumpManagerError> = .success(mockDelay)
+        completion(result)
+    }
 
+    func checkCannulaInsertionFinished(completion: @escaping (OmniBLEPumpManagerError?) -> Void) {
+        completion(nil)
+    }
+}
+
+struct InsertCannulaView_Previews: PreviewProvider {
+    static var mockInserter = MockCannulaInserter()
+    static var model = InsertCannulaViewModel(cannulaInserter: mockInserter)
+    static var previews: some View {
+        InsertCannulaView(viewModel: model)
+    }
 }

+ 27 - 0
Dependencies/OmniKit/OmniKit.xcodeproj/project.pbxproj

@@ -166,6 +166,7 @@
 		D845A1522AF8A51000EA0853 /* SilencePodSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D845A1512AF8A51000EA0853 /* SilencePodSelectionView.swift */; };
 		D85AEAC82B1403C000081044 /* PodDiagnostics.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85AEAC72B1403C000081044 /* PodDiagnostics.swift */; };
 		D85AEACA2B1403CB00081044 /* ReadPodInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85AEAC92B1403CB00081044 /* ReadPodInfoView.swift */; };
+		D8DA5CCB2B49EDD400C54E6C /* SlideButton in Frameworks */ = {isa = PBXBuildFile; productRef = D8DA5CCA2B49EDD400C54E6C /* SlideButton */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -444,6 +445,7 @@
 			isa = PBXFrameworksBuildPhase;
 			buildActionMask = 2147483647;
 			files = (
+				D8DA5CCB2B49EDD400C54E6C /* SlideButton in Frameworks */,
 				CEF2639B29D88516009921F1 /* OmniKit.framework in Frameworks */,
 				CEC751E329D88392006E9D24 /* LoopKitUI.framework in Frameworks */,
 			);
@@ -889,6 +891,9 @@
 			dependencies = (
 			);
 			name = OmniKitUI;
+			packageProductDependencies = (
+				D8DA5CCA2B49EDD400C54E6C /* SlideButton */,
+			);
 			productName = OmniKitUI;
 			productReference = C124020B29C7D92700B32844 /* OmniKitUI.framework */;
 			productType = "com.apple.product-type.framework";
@@ -993,6 +998,9 @@
 				hi,
 			);
 			mainGroup = C124016229C7D87A00B32844;
+			packageReferences = (
+				D8DA5CC92B49EDD400C54E6C /* XCRemoteSwiftPackageReference "SlideButton" */,
+			);
 			productRefGroup = C124016D29C7D87A00B32844 /* Products */;
 			projectDirPath = "";
 			projectRoot = "";
@@ -1722,6 +1730,25 @@
 			defaultConfigurationName = Release;
 		};
 /* End XCConfigurationList section */
+
+/* Begin XCRemoteSwiftPackageReference section */
+		D8DA5CC92B49EDD400C54E6C /* XCRemoteSwiftPackageReference "SlideButton" */ = {
+			isa = XCRemoteSwiftPackageReference;
+			repositoryURL = "https://github.com/no-comment/SlideButton";
+			requirement = {
+				branch = main;
+				kind = branch;
+			};
+		};
+/* End XCRemoteSwiftPackageReference section */
+
+/* Begin XCSwiftPackageProductDependency section */
+		D8DA5CCA2B49EDD400C54E6C /* SlideButton */ = {
+			isa = XCSwiftPackageProductDependency;
+			package = D8DA5CC92B49EDD400C54E6C /* XCRemoteSwiftPackageReference "SlideButton" */;
+			productName = SlideButton;
+		};
+/* End XCSwiftPackageProductDependency section */
 	};
 	rootObject = C124016329C7D87A00B32844 /* Project object */;
 }

+ 10 - 1
Dependencies/OmniKit/OmniKitUI/ViewModels/DeactivatePodViewModel.swift

@@ -42,7 +42,7 @@ class DeactivatePodViewModel: ObservableObject, Identifiable {
         var actionButtonDescription: String {
             switch self {
             case .active:
-                return LocalizedString("Deactivate Pod", comment: "Action button description for deactivate while pod still active")
+                return LocalizedString("Slide to Deactivate Pod", comment: "Action button description for deactivate while pod still active")
             case .resultError:
                 return LocalizedString("Retry", comment: "Action button description for deactivate after failed attempt")
             case .deactivating:
@@ -102,6 +102,15 @@ class DeactivatePodViewModel: ObservableObject, Identifiable {
     
     @Published var state: DeactivatePodViewModelState = .active
 
+    public var stateNeedsDeliberateUserAcceptance : Bool {
+        switch state {
+        case .active:
+            true
+        default:
+            false
+        }
+    }
+
     var error: DeactivationError? {
         if case .resultError(let error) = self.state {
             return error

+ 10 - 2
Dependencies/OmniKit/OmniKitUI/ViewModels/InsertCannulaViewModel.swift

@@ -30,7 +30,7 @@ class InsertCannulaViewModel: ObservableObject, Identifiable {
         var actionButtonAccessibilityLabel: String {
             switch self {
             case .ready, .startingInsertion:
-                return LocalizedString("Insert Cannula", comment: "Insert cannula action button accessibility label while ready to pair")
+                return LocalizedString("Slide Button to insert Cannula", comment: "Insert cannula slider button accessibility label while ready to pair")
             case .inserting:
                 return LocalizedString("Inserting. Please wait.", comment: "Insert cannula action button accessibility label while pairing")
             case .checkingInsertion:
@@ -54,7 +54,7 @@ class InsertCannulaViewModel: ObservableObject, Identifiable {
         var nextActionButtonDescription: String {
             switch self {
             case .ready:
-                return LocalizedString("Insert Cannula", comment: "Cannula insertion button text while ready to insert")
+                return LocalizedString("Slide to Insert Cannula", comment: "Cannula insertion button text while ready to insert")
             case .error:
                 return LocalizedString("Retry", comment: "Cannula insertion button text while showing error")
             case .inserting, .startingInsertion:
@@ -125,6 +125,14 @@ class InsertCannulaViewModel: ObservableObject, Identifiable {
     }
 
     @Published var state: InsertCannulaViewModelState = .ready
+    public var stateNeedsDeliberateUserAcceptance : Bool {
+        switch state {
+        case .ready:
+            true
+        default:
+            false
+        }
+    }
     
     var didFinish: (() -> Void)?
     

+ 28 - 9
Dependencies/OmniKit/OmniKitUI/Views/DeactivatePodView.swift

@@ -8,6 +8,7 @@
 
 import SwiftUI
 import LoopKitUI
+import SlideButton
 
 struct DeactivatePodView: View {
     
@@ -65,15 +66,8 @@ struct DeactivatePodView: View {
                     }
                     .disabled(viewModel.state.isProcessing)
                 }
-                Button(action: {
-                    viewModel.continueButtonTapped()
-                }) {
-                    Text(viewModel.state.actionButtonDescription)
-                        .accessibility(identifier: "button_next_action")
-                        .accessibility(label: Text(viewModel.state.actionButtonAccessibilityLabel))
-                        .actionButtonStyle(viewModel.state.actionButtonStyle)
-                }
-                .disabled(viewModel.state.isProcessing)
+                actionButton
+                    .disabled(viewModel.state.isProcessing)
             }
             .padding()
         }
@@ -94,4 +88,29 @@ struct DeactivatePodView: View {
             secondaryButton: .default(FrameworkLocalText("Continue", comment: "Title of button to continue discard"), action: { viewModel.discardPod() })
         )
     }
+
+    var actionText: some View {
+        Text(self.viewModel.state.actionButtonDescription)
+            .accessibility(identifier: "button_next_action")
+            .accessibility(label: Text(self.viewModel.state.actionButtonAccessibilityLabel))
+            .font(.headline)
+    }
+
+    @ViewBuilder
+    var actionButton: some View {
+        if self.viewModel.stateNeedsDeliberateUserAcceptance {
+            SlideButton(styling: .init(indicatorSize: 60, indicatorColor: Color.red), action: {
+                self.viewModel.continueButtonTapped()
+            }) {
+                actionText
+            }
+        } else {
+            Button(action: {
+                self.viewModel.continueButtonTapped()
+            }) {
+                actionText
+                    .actionButtonStyle(.primary)
+            }
+        }
+    }
 }

+ 49 - 12
Dependencies/OmniKit/OmniKitUI/Views/InsertCannulaView.swift

@@ -8,6 +8,8 @@
 
 import SwiftUI
 import LoopKitUI
+import SlideButton
+import OmniKit
 
 struct InsertCannulaView: View {
     
@@ -24,7 +26,7 @@ struct InsertCannulaView: View {
 
                 HStack {
                     InstructionList(instructions: [
-                        LocalizedString("Tap below to start cannula insertion.", comment: "Label text for step one of insert cannula instructions"),
+                        LocalizedString("Slide the switch below to start cannula insertion.", comment: "Label text for step one of insert cannula instructions"),
                         LocalizedString("Wait until insertion is completed.", comment: "Label text for step two of insert cannula instructions"),
                     ])
                     .disabled(viewModel.state.instructionsDisabled)
@@ -65,29 +67,45 @@ struct InsertCannulaView: View {
                 }
                 
                 if (self.viewModel.error == nil || self.viewModel.error?.recoverable == true) {
-                    Button(action: {
-                        self.viewModel.continueButtonTapped()
-                    }) {
-                        Text(self.viewModel.state.nextActionButtonDescription)
-                            .accessibility(identifier: "button_next_action")
-                            .accessibility(label: Text(self.viewModel.state.actionButtonAccessibilityLabel))
-                            .actionButtonStyle(.primary)
-                    }
+                    actionButton
                     .disabled(self.viewModel.state.isProcessing)
-                    .animation(nil)
                     .zIndex(1)
                 }
             }
             .transition(AnyTransition.opacity.combined(with: .move(edge: .bottom)))
             .padding()
         }
-        .animation(.default)
         .alert(isPresented: $cancelModalIsPresented) { cancelPairingModal }
         .navigationBarTitle(LocalizedString("Insert Cannula", comment: "navigation bar title for insert cannula"), displayMode: .automatic)
         .navigationBarBackButtonHidden(true)
         .navigationBarItems(trailing: cancelButton)
     }
-    
+
+    var actionText : some View {
+        Text(self.viewModel.state.nextActionButtonDescription)
+            .accessibility(identifier: "button_next_action")
+            .accessibility(label: Text(self.viewModel.state.actionButtonAccessibilityLabel))
+            .font(.headline)
+    }
+
+    @ViewBuilder
+    var actionButton: some View {
+        if self.viewModel.stateNeedsDeliberateUserAcceptance {
+            SlideButton(action: {
+                self.viewModel.continueButtonTapped()
+            }) {
+                actionText
+            }
+        } else {
+            Button(action: {
+                self.viewModel.continueButtonTapped()
+            }) {
+                actionText
+                    .actionButtonStyle(.primary)
+            }
+        }
+    }
+
     var cancelButton: some View {
         Button(LocalizedString("Cancel", comment: "Cancel button text in navigation bar on insert cannula screen")) {
             cancelModalIsPresented = true
@@ -103,5 +121,24 @@ struct InsertCannulaView: View {
             secondaryButton: .default(FrameworkLocalText("No, Continue With Pod", comment: "Continue pairing button title of in pairing cancel modal"))
         )
     }
+}
+
+class MockCannulaInserter: CannulaInserter {
+    public func insertCannula(completion: @escaping (Result<TimeInterval,OmnipodPumpManagerError>) -> Void) {
+        let mockDelay = TimeInterval(seconds: 3)
+        let result :Result<TimeInterval, OmnipodPumpManagerError> = .success(mockDelay)
+        completion(result)
+    }
 
+    func checkCannulaInsertionFinished(completion: @escaping (OmnipodPumpManagerError?) -> Void) {
+        completion(nil)
+    }
+}
+
+struct InsertCannulaView_Previews: PreviewProvider {
+    static var mockInserter = MockCannulaInserter()
+    static var model = InsertCannulaViewModel(cannulaInserter: mockInserter)
+    static var previews: some View {
+        InsertCannulaView(viewModel: model)
+    }
 }

+ 17 - 0
FreeAPS.xcodeproj/project.pbxproj

@@ -393,6 +393,7 @@
 		D6D02515BBFBE64FEBE89856 /* DataTableRootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 881E04BA5E0A003DE8E0A9C6 /* DataTableRootView.swift */; };
 		D6DEC113821A7F1056C4AA1E /* NightscoutConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F2A13DF0EDEEEDC4106AA2A /* NightscoutConfigDataFlow.swift */; };
 		D76333C9256787610B3B4875 /* AutotuneConfigStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = D295A3F870E826BE371C0BB5 /* AutotuneConfigStateModel.swift */; };
+		D8DA5CD12B49EEF000C54E6C /* SlideButton in Frameworks */ = {isa = PBXBuildFile; productRef = D8DA5CD02B49EEF000C54E6C /* SlideButton */; };
 		DBA5254DBB2586C98F61220C /* ISFEditorProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9F9F137F126D9F8DEB799F26 /* ISFEditorProvider.swift */; };
 		DD399FB31EACB9343C944C4C /* PreferencesEditorStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0CA3E609094E064C99A4752C /* PreferencesEditorStateModel.swift */; };
 		E00EEC0327368630002FF094 /* ServiceAssembly.swift in Sources */ = {isa = PBXBuildFile; fileRef = E00EEBFD27368630002FF094 /* ServiceAssembly.swift */; };
@@ -982,6 +983,7 @@
 			buildActionMask = 2147483647;
 			files = (
 				CEC751D829D88262006E9D24 /* MinimedKitUI.framework in Frameworks */,
+				D8DA5CD12B49EEF000C54E6C /* SlideButton in Frameworks */,
 				CEC751D629D88262006E9D24 /* MinimedKit.framework in Frameworks */,
 				CEC751D429D88257006E9D24 /* OmniKitUI.framework in Frameworks */,
 				CEC751D229D88257006E9D24 /* OmniKit.framework in Frameworks */,
@@ -2459,6 +2461,7 @@
 				3818AA46274C255A00843DB3 /* LibreTransmitter */,
 				38DF1788276FC8C400B3528F /* SwiftMessages */,
 				CEB434FC28B90B7C00B70274 /* SwiftCharts */,
+				D8DA5CD02B49EEF000C54E6C /* SlideButton */,
 			);
 			productName = FreeAPS;
 			productReference = 388E595825AD948C0019842D /* FreeAPS.app */;
@@ -2594,6 +2597,7 @@
 				3833B46B26012030003021B3 /* XCRemoteSwiftPackageReference "swift-algorithms" */,
 				38DF1787276FC8C300B3528F /* XCRemoteSwiftPackageReference "SwiftMessages" */,
 				CEB434FB28B90B7C00B70274 /* XCRemoteSwiftPackageReference "SwiftCharts" */,
+				D8DA5CCF2B49EEF000C54E6C /* XCRemoteSwiftPackageReference "SlideButton" */,
 			);
 			productRefGroup = 388E595925AD948C0019842D /* Products */;
 			projectDirPath = "";
@@ -3728,6 +3732,14 @@
 				kind = branch;
 			};
 		};
+		D8DA5CCF2B49EEF000C54E6C /* XCRemoteSwiftPackageReference "SlideButton" */ = {
+			isa = XCRemoteSwiftPackageReference;
+			repositoryURL = "https://github.com/no-comment/SlideButton";
+			requirement = {
+				branch = main;
+				kind = branch;
+			};
+		};
 /* End XCRemoteSwiftPackageReference section */
 
 /* Begin XCSwiftPackageProductDependency section */
@@ -3765,6 +3777,11 @@
 			package = CEB434FB28B90B7C00B70274 /* XCRemoteSwiftPackageReference "SwiftCharts" */;
 			productName = SwiftCharts;
 		};
+		D8DA5CD02B49EEF000C54E6C /* SlideButton */ = {
+			isa = XCSwiftPackageProductDependency;
+			package = D8DA5CCF2B49EEF000C54E6C /* XCRemoteSwiftPackageReference "SlideButton" */;
+			productName = SlideButton;
+		};
 /* End XCSwiftPackageProductDependency section */
 
 /* Begin XCVersionGroup section */

+ 9 - 0
FreeAPS.xcworkspace/xcshareddata/swiftpm/Package.resolved

@@ -11,6 +11,15 @@
         }
       },
       {
+        "package": "SlideButton",
+        "repositoryURL": "https://github.com/no-comment/SlideButton",
+        "state": {
+          "branch": "main",
+          "revision": "5eacebba4d7deeb693592bc9a62ab2d2181e133b",
+          "version": null
+        }
+      },
+      {
         "package": "swift-algorithms",
         "repositoryURL": "https://github.com/apple/swift-algorithms",
         "state": {