refactor: notifications and settings view models; enhance login and registration UI
This commit is contained in:
@@ -4,6 +4,7 @@ import SwiftUI
|
||||
@MainActor
|
||||
class AuthViewModel: ObservableObject {
|
||||
@Published var isAuthenticated = false
|
||||
@Published var isCheckingAuth = true
|
||||
@Published var currentUser: UserResponse?
|
||||
@Published var isLoading = false
|
||||
@Published var error: String?
|
||||
@@ -12,39 +13,28 @@ class AuthViewModel: ObservableObject {
|
||||
private let keychain = KeychainService.shared
|
||||
|
||||
func checkAuthStatus() async {
|
||||
#if DEBUG
|
||||
if PreviewData.isPreviewMode {
|
||||
currentUser = PreviewData.mockUser
|
||||
isAuthenticated = true
|
||||
return
|
||||
}
|
||||
#endif
|
||||
guard keychain.loadAccessToken() != nil else {
|
||||
isAuthenticated = false
|
||||
isCheckingAuth = false
|
||||
return
|
||||
}
|
||||
isLoading = true
|
||||
defer { isLoading = false }
|
||||
do {
|
||||
currentUser = try await auth.getMe()
|
||||
isAuthenticated = true
|
||||
isCheckingAuth = false
|
||||
await requestPushIfNeeded()
|
||||
} catch APIError.unauthorized {
|
||||
isAuthenticated = false
|
||||
isCheckingAuth = false
|
||||
} catch {
|
||||
isAuthenticated = false
|
||||
// Network/transient errors — keep authenticated if we already were
|
||||
if !isAuthenticated {
|
||||
isAuthenticated = false
|
||||
}
|
||||
isCheckingAuth = false
|
||||
}
|
||||
}
|
||||
|
||||
#if DEBUG
|
||||
func enterPreviewMode() async {
|
||||
PreviewData.isPreviewMode = true
|
||||
currentUser = PreviewData.mockUser
|
||||
isAuthenticated = true
|
||||
await PreviewData.startMockLiveActivity()
|
||||
}
|
||||
#endif
|
||||
|
||||
func login(email: String, password: String) async {
|
||||
isLoading = true
|
||||
error = nil
|
||||
@@ -84,15 +74,6 @@ class AuthViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
func logout() async {
|
||||
#if DEBUG
|
||||
if PreviewData.isPreviewMode {
|
||||
await PreviewData.stopMockLiveActivity()
|
||||
PreviewData.isPreviewMode = false
|
||||
isAuthenticated = false
|
||||
currentUser = nil
|
||||
return
|
||||
}
|
||||
#endif
|
||||
isLoading = true
|
||||
defer { isLoading = false }
|
||||
do {
|
||||
|
||||
@@ -10,6 +10,7 @@ class NotificationsViewModel: ObservableObject {
|
||||
@Published var isLoadingMore = false
|
||||
@Published var error: String?
|
||||
@Published var hasMore = true
|
||||
private var hasLoadedOnce = false
|
||||
|
||||
private let service = NotificationsAPIService.shared
|
||||
private let limit = 50
|
||||
@@ -17,17 +18,13 @@ class NotificationsViewModel: ObservableObject {
|
||||
private var pollingTask: Task<Void, Never>?
|
||||
|
||||
func load() async {
|
||||
#if DEBUG
|
||||
if PreviewData.isPreviewMode {
|
||||
notifications = PreviewData.mockNotifications
|
||||
hasMore = false
|
||||
return
|
||||
}
|
||||
#endif
|
||||
isLoading = true
|
||||
isLoading = !hasLoadedOnce
|
||||
error = nil
|
||||
currentOffset = 0
|
||||
defer { isLoading = false }
|
||||
defer {
|
||||
isLoading = false
|
||||
hasLoadedOnce = true
|
||||
}
|
||||
do {
|
||||
let page = try await service.getNotifications(limit: limit, offset: 0)
|
||||
notifications = page.notifications
|
||||
@@ -40,9 +37,6 @@ class NotificationsViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
func loadMore() async {
|
||||
#if DEBUG
|
||||
if PreviewData.isPreviewMode { return }
|
||||
#endif
|
||||
guard !isLoadingMore && hasMore else { return }
|
||||
isLoadingMore = true
|
||||
defer { isLoadingMore = false }
|
||||
@@ -60,29 +54,33 @@ class NotificationsViewModel: ObservableObject {
|
||||
|
||||
func markAsRead(_ notification: AppNotification) async {
|
||||
guard !notification.isRead else { return }
|
||||
#if DEBUG
|
||||
if PreviewData.isPreviewMode {
|
||||
if let index = notifications.firstIndex(where: { $0.id == notification.id }) {
|
||||
notifications[index] = notification.withReadAt(Date())
|
||||
}
|
||||
return
|
||||
|
||||
// Optimistic update — reflect read state immediately so the list
|
||||
// shows the correct card style even if the user navigates back
|
||||
// before the API call completes.
|
||||
if let index = notifications.firstIndex(where: { $0.id == notification.id }) {
|
||||
notifications[index] = notification.withReadAt(Date())
|
||||
unreadCount = max(0, unreadCount - 1)
|
||||
updateBadge()
|
||||
}
|
||||
#endif
|
||||
|
||||
do {
|
||||
try await service.markAsRead(id: notification.id)
|
||||
if let index = notifications.firstIndex(where: { $0.id == notification.id }) {
|
||||
notifications[index] = notification.withReadAt(Date())
|
||||
}
|
||||
updateBadge()
|
||||
} catch is CancellationError {
|
||||
// View disappeared before the request finished — keep
|
||||
// optimistic state; polling will reconcile if needed.
|
||||
} catch {
|
||||
// Rollback on real failure
|
||||
if let index = notifications.firstIndex(where: { $0.id == notification.id }) {
|
||||
notifications[index] = notification
|
||||
unreadCount += 1
|
||||
updateBadge()
|
||||
}
|
||||
self.error = error.localizedDescription
|
||||
}
|
||||
}
|
||||
|
||||
func markAllAsRead() async {
|
||||
#if DEBUG
|
||||
if PreviewData.isPreviewMode { return }
|
||||
#endif
|
||||
do {
|
||||
try await service.markAllAsRead()
|
||||
await load()
|
||||
@@ -92,9 +90,6 @@ class NotificationsViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
func startPolling() {
|
||||
#if DEBUG
|
||||
if PreviewData.isPreviewMode { return }
|
||||
#endif
|
||||
guard pollingTask == nil else { return }
|
||||
pollingTask = Task {
|
||||
while !Task.isCancelled {
|
||||
|
||||
@@ -11,12 +11,6 @@ class SettingsViewModel: ObservableObject {
|
||||
private let service = NotificationsAPIService.shared
|
||||
|
||||
func loadSessions() async {
|
||||
#if DEBUG
|
||||
if PreviewData.isPreviewMode {
|
||||
sessions = PreviewData.mockSessions
|
||||
return
|
||||
}
|
||||
#endif
|
||||
isLoading = true
|
||||
defer { isLoading = false }
|
||||
do {
|
||||
@@ -27,12 +21,6 @@ class SettingsViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
func deleteSession(_ session: SessionResponse) async {
|
||||
#if DEBUG
|
||||
if PreviewData.isPreviewMode {
|
||||
sessions.removeAll { $0.id == session.id }
|
||||
return
|
||||
}
|
||||
#endif
|
||||
do {
|
||||
try await service.deleteSession(id: session.id)
|
||||
sessions.removeAll { $0.id == session.id }
|
||||
@@ -42,12 +30,6 @@ class SettingsViewModel: ObservableObject {
|
||||
}
|
||||
|
||||
func changePassword(current: String, new: String) async -> Bool {
|
||||
#if DEBUG
|
||||
if PreviewData.isPreviewMode {
|
||||
successMessage = String(localized: "password_changed_success")
|
||||
return true
|
||||
}
|
||||
#endif
|
||||
isLoading = true
|
||||
error = nil
|
||||
defer { isLoading = false }
|
||||
|
||||
Reference in New Issue
Block a user