Files
bvandeusen d5c8d316c5 fix(flutter): clear analyze --fatal-infos errors from admin parity slice
CI caught six issues against the new admin slice:

- AsyncValue<T> in this Riverpod 3.3.1 codebase exposes `.value`
  (returns T?), not `.valueOrNull`. Switched the three admin readers
  to match the established convention (player_bar, queue_screen,
  now_playing_screen all use `.value`).
- home_screen.dart still has ctx.push calls in _albumsRow / _artistsRow
  (carousel tap handlers) — restored the go_router import that
  Task 2 over-eagerly removed.
- admin_user_edit_sheet.dart had a single-line `if (!await ...) return;`
  that violated curly_braces_in_flow_control_structures. Wrapped in
  braces.
- admin_quarantine_item.dart doc comment had `<top>` placeholders that
  the analyzer flagged as unintended HTML. Rephrased without angle
  brackets.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-08 22:02:14 -04:00

168 lines
5.1 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../models/admin_user.dart';
import '../../theme/theme_extension.dart';
import '../admin_providers.dart';
import 'typed_confirm_sheet.dart';
class AdminUserEditSheet extends ConsumerWidget {
const AdminUserEditSheet({super.key, required this.user});
final AdminUser user;
static Future<void> show(BuildContext context, AdminUser user) =>
showModalBottomSheet<void>(
context: context,
isScrollControlled: true,
builder: (_) => AdminUserEditSheet(user: user),
);
@override
Widget build(BuildContext context, WidgetRef ref) {
final fs = Theme.of(context).extension<FabledSwordTheme>()!;
final users = ref.read(adminUsersProvider.notifier);
return Padding(
padding: EdgeInsets.only(
bottom: MediaQuery.of(context).viewInsets.bottom,
),
child: Container(
color: fs.iron,
padding: const EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
user.username,
style: TextStyle(
color: fs.parchment,
fontFamily: 'Fraunces',
fontSize: 20,
),
),
const SizedBox(height: 12),
SwitchListTile(
key: const Key('user_edit_is_admin'),
title: Text('Admin', style: TextStyle(color: fs.parchment)),
value: user.isAdmin,
onChanged: (v) async {
try {
await users.setAdmin(user.id, v);
} catch (e) {
if (context.mounted) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text('$e')));
}
}
},
),
SwitchListTile(
key: const Key('user_edit_auto_approve'),
title: Text('Auto-approve requests',
style: TextStyle(color: fs.parchment)),
value: user.autoApproveRequests,
onChanged: (v) async {
try {
await users.setAutoApprove(user.id, v);
} catch (e) {
if (context.mounted) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text('$e')));
}
}
},
),
const SizedBox(height: 12),
Row(
children: [
TextButton(
key: const Key('user_edit_reset_password'),
onPressed: () => _resetPassword(context, users),
child: const Text('Reset password'),
),
const Spacer(),
TextButton(
key: const Key('user_edit_delete'),
style: TextButton.styleFrom(foregroundColor: fs.oxblood),
onPressed: () => _deleteUser(context, users),
child: const Text('Delete'),
),
],
),
],
),
),
);
}
Future<void> _resetPassword(
BuildContext context, AdminUsersController users) async {
final controller = TextEditingController();
final newPw = await showDialog<String>(
context: context,
builder: (ctx) => AlertDialog(
title: const Text('Reset password'),
content: TextField(
controller: controller,
obscureText: true,
decoration: const InputDecoration(
labelText: 'New password',
helperText: 'Minimum 8 characters',
),
),
actions: [
TextButton(
onPressed: () => Navigator.pop(ctx),
child: const Text('Cancel'),
),
TextButton(
onPressed: () {
if (controller.text.length >= 8) {
Navigator.pop(ctx, controller.text);
}
},
child: const Text('Reset'),
),
],
),
);
if (newPw == null || !context.mounted) return;
try {
await users.resetPassword(user.id, newPw);
if (context.mounted) {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('Password reset.')),
);
}
} catch (e) {
if (context.mounted) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text('$e')));
}
}
}
Future<void> _deleteUser(
BuildContext context, AdminUsersController users) async {
if (!await TypedConfirmSheet.show(
context,
title: 'Delete user?',
message:
'Permanently delete user "${user.username}" and all their data. '
'Cannot be undone.',
)) {
return;
}
try {
await users.delete(user.id);
if (context.mounted) Navigator.pop(context);
} catch (e) {
if (context.mounted) {
ScaffoldMessenger.of(context)
.showSnackBar(SnackBar(content: Text('$e')));
}
}
}
}