Files
honeyDueKMP/iosApp/iosApp/Profile/ProfileViewModel.swift
Trey t 63a54434ed Add 1-hour cache timeout and fix pull-to-refresh across iOS
- Add configurable cache timeout (CACHE_TIMEOUT_MS) to DataManager
- Fix cache to work with empty results (contractors, documents, residences)
- Change Documents/Warranties view to use client-side filtering for cache efficiency
- Add pull-to-refresh support for empty state views in ListAsyncContentView
- Fix ContractorsListView to pass forceRefresh parameter correctly
- Fix TaskViewModel loading spinner not stopping after refresh completes
- Remove duplicate cache checks in iOS ViewModels, delegate to Kotlin APILayer

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-12-03 09:50:57 -06:00

127 lines
4.1 KiB
Swift

import Foundation
import ComposeApp
import Combine
/// ViewModel for user profile management.
/// Observes DataManagerObservable for current user.
/// Calls APILayer directly for profile updates.
@MainActor
class ProfileViewModel: ObservableObject {
// MARK: - Published Properties
@Published var firstName: String = ""
@Published var lastName: String = ""
@Published var email: String = ""
@Published var isLoading: Bool = false
@Published var isLoadingUser: Bool = true
@Published var errorMessage: String?
@Published var successMessage: String?
// MARK: - Private Properties
private let tokenStorage: TokenStorageProtocol
private var cancellables = Set<AnyCancellable>()
// MARK: - Initialization
init(tokenStorage: TokenStorageProtocol? = nil) {
self.tokenStorage = tokenStorage ?? Dependencies.current.makeTokenStorage()
// Observe current user from DataManagerObservable
DataManagerObservable.shared.$currentUser
.receive(on: DispatchQueue.main)
.sink { [weak self] user in
if let user = user {
self?.firstName = user.firstName ?? ""
self?.lastName = user.lastName ?? ""
self?.email = user.email
self?.isLoadingUser = false
}
}
.store(in: &cancellables)
// Load current user data
loadCurrentUser()
}
// MARK: - Public Methods
func loadCurrentUser() {
guard tokenStorage.getToken() != nil else {
errorMessage = "Not authenticated"
isLoadingUser = false
return
}
// Check if we already have user data
if DataManagerObservable.shared.currentUser != nil {
isLoadingUser = false
return
}
isLoadingUser = true
errorMessage = nil
Task {
do {
let result = try await APILayer.shared.getCurrentUser(forceRefresh: false)
// DataManager is updated by APILayer, UI updates via Combine observation
if result is ApiResultSuccess<User> {
self.isLoadingUser = false
self.errorMessage = nil
} else if let error = result as? ApiResultError {
self.errorMessage = ErrorMessageParser.parse(error.message)
self.isLoadingUser = false
}
} catch {
self.errorMessage = error.localizedDescription
self.isLoadingUser = false
}
}
}
func updateProfile() {
guard !email.isEmpty else {
errorMessage = "Email is required"
return
}
guard let token = tokenStorage.getToken() else {
errorMessage = "Not authenticated"
return
}
isLoading = true
errorMessage = nil
successMessage = nil
Task {
do {
let request = UpdateProfileRequest(
firstName: firstName.isEmpty ? nil : firstName,
lastName: lastName.isEmpty ? nil : lastName,
email: email
)
let result = try await APILayer.shared.updateProfile(token: token, request: request)
// DataManager is updated by APILayer, UI updates via Combine observation
if result is ApiResultSuccess<User> {
self.isLoading = false
self.errorMessage = nil
self.successMessage = "Profile updated successfully"
} else if let error = result as? ApiResultError {
self.isLoading = false
self.errorMessage = ErrorMessageParser.parse(error.message)
self.successMessage = nil
}
} catch {
self.isLoading = false
self.errorMessage = error.localizedDescription
self.successMessage = nil
}
}
}
func clearMessages() {
errorMessage = nil
successMessage = nil
}
}