diff options
Diffstat (limited to 'player-android/lib/screens')
| -rw-r--r-- | player-android/lib/screens/admin_permissions_screen.dart | 8 | ||||
| -rw-r--r-- | player-android/lib/screens/admin_rescan_screen.dart | 20 | ||||
| -rw-r--r-- | player-android/lib/screens/admin_trash_screen.dart | 11 |
3 files changed, 23 insertions, 16 deletions
diff --git a/player-android/lib/screens/admin_permissions_screen.dart b/player-android/lib/screens/admin_permissions_screen.dart index 0c35a1b..4f7002d 100644 --- a/player-android/lib/screens/admin_permissions_screen.dart +++ b/player-android/lib/screens/admin_permissions_screen.dart @@ -84,8 +84,12 @@ class _AdminPermissionsScreenState if (!mounted || generation != _generation) return; - final users = results[0] as List<User>; - final sets = results[1] as List<MediaSet>; + // Use .cast<T>() for list results so a type mismatch produces a useful + // error at element access rather than silently failing on a direct cast. + // The Map result is kept as-is with a cast because there is no cast() + // method on Map in the Dart core library. + final users = (results[0] as List).cast<User>(); + final sets = (results[1] as List).cast<MediaSet>(); final permsData = results[2] as Map<String, dynamic>; setState(() { diff --git a/player-android/lib/screens/admin_rescan_screen.dart b/player-android/lib/screens/admin_rescan_screen.dart index 3ce8953..4e275c4 100644 --- a/player-android/lib/screens/admin_rescan_screen.dart +++ b/player-android/lib/screens/admin_rescan_screen.dart @@ -39,7 +39,7 @@ class _AdminRescanScreenState extends ConsumerState<AdminRescanScreen> { String? _error; // True while the trigger request is in flight. - bool _isTriggerring = false; + bool _isTriggering = false; // Active polling timer; cancelled in dispose and whenever the scan finishes. Timer? _pollTimer; @@ -117,23 +117,23 @@ class _AdminRescanScreenState extends ConsumerState<AdminRescanScreen> { /// Sends a trigger-rescan request and immediately begins polling for progress. Future<void> _triggerRescan() async { - if (!mounted || _isTriggerring) return; + if (!mounted || _isTriggering) return; setState(() { - _isTriggerring = true; + _isTriggering = true; _error = null; }); try { await ref.read(apiClientProvider).triggerRescan(); if (!mounted) return; - setState(() => _isTriggerring = false); + setState(() => _isTriggering = false); // Start polling immediately so the user sees progress as soon as the // server reports the scan has begun. await _fetchStatus(); } catch (e) { if (!mounted) return; setState(() { - _isTriggerring = false; + _isTriggering = false; _error = adminRescanErrorMessage(e); }); } @@ -175,7 +175,7 @@ class _AdminRescanScreenState extends ConsumerState<AdminRescanScreen> { const SizedBox(height: 32), _TriggerButton( isRunning: _status?.isRunning ?? false, - isTriggerring: _isTriggerring, + isTriggering: _isTriggering, onTap: _triggerRescan, ), ], @@ -398,23 +398,23 @@ class _ErrorRow extends StatelessWidget { class _TriggerButton extends StatelessWidget { const _TriggerButton({ required this.isRunning, - required this.isTriggerring, + required this.isTriggering, required this.onTap, }); final bool isRunning; - final bool isTriggerring; + final bool isTriggering; final VoidCallback onTap; @override Widget build(BuildContext context) { // Disable the button while a scan is active or the trigger is in flight. - final canTrigger = !isRunning && !isTriggerring; + final canTrigger = !isRunning && !isTriggering; return FilledButton.icon( key: const Key('admin_rescan_trigger'), onPressed: canTrigger ? onTap : null, - icon: isTriggerring + icon: isTriggering ? const SizedBox( width: 18, height: 18, diff --git a/player-android/lib/screens/admin_trash_screen.dart b/player-android/lib/screens/admin_trash_screen.dart index 1b63596..db0354a 100644 --- a/player-android/lib/screens/admin_trash_screen.dart +++ b/player-android/lib/screens/admin_trash_screen.dart @@ -87,8 +87,9 @@ class _AdminTrashScreenState extends ConsumerState<AdminTrashScreen> { /// problem. Re-appending (rather than re-inserting at the original index) /// avoids position jitter from concurrent mutations. Future<void> _restore(Media item, int index) async { - // Optimistic removal: remove the item from the list immediately. - setState(() => _items!.removeAt(index)); + // Identity-based removal (by id) avoids position drift from concurrent + // operations that could shift list indices between tap and setState. + setState(() => _items!.removeWhere((e) => e.id == item.id)); try { await ref.read(apiClientProvider).restoreMedia(item.id); @@ -122,8 +123,9 @@ class _AdminTrashScreenState extends ConsumerState<AdminTrashScreen> { final confirmed = await _confirmHardDelete(item.fileName); if (!confirmed || !mounted) return; - // Optimistic removal. - setState(() => _items!.removeAt(index)); + // Identity-based removal (by id) avoids position drift from concurrent + // operations that could shift list indices between tap and setState. + setState(() => _items!.removeWhere((e) => e.id == item.id)); try { // deleteMedia soft-deletes again (no-op for an already-deleted item) @@ -174,6 +176,7 @@ class _AdminTrashScreenState extends ConsumerState<AdminTrashScreen> { ], ), ); + // Tapping outside the dialog returns null; treat as cancel (no deletion). return result ?? false; } |
