Localized files: - screens: album_details, artist_details, audiobook_*, debug_log, home, library_*, new_library, playlist_details, queue - widgets: disconnected_state, empty_state, expandable_player, chapters_panel All hardcoded English strings replaced with S.of(context)! calls. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
116 lines
3.8 KiB
Dart
116 lines
3.8 KiB
Dart
import 'package:flutter/material.dart';
|
|
import '../../l10n/app_localizations.dart';
|
|
import '../../theme/design_tokens.dart';
|
|
|
|
/// A reusable widget for displaying disconnected/offline state.
|
|
class DisconnectedState extends StatelessWidget {
|
|
final String title;
|
|
final String? subtitle;
|
|
final String? actionLabel;
|
|
final VoidCallback? onAction;
|
|
final IconData? actionIcon;
|
|
final double iconSize;
|
|
|
|
const DisconnectedState({
|
|
super.key,
|
|
required this.title,
|
|
this.subtitle,
|
|
this.actionLabel,
|
|
this.onAction,
|
|
this.actionIcon,
|
|
this.iconSize = 64,
|
|
});
|
|
|
|
/// Simple disconnected state without action button
|
|
factory DisconnectedState.simple(BuildContext context) => DisconnectedState(
|
|
title: S.of(context)!.notConnected,
|
|
);
|
|
|
|
/// Disconnected state with settings button for navigation
|
|
factory DisconnectedState.withSettingsAction({
|
|
required BuildContext context,
|
|
required VoidCallback onSettings,
|
|
}) =>
|
|
DisconnectedState(
|
|
title: S.of(context)!.notConnected,
|
|
actionLabel: S.of(context)!.configureServer,
|
|
actionIcon: Icons.settings_rounded,
|
|
onAction: onSettings,
|
|
);
|
|
|
|
/// Full disconnected state with title, subtitle, and action (for home screen)
|
|
factory DisconnectedState.full({
|
|
required BuildContext context,
|
|
required VoidCallback onSettings,
|
|
}) =>
|
|
DisconnectedState(
|
|
title: S.of(context)!.notConnectedTitle,
|
|
subtitle: S.of(context)!.connectHint,
|
|
actionLabel: S.of(context)!.configureServer,
|
|
actionIcon: Icons.settings_rounded,
|
|
onAction: onSettings,
|
|
iconSize: 80,
|
|
);
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final colorScheme = Theme.of(context).colorScheme;
|
|
|
|
return Center(
|
|
child: Padding(
|
|
padding: Spacing.paddingAll32,
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
Icon(
|
|
Icons.cloud_off_rounded,
|
|
size: iconSize,
|
|
color: colorScheme.onSurface.withOpacity(0.54),
|
|
),
|
|
SizedBox(height: iconSize > IconSizes.xxl ? Spacing.xl : Spacing.lg),
|
|
Text(
|
|
title,
|
|
style: TextStyle(
|
|
color: colorScheme.onSurface.withOpacity(subtitle != null ? 1.0 : 0.7),
|
|
fontSize: subtitle != null ? 24 : 16,
|
|
fontWeight: subtitle != null ? FontWeight.w300 : FontWeight.normal,
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
if (subtitle != null) ...[
|
|
Spacing.vGap12,
|
|
Text(
|
|
subtitle!,
|
|
style: TextStyle(
|
|
color: colorScheme.onSurface.withOpacity(0.7),
|
|
fontSize: 14,
|
|
),
|
|
textAlign: TextAlign.center,
|
|
),
|
|
],
|
|
if (actionLabel != null && onAction != null) ...[
|
|
SizedBox(height: subtitle != null ? Spacing.xxl : Spacing.xl),
|
|
ElevatedButton.icon(
|
|
onPressed: onAction,
|
|
icon: actionIcon != null ? Icon(actionIcon) : const SizedBox.shrink(),
|
|
label: Text(actionLabel!),
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: colorScheme.primary,
|
|
foregroundColor: colorScheme.onPrimary,
|
|
padding: EdgeInsets.symmetric(
|
|
horizontal: subtitle != null ? Spacing.xxl : Spacing.xl,
|
|
vertical: subtitle != null ? Spacing.lg : Spacing.md,
|
|
),
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(Radii.xxl),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|