159 lines
4.8 KiB
Swift
159 lines
4.8 KiB
Swift
import Foundation
|
|
import SwiftUI
|
|
import Combine
|
|
|
|
@Observable
|
|
class OfflineBookmarksViewModel {
|
|
var state: OfflineBookmarkSyncState = .idle
|
|
|
|
private let syncUseCase: POfflineBookmarkSyncUseCase
|
|
private var cancellables = Set<AnyCancellable>()
|
|
private let successDelaySubject = PassthroughSubject<Int, Never>()
|
|
private var completionTimerActive = false
|
|
|
|
init(_ factory: UseCaseFactory = DefaultUseCaseFactory.shared) {
|
|
self.syncUseCase = factory.makeOfflineBookmarkSyncUseCase()
|
|
setupBindings()
|
|
refreshState()
|
|
}
|
|
|
|
// MARK: - Public Methods
|
|
|
|
func syncOfflineBookmarks() async {
|
|
guard case .pending(let count) = state else { return }
|
|
|
|
state = .syncing(count: count, status: nil)
|
|
await syncUseCase.syncOfflineBookmarks()
|
|
}
|
|
|
|
func refreshState() {
|
|
let currentCount = syncUseCase.getOfflineBookmarksCount()
|
|
updateStateWithCount(currentCount)
|
|
}
|
|
|
|
// MARK: - Private Setup
|
|
|
|
private func setupBindings() {
|
|
setupSyncBindings()
|
|
setupAppLifecycleBindings()
|
|
}
|
|
|
|
private func setupSyncBindings() {
|
|
syncUseCase.isSyncing
|
|
.receive(on: DispatchQueue.main)
|
|
.sink { [weak self] isSyncing in
|
|
self?.handleSyncingStateChange(isSyncing)
|
|
}
|
|
.store(in: &cancellables)
|
|
|
|
syncUseCase.syncStatus
|
|
.receive(on: DispatchQueue.main)
|
|
.sink { [weak self] status in
|
|
self?.handleSyncStatusUpdate(status)
|
|
}
|
|
.store(in: &cancellables)
|
|
|
|
// Auto-reset success state after 2 seconds
|
|
successDelaySubject
|
|
.delay(for: .seconds(2), scheduler: DispatchQueue.main)
|
|
.sink { [weak self] _ in
|
|
self?.state = .idle
|
|
}
|
|
.store(in: &cancellables)
|
|
}
|
|
|
|
private func setupAppLifecycleBindings() {
|
|
let foregroundPublisher = NotificationCenter.default.publisher(for: UIApplication.willEnterForegroundNotification)
|
|
let activePublisher = NotificationCenter.default.publisher(for: UIApplication.didBecomeActiveNotification)
|
|
|
|
Publishers.Merge(foregroundPublisher, activePublisher)
|
|
.receive(on: DispatchQueue.main)
|
|
.sink { [weak self] _ in
|
|
self?.refreshState()
|
|
}
|
|
.store(in: &cancellables)
|
|
}
|
|
|
|
// MARK: - State Management
|
|
|
|
private func updateStateWithCount(_ count: Int) {
|
|
switch state {
|
|
case .idle:
|
|
if count > 0 {
|
|
state = .pending(count: count)
|
|
}
|
|
case .pending:
|
|
state = count > 0 ? .pending(count: count) : .idle
|
|
case .syncing:
|
|
// Keep syncing state - will be updated by sync handlers
|
|
break
|
|
case .success:
|
|
// Success state is temporary - handled by timer
|
|
break
|
|
case .error:
|
|
state = count > 0 ? .pending(count: count) : .idle
|
|
}
|
|
}
|
|
|
|
// MARK: - Sync Event Handlers
|
|
|
|
private func handleSyncingStateChange(_ isSyncing: Bool) {
|
|
if isSyncing {
|
|
transitionToSyncingIfPending()
|
|
} else {
|
|
// Only handle completion if we were actually syncing
|
|
if case .syncing = state {
|
|
handleSyncCompletion()
|
|
}
|
|
}
|
|
}
|
|
|
|
private func transitionToSyncingIfPending() {
|
|
if case .pending(let count) = state {
|
|
state = .syncing(count: count, status: nil)
|
|
}
|
|
}
|
|
|
|
private func handleSyncCompletion() {
|
|
guard !completionTimerActive else {
|
|
return
|
|
}
|
|
|
|
completionTimerActive = true
|
|
|
|
// wait for 0.5 seconds
|
|
Timer.publish(every: 0.5, on: .main, in: .common)
|
|
.autoconnect()
|
|
.first()
|
|
.sink { [weak self] _ in
|
|
guard let self = self else { return }
|
|
|
|
self.completionTimerActive = false
|
|
|
|
guard case .syncing(let originalCount, _) = self.state else {
|
|
return
|
|
}
|
|
|
|
let remainingCount = self.syncUseCase.getOfflineBookmarksCount()
|
|
|
|
if remainingCount == 0 {
|
|
self.state = .success(syncedCount: originalCount)
|
|
self.successDelaySubject.send(originalCount)
|
|
} else {
|
|
self.state = .pending(count: remainingCount)
|
|
}
|
|
}
|
|
.store(in: &cancellables)
|
|
}
|
|
|
|
private func handleSyncStatusUpdate(_ status: String?) {
|
|
if case .syncing(let count, _) = state {
|
|
state = .syncing(count: count, status: status)
|
|
}
|
|
}
|
|
|
|
deinit {
|
|
cancellables.removeAll()
|
|
}
|
|
}
|