Files
platform/ios/Platform/Platform/Features/Home/HomeViewModel.swift
Yusuf Suleman 69af4b84a5
All checks were successful
Security Checks / dependency-audit (push) Successful in 12s
Security Checks / secret-scanning (push) Successful in 3s
Security Checks / dockerfile-lint (push) Successful in 3s
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>
2026-04-03 02:36:43 -05:00

75 lines
2.2 KiB
Swift

import SwiftUI
import PhotosUI
@Observable
final class HomeViewModel {
private let repo = FitnessRepository.shared
var totalCalories: Double = 0
var calorieGoal: Double = 2000
var isLoading = false
// Background image
var backgroundImage: UIImage?
var selectedPhoto: PhotosPickerItem?
private let backgroundKey = "homeBackgroundImage"
init() {
loadSavedBackground()
}
var hasBackground: Bool {
backgroundImage != nil
}
func loadTodayData() async {
isLoading = true
let today = Date().apiDateString
async let entriesTask: () = repo.loadEntries(date: today)
async let goalTask: () = repo.loadGoal(date: today)
_ = await (entriesTask, goalTask)
totalCalories = repo.entries.reduce(0) { $0 + $1.snapshotCalories }
calorieGoal = repo.goal?.calories ?? 2000
isLoading = false
}
// MARK: - Background Image
func handlePhotoSelection() async {
guard let item = selectedPhoto else { return }
guard let data = try? await item.loadTransferable(type: Data.self) else { return }
guard let original = UIImage(data: data) else { return }
let resized = resizeImage(original, maxWidth: 1200)
backgroundImage = resized
if let jpegData = resized.jpegData(compressionQuality: 0.8) {
UserDefaults.standard.set(jpegData, forKey: backgroundKey)
}
selectedPhoto = nil
}
func removeBackground() {
backgroundImage = nil
UserDefaults.standard.removeObject(forKey: backgroundKey)
}
private func loadSavedBackground() {
if let data = UserDefaults.standard.data(forKey: backgroundKey),
let image = UIImage(data: data) {
backgroundImage = image
}
}
private func resizeImage(_ image: UIImage, maxWidth: CGFloat) -> UIImage {
let scale = maxWidth / image.size.width
guard scale < 1 else { return image }
let newSize = CGSize(width: maxWidth, height: image.size.height * scale)
let renderer = UIGraphicsImageRenderer(size: newSize)
return renderer.image { _ in
image.draw(in: CGRect(origin: .zero, size: newSize))
}
}
}