Przeglądaj źródła

Limit out-of-bounds scroll behavior for TDD popover; adjust design

Deniz Cengiz 1 rok temu
rodzic
commit
3945fc17c0

+ 80 - 10
Trio/Sources/Modules/Stat/View/ViewElements/Insulin/TotalDailyDoseChart.swift

@@ -23,6 +23,8 @@ struct TotalDailyDoseChart: View {
     @State private var updateTimer = Stat.UpdateTimer()
     /// Sum of hourly doses for `Day` view
     @State private var sumOfHourlyDoses: Double = 0
+    /// The actual chart plot's width in pixel
+    @State private var chartWidth: CGFloat = 0
 
     /// Computes the visible date range based on the current scroll position.
     private var visibleDateRange: (start: Date, end: Date) {
@@ -62,6 +64,13 @@ struct TotalDailyDoseChart: View {
                     .padding(.bottom, 4)
 
                 chartsView
+                    .background(
+                        GeometryReader { geo in
+                            Color.clear
+                                .onAppear { chartWidth = geo.size.width }
+                                .onChange(of: geo.size.width) { _, newValue in chartWidth = newValue }
+                        }
+                    )
             }
         }
         .onAppear {
@@ -153,7 +162,13 @@ struct TotalDailyDoseChart: View {
                     spacing: 0,
                     overflowResolution: .init(x: .fit(to: .chart), y: .fit(to: .chart))
                 ) {
-                    TDDSelectionPopover(date: selectedDate, tdd: selectedTDD, selectedInterval: selectedInterval)
+                    TDDSelectionPopover(
+                        selectedDate: selectedDate,
+                        tdd: selectedTDD,
+                        selectedInterval: selectedInterval,
+                        domain: visibleDateRange,
+                        chartWidth: chartWidth
+                    )
                 }
             }
 
@@ -240,33 +255,88 @@ struct TotalDailyDoseChart: View {
 ///   - tdd: The TDDStats containing insulin usage data.
 ///   - selectedInterval: The selected time interval (hourly or daily).
 private struct TDDSelectionPopover: View {
-    let date: Date
+    let selectedDate: Date
     let tdd: TDDStats
     let selectedInterval: Stat.StateModel.StatsTimeInterval
+    let domain: (start: Date, end: Date)
+    let chartWidth: CGFloat
+
+    @State private var popoverSize: CGSize = .zero
+
+    @Environment(\.colorScheme) var colorScheme
 
     private var timeText: String {
         if selectedInterval == .day {
-            let hour = Calendar.current.component(.hour, from: date)
-            return "\(hour):00-\(hour + 1):00"
+            let hour = Calendar.current.component(.hour, from: selectedDate)
+            return selectedDate.formatted(.dateTime.month().day().weekday()) + "\n" + "\(hour):00-\(hour + 1):00"
         } else {
-            return date.formatted(.dateTime.month().day())
+            return selectedDate.formatted(.dateTime.month().day().weekday())
         }
     }
 
+    private func xOffset() -> CGFloat {
+        let domainDuration = domain.end.timeIntervalSince(domain.start)
+        guard domainDuration > 0, chartWidth > 0 else { return 0 }
+
+        let popoverWidth = popoverSize.width
+
+        // Convert dates to pixel'd x-condition
+        let dateFraction = selectedDate.timeIntervalSince(domain.start) / domainDuration
+        let x_selected = dateFraction * chartWidth
+
+        // TODO: this is semi hacky, can this be improved?
+        let x_left = x_selected - (popoverWidth / 2) // Left edge of popover
+        let x_right = x_selected + (popoverWidth / 2) // Right edge of popover
+
+        var offset: CGFloat = 0 // Default = no shift
+
+        // Push popover to right if its left edge is (nearing) out-of-bounds
+        if x_left < 0 {
+            offset = abs(x_left) // push to right
+        }
+
+        // Push popover to left if its right edge is (nearing) out-of-bounds)
+        if x_right > chartWidth {
+            offset = -(x_right - chartWidth) // push to left
+        }
+
+        return offset
+    }
+
     var body: some View {
         VStack(alignment: .leading, spacing: 4) {
             Text(timeText)
                 .font(.subheadline)
-                .fontWeight(.bold)
+                .bold()
+                .foregroundStyle(Color.secondary)
+
+            Divider()
 
-            Text(tdd.amount.formatted(.number.precision(.fractionLength(1))) + " U")
-                .font(.title3.bold())
+            HStack {
+                Text(tdd.amount.formatted(.number.precision(.fractionLength(1))))
+                Text("U").foregroundStyle(Color.secondary)
+            }
+            .font(.headline)
         }
-        .foregroundStyle(.white)
         .padding(20)
         .background {
             RoundedRectangle(cornerRadius: 10)
-                .fill(Color.insulin)
+                .fill(colorScheme == .dark ? Color.bgDarkBlue.opacity(0.9) : Color.white.opacity(0.95))
+                .shadow(color: Color.secondary, radius: 2)
+                .overlay(
+                    RoundedRectangle(cornerRadius: 4)
+                        .stroke(Color.blue, lineWidth: 2)
+                )
         }
+        .frame(minWidth: 100, maxWidth: .infinity) // Ensures proper width
+        .background(
+            GeometryReader { geo in
+                Color.clear
+                    .onAppear { popoverSize = geo.size }
+                    .onChange(of: geo.size) { _, newValue in popoverSize = newValue }
+            }
+        )
+        // Apply calculated xOffset to keep within bounds
+        .offset(x: xOffset(), y: 0)
     }
 }