mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-03-31 17:10:29 +02:00
- Add animation_utils.dart with skeleton loaders, staggered list animations, animated checkboxes, badge bump, download success overlay, and shared page route helper - Replace CircularProgressIndicator with shimmer skeleton loaders across album, artist, playlist, search, store, and extension screens - Unify page transitions via slidePageRoute (MaterialPageRoute) for Android predictive back gesture support - Extract AnimatedSelectionCheckbox with configurable unselectedColor to preserve original transparent/opaque backgrounds per context - Add swipe-to-dismiss on download queue items with confirmDismiss dialog for active downloads to prevent accidental cancellation - Add Hero animations for cover art transitions between list and detail - Add AnimatedBadge bump on navigation bar badge count changes - Add DownloadSuccessOverlay green flash on download completion - Restore fine-grained ref.watch(.select()) in _CollectionTrackTile to avoid full list rebuilds on download history changes - Fix DownloadSuccessOverlay re-flashing on widget recreation by initialising _wasSuccess from initial widget state - Remove orphan Hero tag in search_screen that had no matching pair - Chip borderRadius updated from 8 to 20 for consistency
242 lines
8.5 KiB
Dart
242 lines
8.5 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:spotiflac_android/models/theme_settings.dart';
|
|
|
|
class AppTheme {
|
|
/// Default seed color (Spotify green)
|
|
static const Color defaultSeedColor = Color(kDefaultSeedColor);
|
|
|
|
static ThemeData light({ColorScheme? dynamicScheme, Color? seedColor}) {
|
|
final scheme =
|
|
dynamicScheme ??
|
|
ColorScheme.fromSeed(
|
|
seedColor: seedColor ?? defaultSeedColor,
|
|
brightness: Brightness.light,
|
|
);
|
|
|
|
return ThemeData(
|
|
useMaterial3: true,
|
|
colorScheme: scheme,
|
|
appBarTheme: _appBarTheme(scheme),
|
|
cardTheme: _cardTheme(scheme),
|
|
elevatedButtonTheme: _elevatedButtonTheme(scheme),
|
|
filledButtonTheme: _filledButtonTheme(scheme),
|
|
outlinedButtonTheme: _outlinedButtonTheme(scheme),
|
|
textButtonTheme: _textButtonTheme(scheme),
|
|
floatingActionButtonTheme: _fabTheme(scheme),
|
|
inputDecorationTheme: _inputDecorationTheme(scheme),
|
|
listTileTheme: _listTileTheme(scheme),
|
|
dialogTheme: _dialogTheme(scheme),
|
|
navigationBarTheme: _navigationBarTheme(scheme),
|
|
snackBarTheme: _snackBarTheme(scheme),
|
|
progressIndicatorTheme: _progressIndicatorTheme(scheme),
|
|
switchTheme: _switchTheme(scheme),
|
|
chipTheme: _chipTheme(scheme),
|
|
dividerTheme: _dividerTheme(scheme),
|
|
);
|
|
}
|
|
|
|
static ThemeData dark({
|
|
ColorScheme? dynamicScheme,
|
|
Color? seedColor,
|
|
bool isAmoled = false,
|
|
}) {
|
|
final scheme =
|
|
dynamicScheme ??
|
|
ColorScheme.fromSeed(
|
|
seedColor: seedColor ?? defaultSeedColor,
|
|
brightness: Brightness.dark,
|
|
);
|
|
|
|
return ThemeData(
|
|
useMaterial3: true,
|
|
colorScheme: scheme,
|
|
scaffoldBackgroundColor: isAmoled ? Colors.black : null,
|
|
appBarTheme: _appBarTheme(scheme, isAmoled: isAmoled),
|
|
cardTheme: _cardTheme(scheme),
|
|
elevatedButtonTheme: _elevatedButtonTheme(scheme),
|
|
filledButtonTheme: _filledButtonTheme(scheme),
|
|
outlinedButtonTheme: _outlinedButtonTheme(scheme),
|
|
textButtonTheme: _textButtonTheme(scheme),
|
|
floatingActionButtonTheme: _fabTheme(scheme),
|
|
inputDecorationTheme: _inputDecorationTheme(scheme),
|
|
listTileTheme: _listTileTheme(scheme),
|
|
dialogTheme: _dialogTheme(scheme),
|
|
navigationBarTheme: _navigationBarTheme(scheme, isAmoled: isAmoled),
|
|
snackBarTheme: _snackBarTheme(scheme),
|
|
progressIndicatorTheme: _progressIndicatorTheme(scheme),
|
|
switchTheme: _switchTheme(scheme),
|
|
chipTheme: _chipTheme(scheme),
|
|
dividerTheme: _dividerTheme(scheme),
|
|
);
|
|
}
|
|
|
|
static AppBarTheme _appBarTheme(
|
|
ColorScheme scheme, {
|
|
bool isAmoled = false,
|
|
}) => AppBarTheme(
|
|
elevation: 0,
|
|
scrolledUnderElevation: isAmoled ? 0 : 3,
|
|
backgroundColor: isAmoled ? Colors.black : scheme.surface,
|
|
foregroundColor: scheme.onSurface,
|
|
surfaceTintColor: isAmoled ? Colors.transparent : scheme.surfaceTint,
|
|
centerTitle: true,
|
|
titleTextStyle: TextStyle(
|
|
color: scheme.onSurface,
|
|
fontSize: 22,
|
|
fontWeight: FontWeight.w500,
|
|
),
|
|
);
|
|
|
|
static CardThemeData _cardTheme(ColorScheme scheme) => CardThemeData(
|
|
elevation: 0,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
|
color: scheme.surfaceContainerLow,
|
|
surfaceTintColor: scheme.surfaceTint,
|
|
);
|
|
|
|
static ElevatedButtonThemeData _elevatedButtonTheme(ColorScheme scheme) =>
|
|
ElevatedButtonThemeData(
|
|
style: ElevatedButton.styleFrom(
|
|
elevation: 1,
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
|
),
|
|
);
|
|
|
|
static FilledButtonThemeData _filledButtonTheme(ColorScheme scheme) =>
|
|
FilledButtonThemeData(
|
|
style: FilledButton.styleFrom(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
|
),
|
|
);
|
|
|
|
static OutlinedButtonThemeData _outlinedButtonTheme(ColorScheme scheme) =>
|
|
OutlinedButtonThemeData(
|
|
style: OutlinedButton.styleFrom(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
|
),
|
|
);
|
|
|
|
static TextButtonThemeData _textButtonTheme(ColorScheme scheme) =>
|
|
TextButtonThemeData(
|
|
style: TextButton.styleFrom(
|
|
shape: RoundedRectangleBorder(
|
|
borderRadius: BorderRadius.circular(16),
|
|
),
|
|
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
|
),
|
|
);
|
|
|
|
static FloatingActionButtonThemeData _fabTheme(ColorScheme scheme) =>
|
|
FloatingActionButtonThemeData(
|
|
elevation: 3,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
|
backgroundColor: scheme.primaryContainer,
|
|
foregroundColor: scheme.onPrimaryContainer,
|
|
);
|
|
|
|
static InputDecorationTheme _inputDecorationTheme(ColorScheme scheme) =>
|
|
InputDecorationTheme(
|
|
filled: true,
|
|
fillColor: scheme.surfaceContainerHighest.withValues(alpha: 0.3),
|
|
border: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(16),
|
|
borderSide: BorderSide.none,
|
|
),
|
|
enabledBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(16),
|
|
borderSide: BorderSide.none,
|
|
),
|
|
focusedBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(16),
|
|
borderSide: BorderSide(color: scheme.primary, width: 2),
|
|
),
|
|
errorBorder: OutlineInputBorder(
|
|
borderRadius: BorderRadius.circular(16),
|
|
borderSide: BorderSide(color: scheme.error, width: 1),
|
|
),
|
|
contentPadding: const EdgeInsets.symmetric(
|
|
horizontal: 20,
|
|
vertical: 16,
|
|
),
|
|
);
|
|
|
|
static ListTileThemeData _listTileTheme(ColorScheme scheme) =>
|
|
ListTileThemeData(
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
|
contentPadding: const EdgeInsets.symmetric(horizontal: 16, vertical: 4),
|
|
);
|
|
|
|
static DialogThemeData _dialogTheme(ColorScheme scheme) => DialogThemeData(
|
|
elevation: 6,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(28)),
|
|
backgroundColor: scheme.surfaceContainerHigh,
|
|
surfaceTintColor: scheme.surfaceTint,
|
|
);
|
|
|
|
static NavigationBarThemeData _navigationBarTheme(
|
|
ColorScheme scheme, {
|
|
bool isAmoled = false,
|
|
}) => NavigationBarThemeData(
|
|
elevation: 0,
|
|
backgroundColor: isAmoled ? Colors.black : scheme.surfaceContainer,
|
|
indicatorColor: scheme.secondaryContainer,
|
|
surfaceTintColor: isAmoled ? Colors.transparent : scheme.surfaceTint,
|
|
labelBehavior: NavigationDestinationLabelBehavior.alwaysShow,
|
|
);
|
|
|
|
static SnackBarThemeData _snackBarTheme(ColorScheme scheme) =>
|
|
SnackBarThemeData(
|
|
behavior: SnackBarBehavior.floating,
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
|
backgroundColor: scheme.inverseSurface,
|
|
contentTextStyle: TextStyle(color: scheme.onInverseSurface),
|
|
);
|
|
|
|
static ProgressIndicatorThemeData _progressIndicatorTheme(
|
|
ColorScheme scheme,
|
|
) => ProgressIndicatorThemeData(
|
|
color: scheme.primary,
|
|
linearTrackColor: scheme.surfaceContainerHighest,
|
|
circularTrackColor: scheme.surfaceContainerHighest,
|
|
);
|
|
|
|
static SwitchThemeData _switchTheme(ColorScheme scheme) => SwitchThemeData(
|
|
thumbColor: WidgetStateProperty.resolveWith((states) {
|
|
if (states.contains(WidgetState.selected)) {
|
|
return scheme.onPrimary;
|
|
}
|
|
return scheme.outline;
|
|
}),
|
|
trackColor: WidgetStateProperty.resolveWith((states) {
|
|
if (states.contains(WidgetState.selected)) {
|
|
return scheme.primary;
|
|
}
|
|
return scheme.surfaceContainerHighest;
|
|
}),
|
|
thumbIcon: WidgetStateProperty.resolveWith((states) {
|
|
if (states.contains(WidgetState.selected)) {
|
|
return Icon(Icons.check, color: scheme.primary);
|
|
}
|
|
return null;
|
|
}),
|
|
);
|
|
|
|
static ChipThemeData _chipTheme(ColorScheme scheme) => ChipThemeData(
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(20)),
|
|
backgroundColor: scheme.surfaceContainerLow,
|
|
selectedColor: scheme.secondaryContainer,
|
|
);
|
|
|
|
static DividerThemeData _dividerTheme(ColorScheme scheme) =>
|
|
DividerThemeData(color: scheme.outlineVariant, thickness: 1, space: 1);
|
|
}
|