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

Change widget dragndrop to buttons - not compiling WIP

Deniz Cengiz 1 год назад
Родитель
Сommit
de4f7d557e

+ 4 - 4
FreeAPS.xcodeproj/project.pbxproj

@@ -321,7 +321,7 @@
 		BD2FF1A02AE29D43005D1C5D /* CheckboxToggleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD2FF19F2AE29D43005D1C5D /* CheckboxToggleStyle.swift */; };
 		BD3CC0722B0B89D50013189E /* MainChartView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD3CC0712B0B89D50013189E /* MainChartView.swift */; };
 		BD4064D12C4ED26900582F43 /* CoreDataObserver.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD4064D02C4ED26900582F43 /* CoreDataObserver.swift */; };
-		BD6EB2D62C7D049B0086BBB6 /* LiveActivityBottomRowConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD6EB2D52C7D049B0086BBB6 /* LiveActivityBottomRowConfiguration.swift */; };
+		BD6EB2D62C7D049B0086BBB6 /* LiveActivityWidgetConfiguration.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD6EB2D52C7D049B0086BBB6 /* LiveActivityWidgetConfiguration.swift */; };
 		BD7DA9A52AE06DFC00601B20 /* BolusCalculatorConfigDataFlow.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD7DA9A42AE06DFC00601B20 /* BolusCalculatorConfigDataFlow.swift */; };
 		BD7DA9A72AE06E2B00601B20 /* BolusCalculatorConfigProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD7DA9A62AE06E2B00601B20 /* BolusCalculatorConfigProvider.swift */; };
 		BD7DA9A92AE06E9200601B20 /* BolusCalculatorStateModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = BD7DA9A82AE06E9200601B20 /* BolusCalculatorStateModel.swift */; };
@@ -985,7 +985,7 @@
 		BD2FF19F2AE29D43005D1C5D /* CheckboxToggleStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CheckboxToggleStyle.swift; sourceTree = "<group>"; };
 		BD3CC0712B0B89D50013189E /* MainChartView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MainChartView.swift; sourceTree = "<group>"; };
 		BD4064D02C4ED26900582F43 /* CoreDataObserver.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreDataObserver.swift; sourceTree = "<group>"; };
-		BD6EB2D52C7D049B0086BBB6 /* LiveActivityBottomRowConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityBottomRowConfiguration.swift; sourceTree = "<group>"; };
+		BD6EB2D52C7D049B0086BBB6 /* LiveActivityWidgetConfiguration.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveActivityWidgetConfiguration.swift; sourceTree = "<group>"; };
 		BD7DA9A42AE06DFC00601B20 /* BolusCalculatorConfigDataFlow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusCalculatorConfigDataFlow.swift; sourceTree = "<group>"; };
 		BD7DA9A62AE06E2B00601B20 /* BolusCalculatorConfigProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusCalculatorConfigProvider.swift; sourceTree = "<group>"; };
 		BD7DA9A82AE06E9200601B20 /* BolusCalculatorStateModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BolusCalculatorStateModel.swift; sourceTree = "<group>"; };
@@ -2772,7 +2772,7 @@
 			isa = PBXGroup;
 			children = (
 				DDF847E32C5C288F0049BB3B /* LiveActivitySettingsRootView.swift */,
-				BD6EB2D52C7D049B0086BBB6 /* LiveActivityBottomRowConfiguration.swift */,
+				BD6EB2D52C7D049B0086BBB6 /* LiveActivityWidgetConfiguration.swift */,
 			);
 			path = View;
 			sourceTree = "<group>";
@@ -3496,7 +3496,7 @@
 				BD7DA9A72AE06E2B00601B20 /* BolusCalculatorConfigProvider.swift in Sources */,
 				38192E0D261BAF980094D973 /* ConvenienceExtensions.swift in Sources */,
 				88AB39B23C9552BD6E0C9461 /* ISFEditorRootView.swift in Sources */,
-				BD6EB2D62C7D049B0086BBB6 /* LiveActivityBottomRowConfiguration.swift in Sources */,
+				BD6EB2D62C7D049B0086BBB6 /* LiveActivityWidgetConfiguration.swift in Sources */,
 				F816825E28DB441200054060 /* HeartBeatManager.swift in Sources */,
 				58F107742BD1A4D000B1A680 /* Determination+helper.swift in Sources */,
 				38FEF413273B317A00574A46 /* HKUnit.swift in Sources */,

+ 0 - 469
FreeAPS/Sources/Modules/LiveActivitySettings/View/LiveActivityBottomRowConfiguration.swift

@@ -1,469 +0,0 @@
-import Charts
-import Foundation
-import SwiftUI
-import Swinject
-import UniformTypeIdentifiers
-
-struct LiveActivityBottomRowConfiguration: BaseView {
-    let resolver: Resolver
-
-    @ObservedObject var state: LiveActivitySettings.StateModel
-
-    @State private var selectedItems: [LiveActivityItem] = []
-    @State private var showAddItemDialog: Bool = false
-    @State private var isEditMode: Bool = false
-    @State private var draggingItem: LiveActivityItem?
-    @State private var showDeleteAlert: Bool = false
-
-    @Environment(\.colorScheme) var colorScheme
-
-    private var color: LinearGradient {
-        colorScheme == .dark ? LinearGradient(
-            gradient: Gradient(colors: [
-                Color.bgDarkBlue,
-                Color.bgDarkerDarkBlue
-            ]),
-            startPoint: .top,
-            endPoint: .bottom
-        )
-            :
-            LinearGradient(
-                gradient: Gradient(colors: [Color.gray.opacity(0.1)]),
-                startPoint: .top,
-                endPoint: .bottom
-            )
-    }
-
-    // Dummy data for glucose levels
-    private var glucoseData: [DummyChart] {
-        var data = [DummyChart]()
-        let totalMinutes = 6 * 60 // 6 hours in minutes
-        let interval = 5 // 5 minutes interval for each data point
-
-        for minute in stride(from: 0, to: totalMinutes, by: interval) {
-            let time = Double(minute) / 60.0 // Convert minutes to hours
-            let glucoseLevel = 100 + 20 * sin(time) // Oscillating sine wave pattern
-            data.append(DummyChart(time: Double(minute), glucoseLevel: glucoseLevel))
-        }
-        return data
-    }
-
-    var body: some View {
-        VStack {
-            Group {
-                VStack(alignment: .leading, spacing: 0) {
-                    Text("Live Activity Personalization".uppercased())
-                        .frame(maxWidth: .infinity, alignment: .leading)
-                        .foregroundColor(.secondary)
-                        .font(.footnote)
-                        .padding(.leading)
-                }
-                VStack {
-                    Text(
-                        "Trio offers you to customize your Live Activity lock screen widget. The default configuration will display current glucose, IOB, COB and the time of last algorithm run."
-                    )
-                    .padding()
-                    .font(.footnote)
-                    .foregroundColor(.secondary)
-                }
-                .background(
-                    RoundedRectangle(cornerRadius: 10, style: .continuous)
-                        .fill(Color.chart)
-                )
-            }
-
-            GroupBox {
-                VStack {
-                    dummyChart
-
-                    HStack {
-                        if selectedItems.isEmpty {
-                            Spacer()
-                            Button(action: {
-                                showAddItemDialog.toggle()
-                            }) {
-                                VStack {
-                                    Image(systemName: "plus")
-                                        .font(.title2)
-                                        .foregroundColor(.gray)
-                                }
-                                .frame(width: 60, height: 40)
-                                .overlay(
-                                    RoundedRectangle(cornerRadius: 12)
-                                        .stroke(style: StrokeStyle(lineWidth: 2, dash: [5]))
-                                        .foregroundColor(.gray)
-                                )
-                            }
-                            Spacer()
-                        } else {
-                            ForEach(Array(selectedItems.enumerated()), id: \.element) { index, item in
-                                if index > 0 {
-                                    Divider()
-                                        .frame(height: 50)
-                                }
-
-                                ZStack(alignment: .topTrailing) {
-                                    getItemPreview(for: item)
-                                        .frame(width: 50, height: 50)
-                                        .padding(5)
-                                        .background(
-                                            draggingItem == item ? Color.blue.opacity(0.2) : Color.clear
-                                        )
-                                        .cornerRadius(12)
-                                        .overlay(
-                                            RoundedRectangle(cornerRadius: 12)
-                                                .stroke(
-                                                    draggingItem == item ? Color.blue : Color.primary,
-                                                    lineWidth: draggingItem == item ? 2 : 1
-                                                )
-                                        )
-                                        .opacity(draggingItem == item ? 0.85 : 1.0)
-                                        .onDrag {
-                                            self.draggingItem = item
-                                            return NSItemProvider(object: item.rawValue as NSString)
-                                        }
-                                        .onDrop(
-                                            of: [UTType.text],
-                                            delegate: DropViewDelegate(
-                                                item: item,
-                                                items: $selectedItems,
-                                                draggingItem: $draggingItem
-                                            )
-                                        )
-                                        .disabled(!isEditMode)
-                                        .rotationEffect(.degrees(isEditMode ? 2.5 : 0))
-                                        .rotation3DEffect(.degrees(isEditMode ? 2.5 : 0), axis: (x: 0, y: -5, z: 0))
-                                        .animation(
-                                            isEditMode ? Animation.easeInOut(duration: 0.15)
-                                                .repeatForever(autoreverses: true) : .default,
-                                            value: isEditMode
-                                        )
-                                    if isEditMode {
-                                        Button(action: {
-                                            showDeleteAlert = true
-                                        }) {
-                                            Image(systemName: "minus.circle.fill")
-                                                .foregroundColor(Color(UIColor.systemGray2)) // Opaque foreground color
-                                                .background(Color.white) // Adding a background for contrast
-                                                .clipShape(Circle()) // Make sure the background stays circular
-                                                .font(.system(size: 20))
-                                        }
-                                        .offset(x: -45, y: -10)
-                                        .alert(isPresented: $showDeleteAlert) {
-                                            Alert(
-                                                title: Text("Delete Widget"),
-                                                message: Text("Are you sure you want to delete this widget?"),
-                                                primaryButton: .destructive(Text("Delete")) {
-                                                    removeItem(item)
-                                                },
-                                                secondaryButton: .cancel()
-                                            )
-                                        }
-                                    }
-                                }
-                                .animation(.easeInOut, value: draggingItem)
-                                .frame(maxWidth: .infinity)
-                            }
-                        }
-                    }
-                    .padding()
-                    .overlay(
-                        RoundedRectangle(cornerRadius: 12)
-                            .stroke(style: StrokeStyle(lineWidth: 2, dash: [5]))
-                            .foregroundColor(.gray)
-                    )
-                    .cornerRadius(12)
-                }
-            }.padding(.vertical).groupBoxStyle(.dummyChart)
-
-            if isEditMode {
-                HStack {
-                    Image(systemName: "hand.draw.fill")
-                    Text("Tap 'Add +' to add a widget. Press, drag and drop a widget to re-order a widget.")
-                }.frame(maxWidth: .infinity, alignment: .leading)
-                    .foregroundColor(.secondary)
-                    .font(.footnote)
-                    .padding(.horizontal)
-            }
-
-            Spacer()
-        }
-        .padding()
-        .scrollContentBackground(.hidden).background(color)
-        .navigationTitle("Widget Configuration")
-        .navigationBarTitleDisplayMode(.automatic)
-        .toolbar {
-            ToolbarItem(placement: .topBarTrailing) {
-                Button {
-                    isEditMode.toggle()
-                } label: {
-                    HStack {
-                        Text("Edit")
-                    }
-                }
-            }
-            ToolbarItem(placement: .topBarTrailing) {
-                Button {
-                    showAddItemDialog.toggle()
-                } label: {
-                    HStack {
-                        Text("Add")
-                        Image(systemName: "plus")
-                    }
-                }
-            }
-        }
-        .confirmationDialog("Add Widget", isPresented: $showAddItemDialog, titleVisibility: .visible) {
-            ForEach(LiveActivityItem.allCases, id: \.self) { item in
-                Button(item.displayName) {
-                    addItem(item)
-                }.disabled(selectedItems.contains(item))
-            }
-        }
-        .onAppear {
-            configureView()
-            loadOrder()
-        }
-    }
-
-    private func getItemPreview(for item: LiveActivityItem) -> some View {
-        switch item {
-        case .currentGlucose:
-            return AnyView(currentGlucosePreview)
-        case .cob:
-            return AnyView(cobPreview)
-        case .iob:
-            return AnyView(iobPreview)
-        case .updatedLabel:
-            return AnyView(updatedLabelPreview)
-        }
-    }
-
-    private var dummyChart: some View {
-        Chart {
-            ForEach(glucoseData) { data in
-                PointMark(
-                    x: .value("Time", data.time),
-                    y: .value("Glucose Level", data.glucoseLevel)
-                ).foregroundStyle(.green.gradient).symbolSize(15)
-            }
-        }
-        .chartPlotStyle { plotContent in
-            plotContent
-                .background(
-                    RoundedRectangle(cornerRadius: 12)
-                        .fill(Color.cyan.opacity(0.15))
-                )
-                .clipShape(RoundedRectangle(cornerRadius: 12))
-        }
-        .chartYAxis {
-            AxisMarks(position: .trailing) { _ in
-                AxisGridLine(stroke: .init(lineWidth: 0.2, dash: [2, 3])).foregroundStyle(Color.primary)
-            }
-        }
-        .chartYAxis(.hidden)
-        .chartYScale(domain: 40 ... 200)
-        .chartXAxis {
-            AxisMarks(position: .automatic) { _ in
-                AxisGridLine(stroke: .init(lineWidth: 0.2, dash: [2, 3])).foregroundStyle(Color.primary)
-            }
-        }
-        .chartXAxis(.hidden)
-        .frame(height: 100)
-    }
-
-    private var currentGlucosePreview: some View {
-        VStack {
-            HStack(alignment: .center) {
-                Text("123")
-                    .fontWeight(.bold)
-                    .font(.caption)
-            }
-            HStack(spacing: -5) {
-                HStack {
-                    Text("\u{2192}")
-                    Text("+6")
-                }.foregroundStyle(.primary).font(.caption2)
-            }
-        }
-    }
-
-    private var cobPreview: some View {
-        VStack(spacing: 2) {
-            Text("25 g").fontWeight(.bold).font(.caption)
-            Text("COB").font(.caption2).foregroundStyle(.primary)
-        }
-    }
-
-    private var iobPreview: some View {
-        VStack(spacing: 2) {
-            Text("2 U").fontWeight(.bold).font(.caption)
-            Text("IOB").font(.caption2).foregroundStyle(.primary)
-        }
-    }
-
-    private var updatedLabelPreview: some View {
-        VStack {
-            Text("19:05")
-                .fontWeight(.bold)
-                .font(.caption)
-                .foregroundStyle(.primary)
-
-            Text("Updated").font(.caption2).foregroundStyle(.primary)
-        }
-    }
-
-    private func loadOrder() {
-        if let savedItems = UserDefaults.standard.loadLiveActivityOrder() {
-            selectedItems = savedItems
-        } else {
-            selectedItems = LiveActivityItem.defaultItems
-            saveOrder()
-        }
-        print("Loaded order: \(selectedItems.map(\.rawValue))")
-        updateVisibilityForSelectedItems()
-    }
-
-    private func saveOrder() {
-        print("Saving order: \(selectedItems.map(\.rawValue))")
-        UserDefaults.standard.saveLiveActivityOrder(selectedItems)
-    }
-
-    private func addItem(_ item: LiveActivityItem) {
-        setItemVisibility(item: item, isVisible: true)
-        selectedItems.append(item)
-        saveOrder()
-    }
-
-    private func removeItem(_ item: LiveActivityItem) {
-        setItemVisibility(item: item, isVisible: false)
-        selectedItems.removeAll { $0 == item }
-        saveOrder()
-    }
-
-    private func setItemVisibility(item: LiveActivityItem, isVisible: Bool) {
-        switch item {
-        case .currentGlucose:
-            state.showCurrentGlucose = isVisible
-        case .iob:
-            state.showIOB = isVisible
-        case .cob:
-            state.showCOB = isVisible
-        case .updatedLabel:
-            state.showUpdatedLabel = isVisible
-        }
-    }
-
-    private func updateVisibilityForSelectedItems() {
-        for item in selectedItems {
-            setItemVisibility(item: item, isVisible: true)
-        }
-        let allItems = LiveActivityItem.allCases
-        let hiddenItems = allItems.filter { !selectedItems.contains($0) }
-        for item in hiddenItems {
-            setItemVisibility(item: item, isVisible: false)
-        }
-    }
-}
-
-struct DropViewDelegate: DropDelegate {
-    let item: LiveActivityItem
-    @Binding var items: [LiveActivityItem]
-    @Binding var draggingItem: LiveActivityItem?
-
-    func dropEntered(info _: DropInfo) {
-        guard let draggingItem = draggingItem else { return }
-
-        if draggingItem != item {
-            let fromIndex = items.firstIndex(of: draggingItem)!
-            let toIndex = items.firstIndex(of: item)!
-
-            withAnimation {
-                items.move(fromOffsets: IndexSet(integer: fromIndex), toOffset: toIndex > fromIndex ? toIndex + 1 : toIndex)
-            }
-
-            // Save to User Defaults
-            saveOrder()
-
-            // Trigger Live Activity Update
-            Foundation.NotificationCenter.default.post(name: .liveActivityOrderDidChange, object: nil)
-        }
-    }
-
-    func performDrop(info _: DropInfo) -> Bool {
-        draggingItem = nil
-        return true
-    }
-
-    private func saveOrder() {
-        UserDefaults.standard.saveLiveActivityOrder(items)
-    }
-}
-
-// Extension for UserDefaults to save and load the order
-extension UserDefaults {
-    private enum Keys {
-        static let liveActivityOrder = "liveActivityOrder"
-    }
-
-    func saveLiveActivityOrder(_ items: [LiveActivityItem]) {
-        let itemStrings = items.map(\.rawValue)
-        set(itemStrings, forKey: Keys.liveActivityOrder)
-    }
-
-    func loadLiveActivityOrder() -> [LiveActivityItem]? {
-        if let itemStrings = array(forKey: Keys.liveActivityOrder) as? [String] {
-            return itemStrings.compactMap { LiveActivityItem(rawValue: $0) }
-        }
-        return nil
-    }
-}
-
-// Enum to represent each live activity item
-enum LiveActivityItem: String, CaseIterable, Identifiable {
-    case currentGlucose
-    case iob
-    case cob
-    case updatedLabel
-
-    var id: String { rawValue }
-
-    static var defaultItems: [LiveActivityItem] {
-        [.currentGlucose, .iob, .cob, .updatedLabel]
-    }
-
-    var displayName: String {
-        switch self {
-        case .currentGlucose:
-            return "Current Glucose"
-        case .iob:
-            return "IOB"
-        case .cob:
-            return "COB"
-        case .updatedLabel:
-            return "Updated Label"
-        }
-    }
-}
-
-struct DummyChart: Identifiable {
-    let id = UUID()
-    let time: Double // Time in hours
-    let glucoseLevel: Double // Glucose level in mg/dL
-}
-
-struct DummyChartGroupBoxStyle: GroupBoxStyle {
-    func makeBody(configuration: Configuration) -> some View {
-        VStack {
-            configuration.content
-        }
-        .padding()
-        .clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous))
-        .background(Color.chart, in: RoundedRectangle(cornerRadius: 12))
-        .frame(width: UIScreen.main.bounds.width * 0.9)
-    }
-}
-
-extension GroupBoxStyle where Self == DummyChartGroupBoxStyle {
-    static var dummyChart: DummyChartGroupBoxStyle { .init() }
-}

+ 1 - 1
FreeAPS/Sources/Modules/LiveActivitySettings/View/LiveActivitySettingsRootView.swift

@@ -122,7 +122,7 @@ extension LiveActivitySettings {
                                 content: {
                                     NavigationLink(
                                         "Widget Configuration",
-                                        destination: LiveActivityBottomRowConfiguration(resolver: resolver, state: state)
+                                        destination: LiveActivityWidgetConfiguration(resolver: resolver, state: state)
                                     )
                                 }
                             ).listRowBackground(Color.chart)

+ 526 - 0
FreeAPS/Sources/Modules/LiveActivitySettings/View/LiveActivityWidgetConfiguration.swift

@@ -0,0 +1,526 @@
+import Charts
+import Foundation
+import SwiftUI
+import Swinject
+import UniformTypeIdentifiers
+
+struct LiveActivityWidgetConfiguration: BaseView {
+    let resolver: Resolver
+
+    @ObservedObject var state: LiveActivitySettings.StateModel
+
+    @State private var selectedItems: [LiveActivityItem] = []
+    @State private var showAddItemDialog: Bool = false
+    @State private var buttonIndexToUpdate: Int?
+
+    @State private var isEditMode: Bool = false
+    @State private var draggingItem: LiveActivityItem?
+    @State private var showDeleteAlert: Bool = false
+
+    @Environment(\.colorScheme) var colorScheme
+
+    private var color: LinearGradient {
+        colorScheme == .dark ? LinearGradient(
+            gradient: Gradient(colors: [
+                Color.bgDarkBlue,
+                Color.bgDarkerDarkBlue
+            ]),
+            startPoint: .top,
+            endPoint: .bottom
+        )
+            :
+            LinearGradient(
+                gradient: Gradient(colors: [Color.gray.opacity(0.1)]),
+                startPoint: .top,
+                endPoint: .bottom
+            )
+    }
+
+    // Dummy data for glucose levels
+    private var glucoseData: [DummyChart] {
+        var data = [DummyChart]()
+        let totalMinutes = 6 * 60 // 6 hours in minutes
+        let interval = 5 // 5 minutes interval for each data point
+
+        for minute in stride(from: 0, to: totalMinutes, by: interval) {
+            let time = Double(minute) / 60.0 // Convert minutes to hours
+            let glucoseLevel = 100 + 20 * sin(time) // Oscillating sine wave pattern
+            data.append(DummyChart(time: Double(minute), glucoseLevel: glucoseLevel))
+        }
+        return data
+    }
+    
+//    var body: some View {
+//            VStack {
+//                Group {
+//                    VStack(alignment: .leading, spacing: 0) {
+//                        Text("Live Activity Personalization".uppercased())
+//                            .frame(maxWidth: .infinity, alignment: .leading)
+//                            .foregroundColor(.secondary)
+//                            .font(.footnote)
+//                            .padding(.leading)
+//                    }
+//                    VStack {
+//                        Text(
+//                            "Trio offers you to customize your Live Activity lock screen widget. The default configuration will display current glucose, IOB, COB and the time of last algorithm run."
+//                        )
+//                        .padding()
+//                        .font(.footnote)
+//                        .foregroundColor(.secondary)
+//                    }
+//                    .background(
+//                        RoundedRectangle(cornerRadius: 10, style: .continuous)
+//                            .fill(Color.chart)
+//                    )
+//                }
+//
+//                GroupBox {
+//                    VStack {
+//                        dummyChart
+//
+//                        HStack {
+//                            if selectedItems.isEmpty {
+//                                Spacer()
+//                                Button(action: {
+//                                    showAddItemDialog.toggle()
+//                                }) {
+//                                    VStack {
+//                                        Image(systemName: "plus")
+//                                            .font(.title2)
+//                                            .foregroundColor(.gray)
+//                                    }
+//                                    .frame(width: 60, height: 40)
+//                                    .overlay(
+//                                        RoundedRectangle(cornerRadius: 12)
+//                                            .stroke(style: StrokeStyle(lineWidth: 2, dash: [5]))
+//                                            .foregroundColor(.gray)
+//                                    )
+//                                }
+//                                Spacer()
+//                            } else {
+//                                ForEach(Array(selectedItems.enumerated()), id: \.element) { index, item in
+//                                    if index > 0 {
+//                                        Divider()
+//                                            .frame(height: 50)
+//                                    }
+//
+//                                    ZStack(alignment: .topTrailing) {
+//                                        getItemPreview(for: item)
+//                                            .frame(width: 50, height: 50)
+//                                            .padding(5)
+//                                            .background(
+//                                                draggingItem == item ? Color.blue.opacity(0.2) : Color.clear
+//                                            )
+//                                            .cornerRadius(12)
+//                                            .overlay(
+//                                                RoundedRectangle(cornerRadius: 12)
+//                                                    .stroke(
+//                                                        draggingItem == item ? Color.blue : Color.primary,
+//                                                        lineWidth: draggingItem == item ? 2 : 1
+//                                                    )
+//                                            )
+//                                            .opacity(draggingItem == item ? 0.85 : 1.0)
+//                                            .onDrag {
+//                                                self.draggingItem = item
+//                                                return NSItemProvider(object: item.rawValue as NSString)
+//                                            }
+//                                            .onDrop(
+//                                                of: [UTType.text],
+//                                                delegate: DropViewDelegate(
+//                                                    item: item,
+//                                                    items: $selectedItems,
+//                                                    draggingItem: $draggingItem
+//                                                )
+//                                            )
+//                                            .disabled(!isEditMode)
+//                                            .rotationEffect(.degrees(isEditMode ? 2.5 : 0))
+//                                            .rotation3DEffect(.degrees(isEditMode ? 2.5 : 0), axis: (x: 0, y: -5, z: 0))
+//                                            .animation(
+//                                                isEditMode ? Animation.easeInOut(duration: 0.15)
+//                                                    .repeatForever(autoreverses: true) : .default,
+//                                                value: isEditMode
+//                                            )
+//                                        if isEditMode {
+//                                            Button(action: {
+//                                                showDeleteAlert = true
+//                                            }) {
+//                                                Image(systemName: "minus.circle.fill")
+//                                                    .foregroundColor(Color(UIColor.systemGray2)) // Opaque foreground color
+//                                                    .background(Color.white) // Adding a background for contrast
+//                                                    .clipShape(Circle()) // Make sure the background stays circular
+//                                                    .font(.system(size: 20))
+//                                            }
+//                                            .offset(x: -45, y: -10)
+//                                            .alert(isPresented: $showDeleteAlert) {
+//                                                Alert(
+//                                                    title: Text("Delete Widget"),
+//                                                    message: Text("Are you sure you want to delete this widget?"),
+//                                                    primaryButton: .destructive(Text("Delete")) {
+//                                                        removeItem(item)
+//                                                    },
+//                                                    secondaryButton: .cancel()
+//                                                )
+//                                            }
+//                                        }
+//                                    }
+//                                    .animation(.easeInOut, value: draggingItem)
+//                                    .frame(maxWidth: .infinity)
+//                                }
+//                            }
+//                        }
+//                        .padding()
+//                        .overlay(
+//                            RoundedRectangle(cornerRadius: 12)
+//                                .stroke(style: StrokeStyle(lineWidth: 2, dash: [5]))
+//                                .foregroundColor(.gray)
+//                        )
+//                        .cornerRadius(12)
+//                    }
+//                }.padding(.vertical).groupBoxStyle(.dummyChart)
+//
+//                if isEditMode {
+//                    HStack {
+//                        Image(systemName: "hand.draw.fill")
+//                        Text("Tap 'Add +' to add a widget. Press, drag and drop a widget to re-order a widget.")
+//                    }.frame(maxWidth: .infinity, alignment: .leading)
+//                        .foregroundColor(.secondary)
+//                        .font(.footnote)
+//                        .padding(.horizontal)
+//                }
+//
+//                Spacer()
+//            }
+//            .padding()
+//            .scrollContentBackground(.hidden).background(color)
+//            .navigationTitle("Widget Configuration")
+//            .navigationBarTitleDisplayMode(.automatic)
+//            .toolbar {
+//                ToolbarItem(placement: .topBarTrailing) {
+//                    Button {
+//                        isEditMode.toggle()
+//                    } label: {
+//                        HStack {
+//                            Text("Edit")
+//                        }
+//                    }
+//                }
+//                ToolbarItem(placement: .topBarTrailing) {
+//                    Button {
+//                        showAddItemDialog.toggle()
+//                    } label: {
+//                        HStack {
+//                            Text("Add")
+//                            Image(systemName: "plus")
+//                        }
+//                    }
+//                }
+//            }
+//            .confirmationDialog("Add Widget", isPresented: $showAddItemDialog, titleVisibility: .visible) {
+//                ForEach(LiveActivityItem.allCases, id: \.self) { item in
+//                    Button(item.displayName) {
+//                        addItem(item)
+//                    }.disabled(selectedItems.contains(item))
+//                }
+//            }
+//            .onAppear {
+//                configureView()
+//                loadOrder()
+//            }
+//        }
+
+    var body: some View {
+        VStack {
+            dummyChart
+
+            HStack {
+                ForEach(0 ..< 4) { index in
+                    widgetButton(for: index)
+                }
+            }
+            .padding()
+        }
+        .padding()
+        .scrollContentBackground(.hidden).background(color)
+        .navigationTitle("Widget Configuration")
+        .navigationBarTitleDisplayMode(.automatic)
+        .onAppear {
+            loadOrder() // Load the saved order when the view appears
+        }
+        .confirmationDialog("Add Widget", isPresented: $showAddItemDialog, titleVisibility: .visible) {
+            ForEach(LiveActivityItem.allCases, id: \.self) { item in
+                Button(item.displayName) {
+                    if let index = buttonIndexToUpdate {
+                        selectedItems[index] = item // Update button index to selected item
+                        saveOrder() // Save the order to UserDefaults
+                    }
+                }.disabled(selectedItems.contains { $0.value == item }) // Disable already selected items
+            }
+        }
+    }
+
+    @ViewBuilder private func widgetButton(for index: Int) -> some View {
+        Button(action: {
+            buttonIndexToUpdate = index
+            showAddItemDialog.toggle()
+        }) {
+            if let selectedItem = LiveActivityItem.allCases.first(where: { $0.id == selectedItems[index].id }) {
+                // Show item preview if an item is selected
+                getItemPreview(for: selectedItem)
+            } else {
+                // Show "+" symbol if no item is selected
+                VStack {
+                    Image(systemName: "plus")
+                        .font(.title2)
+                        .foregroundColor(.gray)
+                }
+                .frame(width: 60, height: 40)
+                .overlay(
+                    RoundedRectangle(cornerRadius: 12)
+                        .stroke(style: StrokeStyle(lineWidth: 2, dash: [5]))
+                        .foregroundColor(.gray)
+                )
+            }
+        }
+    }
+
+    private func getItemPreview(for item: LiveActivityItem) -> some View {
+        switch item {
+        case .currentGlucose:
+            return AnyView(currentGlucosePreview)
+        case .cob:
+            return AnyView(cobPreview)
+        case .iob:
+            return AnyView(iobPreview)
+        case .updatedLabel:
+            return AnyView(updatedLabelPreview)
+        }
+    }
+
+    private var dummyChart: some View {
+        Chart {
+            ForEach(glucoseData) { data in
+                PointMark(
+                    x: .value("Time", data.time),
+                    y: .value("Glucose Level", data.glucoseLevel)
+                ).foregroundStyle(.green.gradient).symbolSize(15)
+            }
+        }
+        .chartPlotStyle { plotContent in
+            plotContent
+                .background(
+                    RoundedRectangle(cornerRadius: 12)
+                        .fill(Color.cyan.opacity(0.15))
+                )
+                .clipShape(RoundedRectangle(cornerRadius: 12))
+        }
+        .chartYAxis {
+            AxisMarks(position: .trailing) { _ in
+                AxisGridLine(stroke: .init(lineWidth: 0.2, dash: [2, 3])).foregroundStyle(Color.primary)
+            }
+        }
+        .chartYAxis(.hidden)
+        .chartYScale(domain: 40 ... 200)
+        .chartXAxis {
+            AxisMarks(position: .automatic) { _ in
+                AxisGridLine(stroke: .init(lineWidth: 0.2, dash: [2, 3])).foregroundStyle(Color.primary)
+            }
+        }
+        .chartXAxis(.hidden)
+        .frame(height: 100)
+    }
+
+    private var currentGlucosePreview: some View {
+        VStack {
+            HStack(alignment: .center) {
+                Text("123")
+                    .fontWeight(.bold)
+                    .font(.caption)
+            }
+            HStack(spacing: -5) {
+                HStack {
+                    Text("\u{2192}")
+                    Text("+6")
+                }.foregroundStyle(.primary).font(.caption2)
+            }
+        }
+    }
+
+    private var cobPreview: some View {
+        VStack(spacing: 2) {
+            Text("25 g").fontWeight(.bold).font(.caption)
+            Text("COB").font(.caption2).foregroundStyle(.primary)
+        }
+    }
+
+    private var iobPreview: some View {
+        VStack(spacing: 2) {
+            Text("2 U").fontWeight(.bold).font(.caption)
+            Text("IOB").font(.caption2).foregroundStyle(.primary)
+        }
+    }
+
+    private var updatedLabelPreview: some View {
+        VStack {
+            Text("19:05")
+                .fontWeight(.bold)
+                .font(.caption)
+                .foregroundStyle(.primary)
+
+            Text("Updated").font(.caption2).foregroundStyle(.primary)
+        }
+    }
+
+    private func loadOrder() {
+        if let savedItems = UserDefaults.standard.loadLiveActivityOrder() {
+            selectedItems = savedItems
+        } else {
+            selectedItems = LiveActivityItem.defaultItems
+            saveOrder()
+        }
+        print("Loaded order: \(selectedItems.map(\.rawValue))")
+        updateVisibilityForSelectedItems()
+    }
+
+    private func saveOrder() {
+        print("Saving order: \(selectedItems.map(\.rawValue))")
+        UserDefaults.standard.saveLiveActivityOrder(selectedItems)
+    }
+
+    private func addItem(_ item: LiveActivityItem) {
+        setItemVisibility(item: item, isVisible: true)
+        selectedItems.append(item)
+        saveOrder()
+    }
+
+    private func removeItem(_ item: LiveActivityItem) {
+        setItemVisibility(item: item, isVisible: false)
+        selectedItems.removeAll { $0 == item }
+        saveOrder()
+    }
+
+    private func setItemVisibility(item: LiveActivityItem, isVisible: Bool) {
+        switch item {
+        case .currentGlucose:
+            state.showCurrentGlucose = isVisible
+        case .iob:
+            state.showIOB = isVisible
+        case .cob:
+            state.showCOB = isVisible
+        case .updatedLabel:
+            state.showUpdatedLabel = isVisible
+        }
+    }
+
+    private func updateVisibilityForSelectedItems() {
+        for item in selectedItems {
+            setItemVisibility(item: item, isVisible: true)
+        }
+        let allItems = LiveActivityItem.allCases
+        let hiddenItems = allItems.filter { !selectedItems.contains($0) }
+        for item in hiddenItems {
+            setItemVisibility(item: item, isVisible: false)
+        }
+    }
+}
+
+struct DropViewDelegate: DropDelegate {
+    let item: LiveActivityItem
+    @Binding var items: [LiveActivityItem]
+    @Binding var draggingItem: LiveActivityItem?
+
+    func dropEntered(info _: DropInfo) {
+        guard let draggingItem = draggingItem else { return }
+
+        if draggingItem != item {
+            let fromIndex = items.firstIndex(of: draggingItem)!
+            let toIndex = items.firstIndex(of: item)!
+
+            withAnimation {
+                items.move(fromOffsets: IndexSet(integer: fromIndex), toOffset: toIndex > fromIndex ? toIndex + 1 : toIndex)
+            }
+
+            // Save to User Defaults
+            saveOrder()
+
+            // Trigger Live Activity Update
+            Foundation.NotificationCenter.default.post(name: .liveActivityOrderDidChange, object: nil)
+        }
+    }
+
+    func performDrop(info _: DropInfo) -> Bool {
+        draggingItem = nil
+        return true
+    }
+
+    private func saveOrder() {
+        UserDefaults.standard.saveLiveActivityOrder(items)
+    }
+}
+
+// Extension for UserDefaults to save and load the order
+extension UserDefaults {
+    private enum Keys {
+        static let liveActivityOrder = "liveActivityOrder"
+    }
+
+    func saveLiveActivityOrder(_ items: [LiveActivityItem]) {
+        let itemStrings = items.map(\.rawValue)
+        set(itemStrings, forKey: Keys.liveActivityOrder)
+    }
+
+    func loadLiveActivityOrder() -> [LiveActivityItem]? {
+        if let itemStrings = array(forKey: Keys.liveActivityOrder) as? [String] {
+            return itemStrings.compactMap { LiveActivityItem(rawValue: $0) }
+        }
+        return nil
+    }
+}
+
+// Enum to represent each live activity item
+enum LiveActivityItem: String, CaseIterable, Identifiable {
+    case currentGlucose
+    case iob
+    case cob
+    case updatedLabel
+
+    var id: String { rawValue }
+
+    static var defaultItems: [LiveActivityItem] {
+        [.currentGlucose, .iob, .cob, .updatedLabel]
+    }
+
+    var displayName: String {
+        switch self {
+        case .currentGlucose:
+            return "Current Glucose"
+        case .iob:
+            return "IOB"
+        case .cob:
+            return "COB"
+        case .updatedLabel:
+            return "Updated Label"
+        }
+    }
+}
+
+struct DummyChart: Identifiable {
+    let id = UUID()
+    let time: Double // Time in hours
+    let glucoseLevel: Double // Glucose level in mg/dL
+}
+
+struct DummyChartGroupBoxStyle: GroupBoxStyle {
+    func makeBody(configuration: Configuration) -> some View {
+        VStack {
+            configuration.content
+        }
+        .padding()
+        .clipShape(RoundedRectangle(cornerRadius: 12, style: .continuous))
+        .background(Color.chart, in: RoundedRectangle(cornerRadius: 12))
+        .frame(width: UIScreen.main.bounds.width * 0.9)
+    }
+}
+
+extension GroupBoxStyle where Self == DummyChartGroupBoxStyle {
+    static var dummyChart: DummyChartGroupBoxStyle { .init() }
+}

+ 1 - 1
FreeAPS/Sources/Router/Screen.swift

@@ -132,7 +132,7 @@ extension Screen {
         case .liveActivitySettings:
             LiveActivitySettings.RootView(resolver: resolver)
         case .liveActivityBottomRowSettings:
-            LiveActivityBottomRowConfiguration(resolver: resolver, state: LiveActivitySettings.StateModel())
+            LiveActivityWidgetConfiguration(resolver: resolver, state: LiveActivitySettings.StateModel())
         case .calendarEventSettings:
             CalendarEventSettings.RootView(resolver: resolver)
         case .serviceSettings: