refactor(providers): apply DisposableChangeNotifierMixin to 11 providers
This commit is contained in:
@@ -12,12 +12,13 @@ import '../services/companion_remote/lan_discovery_service.dart';
|
||||
import '../services/companion_remote/remote_auth_service.dart';
|
||||
import '../services/storage_service.dart';
|
||||
import '../utils/app_logger.dart';
|
||||
import '../mixins/disposable_change_notifier_mixin.dart';
|
||||
|
||||
export '../services/companion_remote/lan_discovery_service.dart' show DiscoveredHost;
|
||||
|
||||
typedef CommandReceivedCallback = void Function(RemoteCommand command);
|
||||
|
||||
class CompanionRemoteProvider with ChangeNotifier {
|
||||
class CompanionRemoteProvider with ChangeNotifier, DisposableChangeNotifierMixin {
|
||||
RemoteSession? _session;
|
||||
CompanionRemotePeerService? _peerService;
|
||||
LanDiscoveryService? _discoveryService;
|
||||
@@ -97,7 +98,7 @@ class CompanionRemoteProvider with ChangeNotifier {
|
||||
_platform = Platform.operatingSystem;
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
}
|
||||
|
||||
/// Initialize crypto context from Plex home data.
|
||||
@@ -159,7 +160,7 @@ class CompanionRemoteProvider with ChangeNotifier {
|
||||
);
|
||||
|
||||
_session = RemoteSession(role: RemoteSessionRole.host, status: RemoteSessionStatus.connected);
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
|
||||
// Start LAN discovery broadcasting
|
||||
_discoveryService ??= LanDiscoveryService();
|
||||
@@ -181,7 +182,7 @@ class CompanionRemoteProvider with ChangeNotifier {
|
||||
status: RemoteSessionStatus.error,
|
||||
errorMessage: e.toString(),
|
||||
);
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -199,7 +200,7 @@ class CompanionRemoteProvider with ChangeNotifier {
|
||||
_session = null;
|
||||
_isPlayerActive = false;
|
||||
_intentionalDisconnect = false;
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
}
|
||||
|
||||
// ── Client: Discovery ──
|
||||
@@ -237,7 +238,7 @@ class CompanionRemoteProvider with ChangeNotifier {
|
||||
_setupPeerServiceListeners();
|
||||
|
||||
_session = RemoteSession(role: RemoteSessionRole.remote, status: RemoteSessionStatus.connecting);
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
|
||||
try {
|
||||
final winner = await _peerService!.joinSessionRacing(
|
||||
@@ -252,12 +253,12 @@ class CompanionRemoteProvider with ChangeNotifier {
|
||||
_lastHostAddresses = [winner];
|
||||
|
||||
_session = _session?.copyWith(status: RemoteSessionStatus.connected);
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
appLogger.d('CompanionRemote: Connected to ${host.name} via $winner');
|
||||
} catch (e) {
|
||||
appLogger.e('CompanionRemote: Failed to connect to host', error: e);
|
||||
_session = _session?.copyWith(status: RemoteSessionStatus.error, errorMessage: e.toString());
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
@@ -279,7 +280,7 @@ class CompanionRemoteProvider with ChangeNotifier {
|
||||
_setupPeerServiceListeners();
|
||||
|
||||
_session = RemoteSession(role: RemoteSessionRole.remote, status: RemoteSessionStatus.connecting);
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
|
||||
try {
|
||||
await _peerService!.joinSession(
|
||||
@@ -293,11 +294,11 @@ class CompanionRemoteProvider with ChangeNotifier {
|
||||
);
|
||||
|
||||
_session = _session?.copyWith(status: RemoteSessionStatus.connected);
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
} catch (e) {
|
||||
appLogger.e('CompanionRemote: Failed to connect to manual host', error: e);
|
||||
_session = _session?.copyWith(status: RemoteSessionStatus.error, errorMessage: e.toString());
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
@@ -327,25 +328,25 @@ class CompanionRemoteProvider with ChangeNotifier {
|
||||
_deviceConnectedSubscription = _peerService!.onDeviceConnected.listen((device) {
|
||||
appLogger.d('CompanionRemote: Device connected: ${device.name}');
|
||||
_session = _session?.copyWith(status: RemoteSessionStatus.connected, connectedDevice: device);
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
});
|
||||
|
||||
_deviceDisconnectedSubscription = _peerService!.onDeviceDisconnected.listen((_) {
|
||||
appLogger.d('CompanionRemote: Device disconnected (intentional: $_intentionalDisconnect)');
|
||||
if (_intentionalDisconnect) {
|
||||
_session = _session?.copyWith(status: RemoteSessionStatus.disconnected, clearConnectedDevice: true);
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
} else if (isHost) {
|
||||
_session = _session?.copyWith(
|
||||
status: RemoteSessionStatus.reconnecting,
|
||||
clearConnectedDevice: true,
|
||||
clearErrorMessage: true,
|
||||
);
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
appLogger.d('CompanionRemote: Host waiting for client to reconnect');
|
||||
} else {
|
||||
_session = _session?.copyWith(status: RemoteSessionStatus.reconnecting);
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
_scheduleReconnect();
|
||||
}
|
||||
});
|
||||
@@ -353,13 +354,13 @@ class CompanionRemoteProvider with ChangeNotifier {
|
||||
_errorSubscription = _peerService!.onError.listen((error) {
|
||||
appLogger.e('CompanionRemote: Error: ${error.message}');
|
||||
_session = _session?.copyWith(status: RemoteSessionStatus.error, errorMessage: error.message);
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
});
|
||||
|
||||
_statusSubscription = _peerService!.onConnectionStateChanged.listen((status) {
|
||||
appLogger.d('CompanionRemote: Status changed: $status');
|
||||
_session = _session?.copyWith(status: status);
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -375,7 +376,7 @@ class CompanionRemoteProvider with ChangeNotifier {
|
||||
final device = RemoteDevice(id: id, name: name, platform: platform);
|
||||
|
||||
_session = _session?.copyWith(connectedDevice: device);
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -383,7 +384,7 @@ class CompanionRemoteProvider with ChangeNotifier {
|
||||
final playerActive = command.data?['playerActive'] as bool? ?? false;
|
||||
if (_isPlayerActive != playerActive) {
|
||||
_isPlayerActive = playerActive;
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,7 +423,7 @@ class CompanionRemoteProvider with ChangeNotifier {
|
||||
errorMessage: 'Connection lost after $_maxReconnectAttempts attempts',
|
||||
);
|
||||
_reconnectAttempts = 0;
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -438,7 +439,7 @@ class CompanionRemoteProvider with ChangeNotifier {
|
||||
if (_lastHostAddresses == null || !isCryptoReady) {
|
||||
appLogger.w('CompanionRemote: No stored context for reconnect');
|
||||
_session = _session?.copyWith(status: RemoteSessionStatus.error, errorMessage: 'Connection lost');
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -464,7 +465,7 @@ class CompanionRemoteProvider with ChangeNotifier {
|
||||
|
||||
_session = _session?.copyWith(status: RemoteSessionStatus.connected, clearErrorMessage: true);
|
||||
_reconnectAttempts = 0;
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
appLogger.d('CompanionRemote: Reconnected successfully');
|
||||
} catch (e) {
|
||||
appLogger.e('CompanionRemote: Reconnect failed', error: e);
|
||||
@@ -484,7 +485,7 @@ class CompanionRemoteProvider with ChangeNotifier {
|
||||
_reconnectTimer?.cancel();
|
||||
_reconnectAttempts = 0;
|
||||
_session = _session?.copyWith(status: RemoteSessionStatus.disconnected, clearConnectedDevice: true);
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
}
|
||||
|
||||
Future<void> leaveSession() async {
|
||||
@@ -506,7 +507,7 @@ class CompanionRemoteProvider with ChangeNotifier {
|
||||
}
|
||||
_isPlayerActive = false;
|
||||
_intentionalDisconnect = false;
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
import '../mixins/disposable_change_notifier_mixin.dart';
|
||||
import '../services/storage_service.dart';
|
||||
|
||||
/// Provider for managing hidden library state across the app.
|
||||
/// This ensures that when a library is hidden/unhidden in one screen,
|
||||
/// all other screens are automatically updated.
|
||||
class HiddenLibrariesProvider extends ChangeNotifier {
|
||||
class HiddenLibrariesProvider extends ChangeNotifier with DisposableChangeNotifierMixin {
|
||||
late StorageService _storageService;
|
||||
Set<String> _hiddenLibraryKeys = {};
|
||||
bool _isInitialized = false;
|
||||
@@ -31,7 +32,7 @@ class HiddenLibrariesProvider extends ChangeNotifier {
|
||||
_storageService = await StorageService.getInstance();
|
||||
_hiddenLibraryKeys = _storageService.getHiddenLibraries();
|
||||
_isInitialized = true;
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
}
|
||||
|
||||
/// Hide a library by its key
|
||||
@@ -41,7 +42,7 @@ class HiddenLibrariesProvider extends ChangeNotifier {
|
||||
if (!_hiddenLibraryKeys.contains(libraryKey)) {
|
||||
_hiddenLibraryKeys = Set.from(_hiddenLibraryKeys)..add(libraryKey);
|
||||
await _storageService.saveHiddenLibraries(_hiddenLibraryKeys);
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,7 +53,7 @@ class HiddenLibrariesProvider extends ChangeNotifier {
|
||||
if (_hiddenLibraryKeys.contains(libraryKey)) {
|
||||
_hiddenLibraryKeys = Set.from(_hiddenLibraryKeys)..remove(libraryKey);
|
||||
await _storageService.saveHiddenLibraries(_hiddenLibraryKeys);
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,6 +64,6 @@ class HiddenLibrariesProvider extends ChangeNotifier {
|
||||
/// Useful if storage was modified outside the provider
|
||||
Future<void> refresh() async {
|
||||
_hiddenLibraryKeys = _storageService.getHiddenLibraries();
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import '../mixins/disposable_change_notifier_mixin.dart';
|
||||
import '../models/plex_library.dart';
|
||||
import '../services/data_aggregation_service.dart';
|
||||
import '../services/storage_service.dart';
|
||||
@@ -12,7 +13,7 @@ enum LibrariesLoadState { initial, loading, loaded, error }
|
||||
/// Provider that serves as the single source of truth for library data.
|
||||
/// Both SideNavigationRail and LibrariesScreen consume this provider
|
||||
/// instead of independently fetching library data.
|
||||
class LibrariesProvider extends ChangeNotifier {
|
||||
class LibrariesProvider extends ChangeNotifier with DisposableChangeNotifierMixin {
|
||||
DataAggregationService? _aggregationService;
|
||||
List<PlexLibrary> _libraries = [];
|
||||
LibrariesLoadState _loadState = LibrariesLoadState.initial;
|
||||
@@ -60,7 +61,7 @@ class LibrariesProvider extends ChangeNotifier {
|
||||
|
||||
_loadState = LibrariesLoadState.loading;
|
||||
_errorMessage = null;
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
|
||||
try {
|
||||
// Fetch libraries from all servers
|
||||
@@ -79,12 +80,12 @@ class LibrariesProvider extends ChangeNotifier {
|
||||
_errorMessage = null;
|
||||
|
||||
appLogger.i('LibrariesProvider: Loaded ${_libraries.length} libraries');
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
} catch (e, stackTrace) {
|
||||
appLogger.e('LibrariesProvider: Failed to load libraries', error: e, stackTrace: stackTrace);
|
||||
_loadState = LibrariesLoadState.error;
|
||||
_errorMessage = e.toString();
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +101,7 @@ class LibrariesProvider extends ChangeNotifier {
|
||||
/// Update the library order and persist it.
|
||||
Future<void> updateLibraryOrder(List<PlexLibrary> orderedLibraries) async {
|
||||
_libraries = List.from(orderedLibraries);
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
|
||||
// Save the new order
|
||||
final storage = await StorageService.getInstance();
|
||||
@@ -115,7 +116,7 @@ class LibrariesProvider extends ChangeNotifier {
|
||||
_libraries = [];
|
||||
_loadState = LibrariesLoadState.initial;
|
||||
_errorMessage = null;
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
appLogger.d('LibrariesProvider: Cleared library data');
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import '../i18n/strings.g.dart';
|
||||
import '../mixins/disposable_change_notifier_mixin.dart';
|
||||
import '../models/download_models.dart';
|
||||
import '../models/plex_metadata.dart';
|
||||
import '../services/offline_watch_sync_service.dart';
|
||||
@@ -18,7 +19,7 @@ import '../utils/global_key_utils.dart';
|
||||
/// - Effective watch status (local changes + cached server data)
|
||||
/// - Offline "OnDeck" calculation for shows
|
||||
/// - Manual mark watched/unwatched while offline
|
||||
class OfflineWatchProvider extends ChangeNotifier {
|
||||
class OfflineWatchProvider extends ChangeNotifier with DisposableChangeNotifierMixin {
|
||||
final OfflineWatchSyncService _syncService;
|
||||
final DownloadProvider _downloadProvider;
|
||||
|
||||
@@ -30,7 +31,7 @@ class OfflineWatchProvider extends ChangeNotifier {
|
||||
}
|
||||
|
||||
void _onSyncServiceChanged() {
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
}
|
||||
|
||||
/// Whether a sync is in progress
|
||||
@@ -178,7 +179,7 @@ class OfflineWatchProvider extends ChangeNotifier {
|
||||
isNowWatched: true,
|
||||
changeType: WatchStateChangeType.watched,
|
||||
);
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
_autoDeleteIfWatched(serverId, ratingKey);
|
||||
}
|
||||
|
||||
@@ -219,7 +220,7 @@ class OfflineWatchProvider extends ChangeNotifier {
|
||||
isNowWatched: false,
|
||||
changeType: WatchStateChangeType.unwatched,
|
||||
);
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
}
|
||||
|
||||
/// Get downloaded episodes for a show with their watch status.
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart';
|
||||
import '../models/plex_metadata.dart';
|
||||
import '../models/play_queue_response.dart';
|
||||
import '../services/plex_client.dart';
|
||||
import '../mixins/disposable_change_notifier_mixin.dart';
|
||||
|
||||
/// Result of trying to locate the current queue index.
|
||||
class _IndexLookupResult {
|
||||
@@ -14,7 +15,7 @@ class _IndexLookupResult {
|
||||
|
||||
/// Manages playback state using Plex's play queue API.
|
||||
/// This provider is session-only and does not persist across app restarts.
|
||||
class PlaybackStateProvider with ChangeNotifier {
|
||||
class PlaybackStateProvider with ChangeNotifier, DisposableChangeNotifierMixin {
|
||||
// Play queue state
|
||||
int? _playQueueId;
|
||||
int _playQueueTotalCount = 0;
|
||||
@@ -61,7 +62,7 @@ class PlaybackStateProvider with ChangeNotifier {
|
||||
void setCurrentItem(PlexMetadata metadata) {
|
||||
if (_isQueueMode && metadata.playQueueItemID != null) {
|
||||
_currentPlayQueueItemID = metadata.playQueueItemID;
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +80,7 @@ class PlaybackStateProvider with ChangeNotifier {
|
||||
|
||||
_contextKey = contextKey;
|
||||
_isQueueMode = true;
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
}
|
||||
|
||||
/// Load more items from the play queue if needed
|
||||
@@ -106,7 +107,7 @@ class PlaybackStateProvider with ChangeNotifier {
|
||||
// Use size or items length as fallback if totalCount is null
|
||||
_playQueueTotalCount = response.playQueueTotalCount ?? response.size ?? response.items!.length;
|
||||
_playQueueShuffled = response.playQueueShuffled;
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
return true;
|
||||
}
|
||||
} catch (e) {
|
||||
@@ -249,6 +250,6 @@ class PlaybackStateProvider with ChangeNotifier {
|
||||
_loadedItems = [];
|
||||
_contextKey = null;
|
||||
_isQueueMode = false;
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../mixins/disposable_change_notifier_mixin.dart';
|
||||
import '../models/transcode_quality_preset.dart';
|
||||
import '../services/settings_service.dart';
|
||||
|
||||
class SettingsProvider extends ChangeNotifier {
|
||||
class SettingsProvider extends ChangeNotifier with DisposableChangeNotifierMixin {
|
||||
SettingsService? _settingsService;
|
||||
bool _isInitialized = false;
|
||||
Future<void>? _initFuture;
|
||||
@@ -21,7 +22,7 @@ class SettingsProvider extends ChangeNotifier {
|
||||
if (_isInitialized) return;
|
||||
_settingsService = await SettingsService.getInstance();
|
||||
_isInitialized = true;
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
}
|
||||
|
||||
/// Re-read settings after an external mutation (import, reset). The provider
|
||||
@@ -30,7 +31,7 @@ class SettingsProvider extends ChangeNotifier {
|
||||
Future<void> reload() async {
|
||||
_settingsService = await SettingsService.getInstance();
|
||||
_isInitialized = true;
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
}
|
||||
|
||||
T _read<T>(Pref<T> pref, T fallback) => _isInitialized ? _settingsService!.read(pref) : fallback;
|
||||
@@ -39,7 +40,7 @@ class SettingsProvider extends ChangeNotifier {
|
||||
if (!_isInitialized) await _initializeSettings();
|
||||
if (_settingsService!.read(pref) == value) return;
|
||||
await _settingsService!.write(pref, value);
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
}
|
||||
|
||||
int get libraryDensity => _read(SettingsService.libraryDensity, LibraryDensity.defaultValue);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import '../mixins/disposable_change_notifier_mixin.dart';
|
||||
import '../models/shader_preset.dart';
|
||||
import '../services/settings_service.dart';
|
||||
import '../services/shader_asset_loader.dart';
|
||||
@@ -7,7 +8,7 @@ import '../services/shader_asset_loader.dart';
|
||||
/// Provider for managing shader preset state.
|
||||
///
|
||||
/// Persists the selected shader preset so it is restored across sessions.
|
||||
class ShaderProvider extends ChangeNotifier {
|
||||
class ShaderProvider extends ChangeNotifier with DisposableChangeNotifierMixin {
|
||||
late SettingsService _settingsService;
|
||||
|
||||
ShaderPreset _savedPreset = ShaderPreset.none;
|
||||
@@ -31,7 +32,7 @@ class ShaderProvider extends ChangeNotifier {
|
||||
_currentPreset = _savedPreset;
|
||||
|
||||
_initialized = true;
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
}
|
||||
|
||||
/// Whether the provider has finished initializing
|
||||
@@ -63,7 +64,7 @@ class ShaderProvider extends ChangeNotifier {
|
||||
_savedPreset = preset;
|
||||
_currentPreset = preset;
|
||||
await _settingsService.write(SettingsService.globalShaderPreset, preset.id);
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
}
|
||||
|
||||
/// Update the current preset without persisting (e.g. toggling off temporarily)
|
||||
@@ -84,7 +85,7 @@ class ShaderProvider extends ChangeNotifier {
|
||||
|
||||
_customPresets.add(preset);
|
||||
await _saveCustomPresets();
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
return preset;
|
||||
}
|
||||
|
||||
@@ -103,7 +104,7 @@ class ShaderProvider extends ChangeNotifier {
|
||||
await _settingsService.write(SettingsService.globalShaderPreset, ShaderPreset.none.id);
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
}
|
||||
|
||||
Future<void> _saveCustomPresets() async {
|
||||
|
||||
@@ -2,10 +2,11 @@ import 'dart:io' show Platform;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:material_symbols_icons/symbols.dart';
|
||||
import '../mixins/disposable_change_notifier_mixin.dart';
|
||||
import '../services/settings_service.dart' as settings;
|
||||
import '../theme/mono_theme.dart';
|
||||
|
||||
class ThemeProvider extends ChangeNotifier {
|
||||
class ThemeProvider extends ChangeNotifier with DisposableChangeNotifierMixin {
|
||||
late settings.SettingsService _settingsService;
|
||||
settings.ThemeMode _themeMode = settings.ThemeMode.system;
|
||||
late Brightness _systemBrightness;
|
||||
@@ -19,7 +20,7 @@ class ThemeProvider extends ChangeNotifier {
|
||||
void _onBrightnessChanged() {
|
||||
_systemBrightness = WidgetsBinding.instance.platformDispatcher.platformBrightness;
|
||||
if (_themeMode == settings.ThemeMode.system) {
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -35,7 +36,7 @@ class ThemeProvider extends ChangeNotifier {
|
||||
_settingsService = await settings.SettingsService.getInstance();
|
||||
_themeMode = _settingsService.read(settings.SettingsService.themeMode);
|
||||
_updateSplashTheme(_themeMode);
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
}
|
||||
|
||||
settings.ThemeMode get themeMode => _themeMode;
|
||||
@@ -81,7 +82,7 @@ class ThemeProvider extends ChangeNotifier {
|
||||
_themeMode = mode;
|
||||
await _settingsService.write(settings.SettingsService.themeMode, mode);
|
||||
_updateSplashTheme(mode);
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,7 +93,7 @@ class ThemeProvider extends ChangeNotifier {
|
||||
final mode = _settingsService.read(settings.SettingsService.themeMode);
|
||||
_themeMode = mode;
|
||||
_updateSplashTheme(mode);
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
}
|
||||
|
||||
void _updateSplashTheme(settings.ThemeMode mode) {
|
||||
|
||||
@@ -24,11 +24,12 @@ import '../services/trackers/tracker_connect_runner.dart';
|
||||
import '../services/trackers/tracker_constants.dart';
|
||||
import '../services/trackers/tracker_coordinator.dart';
|
||||
import '../utils/app_logger.dart';
|
||||
import '../mixins/disposable_change_notifier_mixin.dart';
|
||||
|
||||
/// Owns the active MAL / AniList / Simkl sessions for the currently-selected
|
||||
/// Plex profile. Single rebind seam: [onActiveProfileChanged] loads all three
|
||||
/// sessions from their stores and pushes them to their trackers.
|
||||
class TrackersProvider extends ChangeNotifier {
|
||||
class TrackersProvider extends ChangeNotifier with DisposableChangeNotifierMixin {
|
||||
final MalAuthService _malAuth = MalAuthService();
|
||||
final AnilistAuthService _anilistAuth = AnilistAuthService();
|
||||
final SimklAuthService _simklAuth = SimklAuthService();
|
||||
@@ -77,7 +78,7 @@ class TrackersProvider extends ChangeNotifier {
|
||||
_anilist = results[1] as AnilistSession?;
|
||||
_simkl = results[2] as SimklSession?;
|
||||
_rebindAll();
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
}
|
||||
|
||||
// ───── Connect / disconnect ─────
|
||||
@@ -159,7 +160,7 @@ class TrackersProvider extends ChangeNotifier {
|
||||
if (_connecting != null || alreadyConnected) return false;
|
||||
_connecting = service;
|
||||
_cancelCompleter = Completer<void>();
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
try {
|
||||
return await runConnectPipeline<T>(
|
||||
logLabel: service.name,
|
||||
@@ -173,14 +174,14 @@ class TrackersProvider extends ChangeNotifier {
|
||||
if (c != null && !c.isCompleted) c.complete();
|
||||
_cancelCompleter = null;
|
||||
_connecting = null;
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _clearAndRebind<T>(TrackerAccountStore<T> store, void Function() clearAndRebind) async {
|
||||
await store.clear(_activeUserUuid);
|
||||
clearAndRebind();
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
}
|
||||
|
||||
Future<MalSession> _enrichMal(MalSession raw) async {
|
||||
@@ -240,7 +241,7 @@ class TrackersProvider extends ChangeNotifier {
|
||||
onSessionUpdated: (next) {
|
||||
_mal = next;
|
||||
malAccountStore.save(_activeUserUuid, next);
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -263,7 +264,7 @@ class TrackersProvider extends ChangeNotifier {
|
||||
store.clear(_activeUserUuid);
|
||||
clearSession();
|
||||
rebind();
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@@ -2,6 +2,7 @@ import 'dart:async';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
import '../mixins/disposable_change_notifier_mixin.dart';
|
||||
import '../models/trackers/device_code.dart';
|
||||
import '../services/trackers/tracker_connect_runner.dart';
|
||||
import '../services/trakt/trakt_account_store.dart';
|
||||
@@ -16,7 +17,7 @@ import '../utils/app_logger.dart';
|
||||
///
|
||||
/// Single rebind seam: `onActiveProfileChanged` loads the new profile's
|
||||
/// session and pushes it to both `TraktScrobbleService` and `TraktSyncService`.
|
||||
class TraktAccountProvider extends ChangeNotifier {
|
||||
class TraktAccountProvider extends ChangeNotifier with DisposableChangeNotifierMixin {
|
||||
final TraktAuthService _auth = TraktAuthService();
|
||||
final _store = traktAccountStore;
|
||||
|
||||
@@ -71,7 +72,7 @@ class TraktAccountProvider extends ChangeNotifier {
|
||||
if (c != null && !c.isCompleted) c.complete();
|
||||
_cancelCompleter = null;
|
||||
_isConnecting = false;
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,7 +108,7 @@ class TraktAccountProvider extends ChangeNotifier {
|
||||
session,
|
||||
onSessionInvalidated: _handleSessionInvalidated,
|
||||
);
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
}
|
||||
|
||||
/// Called by [TraktClient] when refresh fails permanently. Clears local state
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../mixins/disposable_change_notifier_mixin.dart';
|
||||
import '../utils/plex_http_exception.dart';
|
||||
import '../models/plex_home.dart';
|
||||
import '../models/plex_home_user.dart';
|
||||
@@ -8,7 +9,7 @@ import '../services/storage_service.dart';
|
||||
import '../utils/app_logger.dart';
|
||||
import '../screens/profile/pin_entry_dialog.dart';
|
||||
|
||||
class UserProfileProvider extends ChangeNotifier {
|
||||
class UserProfileProvider extends ChangeNotifier with DisposableChangeNotifierMixin {
|
||||
PlexHome? _home;
|
||||
PlexHomeUser? _currentUser;
|
||||
PlexUserProfile? _profileSettings;
|
||||
@@ -117,7 +118,7 @@ class UserProfileProvider extends ChangeNotifier {
|
||||
// Profile settings are NOT cached - they will be fetched fresh from API
|
||||
// in refreshProfileSettings()
|
||||
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
}
|
||||
|
||||
/// Fetch the user's profile settings from the API
|
||||
@@ -140,7 +141,7 @@ class UserProfileProvider extends ChangeNotifier {
|
||||
|
||||
appLogger.i('Successfully fetched user profile settings from API');
|
||||
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
} catch (e) {
|
||||
appLogger.w('Failed to fetch user profile settings from API', error: e);
|
||||
// Don't set error state, profile will remain null or keep existing value
|
||||
@@ -210,7 +211,7 @@ class UserProfileProvider extends ChangeNotifier {
|
||||
}
|
||||
}
|
||||
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
} catch (e) {
|
||||
_setError('Failed to load home users: $e');
|
||||
appLogger.e('Failed to load home users', error: e);
|
||||
@@ -286,7 +287,7 @@ class UserProfileProvider extends ChangeNotifier {
|
||||
},
|
||||
);
|
||||
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
|
||||
// Invalidate all cached data and reconnect to all servers with new tokens
|
||||
// The callback will handle server reconnection using the servers list
|
||||
@@ -337,7 +338,7 @@ class UserProfileProvider extends ChangeNotifier {
|
||||
// Update current user from refreshed data
|
||||
if (_home != null) {
|
||||
_currentUser = _home!.getUserByUUID(_currentUser!.uuid);
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -361,7 +362,7 @@ class UserProfileProvider extends ChangeNotifier {
|
||||
_isInitialized = false;
|
||||
|
||||
_clearError();
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
|
||||
appLogger.i('User logged out successfully');
|
||||
} catch (e) {
|
||||
@@ -373,12 +374,12 @@ class UserProfileProvider extends ChangeNotifier {
|
||||
|
||||
void _setLoading(bool loading) {
|
||||
_isLoading = loading;
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
}
|
||||
|
||||
void _setError(String error) {
|
||||
_error = error;
|
||||
notifyListeners();
|
||||
safeNotifyListeners();
|
||||
}
|
||||
|
||||
void _clearError() {
|
||||
|
||||
Reference in New Issue
Block a user