import 'dart:async'; import 'package:flutter/foundation.dart'; import '../database/app_database.dart'; import '../models/remote_command.dart'; import '../services/remote_sync_service.dart'; class RemoteSyncViewModel extends ChangeNotifier { final RemoteSyncService syncService; RemoteSyncViewModel({required this.syncService}) { _eventSubscription = syncService.events.listen(_onEvent); } late final StreamSubscription _eventSubscription; bool _hasLoaded = false; bool get hasLoaded => _hasLoaded; bool _isSyncing = false; bool get isSyncing => _isSyncing; bool _isConfigured = false; bool get isConfigured => _isConfigured; bool _wasRevoked = false; /// The server rejected this device's token; it needs to be linked again. bool get wasRevoked => _wasRevoked; DateTime? _lastSyncAt; DateTime? get lastSyncAt => _lastSyncAt; String? _lastError; String? get lastError => _lastError; bool _isOffline = false; /// The last sync failed for lack of a connection, not because of an error. bool get isOffline => _isOffline; String? _venueId; String? get venueId => _venueId; List _activity = []; List get activity => _activity; Stream get tabsClosedRemotely => syncService.events.where( (event) => event.type == RemoteSyncEventType.tabClosedRemotely, ); Future init() async { await _refreshStatus(); _lastSyncAt = await syncService.lastSuccessfulSyncAt; await _loadActivity(); if (_isConfigured) syncService.startPeriodicSync(); _hasLoaded = true; notifyListeners(); } /// Called once registration stored new credentials. Future activate() async { await _refreshStatus(); if (_isConfigured) _wasRevoked = false; notifyListeners(); if (!_isConfigured) return; syncService.startPeriodicSync(); await syncNow(); } Future _refreshStatus() async { _isConfigured = await syncService.isConfigured; _venueId = await syncService.venueId; } Future syncNow() async { _isSyncing = true; notifyListeners(); await syncService.sync(); _isSyncing = false; await _loadActivity(); notifyListeners(); } Future _loadActivity() async { try { _activity = await syncService.getRecentActivity(); } on Exception catch (e) { debugPrint('RemoteSyncViewModel: loadActivity error: $e'); } } Future unlink() async { syncService.stopPeriodicSync(); await syncService.clearCredentials(); _isConfigured = false; _wasRevoked = false; _venueId = null; _lastSyncAt = null; _lastError = null; _activity = []; notifyListeners(); } void _onEvent(RemoteSyncEvent event) { switch (event.type) { case RemoteSyncEventType.syncCompleted: _lastSyncAt = DateTime.now(); _lastError = null; _isOffline = false; case RemoteSyncEventType.syncFailed: _lastError = event.message; _isOffline = event.offline; case RemoteSyncEventType.unauthorized: _isConfigured = false; _wasRevoked = true; case RemoteSyncEventType.tabClosedRemotely: case RemoteSyncEventType.conflict: break; } notifyListeners(); } void pause() { syncService.pause(); } void resume() { syncService.resume(); unawaited(syncNow()); } @override void dispose() { _eventSubscription.cancel(); super.dispose(); } }