feat: floating tab bar, NLP date parsing, and task date picker
- Tab bar: TickTick-style floating pill (cornerRadius 28, shadow, systemGray5 active highlight) - NLP: month+day parsing (8th June, June 8), recurrence (every year/week/etc), intent stripping (remind me of) - Recurrence chip in add-task toolbar; isRecurring saved with task - Date chip defaults to Today on new tasks; tapping opens full date picker - Date picker: calendar grid, inline time wheel, Repeat menu (Date tab) - Duration tab: Start/End column selector, live duration counter, All Day toggle - TaskItem: added endDate and isAllDay fields - Fixed hidden Unicode character corrupting + operator in TodayView Co-Authored-By: Kutesir <tqwyy79vzn@privaterelay.appleid.com>
This commit is contained in:
@@ -77,6 +77,7 @@ struct ContentView: View {
|
||||
.preferredColorScheme(preferredScheme)
|
||||
.fullScreenCover(isPresented: $showOnboarding) {
|
||||
OnboardingView(isPresented: $showOnboarding)
|
||||
.environmentObject(workoutVM)
|
||||
.onDisappear { hasOnboarded = true }
|
||||
}
|
||||
.onAppear {
|
||||
@@ -101,6 +102,8 @@ struct ContentView: View {
|
||||
showQuickAddTask = true
|
||||
case .today:
|
||||
withAnimation(KisaniSpring.micro) { selectedTab = 0 }
|
||||
case .next3:
|
||||
withAnimation(KisaniSpring.micro) { selectedTab = 0 }
|
||||
case .calendar:
|
||||
withAnimation(KisaniSpring.micro) { selectedTab = 1 }
|
||||
case .workout:
|
||||
@@ -165,11 +168,8 @@ struct ContentView: View {
|
||||
selection: $selectedTab,
|
||||
calIcon: calIconImage,
|
||||
showMatrix: showMatrixTab,
|
||||
showWorkout: showWorkoutTab,
|
||||
isCollapsed: tabState.isCollapsed
|
||||
showWorkout: showWorkoutTab
|
||||
)
|
||||
.padding(.horizontal, 24)
|
||||
.padding(.bottom, 8)
|
||||
}
|
||||
.onAppear { UITabBar.appearance().isHidden = true }
|
||||
}
|
||||
@@ -202,12 +202,10 @@ struct ContentView: View {
|
||||
|
||||
// MARK: - Custom Tab Bar
|
||||
|
||||
// Adds a transparent bottom safe-area inset equal to the floating tab bar height,
|
||||
// so scroll content and FABs inside each tab clear the bar.
|
||||
private extension View {
|
||||
func tabBarInset() -> some View {
|
||||
safeAreaInset(edge: .bottom, spacing: 0) {
|
||||
Color.clear.frame(height: 62)
|
||||
Color.clear.frame(height: 64)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -217,7 +215,6 @@ struct KisaniTabBar: View {
|
||||
let calIcon: Image
|
||||
var showMatrix: Bool
|
||||
var showWorkout: Bool
|
||||
var isCollapsed: Bool = false
|
||||
@Environment(\.colorScheme) private var cs
|
||||
|
||||
private struct Entry: Identifiable {
|
||||
@@ -244,33 +241,35 @@ struct KisaniTabBar: View {
|
||||
} label: {
|
||||
ZStack {
|
||||
if selection == entry.id {
|
||||
RoundedRectangle(cornerRadius: 14)
|
||||
.fill(AppColors.surface2(cs))
|
||||
.frame(width: 52, height: 44)
|
||||
.transition(.opacity.combined(with: .scale(scale: 0.9)))
|
||||
RoundedRectangle(cornerRadius: 16)
|
||||
.fill(Color(uiColor: cs == .dark ? .tertiarySystemFill : .systemGray5))
|
||||
.frame(width: 62, height: 42)
|
||||
.transition(.opacity.combined(with: .scale(scale: 0.88)))
|
||||
}
|
||||
|
||||
if let img = entry.custom {
|
||||
img
|
||||
.font(.system(size: isCollapsed ? 17 : 20))
|
||||
.foregroundColor(selection == entry.id ? AppColors.accent : AppColors.text(cs))
|
||||
.font(.system(size: 22))
|
||||
.foregroundColor(selection == entry.id ? AppColors.accent : AppColors.text3(cs))
|
||||
} else if let icon = entry.sfIcon {
|
||||
Image(systemName: icon)
|
||||
.font(.system(size: isCollapsed ? 15 : 17, weight: selection == entry.id ? .semibold : .regular))
|
||||
.foregroundColor(selection == entry.id ? AppColors.accent : AppColors.text(cs))
|
||||
.font(.system(size: 22, weight: selection == entry.id ? .semibold : .regular))
|
||||
.foregroundColor(selection == entry.id ? AppColors.accent : AppColors.text3(cs))
|
||||
}
|
||||
}
|
||||
.frame(maxWidth: .infinity).frame(height: isCollapsed ? 38 : 50)
|
||||
.frame(maxWidth: .infinity)
|
||||
.frame(height: 52)
|
||||
}
|
||||
.buttonStyle(PressButtonStyle(scale: 0.88))
|
||||
.buttonStyle(PressButtonStyle(scale: 0.90))
|
||||
}
|
||||
}
|
||||
.padding(.horizontal, 10)
|
||||
.padding(.horizontal, 8)
|
||||
.background(
|
||||
RoundedRectangle(cornerRadius: 28)
|
||||
.fill(AppColors.surface(cs))
|
||||
.shadow(color: .black.opacity(isCollapsed ? 0.06 : 0.10), radius: 20, x: 0, y: 6)
|
||||
.shadow(color: .black.opacity(0.04), radius: 4, x: 0, y: 1)
|
||||
.fill(Color(uiColor: cs == .dark ? .secondarySystemBackground : .systemBackground))
|
||||
.shadow(color: .black.opacity(cs == .dark ? 0.40 : 0.10), radius: 20, x: 0, y: 6)
|
||||
)
|
||||
.animation(KisaniSpring.snappy, value: isCollapsed)
|
||||
.padding(.horizontal, 20)
|
||||
.padding(.bottom, 10)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user