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>
136 lines
3.9 KiB
Swift
136 lines
3.9 KiB
Swift
import SwiftUI
|
|
|
|
// MARK: - Theme Colors
|
|
|
|
enum Theme {
|
|
static let bg = Color(hex: "06060f")
|
|
static let cardBg = Color.white.opacity(0.06)
|
|
static let cardBorder = Color.white.opacity(0.08)
|
|
static let teal = Color(hex: "0D9488")
|
|
static let tealLight = Color(hex: "14b8a6")
|
|
static let textPrimary = Color.white
|
|
static let textSecondary = Color(hex: "6b7280")
|
|
static let red = Color(hex: "ff4757")
|
|
static let orange = Color(hex: "ffa502")
|
|
static let purple = Color(hex: "7c3aed")
|
|
static let blue = Color(hex: "3b82f6")
|
|
static let pink = Color(hex: "ec4899")
|
|
static let green = Color(hex: "10b981")
|
|
static let indigo = Color(hex: "6366f1")
|
|
}
|
|
|
|
// MARK: - Glass Card Modifier
|
|
|
|
struct GlassCard: ViewModifier {
|
|
var cornerRadius: CGFloat = 20
|
|
func body(content: Content) -> some View {
|
|
content
|
|
.background(
|
|
RoundedRectangle(cornerRadius: cornerRadius)
|
|
.fill(.ultraThinMaterial.opacity(0.3))
|
|
.overlay(
|
|
RoundedRectangle(cornerRadius: cornerRadius)
|
|
.fill(
|
|
LinearGradient(
|
|
colors: [Color.white.opacity(0.08), Color.white.opacity(0.02)],
|
|
startPoint: .topLeading, endPoint: .bottomTrailing
|
|
)
|
|
)
|
|
)
|
|
.overlay(
|
|
RoundedRectangle(cornerRadius: cornerRadius)
|
|
.stroke(
|
|
LinearGradient(
|
|
colors: [Color.white.opacity(0.15), Color.white.opacity(0.03)],
|
|
startPoint: .topLeading, endPoint: .bottomTrailing
|
|
),
|
|
lineWidth: 1
|
|
)
|
|
)
|
|
)
|
|
}
|
|
}
|
|
|
|
extension View {
|
|
func glassCard(cornerRadius: CGFloat = 20) -> some View {
|
|
modifier(GlassCard(cornerRadius: cornerRadius))
|
|
}
|
|
}
|
|
|
|
// MARK: - Glow Icon View
|
|
|
|
struct GlowIcon: View {
|
|
let systemName: String
|
|
let color: Color
|
|
var size: CGFloat = 44
|
|
var iconSize: Font = .body
|
|
|
|
var body: some View {
|
|
ZStack {
|
|
// Glow
|
|
Circle()
|
|
.fill(color.opacity(0.25))
|
|
.frame(width: size * 1.2, height: size * 1.2)
|
|
.blur(radius: 12)
|
|
|
|
// Icon circle
|
|
Circle()
|
|
.fill(color.opacity(0.15))
|
|
.frame(width: size, height: size)
|
|
|
|
Image(systemName: systemName)
|
|
.font(iconSize)
|
|
.foregroundColor(color)
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Glow Stat Card
|
|
|
|
struct GlowStatCard: View {
|
|
let icon: String
|
|
let value: String
|
|
let label: String
|
|
let color: Color
|
|
|
|
var body: some View {
|
|
VStack(spacing: 10) {
|
|
GlowIcon(systemName: icon, color: color, size: 40, iconSize: .title3)
|
|
Text(value)
|
|
.font(.title3.bold().monospacedDigit())
|
|
.foregroundColor(.white)
|
|
Text(label)
|
|
.font(.caption)
|
|
.foregroundColor(Theme.textSecondary)
|
|
.multilineTextAlignment(.center)
|
|
.lineLimit(2)
|
|
}
|
|
.frame(maxWidth: .infinity)
|
|
.padding(.vertical, 18)
|
|
.padding(.horizontal, 8)
|
|
.glassCard(cornerRadius: 18)
|
|
}
|
|
}
|
|
|
|
// MARK: - Section Header
|
|
|
|
struct SectionHeader: View {
|
|
let title: String
|
|
var trailing: String? = nil
|
|
|
|
var body: some View {
|
|
HStack {
|
|
Text(title)
|
|
.font(.headline)
|
|
.foregroundColor(.white)
|
|
Spacer()
|
|
if let t = trailing {
|
|
Text(t)
|
|
.font(.caption)
|
|
.foregroundColor(Theme.textSecondary)
|
|
}
|
|
}
|
|
.padding(.horizontal)
|
|
}
|
|
}
|