API Integration: - Fix logHabit: send "date" instead of "completed_at" - Fix FinanceCategory: "icon" → "emoji" to match API - Fix task priorities: remove level 4, keep 1-3 matching API - Fix habit frequencies: map monthly/interval → "custom" for API - Add token refresh (401 → auto retry with new token) - Add proper error handling (remove try? in save functions, show errors in UI) - Add date field to savings transactions - Add MonthlyPaymentDetail and OverduePayment models - Fix habit completedToday: compute on client from logs (API doesn't return it) - Filter habits by day of week on client (daily/weekly/monthly/interval) Design System (glassmorphism): - New DesignSystem.swift: Theme colors, GlassCard modifier, GlowIcon, GlowStatCard - Custom tab bar with per-tab glow colors (VStack layout, not ZStack overlay) - Deep dark background #06060f across all views - Glass cards with gradient fill + stroke throughout app - App icon: glassmorphism style with teal glow Health Dashboard: - Compact ReadinessBanner with recommendation text - 8 metric tiles: sleep, HR, HRV, steps, SpO2, respiratory rate, energy, distance - Each tile with status indicator (good/ok/bad) and hint text - Heart rate card (min/avg/max) - Weekly trends card (averages) - Recovery score (weighted: 40% sleep, 35% HRV, 25% RHR) - Tips card with actionable recommendations - Sleep detail view with hypnogram (step chart of phases) - Sleep segments timeline from HealthKit (deep/rem/core/awake with exact times) - Line chart replacing bar chart for weekly data - Collect respiratory_rate and sleep phases with timestamps from HealthKit - Background sync every ~30min via BGProcessingTask Notifications: - NotificationService for local push notifications - Morning/evening reminders with native DatePicker (wheel) - Payment reminders: 5 days, 1 day, and day-of for recurring savings - Notification settings in Settings tab UI Fixes: - Fix color picker overflow: HStack → LazyVGrid 5 columns - Fix sheet headers: shorter text, proper padding - Fix task/habit toggle: separate tap zones (checkbox vs edit) - Fix deprecated onChange syntax for iOS 17+ - Savings overview: real monthly payments and detailed overdues from API - Settings: timezone as Menu picker, removed Telegram/server notifications sections - All sheets use .presentationDetents([.large]) Config: - project.yml: real DEVELOPMENT_TEAM, HealthKit + BackgroundModes capabilities - Info.plist: BGTaskScheduler + UIBackgroundModes - Assets.xcassets with AppIcon - CLAUDE.md project documentation Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
107 lines
3.5 KiB
Swift
107 lines
3.5 KiB
Swift
import SwiftUI
|
|
|
|
struct MainTabView: View {
|
|
@EnvironmentObject var authManager: AuthManager
|
|
@AppStorage("colorScheme") private var colorSchemeRaw: String = "dark"
|
|
@State private var selectedTab = 0
|
|
|
|
var preferredColorScheme: ColorScheme? {
|
|
colorSchemeRaw == "light" ? .light : .dark
|
|
}
|
|
|
|
let tabs: [(icon: String, label: String, color: Color)] = [
|
|
("house.fill", "Главная", Theme.teal),
|
|
("chart.bar.fill", "Трекер", Theme.indigo),
|
|
("heart.fill", "Здоровье", Theme.pink),
|
|
("building.columns.fill", "Накопления", Theme.purple),
|
|
("gearshape.fill", "Ещё", Theme.blue),
|
|
]
|
|
|
|
var body: some View {
|
|
VStack(spacing: 0) {
|
|
// Content
|
|
Group {
|
|
switch selectedTab {
|
|
case 0: DashboardView()
|
|
case 1: TrackerView()
|
|
case 2: HealthView()
|
|
case 3: SavingsView()
|
|
case 4: SettingsView()
|
|
default: DashboardView()
|
|
}
|
|
}
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
|
|
|
// Custom Tab Bar
|
|
HStack(spacing: 0) {
|
|
ForEach(0..<tabs.count, id: \.self) { index in
|
|
TabBarButton(
|
|
icon: tabs[index].icon,
|
|
label: tabs[index].label,
|
|
color: tabs[index].color,
|
|
isSelected: selectedTab == index
|
|
) {
|
|
withAnimation(.easeInOut(duration: 0.2)) {
|
|
selectedTab = index
|
|
}
|
|
UIImpactFeedbackGenerator(style: .light).impactOccurred()
|
|
}
|
|
}
|
|
}
|
|
.padding(.horizontal, 8)
|
|
.padding(.top, 8)
|
|
.padding(.bottom, 4)
|
|
.background(
|
|
Color(hex: "06060f")
|
|
.overlay(alignment: .top) {
|
|
Rectangle()
|
|
.fill(Color.white.opacity(0.06))
|
|
.frame(height: 0.5)
|
|
}
|
|
)
|
|
}
|
|
.preferredColorScheme(preferredColorScheme)
|
|
}
|
|
}
|
|
|
|
// MARK: - Tab Bar Button
|
|
|
|
struct TabBarButton: View {
|
|
let icon: String
|
|
let label: String
|
|
let color: Color
|
|
let isSelected: Bool
|
|
let action: () -> Void
|
|
|
|
var body: some View {
|
|
Button(action: action) {
|
|
VStack(spacing: 4) {
|
|
ZStack {
|
|
if isSelected {
|
|
// Glow effect
|
|
Circle()
|
|
.fill(color.opacity(0.3))
|
|
.frame(width: 40, height: 40)
|
|
.blur(radius: 10)
|
|
|
|
Circle()
|
|
.fill(color.opacity(0.15))
|
|
.frame(width: 36, height: 36)
|
|
}
|
|
|
|
Image(systemName: icon)
|
|
.font(.system(size: 18, weight: isSelected ? .semibold : .regular))
|
|
.foregroundColor(isSelected ? color : Color(hex: "555566"))
|
|
}
|
|
.frame(height: 32)
|
|
|
|
Text(label)
|
|
.font(.system(size: 10, weight: isSelected ? .medium : .regular))
|
|
.foregroundColor(isSelected ? color : Color(hex: "555566"))
|
|
}
|
|
.frame(maxWidth: .infinity)
|
|
}
|
|
.buttonStyle(.plain)
|
|
}
|
|
}
|