Files
pulse-mobile/PulseHealth/Views/DesignSystem.swift
Daniil Klimov 28fca1de89 feat: major app overhaul — API fixes, glassmorphism UI, health dashboard, notifications
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>
2026-04-05 23:15:36 +03:00

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)
}
}