From ca8bf4881096b370f33013cadcfef9a44606d01d Mon Sep 17 00:00:00 2001 From: pulipakaa24 Date: Sun, 5 Apr 2026 00:27:29 -0500 Subject: [PATCH] Small spreadsheet UI updates --- LabWise/SpreadsheetView.swift | 188 +++++++++++++++++++--------------- 1 file changed, 107 insertions(+), 81 deletions(-) diff --git a/LabWise/SpreadsheetView.swift b/LabWise/SpreadsheetView.swift index c791431..1574fe7 100644 --- a/LabWise/SpreadsheetView.swift +++ b/LabWise/SpreadsheetView.swift @@ -33,7 +33,7 @@ struct SpreadsheetView: View { ColumnDef(label: "Contact", width: 130) { $0.contact ?? "" }, ColumnDef(label: "Barcode", width: 130) { $0.barcode ?? "" }, ColumnDef(label: "Comments", width: 180) { $0.comments ?? "" }, - ColumnDef(label: "% Full", width: 80) { c in + ColumnDef(label: "% Full", width: 80) { c in c.percentageFull.map { String(format: "%.0f%%", $0) } ?? "" }, ColumnDef(label: "Date Entered", width: 130) { c in @@ -44,25 +44,41 @@ struct SpreadsheetView: View { }, ] - private static let headerHeight: CGFloat = 48 + private static let topBarHeight: CGFloat = 52 + private static let headerHeight: CGFloat = 44 var body: some View { - VStack(spacing: 0) { - // Top bar - HStack(spacing: 4) { - Button { - restorePortrait() - onDismiss() - } label: { - Image(systemName: "xmark.circle.fill") - .font(.title2) - .foregroundStyle(.secondary) + ZStack(alignment: .top) { + Color(.systemBackground).ignoresSafeArea() + + // Full-screen table — padded down so it starts below the floating bar + GeometryReader { proxy in + ScrollView(.horizontal, showsIndicators: true) { + VStack(alignment: .leading, spacing: 0) { + columnHeaderRow + Divider() + ScrollView(.vertical, showsIndicators: true) { + LazyVStack(spacing: 0) { + ForEach(Array(chemicals.enumerated()), id: \.element.id) { idx, chemical in + dataRow(chemical: chemical, index: idx) + Divider() + } + } + } + .frame(height: max(0, proxy.size.height - Self.topBarHeight - Self.headerHeight - 1)) + } } - .padding(.leading, 12) + .padding(.top, Self.topBarHeight) + } + + // Floating Liquid Glass top bar + HStack(spacing: 0) { + closeButton + .padding(.leading, 12) Text("Inventory Spreadsheet") .font(.headline) - .padding(.leading, 4) + .padding(.leading, 8) Spacer() @@ -71,84 +87,94 @@ struct SpreadsheetView: View { .foregroundStyle(.secondary) .padding(.trailing, 16) } - .frame(height: 50) - .background(Color(.systemBackground)) - - Divider() - - // Table - GeometryReader { proxy in - ScrollView(.horizontal, showsIndicators: true) { - VStack(alignment: .leading, spacing: 0) { - // Sticky header row - HStack(spacing: 0) { - ForEach(Array(Self.columnDefs.enumerated()), id: \.offset) { i, col in - Text(col.label) - .font(.caption.weight(.semibold)) - .foregroundStyle(.white) - .lineLimit(2) - .multilineTextAlignment(.center) - .frame(width: col.width, height: Self.headerHeight) - .padding(.horizontal, 4) - - if i < Self.columnDefs.count - 1 { - Rectangle() - .fill(Color.white.opacity(0.35)) - .frame(width: 1, height: Self.headerHeight) - } - } - } - .background(Color.accentColor) - - Divider() - - // Scrollable data rows - ScrollView(.vertical, showsIndicators: true) { - LazyVStack(spacing: 0) { - ForEach(Array(chemicals.enumerated()), id: \.element.id) { idx, chemical in - HStack(spacing: 0) { - ForEach(Array(Self.columnDefs.enumerated()), id: \.offset) { i, col in - Text(col.value(chemical)) - .font(.caption) - .lineLimit(1) - .frame(width: col.width, alignment: .leading) - .padding(.vertical, 7) - .padding(.horizontal, 6) - .background(idx % 2 == 0 - ? Color(.systemBackground) - : Color(.systemGray6)) - - if i < Self.columnDefs.count - 1 { - Rectangle() - .fill(Color(.separator)) - .frame(width: 0.5) - .frame(height: 30) - } - } - } - Divider() - } - } - } - // Subtract header height and the 1-pt divider - .frame(height: proxy.size.height - Self.headerHeight - 1) - } - } - } + .frame(height: Self.topBarHeight) + .background(.regularMaterial) } .onAppear { - // Slight delay so the full-screen cover animation finishes before rotating DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) { setLandscape() } } .onDisappear { - // Safety net in case something else dismisses this view restorePortrait() } } - // MARK: - Orientation helpers + // MARK: - Subviews + + private var columnHeaderRow: some View { + HStack(spacing: 0) { + ForEach(Array(Self.columnDefs.enumerated()), id: \.offset) { i, col in + Text(col.label) + .font(.caption.weight(.semibold)) + .foregroundStyle(.white) + .lineLimit(2) + .multilineTextAlignment(.center) + // 📍 Move padding INSIDE + .padding(.horizontal, 4) + // 📍 Frame goes OUTSIDE to lock the total width safely + .frame(width: col.width, height: Self.headerHeight) + + if i < Self.columnDefs.count - 1 { + Rectangle() + .fill(Color.white.opacity(0.3)) + .frame(width: 1, height: Self.headerHeight) + } + } + } + .background(Color.accentColor) + } + + private func dataRow(chemical: Chemical, index: Int) -> some View { + HStack(spacing: 0) { + ForEach(Array(Self.columnDefs.enumerated()), id: \.offset) { i, col in + Text(col.value(chemical)) + .font(.caption) + .lineLimit(1) + // 📍 Move padding INSIDE + .padding(.vertical, 7) + .padding(.horizontal, 6) + // 📍 Frame goes OUTSIDE to lock the total width safely + .frame(width: col.width, alignment: .leading) + .background(index % 2 == 0 + ? Color(.systemBackground) + : Color(.systemGroupedBackground)) + + if i < Self.columnDefs.count - 1 { + Rectangle() + .fill(Color(.separator)) + // 📍 Changed to 1px to perfectly match the header divider! + .frame(width: 1, height: 30) + } + } + } + } + + /// Close button: Liquid Glass on iOS 26+, plain icon on older releases. + @ViewBuilder + private var closeButton: some View { + if #available(iOS 26.0, *) { + Button { + restorePortrait() + onDismiss() + } label: { + Image(systemName: "xmark") + .font(.system(size: 14, weight: .bold)) + } + .buttonStyle(.glass) + } else { + Button { + restorePortrait() + onDismiss() + } label: { + Image(systemName: "xmark.circle.fill") + .font(.title2) + .foregroundStyle(.secondary) + } + } + } + + // MARK: - Orientation private func setLandscape() { AppDelegate.orientationLock = .landscape