diff --git a/Andromida/App/Models/InsightCard.swift b/Andromida/App/Models/InsightCard.swift index 31f5d06..d2f6209 100644 --- a/Andromida/App/Models/InsightCard.swift +++ b/Andromida/App/Models/InsightCard.swift @@ -1,8 +1,26 @@ import Foundation +/// Identifies each type of insight card for ordering and identification. +enum InsightCardType: String, CaseIterable, Codable { + case active + case streak + case habitsToday + case completion + case daysActive + case sevenDayAvg + case totalCheckIns + case bestRitual + + /// Default order for cards (prioritizes motivation → action → context → history) + static var defaultOrder: [InsightCardType] { + [.streak, .completion, .habitsToday, .sevenDayAvg, .active, .daysActive, .bestRitual, .totalCheckIns] + } +} + /// A single insight metric displayed on the Insights tab. struct InsightCard: Identifiable { let id: UUID + let type: InsightCardType let title: String let value: String let caption: String // Short description shown on card @@ -13,6 +31,7 @@ struct InsightCard: Identifiable { init( id: UUID = UUID(), + type: InsightCardType, title: String, value: String, caption: String, @@ -22,6 +41,7 @@ struct InsightCard: Identifiable { trendData: [TrendDataPoint]? = nil ) { self.id = id + self.type = type self.title = title self.value = value self.caption = caption diff --git a/Andromida/App/State/RitualStore.swift b/Andromida/App/State/RitualStore.swift index 70d3574..79ca354 100644 --- a/Andromida/App/State/RitualStore.swift +++ b/Andromida/App/State/RitualStore.swift @@ -479,8 +479,10 @@ final class RitualStore: RitualStoreProviding { return best }() - return [ - InsightCard( + // Build cards dictionary by type + let cardsByType: [InsightCardType: InsightCard] = [ + .active: InsightCard( + type: .active, title: String(localized: "Active"), value: "\(activeRitualCount)", caption: String(localized: "In progress now"), @@ -488,7 +490,8 @@ final class RitualStore: RitualStoreProviding { symbolName: "sparkles", breakdown: currentRituals.map { BreakdownItem(label: $0.title, value: $0.theme) } ), - InsightCard( + .streak: InsightCard( + type: .streak, title: String(localized: "Streak"), value: "\(current)", caption: String(localized: "Consecutive perfect days"), @@ -496,7 +499,8 @@ final class RitualStore: RitualStoreProviding { symbolName: "flame.fill", breakdown: streakBreakdown ), - InsightCard( + .habitsToday: InsightCard( + type: .habitsToday, title: String(localized: "Habits today"), value: "\(completedToday)", caption: String(localized: "Completed today"), @@ -504,7 +508,8 @@ final class RitualStore: RitualStoreProviding { symbolName: "checkmark.circle.fill", breakdown: habitsBreakdown ), - InsightCard( + .completion: InsightCard( + type: .completion, title: String(localized: "Completion"), value: "\(completionRateValue)%", caption: String(localized: "Today's progress"), @@ -513,7 +518,8 @@ final class RitualStore: RitualStoreProviding { breakdown: trendBreakdown, trendData: trendData ), - InsightCard( + .daysActive: InsightCard( + type: .daysActive, title: String(localized: "Days Active"), value: "\(daysActiveCount)", caption: String(localized: "Days you checked in"), @@ -521,7 +527,8 @@ final class RitualStore: RitualStoreProviding { symbolName: "calendar", breakdown: daysActiveBreakdown() ), - InsightCard( + .sevenDayAvg: InsightCard( + type: .sevenDayAvg, title: String(localized: "7-Day Avg"), value: "\(weeklyAvg)%", caption: String(localized: "Weekly average"), @@ -530,16 +537,18 @@ final class RitualStore: RitualStoreProviding { breakdown: trendBreakdown, trendData: trendData ), - InsightCard( + .totalCheckIns: InsightCard( + type: .totalCheckIns, title: String(localized: "Total Check-ins"), value: "\(totalHabitsAllTime)", caption: String(localized: "All-time habits completed"), explanation: String(localized: "The total number of habit check-ins you've made since you started using Rituals. Every check-in counts toward building lasting change."), symbolName: "checkmark.seal.fill" ), - { + .bestRitual: { if let best = bestRitualInfo { return InsightCard( + type: .bestRitual, title: String(localized: "Best Ritual"), value: "\(best.rate)%", caption: best.title, @@ -548,6 +557,7 @@ final class RitualStore: RitualStoreProviding { ) } else { return InsightCard( + type: .bestRitual, title: String(localized: "Best Ritual"), value: "—", caption: String(localized: "No active rituals"), @@ -557,6 +567,43 @@ final class RitualStore: RitualStoreProviding { } }() ] + + // Return cards in user's preferred order + let order = insightCardOrder + return order.compactMap { cardsByType[$0] } + } + + // MARK: - Insight Card Order + + private static let insightCardOrderKey = "insightCardOrder" + + /// The user's preferred order for insight cards + var insightCardOrder: [InsightCardType] { + get { + guard let data = UserDefaults.standard.data(forKey: Self.insightCardOrderKey), + let order = try? JSONDecoder().decode([InsightCardType].self, from: data) else { + return InsightCardType.defaultOrder + } + // Ensure all card types are included (in case new ones were added) + let missingTypes = InsightCardType.allCases.filter { !order.contains($0) } + return order + missingTypes + } + set { + if let data = try? JSONEncoder().encode(newValue) { + UserDefaults.standard.set(data, forKey: Self.insightCardOrderKey) + } + insightCardsNeedRefresh = true + } + } + + /// Reorders insight cards by moving a card from one position to another + func reorderInsightCards(from sourceIndex: Int, to destinationIndex: Int) { + var order = insightCardOrder + guard sourceIndex >= 0 && sourceIndex < order.count else { return } + guard destinationIndex >= 0 && destinationIndex < order.count else { return } + let item = order.remove(at: sourceIndex) + order.insert(item, at: destinationIndex) + insightCardOrder = order } func createQuickRitual() { diff --git a/Andromida/App/Views/History/HistoryDayDetailSheet.swift b/Andromida/App/Views/History/HistoryDayDetailSheet.swift index 5e1dcb0..0d211c0 100644 --- a/Andromida/App/Views/History/HistoryDayDetailSheet.swift +++ b/Andromida/App/Views/History/HistoryDayDetailSheet.swift @@ -76,7 +76,7 @@ struct HistoryDayDetailSheet: View { } } .presentationBackground(AppSurface.primary) - .presentationDetents([.medium, .large]) + .presentationDetents([.large]) .presentationDragIndicator(.visible) } diff --git a/Andromida/App/Views/History/HistoryView.swift b/Andromida/App/Views/History/HistoryView.swift index a7a51f6..7821ef8 100644 --- a/Andromida/App/Views/History/HistoryView.swift +++ b/Andromida/App/Views/History/HistoryView.swift @@ -90,7 +90,7 @@ struct HistoryView: View { ritualPicker // Month calendars - 2-column grid on iPad/landscape - LazyVGrid(columns: monthColumns, alignment: .top, spacing: Design.Spacing.large) { + LazyVGrid(columns: monthColumns, alignment: .leading, spacing: Design.Spacing.large) { ForEach(months, id: \.self) { month in HistoryMonthView( month: month, @@ -103,6 +103,7 @@ struct HistoryView: View { selectedDateItem = IdentifiableDate(date: date) } ) + .frame(maxHeight: .infinity, alignment: .top) } } .id(refreshToken) diff --git a/Andromida/App/Views/Insights/Components/InsightCardView.swift b/Andromida/App/Views/Insights/Components/InsightCardView.swift index 6dcdeb1..a8823d4 100644 --- a/Andromida/App/Views/Insights/Components/InsightCardView.swift +++ b/Andromida/App/Views/Insights/Components/InsightCardView.swift @@ -86,6 +86,7 @@ struct InsightCardView: View { #Preview { InsightCardView( card: InsightCard( + type: .completion, title: "Completion", value: "72%", caption: "Today's progress", diff --git a/Andromida/App/Views/Insights/Components/InsightDetailSheet.swift b/Andromida/App/Views/Insights/Components/InsightDetailSheet.swift index b7c6d69..8b1bb0c 100644 --- a/Andromida/App/Views/Insights/Components/InsightDetailSheet.swift +++ b/Andromida/App/Views/Insights/Components/InsightDetailSheet.swift @@ -74,6 +74,7 @@ struct InsightDetailSheet: View { } } } + .presentationDetents([.large]) } // MARK: - Header Section @@ -272,6 +273,7 @@ struct InsightDetailSheet: View { #Preview { InsightDetailSheet( card: InsightCard( + type: .daysActive, title: "Ritual days", value: "16", caption: "Days on your journey", diff --git a/Andromida/App/Views/Insights/InsightsView.swift b/Andromida/App/Views/Insights/InsightsView.swift index 489f231..35c6235 100644 --- a/Andromida/App/Views/Insights/InsightsView.swift +++ b/Andromida/App/Views/Insights/InsightsView.swift @@ -1,9 +1,14 @@ import SwiftUI import Bedrock +import UniformTypeIdentifiers struct InsightsView: View { @Bindable var store: RitualStore @State private var refreshToken = UUID() + @State private var isEditing = false + @State private var draggingCard: InsightCardType? + @State private var cardOrder: [InsightCardType] = [] + @State private var lastDroppedCard: InsightCardType? private let columns = [ GridItem( @@ -12,44 +17,205 @@ struct InsightsView: View { alignment: .top ) ] + + /// Cards in the current display order (uses local state during editing) + private var orderedCards: [InsightCard] { + let allCards = store.insightCards() + let cardsByType = Dictionary(uniqueKeysWithValues: allCards.map { ($0.type, $0) }) + return cardOrder.compactMap { cardsByType[$0] } + } var body: some View { - ScrollView(.vertical, showsIndicators: false) { - VStack(alignment: .leading, spacing: Design.Spacing.large) { - SectionHeaderView( - title: String(localized: "Insights"), - subtitle: String(localized: "Momentum at a glance") - ) + NavigationStack { + ScrollView(.vertical, showsIndicators: false) { + VStack(alignment: .leading, spacing: Design.Spacing.large) { + SectionHeaderView( + title: String(localized: "Insights"), + subtitle: String(localized: "Momentum at a glance") + ) - LazyVGrid(columns: columns, spacing: Design.Spacing.medium) { - ForEach(store.insightCards()) { card in - InsightCardView(card: card, store: store) + // Grid with drag-and-drop support in edit mode + LazyVGrid(columns: columns, spacing: Design.Spacing.medium) { + ForEach(orderedCards) { card in + insightCardItem(card: card) + } + } + .animation(.spring(response: 0.3, dampingFraction: 0.7), value: cardOrder) + .onDrop(of: [.text], isTargeted: nil) { _ in + // Fallback drop handler - clear dragging state + lastDroppedCard = draggingCard + draggingCard = nil + return false } } - .id(refreshToken) + .padding(Design.Spacing.large) } - .padding(Design.Spacing.large) - } - .background(LinearGradient( - colors: [AppSurface.primary, AppSurface.secondary], - startPoint: .topLeading, - endPoint: .bottomTrailing - )) - .onAppear { - Task { - await Task.yield() + .background(LinearGradient( + colors: [AppSurface.primary, AppSurface.secondary], + startPoint: .topLeading, + endPoint: .bottomTrailing + )) + .navigationTitle(String(localized: "Insights")) + .navigationBarTitleDisplayMode(.inline) + .toolbar { + ToolbarItem(placement: .primaryAction) { + Button { + withAnimation(.easeInOut(duration: Design.Animation.standard)) { + if isEditing { + // Save order and clear all drag state when exiting edit mode + draggingCard = nil + lastDroppedCard = nil + store.insightCardOrder = cardOrder + } + isEditing.toggle() + } + } label: { + Text(isEditing ? String(localized: "Done") : String(localized: "Edit")) + .foregroundStyle(AppAccent.primary) + } + } + } + .onChange(of: isEditing) { _, newValue in + // Clear all drag state when exiting edit mode + if !newValue { + draggingCard = nil + lastDroppedCard = nil + } + } + .onAppear { + cardOrder = store.insightCardOrder + Task { + await Task.yield() + store.refreshAnalyticsIfNeeded() + store.refreshInsightCardsIfNeeded() + } + } + .onChange(of: store.rituals) { _, _ in store.refreshAnalyticsIfNeeded() store.refreshInsightCardsIfNeeded() + cardOrder = store.insightCardOrder + } + .onChange(of: store.insightCardOrder) { _, newOrder in + if !isEditing { + cardOrder = newOrder + } } } - .onChange(of: store.rituals) { _, _ in - store.refreshAnalyticsIfNeeded() - store.refreshInsightCardsIfNeeded() - refreshToken = UUID() + } + + // MARK: - Card Item with Drag Support + + @ViewBuilder + private func insightCardItem(card: InsightCard) -> some View { + let isDragging = draggingCard == card.type + + let cardView = InsightCardView(card: card, store: store) + .overlay(alignment: .topTrailing) { + // Show drag handle in edit mode - sized to cover the disclosure chevron + if isEditing { + Image(systemName: "line.3.horizontal") + .font(.body.weight(.medium)) + .foregroundStyle(AppTextColors.inverse) + .padding(Design.Spacing.small) + .background(AppAccent.primary) + .clipShape(.rect(cornerRadius: Design.CornerRadius.small)) + .padding(Design.Spacing.xSmall) + } + } + .opacity(isDragging ? 0.3 : 1.0) + .scaleEffect(isDragging ? 1.05 : (isEditing ? 0.98 : 1.0)) + .modifier(JiggleModifier(isEnabled: isEditing && draggingCard == nil)) + + // Only enable drag when in edit mode + if isEditing { + cardView + .onDrag { + // Prevent spurious re-trigger: if this is the same card that was just dropped + // and we're not currently dragging, ignore the call + let isSameAsLastDrop = lastDroppedCard == card.type + if !isSameAsLastDrop { + lastDroppedCard = nil + self.draggingCard = card.type + } else if draggingCard == nil { + // Spurious re-trigger - don't set draggingCard + } + return NSItemProvider(object: card.type.rawValue as NSString) + } + .onDrop(of: [.text], delegate: InsightCardDropDelegate( + card: card, + cardOrder: $cardOrder, + draggingCard: $draggingCard, + lastDroppedCard: $lastDroppedCard + )) + } else { + cardView } } } +// MARK: - Drop Delegate + +struct InsightCardDropDelegate: DropDelegate { + let card: InsightCard + @Binding var cardOrder: [InsightCardType] + @Binding var draggingCard: InsightCardType? + @Binding var lastDroppedCard: InsightCardType? + + func performDrop(info: DropInfo) -> Bool { + // Record which card was dropped BEFORE clearing draggingCard + // This prevents spurious onDrag re-triggers from affecting state + lastDroppedCard = draggingCard + draggingCard = nil + return true + } + + func dropEntered(info: DropInfo) { + guard let dragging = draggingCard, dragging != card.type else { return } + + guard let fromIndex = cardOrder.firstIndex(of: dragging), + let toIndex = cardOrder.firstIndex(of: card.type), + fromIndex != toIndex else { + return + } + + // Move the item with animation - updates local state, persisted when Done is tapped + withAnimation(.spring(response: 0.3, dampingFraction: 0.7)) { + let item = cardOrder.remove(at: fromIndex) + cardOrder.insert(item, at: toIndex) + } + } + + func dropUpdated(info: DropInfo) -> DropProposal? { + DropProposal(operation: .move) + } + + func dropExited(info: DropInfo) { + // Don't clear here - wait for performDrop + } +} + +// MARK: - Jiggle Animation Modifier + +struct JiggleModifier: ViewModifier { + let isEnabled: Bool + @State private var isJiggling = false + + func body(content: Content) -> some View { + content + .rotationEffect(.degrees(isEnabled && isJiggling ? -1 : 0)) + .animation( + isEnabled ? Animation.easeInOut(duration: 0.1).repeatForever(autoreverses: true) : .default, + value: isJiggling + ) + .onChange(of: isEnabled) { _, newValue in + isJiggling = newValue + } + .onAppear { + isJiggling = isEnabled + } + } +} + #Preview { InsightsView(store: RitualStore.preview) } diff --git a/Andromida/App/Views/Rituals/ArcDetailView.swift b/Andromida/App/Views/Rituals/ArcDetailView.swift index 7e1a976..e300da4 100644 --- a/Andromida/App/Views/Rituals/ArcDetailView.swift +++ b/Andromida/App/Views/Rituals/ArcDetailView.swift @@ -21,10 +21,36 @@ struct ArcDetailView: View { @Bindable var store: RitualStore let arc: RitualArc let ritual: Ritual + @Environment(\.horizontalSizeClass) private var horizontalSizeClass @State private var selectedDateItem: ArcIdentifiableDate? private let calendar = Calendar.current + /// Whether to use wide layout on iPad/landscape + private var useWideLayout: Bool { + horizontalSizeClass == .regular + } + + /// Grid columns for month calendars - 2 columns on regular width when multiple months + private var monthColumns: [GridItem] { + AdaptiveColumns.columns( + compactCount: 1, + regularCount: monthsInArc.count > 1 ? 2 : 1, + spacing: Design.Spacing.large, + horizontalSizeClass: horizontalSizeClass + ) + } + + /// Grid columns for habit breakdown - 2 columns on regular width when multiple habits + private var habitColumns: [GridItem] { + AdaptiveColumns.columns( + compactCount: 1, + regularCount: habitRates.count > 1 ? 2 : 1, + spacing: Design.Spacing.medium, + horizontalSizeClass: horizontalSizeClass + ) + } + private var overallCompletionRate: Int { let habits = arc.habits ?? [] let totalCheckIns = habits.reduce(0) { $0 + $1.completedDayIDs.count } @@ -243,9 +269,10 @@ struct ArcDetailView: View { .font(.headline) .foregroundStyle(AppTextColors.primary) - VStack(spacing: Design.Spacing.small) { + LazyVGrid(columns: habitColumns, alignment: .leading, spacing: Design.Spacing.small) { ForEach(habitRates, id: \.habit.id) { item in habitRow(habit: item.habit, rate: item.rate) + .frame(maxHeight: .infinity, alignment: .top) } } } @@ -307,21 +334,24 @@ struct ArcDetailView: View { .font(.headline) .foregroundStyle(AppTextColors.primary) - // Show month calendars for the arc's date range - ForEach(monthsInArc, id: \.self) { month in - HistoryMonthView( - month: month, - selectedRitual: ritual, - completionRate: { date, _ in - arcCompletionRate(for: date) - }, - onDayTapped: { date in - // Only allow tapping days within the arc range - if arc.contains(date: date) { - selectedDateItem = ArcIdentifiableDate(date: date) + // Show month calendars for the arc's date range - 2 columns on iPad + LazyVGrid(columns: monthColumns, alignment: .leading, spacing: Design.Spacing.large) { + ForEach(monthsInArc, id: \.self) { month in + HistoryMonthView( + month: month, + selectedRitual: ritual, + completionRate: { date, _ in + arcCompletionRate(for: date) + }, + onDayTapped: { date in + // Only allow tapping days within the arc range + if arc.contains(date: date) { + selectedDateItem = ArcIdentifiableDate(date: date) + } } - } - ) + ) + .frame(maxHeight: .infinity, alignment: .top) + } } } } diff --git a/Andromida/App/Views/Rituals/Components/HabitPerformanceView.swift b/Andromida/App/Views/Rituals/Components/HabitPerformanceView.swift index 30b6312..b1081ff 100644 --- a/Andromida/App/Views/Rituals/Components/HabitPerformanceView.swift +++ b/Andromida/App/Views/Rituals/Components/HabitPerformanceView.swift @@ -11,20 +11,32 @@ import Bedrock /// A view showing habit completion performance within a ritual. struct HabitPerformanceView: View { let habitRates: [(habit: ArcHabit, rate: Double)] + @Environment(\.horizontalSizeClass) private var horizontalSizeClass private var sortedByRate: [(habit: ArcHabit, rate: Double)] { habitRates.sorted { $0.rate > $1.rate } } + /// Grid columns for habits - 2 columns on regular width when multiple habits + private var habitColumns: [GridItem] { + AdaptiveColumns.columns( + compactCount: 1, + regularCount: habitRates.count > 1 ? 2 : 1, + spacing: Design.Spacing.medium, + horizontalSizeClass: horizontalSizeClass + ) + } + var body: some View { VStack(alignment: .leading, spacing: Design.Spacing.small) { Text(String(localized: "Habit Performance")) .font(.headline) .foregroundStyle(AppTextColors.primary) - VStack(spacing: Design.Spacing.xSmall) { + LazyVGrid(columns: habitColumns, alignment: .leading, spacing: Design.Spacing.xSmall) { ForEach(sortedByRate, id: \.habit.id) { item in habitRow(item.habit, rate: item.rate) + .frame(maxHeight: .infinity, alignment: .top) } } } diff --git a/Andromida/App/Views/Rituals/RitualDetailView.swift b/Andromida/App/Views/Rituals/RitualDetailView.swift index 7effbcd..233015b 100644 --- a/Andromida/App/Views/Rituals/RitualDetailView.swift +++ b/Andromida/App/Views/Rituals/RitualDetailView.swift @@ -5,6 +5,7 @@ struct RitualDetailView: View { @Bindable var store: RitualStore @Bindable var categoryStore: CategoryStore @Environment(\.dismiss) private var dismiss + @Environment(\.horizontalSizeClass) private var horizontalSizeClass private let ritual: Ritual @@ -18,6 +19,32 @@ struct RitualDetailView: View { self.categoryStore = categoryStore self.ritual = ritual } + + /// Whether to use wide layout on iPad/landscape + private var useWideLayout: Bool { + horizontalSizeClass == .regular + } + + /// Grid columns for habits - 2 columns on regular width when multiple habits + private var habitColumns: [GridItem] { + let habits = store.habits(for: ritual) + return AdaptiveColumns.columns( + compactCount: 1, + regularCount: habits.count > 1 ? 2 : 1, + spacing: Design.Spacing.medium, + horizontalSizeClass: horizontalSizeClass + ) + } + + /// Grid columns for arc history - 2 columns on regular width when multiple arcs + private var arcColumns: [GridItem] { + AdaptiveColumns.columns( + compactCount: 1, + regularCount: completedArcs.count > 1 ? 2 : 1, + spacing: Design.Spacing.medium, + horizontalSizeClass: horizontalSizeClass + ) + } private var daysRemaining: Int { store.daysRemaining(for: ritual) @@ -275,7 +302,7 @@ struct RitualDetailView: View { subtitle: String(localized: "Tap to check in") ) - VStack(spacing: Design.Spacing.medium) { + LazyVGrid(columns: habitColumns, alignment: .leading, spacing: Design.Spacing.medium) { ForEach(store.habits(for: ritual)) { habit in TodayHabitRowView( title: habit.title, @@ -283,6 +310,7 @@ struct RitualDetailView: View { isCompleted: store.isHabitCompletedToday(habit), action: { store.toggleHabitCompletion(habit) } ) + .frame(maxHeight: .infinity, alignment: .top) } } } @@ -378,9 +406,10 @@ struct RitualDetailView: View { .font(.caption) .foregroundStyle(AppTextColors.tertiary) } else { - VStack(spacing: Design.Spacing.small) { + LazyVGrid(columns: arcColumns, alignment: .leading, spacing: Design.Spacing.small) { ForEach(completedArcs) { arc in arcHistoryRow(arc) + .frame(maxHeight: .infinity, alignment: .top) } } } diff --git a/Andromida/App/Views/Rituals/Sheets/ArcRenewalSheet.swift b/Andromida/App/Views/Rituals/Sheets/ArcRenewalSheet.swift index 85d73e8..51e6062 100644 --- a/Andromida/App/Views/Rituals/Sheets/ArcRenewalSheet.swift +++ b/Andromida/App/Views/Rituals/Sheets/ArcRenewalSheet.swift @@ -58,6 +58,7 @@ struct ArcRenewalSheet: View { .sheet(isPresented: $showingEditSheet) { RitualEditSheet(store: store, categoryStore: categoryStore, ritual: ritual) } + .presentationDetents([.large]) } private var celebrationHeader: some View { diff --git a/Andromida/App/Views/Rituals/Sheets/PresetLibrarySheet.swift b/Andromida/App/Views/Rituals/Sheets/PresetLibrarySheet.swift index 6bfa67c..e959984 100644 --- a/Andromida/App/Views/Rituals/Sheets/PresetLibrarySheet.swift +++ b/Andromida/App/Views/Rituals/Sheets/PresetLibrarySheet.swift @@ -194,7 +194,7 @@ struct PresetDetailSheet: View { } } } - .presentationDetents([.medium, .large]) + .presentationDetents([.large]) .presentationDragIndicator(.visible) } diff --git a/Andromida/App/Views/Rituals/Sheets/RitualEditSheet.swift b/Andromida/App/Views/Rituals/Sheets/RitualEditSheet.swift index 323aea2..324a810 100644 --- a/Andromida/App/Views/Rituals/Sheets/RitualEditSheet.swift +++ b/Andromida/App/Views/Rituals/Sheets/RitualEditSheet.swift @@ -554,7 +554,7 @@ struct IconPickerSheet: View { } } } - .presentationDetents([.medium, .large]) + .presentationDetents([.large]) .presentationDragIndicator(.visible) } @@ -670,7 +670,7 @@ struct HabitIconPickerSheet: View { } } } - .presentationDetents([.medium, .large]) + .presentationDetents([.large]) .presentationDragIndicator(.visible) } diff --git a/Andromida/App/Views/Settings/CategoryEditSheet.swift b/Andromida/App/Views/Settings/CategoryEditSheet.swift index 09c321c..a715bf6 100644 --- a/Andromida/App/Views/Settings/CategoryEditSheet.swift +++ b/Andromida/App/Views/Settings/CategoryEditSheet.swift @@ -119,7 +119,7 @@ struct CategoryEditSheet: View { Text(String(localized: "Rituals using this category will be set to no category.")) } } - .presentationDetents([.medium]) + .presentationDetents([.large]) } private func loadCategory() { diff --git a/Andromida/App/Views/Today/TodayView.swift b/Andromida/App/Views/Today/TodayView.swift index a6bcd95..153fdbe 100644 --- a/Andromida/App/Views/Today/TodayView.swift +++ b/Andromida/App/Views/Today/TodayView.swift @@ -51,7 +51,7 @@ struct TodayView: View { } } else { // Use 2-column grid on iPad/landscape when multiple rituals - LazyVGrid(columns: ritualColumns, alignment: .top, spacing: Design.Spacing.large) { + LazyVGrid(columns: ritualColumns, alignment: .leading, spacing: Design.Spacing.large) { ForEach(todayRituals) { ritual in TodayRitualSectionView( focusTitle: ritual.title, @@ -63,6 +63,7 @@ struct TodayView: View { iconName: ritual.iconName, timeOfDay: ritual.timeOfDay ) + .frame(maxHeight: .infinity, alignment: .top) } } }