From ee28fab17a406f3e8a23f8687a6e9c879f88d634 Mon Sep 17 00:00:00 2001 From: Dallas Groot Date: Mon, 6 Apr 2026 02:16:19 -0700 Subject: [PATCH] fixed iphone layout issue now :P --- MoneyCounter/ContentView.swift | 330 ++++++++++++++++++--------------- 1 file changed, 180 insertions(+), 150 deletions(-) diff --git a/MoneyCounter/ContentView.swift b/MoneyCounter/ContentView.swift index 1de7236..57ef09c 100644 --- a/MoneyCounter/ContentView.swift +++ b/MoneyCounter/ContentView.swift @@ -8,140 +8,97 @@ struct ContentView: View { @AppStorage("currentSessionNotes") private var sessionNotes: String = "" @Environment(\.modelContext) private var modelContext + @Environment(\.horizontalSizeClass) private var horizontalSizeClass @State private var showingNotes = false @State private var showingHistory = false @State private var showingResetAlert = false var body: some View { - VStack(spacing: 0) { - // Header - HStack { - Menu { - Button(action: { showingNotes = true }) { - Label("Session Notes", systemImage: "note.text") - } - Button(action: { showingHistory = true }) { - Label("Saved History", systemImage: "clock.arrow.circlepath") - } - Divider() - Button(action: saveCurrentSession) { - Label("Save This Total", systemImage: "square.and.arrow.down") - } - } label: { - Image(systemName: "ellipsis.circle") - .font(.title) - .foregroundColor(.white) - } + ZStack(alignment: .trailing) { + // 1. MAIN INTERFACE LAYER + VStack(spacing: 0) { + headerView - Spacer() - Text("Money Counter") - .font(.title2).bold() - .foregroundColor(.white) - Spacer() - - Button(action: { showingResetAlert = true }) { - Image(systemName: "arrow.counterclockwise") - .font(.title) - .foregroundColor(.white) + // Magic Layout Container: Automatically snaps between layouts based on exact window width + ViewThatFits(in: .horizontal) { + + // OPTION 1: Wide iPad Layout + HStack(alignment: .top, spacing: 0) { + ScrollView { VStack(spacing: 24) { looseSection }.padding() } + .frame(minWidth: 280, maxWidth: .infinity) + + Divider() + + ScrollView { VStack(spacing: 24) { rollsSection }.padding() } + .frame(minWidth: 280, maxWidth: .infinity) + + Divider() + + dashboardCard + .frame(width: 320, maxHeight: .infinity, alignment: .top) // Forces iPad column to stretch + .background(Color(UIColor.secondarySystemBackground)) + } + .scrollDismissesKeyboard(.interactively) + + // OPTION 2: Fallback Narrow/iPhone Layout + VStack(spacing: 0) { + ScrollView { + VStack(spacing: 24) { + looseSection + VStack(alignment: .leading, spacing: 8) { + Divider().background(Color.gray) + Text("Rolls").font(.title3).bold().foregroundColor(.secondary) + }.padding(.top, 8) + rollsSection + } + .padding() + } + .scrollDismissesKeyboard(.interactively) + + dashboardCard + .background(Color(UIColor.secondarySystemBackground)) // Hugs tightly on iPhone + } } } - .padding() - .background(Color(red: 0.4, green: 0.7, blue: 0.4)) - - // Scrolling Denominations - ScrollView { - VStack(spacing: 24) { - ForEach($viewModel.looseDenominations) { $denom in - DenominationRow(denomination: $denom, focusedField: $focusedField, valueColor: Color(UIColor.systemGreen)) - } - - VStack(alignment: .leading, spacing: 8) { - Divider().background(Color.gray) - Text("Rolls") - .font(.title3) - .bold() - .foregroundColor(.secondary) - } - .padding(.top, 8) - - ForEach($viewModel.rollDenominations) { $denom in - DenominationRow(denomination: $denom, focusedField: $focusedField, valueColor: Color(UIColor.systemRed)) - } - } - .padding() - } - .contentShape(Rectangle()) + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(Color(UIColor.systemBackground)) .onTapGesture { dismissKeyboard() } - // 1. Native drag-to-dismiss behavior - .scrollDismissesKeyboard(.interactively) - - // Footer (Math & Totals) - VStack(spacing: 8) { - HStack { - Text("Starting Float") - .font(.headline) - .foregroundColor(.secondary) - Spacer() - - HStack(spacing: 2) { - Text("$") - .foregroundColor(.secondary) - TextField("0.00", value: $viewModel.startingFloat, format: .number.precision(.fractionLength(2))) - .keyboardType(.decimalPad) - .focused($isFloatFocused) - .multilineTextAlignment(.trailing) - } - .font(.headline) - .padding(6) - .frame(width: 100) - .background(Color(UIColor.tertiarySystemFill), in: RoundedRectangle(cornerRadius: 8)) - } - - if viewModel.startingFloat > 0 { - HStack { - Text(viewModel.discrepancy >= 0 ? "Deposit Amount" : "Shortage") - .font(.headline) - Spacer() - Text(String(format: "$%.2f", viewModel.discrepancy)) - .font(.headline) - .foregroundColor(viewModel.discrepancy >= 0 ? .primary : .red) - } - } - - Divider() - - HStack { - Text("Total") - .font(.largeTitle) - .bold() - Spacer() - Text(String(format: "$%.2f", viewModel.total)) - .font(.largeTitle) - .bold() - } + + // 2. IPAD SLIDE-OVER PANEL LAYER + if showingNotes && horizontalSizeClass == .regular { + NotesView(notes: $sessionNotes, total: viewModel.total, discrepancy: viewModel.discrepancy, expectedFloat: viewModel.startingFloat, isPresented: $showingNotes) + .frame(width: 375) + .background(Color(UIColor.systemBackground)) + .shadow(color: Color.black.opacity(0.3), radius: 25, x: -10, y: 0) + .transition(.move(edge: .trailing)) + .zIndex(2) + .ignoresSafeArea(.all, edges: .bottom) } - .padding() - .background(Color(UIColor.secondarySystemBackground)) - .foregroundColor(.primary) } - .background(Color(UIColor.systemBackground)) - // 2. Adds the "Done" button directly above the keyboard .toolbar { - ToolbarItemGroup(placement: .keyboard) { - Spacer() - Button("Done") { - dismissKeyboard() + if UIDevice.current.userInterfaceIdiom != .pad { + ToolbarItemGroup(placement: .keyboard) { + Spacer() + Button("Done") { dismissKeyboard() } } - .font(.headline) - .foregroundColor(.blue) } } + .keyboardShortcut("s", modifiers: .command) + .onKeyPress(.downArrow) { moveFocus(forward: true); return .handled } + .onKeyPress(.upArrow) { moveFocus(forward: false); return .handled } + .onChange(of: viewModel.looseDenominations) { _, _ in viewModel.saveCache() } .onChange(of: viewModel.rollDenominations) { _, _ in viewModel.saveCache() } .onChange(of: viewModel.startingFloat) { _, _ in viewModel.saveCache() } - .sheet(isPresented: $showingNotes) { - NotesView(notes: $sessionNotes, total: viewModel.total, discrepancy: viewModel.discrepancy, expectedFloat: viewModel.startingFloat) + + // IPHONE FALLBACK: Continues to use a standard bottom sheet on mobile + .sheet(isPresented: Binding( + get: { showingNotes && horizontalSizeClass != .regular }, + set: { showingNotes = $0 } + )) { + NotesView(notes: $sessionNotes, total: viewModel.total, discrepancy: viewModel.discrepancy, expectedFloat: viewModel.startingFloat, isPresented: $showingNotes) + .presentationDetents([.medium, .large]) } .sheet(isPresented: $showingHistory) { HistoryView(viewModel: viewModel) @@ -149,7 +106,7 @@ struct ContentView: View { .confirmationDialog("Reset Everything?", isPresented: $showingResetAlert) { Button("Reset Counts & Notes", role: .destructive) { withAnimation { viewModel.reset() } - sessionNotes = "" + sessionNotes = "" } Button("Reset Counts Only", role: .destructive) { withAnimation { viewModel.reset() } @@ -158,23 +115,114 @@ struct ContentView: View { } } + // MARK: - Subviews + private var headerView: some View { + HStack { + Menu { + Button(action: { + withAnimation(.spring(response: 0.3, dampingFraction: 0.8)) { + showingNotes.toggle() + } + }) { Label("Session Notes", systemImage: "note.text") } + + Button(action: { showingHistory = true }) { Label("Saved History", systemImage: "clock.arrow.circlepath") } + Divider() + Button(action: saveCurrentSession) { Label("Save This Total", systemImage: "square.and.arrow.down") } + } label: { + Image(systemName: "ellipsis.circle").font(.title).foregroundColor(.white) + } + Spacer() + Text("Money Counter").font(.title2).bold().foregroundColor(.white) + Spacer() + Button(action: { showingResetAlert = true }) { + Image(systemName: "arrow.counterclockwise").font(.title).foregroundColor(.white) + } + } + .padding() + .background(Color(red: 0.4, green: 0.7, blue: 0.4)) + } + + private var looseSection: some View { + ForEach($viewModel.looseDenominations) { $denom in + DenominationRow(denomination: $denom, focusedField: $focusedField, valueColor: Color(UIColor.systemGreen)) + } + } + + private var rollsSection: some View { + ForEach($viewModel.rollDenominations) { $denom in + DenominationRow(denomination: $denom, focusedField: $focusedField, valueColor: Color(UIColor.systemRed)) + } + } + + // Notice: The internal Spacer() and .background() modifiers have been completely removed + private var dashboardCard: some View { + VStack(spacing: 16) { + HStack { + Text("Starting Float").font(.headline).foregroundColor(.secondary) + Spacer() + HStack(spacing: 2) { + Text("$").foregroundColor(.secondary) + TextField("0.00", value: $viewModel.startingFloat, format: .number.precision(.fractionLength(2))) + .keyboardType(.decimalPad) + .focused($isFloatFocused) + .multilineTextAlignment(.trailing) + } + .font(.headline).padding(6).frame(width: 100) + .background(Color(UIColor.tertiarySystemFill), in: RoundedRectangle(cornerRadius: 8)) + } + + if viewModel.startingFloat > 0 { + HStack { + Text(viewModel.discrepancy >= 0 ? "Deposit Amount" : "Shortage").font(.headline) + Spacer() + Text(String(format: "$%.2f", viewModel.discrepancy)) + .font(.headline) + .foregroundColor(viewModel.discrepancy >= 0 ? .primary : .red) + } + } + + Divider() + + HStack { + Text("Total").font(.largeTitle).bold() + Spacer() + Text(String(format: "$%.2f", viewModel.total)).font(.largeTitle).bold() + } + } + .padding() + } + + // MARK: - Logic private func dismissKeyboard() { focusedField = nil isFloatFocused = false } + private func moveFocus(forward: Bool) { + guard let current = focusedField, + let currentIndex = viewModel.allDenominationIDs.firstIndex(of: current) else { return } + + let nextIndex = forward ? currentIndex + 1 : currentIndex - 1 + + if viewModel.allDenominationIDs.indices.contains(nextIndex) { + focusedField = viewModel.allDenominationIDs[nextIndex] + } else if forward && nextIndex == viewModel.allDenominationIDs.count { + focusedField = nil + isFloatFocused = true + } + } + private func saveCurrentSession() { - let newSave = SavedCount( - total: viewModel.total, - startingFloat: viewModel.startingFloat, - notes: sessionNotes, - snapshotData: viewModel.generateSnapshot() - ) + let newSave = SavedCount(total: viewModel.total, startingFloat: viewModel.startingFloat, notes: sessionNotes, snapshotData: viewModel.generateSnapshot()) modelContext.insert(newSave) + + viewModel.reset() + sessionNotes = "" + viewModel.startingFloat = 0.0 } } -// DenominationRow remains exactly the same as the previous iteration +// MARK: - Denomination Row struct DenominationRow: View { @Binding var denomination: MoneyCounterViewModel.Denomination var focusedField: FocusState.Binding @@ -183,52 +231,34 @@ struct DenominationRow: View { var body: some View { HStack(spacing: 16) { HStack { - Text(formatValue(denomination.value)) - .font(.title2).bold() - .foregroundColor(valueColor) - .frame(width: 70, alignment: .leading) + Text(formatValue(denomination.value)).font(.title2).bold().foregroundColor(valueColor).frame(width: 70, alignment: .leading) Spacer() } - .contentShape(Rectangle()) + .contentShape(Rectangle()) .onTapGesture { focusedField.wrappedValue = nil } Button(action: { denomination.count += 1 focusedField.wrappedValue = nil - }) { - Image(systemName: "plus.circle.fill") - .foregroundColor(Color(UIColor.systemGreen)) - .font(.title) - } + }) { Image(systemName: "plus.circle.fill").foregroundColor(Color(UIColor.systemGreen)).font(.title) } Button(action: { if denomination.count > 0 { denomination.count -= 1 } focusedField.wrappedValue = nil - }) { - Image(systemName: "minus.circle.fill") - .foregroundColor(Color(UIColor.systemRed)) - .font(.title) - } + }) { Image(systemName: "minus.circle.fill").foregroundColor(Color(UIColor.systemRed)).font(.title) } TextField("0", value: $denomination.count, format: .number) .keyboardType(.numberPad) .multilineTextAlignment(.center) - .font(.title2).bold() - .frame(width: 80) - .padding(.vertical, 4) + .font(.title2).bold().frame(width: 80).padding(.vertical, 4) .focused(focusedField, equals: denomination.id) - .overlay( - Rectangle() - .frame(height: 1) - .foregroundColor(Color(UIColor.separator)), - alignment: .bottom - ) - .foregroundColor(.primary) + .overlay(Rectangle().frame(height: 1).foregroundColor(Color(UIColor.separator)), alignment: .bottom) + .foregroundColor(.primary) } } private func formatValue(_ value: Double) -> String { - if value >= 1.0 { return String(format: "%.1f", value) } + if value >= 1.0 { return String(format: "%.1f", value) } else { return String(format: "%.2f", value) } } -} +} \ No newline at end of file