summaryrefslogtreecommitdiff
path: root/player-android/lib/screens
diff options
context:
space:
mode:
Diffstat (limited to 'player-android/lib/screens')
-rw-r--r--player-android/lib/screens/admin_permissions_screen.dart8
-rw-r--r--player-android/lib/screens/admin_rescan_screen.dart20
-rw-r--r--player-android/lib/screens/admin_trash_screen.dart11
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;
}