This commit is contained in:
Forrest 2026-08-05 21:57:06 -04:00
parent 2ce32cac4e
commit b6639fb1c9
26 changed files with 454 additions and 204 deletions

View file

@ -19,7 +19,12 @@ enum SearchMode { discovery, standard }
enum AlbumSort { nameAsc, artistAsc, yearDesc, yearAsc, recentlyAdded }
/// Ordering applied to the Tracks browse list (filtered client-side).
enum TrackSort { titleAsc, artistAsc, albumAsc, yearDesc, recentlyAdded }
enum TrackSort { titleAsc, artistAsc, albumAsc, yearDesc, recentlyAdded, ratingDesc }
/// Selectable app-wide color theme. [standard] is the original dark palette
/// (with its static-vs-dynamic accent toggle); [lavender] is a light theme
/// whose accent is locked to its lavender primary. See `theme/tokens.dart`.
enum AppTheme { standard, lavender }
/// A selectable static accent color offered by the theme override (TODO #1).
class AccentChoice {
@ -47,6 +52,7 @@ class AppSettings {
this.albumSort = AlbumSort.nameAsc,
this.trackSort = TrackSort.titleAsc,
this.nowPlayingCassette = false,
this.appTheme = AppTheme.standard,
});
/// Cap for live streaming, in kbps. 0 = original / no transcode.
@ -89,6 +95,15 @@ class AppSettings {
/// album cover.
final bool nowPlayingCassette;
/// The selected app-wide color theme.
final AppTheme appTheme;
/// Whether the accent is pinned (never overridden by album-art extraction):
/// either the user enabled the static accent, or a theme that locks its
/// accent (e.g. [AppTheme.lavender]) is active.
bool get accentIsFixed =>
useStaticAccent || appTheme == AppTheme.lavender;
/// Offered bitrate choices (kbps); 0 renders as "Original".
static const List<int> bitrateChoices = [0, 96, 128, 192, 256, 320];
@ -118,7 +133,8 @@ class AppSettings {
static const Color _defaultStaticAccent = Color(0xFFDF7E35); // Orange
static String bitrateLabel(int rate) => rate == 0 ? 'Original' : '$rate kbps';
static String bitrateLabel(int rate) =>
rate == 0 ? 'Original (native)' : '$rate kbps';
static String formatLabel(String? f) => f ?? 'Original';
static String browseModeLabel(BrowseMode m) => switch (m) {
BrowseMode.artists => 'Artists',
@ -142,6 +158,11 @@ class AppSettings {
TrackSort.albumAsc => 'Album',
TrackSort.yearDesc => 'Year (newest)',
TrackSort.recentlyAdded => 'Recently added',
TrackSort.ratingDesc => 'Rating (highest first)',
};
static String themeLabel(AppTheme t) => switch (t) {
AppTheme.standard => 'Default',
AppTheme.lavender => 'Lavender',
};
AppSettings copyWith({
@ -157,6 +178,7 @@ class AppSettings {
AlbumSort? albumSort,
TrackSort? trackSort,
bool? nowPlayingCassette,
AppTheme? appTheme,
}) =>
AppSettings(
streamMaxBitRate: streamMaxBitRate ?? this.streamMaxBitRate,
@ -173,6 +195,7 @@ class AppSettings {
albumSort: albumSort ?? this.albumSort,
trackSort: trackSort ?? this.trackSort,
nowPlayingCassette: nowPlayingCassette ?? this.nowPlayingCassette,
appTheme: appTheme ?? this.appTheme,
);
static const Object _unset = Object();
@ -189,6 +212,7 @@ class AppSettings {
'albumSort': albumSort.name,
'trackSort': trackSort.name,
'nowPlayingCassette': nowPlayingCassette,
'appTheme': appTheme.name,
};
factory AppSettings.fromJson(Map<String, dynamic> j) => AppSettings(
@ -212,6 +236,8 @@ class AppSettings {
trackSort: _enumByName(TrackSort.values, j['trackSort'] as String?) ??
TrackSort.titleAsc,
nowPlayingCassette: j['nowPlayingCassette'] as bool? ?? false,
appTheme: _enumByName(AppTheme.values, j['appTheme'] as String?) ??
AppTheme.standard,
);
/// Serialize a color to a `#RRGGBB` hex string.
@ -316,6 +342,11 @@ class SettingsController extends StateNotifier<AppSettings> {
_persist();
}
void setAppTheme(AppTheme theme) {
state = state.copyWith(appTheme: theme);
_persist();
}
Future<void> _persist() async {
try {
final file = _file;