Files
plezy/lib/widgets/auth_error_banner.dart

77 lines
2.8 KiB
Dart

import 'package:flutter/material.dart';
import '../media/ids.dart';
import 'package:material_symbols_icons/symbols.dart';
import 'package:provider/provider.dart';
import '../i18n/strings.g.dart';
import '../providers/multi_server_provider.dart';
import '../screens/settings/add_connection_screen.dart';
import '../focus/focusable_button.dart';
import 'app_icon.dart';
/// Top-of-app banner shown when one or more servers' tokens have been
/// rejected (HTTP 401/403 on the health probe). Distinct from "server
/// offline" — taps the user toward re-auth instead of leaving them
/// puzzled by empty hubs.
///
/// Tracks [MultiServerProvider.hasAuthErrorServers] and collapses to
/// `SizedBox.shrink()` when no servers are in the auth-error state. The
/// CTA opens [AddConnectionScreen]; the user picks the right backend and
/// the resulting token replaces the stale row in the registry, which
/// clears the auth-error state on the next health sweep.
class AuthErrorBanner extends StatelessWidget {
const AuthErrorBanner({super.key});
@override
Widget build(BuildContext context) {
final entries = context.select<MultiServerProvider, List<({ServerId serverId, String displayName})>>(
(p) => p.authErrorServers,
);
if (entries.isEmpty) return const SizedBox.shrink();
final theme = Theme.of(context);
final scheme = theme.colorScheme;
final label = entries.length == 1
? t.connections.sessionExpiredOne(name: entries.first.displayName)
: t.connections.sessionExpiredMany(count: entries.length);
return Material(
color: scheme.errorContainer,
child: SafeArea(
bottom: false,
child: Padding(
padding: const EdgeInsets.fromLTRB(16, 8, 8, 8),
child: Row(
children: [
AppIcon(Symbols.lock_rounded, fill: 1, color: scheme.onErrorContainer),
const SizedBox(width: 12),
Expanded(
child: Text(
label,
style: theme.textTheme.bodyMedium?.copyWith(color: scheme.onErrorContainer, fontWeight: .w500),
),
),
const SizedBox(width: 8),
FocusableButton(
onPressed: () => _openReauth(context),
child: FilledButton.tonal(
style: FilledButton.styleFrom(
backgroundColor: scheme.onErrorContainer,
foregroundColor: scheme.errorContainer,
),
onPressed: () => _openReauth(context),
child: Text(t.connections.signInAgain),
),
),
],
),
),
),
);
}
Future<void> _openReauth(BuildContext context) async {
await Navigator.of(context).push(MaterialPageRoute(builder: (_) => const AddConnectionScreen()));
}
}