feat: rebuild iOS app from API audit + new podcast/media service
iOS App (complete rebuild): - Audited all fitness API endpoints against live responses - Models match exact API field names (snapshot_ prefixes, UUID strings) - FoodEntry uses computed properties (foodName, calories, etc.) wrapping snapshot fields - Flexible Int/Double decoding for all numeric fields - AI assistant with raw JSON state management (JSONSerialization, not Codable) - Home dashboard with custom background, frosted glass calorie widget - Fitness: Today/Templates/Goals/Foods tabs - Food search with recent + all sections - Meal sections with colored accent bars, swipe to delete - 120fps ProMotion, iOS 17+ @Observable Podcast/Media Service: - FastAPI backend for podcast RSS + local audiobook folders - Shows, episodes, playback progress, queue management - RSS feed fetching with feedparser + ETag support - Local folder scanning with mutagen for audio metadata - HTTP Range streaming for local audio files - Playback events logging (play/pause/seek/complete) - Reuses brain's PostgreSQL + Redis - media_ prefixed tables Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,37 +1,90 @@
|
||||
import SwiftUI
|
||||
|
||||
struct ContentView: View {
|
||||
@Environment(AuthManager.self) private var authManager
|
||||
@Environment(AuthManager.self) private var auth
|
||||
|
||||
var body: some View {
|
||||
Group {
|
||||
if authManager.isCheckingAuth {
|
||||
LoadingView(message: "Checking session...")
|
||||
} else if authManager.isLoggedIn {
|
||||
if auth.isCheckingAuth {
|
||||
ProgressView()
|
||||
.frame(maxWidth: .infinity, maxHeight: .infinity)
|
||||
.background(Color.canvas)
|
||||
} else if auth.isLoggedIn {
|
||||
MainTabView()
|
||||
} else {
|
||||
LoginView()
|
||||
}
|
||||
}
|
||||
.task {
|
||||
await authManager.checkAuth()
|
||||
await auth.checkAuth()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MainTabView: View {
|
||||
var body: some View {
|
||||
TabView {
|
||||
HomeView()
|
||||
.tabItem {
|
||||
Label("Home", systemImage: "house.fill")
|
||||
}
|
||||
@State private var selectedTab = 0
|
||||
@State private var showAssistant = false
|
||||
|
||||
FitnessTabView()
|
||||
.tabItem {
|
||||
Label("Fitness", systemImage: "flame.fill")
|
||||
}
|
||||
var body: some View {
|
||||
ZStack(alignment: .bottomTrailing) {
|
||||
TabView(selection: $selectedTab) {
|
||||
HomeView()
|
||||
.tabItem {
|
||||
Label("Home", systemImage: "house.fill")
|
||||
}
|
||||
.tag(0)
|
||||
|
||||
FitnessTabView()
|
||||
.tabItem {
|
||||
Label("Fitness", systemImage: "flame.fill")
|
||||
}
|
||||
.tag(1)
|
||||
}
|
||||
.tint(Color.accentWarm)
|
||||
|
||||
Button {
|
||||
showAssistant = true
|
||||
} label: {
|
||||
Image(systemName: "plus")
|
||||
.font(.title2.weight(.semibold))
|
||||
.foregroundStyle(.white)
|
||||
.frame(width: 56, height: 56)
|
||||
.background(Color.accentWarm)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: .black.opacity(0.2), radius: 8, y: 4)
|
||||
}
|
||||
.padding(.trailing, 20)
|
||||
.padding(.bottom, 70)
|
||||
}
|
||||
.sheet(isPresented: $showAssistant) {
|
||||
AssistantSheetView()
|
||||
}
|
||||
.tint(Color.accentWarm)
|
||||
}
|
||||
}
|
||||
|
||||
struct AssistantSheetView: View {
|
||||
@State private var selectedMode = 0
|
||||
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
VStack(spacing: 0) {
|
||||
Picker("Mode", selection: $selectedMode) {
|
||||
Text("AI Chat").tag(0)
|
||||
Text("Quick Add").tag(1)
|
||||
}
|
||||
.pickerStyle(.segmented)
|
||||
.padding(.horizontal)
|
||||
.padding(.top, 8)
|
||||
|
||||
if selectedMode == 0 {
|
||||
AssistantChatView()
|
||||
} else {
|
||||
FoodSearchView(isSheet: true)
|
||||
}
|
||||
}
|
||||
.navigationTitle("Add Food")
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
}
|
||||
.presentationDetents([.large])
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user