mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-09-29 21:02:09 +02:00
feat(ui): add Mornye glass theme and adaptive music player
Add a selectable Mornye theme with shared glass controls, dialogs, sheets, notifications, and collection headers while retaining Material components. Update player artwork, lyrics transitions and highlighting, synchronized rolling time labels, volume controls, and compact navigation. Unify metadata, Library, search, and extension surfaces; preserve restored artwork and report extension load failures. Validation: full Flutter analyzer clean; 713 tests passed and 4 skipped; pinned Dart formatting and diff checks passed; signed ARM64 and ARM32 release APKs built and verified.
This commit is contained in:
@@ -4,6 +4,8 @@ import 'package:dynamic_color/dynamic_color.dart';
|
||||
import 'package:material_ui/material_ui.dart' as material_ui;
|
||||
import 'package:spotiflac_android/providers/theme_provider.dart';
|
||||
import 'package:spotiflac_android/theme/app_theme.dart';
|
||||
import 'package:spotiflac_android/models/theme_settings.dart';
|
||||
import 'package:spotiflac_android/theme/mornye_theme.dart';
|
||||
|
||||
class DynamicColorWrapper extends ConsumerWidget {
|
||||
final Widget Function(ThemeData light, ThemeData dark, ThemeMode mode)
|
||||
@@ -15,6 +17,14 @@ class DynamicColorWrapper extends ConsumerWidget {
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
final themeSettings = ref.watch(themeProvider);
|
||||
|
||||
if (themeSettings.style == AppThemeStyle.mornye) {
|
||||
return builder(
|
||||
MornyeTheme.build(Brightness.light),
|
||||
MornyeTheme.build(Brightness.dark),
|
||||
themeSettings.themeMode,
|
||||
);
|
||||
}
|
||||
|
||||
return DynamicColorBuilder(
|
||||
builder: (lightDynamic, darkDynamic) {
|
||||
ColorScheme lightScheme;
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
import 'package:flutter/cupertino.dart' show CupertinoIcons;
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
/// Cupertino counterparts for the shared Settings and extension UI symbols.
|
||||
IconData mornyeIconFor(IconData icon) {
|
||||
if (icon.fontFamily == CupertinoIcons.iconFont) return icon;
|
||||
return switch (icon) {
|
||||
Icons.extension ||
|
||||
Icons.extension_outlined ||
|
||||
Icons.apps ||
|
||||
Icons.grid_view ||
|
||||
Icons.grid_view_outlined ||
|
||||
Icons.grid_view_rounded => CupertinoIcons.square_grid_2x2,
|
||||
Icons.palette_outlined ||
|
||||
Icons.color_lens_outlined => CupertinoIcons.paintbrush,
|
||||
Icons.library_music ||
|
||||
Icons.library_music_outlined ||
|
||||
Icons.playlist_play_outlined ||
|
||||
Icons.queue_music_outlined => CupertinoIcons.music_note_list,
|
||||
Icons.sell_outlined ||
|
||||
Icons.label_outline ||
|
||||
Icons.tag => CupertinoIcons.tag,
|
||||
Icons.lyrics ||
|
||||
Icons.lyrics_outlined ||
|
||||
Icons.subtitles_outlined ||
|
||||
Icons.forum_outlined => CupertinoIcons.quote_bubble,
|
||||
Icons.download ||
|
||||
Icons.download_outlined ||
|
||||
Icons.file_download_outlined ||
|
||||
Icons.downloading_outlined => CupertinoIcons.arrow_down_circle,
|
||||
Icons.folder_outlined ||
|
||||
Icons.folder_special_outlined => CupertinoIcons.folder,
|
||||
Icons.create_new_folder_outlined => CupertinoIcons.folder_badge_plus,
|
||||
Icons.storage_outlined ||
|
||||
Icons.sd_storage_outlined ||
|
||||
Icons.memory_outlined ||
|
||||
Icons.usb_rounded => CupertinoIcons.tray,
|
||||
Icons.settings_backup_restore ||
|
||||
Icons.history ||
|
||||
Icons.history_toggle_off_outlined => CupertinoIcons.clock,
|
||||
Icons.article_outlined || Icons.source_outlined => CupertinoIcons.doc_text,
|
||||
Icons.favorite_outline || Icons.favorite_border => CupertinoIcons.heart,
|
||||
Icons.favorite => CupertinoIcons.heart_fill,
|
||||
Icons.arrow_back || Icons.chevron_left => CupertinoIcons.chevron_back,
|
||||
Icons.close => CupertinoIcons.xmark,
|
||||
Icons.play_arrow || Icons.play_arrow_rounded => CupertinoIcons.play_fill,
|
||||
Icons.shuffle => CupertinoIcons.shuffle,
|
||||
Icons.playlist_add || Icons.queue_music => CupertinoIcons.text_badge_plus,
|
||||
Icons.edit_outlined => CupertinoIcons.pencil,
|
||||
Icons.edit_note => CupertinoIcons.pencil_ellipsis_rectangle,
|
||||
Icons.fingerprint => CupertinoIcons.number,
|
||||
Icons.playlist_add_check ||
|
||||
Icons.playlist_add_check_circle => CupertinoIcons.text_badge_checkmark,
|
||||
Icons.add_circle_outline => CupertinoIcons.add_circled,
|
||||
Icons.format_list_numbered => CupertinoIcons.list_number,
|
||||
Icons.checklist => CupertinoIcons.list_bullet,
|
||||
Icons.calendar_today_outlined => CupertinoIcons.calendar,
|
||||
Icons.preview_outlined => CupertinoIcons.doc_text_search,
|
||||
Icons.check || Icons.check_rounded => CupertinoIcons.checkmark,
|
||||
Icons.download_for_offline_outlined => CupertinoIcons.arrow_down_circle,
|
||||
Icons.call_split => CupertinoIcons.arrow_branch,
|
||||
Icons.download_rounded => CupertinoIcons.arrow_down,
|
||||
Icons.info_outline => CupertinoIcons.info,
|
||||
Icons.settings_outlined => CupertinoIcons.gear_alt,
|
||||
Icons.tune ||
|
||||
Icons.tune_outlined ||
|
||||
Icons.table_chart_outlined => CupertinoIcons.slider_horizontal_3,
|
||||
Icons.build_outlined => CupertinoIcons.wrench,
|
||||
Icons.people_alt_outlined ||
|
||||
Icons.people_outline => CupertinoIcons.person_2,
|
||||
Icons.person ||
|
||||
Icons.person_outline ||
|
||||
Icons.person_outline_rounded ||
|
||||
Icons.person_search_outlined => CupertinoIcons.person,
|
||||
Icons.group_remove_outlined =>
|
||||
CupertinoIcons.person_crop_circle_badge_xmark,
|
||||
Icons.photo_size_select_large_outlined ||
|
||||
Icons.image_outlined ||
|
||||
Icons.wallpaper => CupertinoIcons.photo,
|
||||
Icons.graphic_eq ||
|
||||
Icons.graphic_eq_outlined ||
|
||||
Icons.graphic_eq_rounded => CupertinoIcons.waveform,
|
||||
Icons.filter_alt_outlined ||
|
||||
Icons.filter_list ||
|
||||
Icons.filter_list_outlined => CupertinoIcons.line_horizontal_3_decrease,
|
||||
Icons.travel_explore ||
|
||||
Icons.language ||
|
||||
Icons.language_outlined ||
|
||||
Icons.public ||
|
||||
Icons.translate ||
|
||||
Icons.translate_outlined => CupertinoIcons.globe,
|
||||
Icons.text_fields ||
|
||||
Icons.text_fields_outlined => CupertinoIcons.textformat,
|
||||
Icons.record_voice_over_outlined => CupertinoIcons.mic,
|
||||
Icons.music_note ||
|
||||
Icons.audio_file_outlined ||
|
||||
Icons.audiotrack ||
|
||||
Icons.music_note_outlined => CupertinoIcons.music_note,
|
||||
Icons.speed || Icons.speed_outlined => CupertinoIcons.gauge,
|
||||
Icons.auto_fix_high ||
|
||||
Icons.auto_fix_high_outlined ||
|
||||
Icons.auto_awesome_outlined => CupertinoIcons.wand_stars,
|
||||
Icons.wifi => CupertinoIcons.wifi,
|
||||
Icons.dynamic_feed_outlined ||
|
||||
Icons.tab_outlined => CupertinoIcons.square_on_square,
|
||||
Icons.security_outlined => CupertinoIcons.lock_shield,
|
||||
Icons.lan_outlined || Icons.link => CupertinoIcons.link,
|
||||
Icons.sync || Icons.autorenew_rounded => CupertinoIcons.arrow_2_circlepath,
|
||||
Icons.refresh ||
|
||||
Icons.update ||
|
||||
Icons.system_update => CupertinoIcons.arrow_clockwise,
|
||||
Icons.content_copy_outlined ||
|
||||
Icons.content_copy_rounded ||
|
||||
Icons.copy_all_rounded ||
|
||||
Icons.copy_outlined ||
|
||||
Icons.copy => CupertinoIcons.doc_on_doc,
|
||||
Icons.swap_horiz ||
|
||||
Icons.compare_arrows ||
|
||||
Icons.difference_outlined ||
|
||||
Icons.alt_route => CupertinoIcons.arrow_right_arrow_left,
|
||||
Icons.cleaning_services_outlined => CupertinoIcons.sparkles,
|
||||
Icons.delete_outline ||
|
||||
Icons.delete_forever ||
|
||||
Icons.delete_sweep_outlined => CupertinoIcons.trash,
|
||||
Icons.open_in_new ||
|
||||
Icons.open_in_new_rounded ||
|
||||
Icons.open_in_browser => CupertinoIcons.arrow_up_right_square,
|
||||
Icons.play_circle_outline => CupertinoIcons.play_circle,
|
||||
Icons.share ||
|
||||
Icons.share_outlined ||
|
||||
Icons.ios_share => CupertinoIcons.share,
|
||||
Icons.brightness_2 || Icons.dark_mode => CupertinoIcons.moon,
|
||||
Icons.light_mode => CupertinoIcons.sun_max,
|
||||
Icons.contrast ||
|
||||
Icons.brightness_auto => CupertinoIcons.circle_lefthalf_fill,
|
||||
Icons.animation => CupertinoIcons.play_rectangle,
|
||||
Icons.blur_on => CupertinoIcons.drop,
|
||||
Icons.new_releases => CupertinoIcons.star_circle,
|
||||
Icons.bug_report || Icons.bug_report_outlined => CupertinoIcons.ant,
|
||||
Icons.timer_outlined => CupertinoIcons.timer,
|
||||
Icons.timer_off_outlined => CupertinoIcons.timer,
|
||||
Icons.bedtime_outlined => CupertinoIcons.moon_zzz,
|
||||
Icons.explore_outlined => CupertinoIcons.compass,
|
||||
Icons.vpn_key_outlined => CupertinoIcons.lock,
|
||||
Icons.bookmark_outline => CupertinoIcons.bookmark,
|
||||
Icons.low_priority => CupertinoIcons.sort_down,
|
||||
Icons.search || Icons.manage_search => CupertinoIcons.search,
|
||||
Icons.add => CupertinoIcons.add,
|
||||
Icons.code => CupertinoIcons.chevron_left_slash_chevron_right,
|
||||
Icons.lightbulb_outline => CupertinoIcons.lightbulb,
|
||||
Icons.telegram => CupertinoIcons.paperplane,
|
||||
Icons.check => CupertinoIcons.check_mark,
|
||||
Icons.album || Icons.album_outlined => CupertinoIcons.square_stack,
|
||||
Icons.playlist_play => CupertinoIcons.music_note_list,
|
||||
Icons.video_library => CupertinoIcons.play_rectangle,
|
||||
Icons.podcasts => CupertinoIcons.mic,
|
||||
Icons.monitor_heart_outlined => CupertinoIcons.waveform_path_ecg,
|
||||
Icons.warning_amber_rounded => CupertinoIcons.exclamationmark_triangle,
|
||||
_ => CupertinoIcons.gear_alt,
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,320 @@
|
||||
import 'package:flutter/cupertino.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:spotiflac_android/theme/app_tokens.dart';
|
||||
|
||||
/// Flutter counterpart of Mornye's iPhone visual system. Reference values come
|
||||
/// from AccentColor.colorset, Components.swift, PlayerViews.swift and Motion.md
|
||||
/// in the Mornye project; keep this palette independent of wallpaper colors.
|
||||
@immutable
|
||||
class MornyeTheme extends ThemeExtension<MornyeTheme> {
|
||||
const MornyeTheme({this.chromeSurface});
|
||||
|
||||
final Color? chromeSurface;
|
||||
|
||||
/// A single translucent fill for controls inside an existing glass surface.
|
||||
static Color controlFill(BuildContext context, {bool enabled = true}) {
|
||||
final theme = Theme.of(context);
|
||||
return theme.colorScheme.onSurface.withValues(
|
||||
alpha: !enabled
|
||||
? 0.04
|
||||
: theme.brightness == Brightness.dark
|
||||
? 0.10
|
||||
: 0.07,
|
||||
);
|
||||
}
|
||||
|
||||
/// Separators blend with their group, keeping definition on pages and sheets.
|
||||
static Color metadataDividerColor(BuildContext context) {
|
||||
final theme = Theme.of(context);
|
||||
return theme.colorScheme.onSurface.withValues(
|
||||
alpha: theme.brightness == Brightness.dark ? 0.14 : 0.17,
|
||||
);
|
||||
}
|
||||
|
||||
static double chromeOpacity(BuildContext context) =>
|
||||
Theme.of(context).extension<MornyeTheme>()?.chromeSurface != null
|
||||
? 0.42
|
||||
: 0.70;
|
||||
|
||||
static const lightAccent = Color.fromRGBO(204, 46, 51, 1);
|
||||
static const darkAccent = Color.fromRGBO(224, 61, 60, 1);
|
||||
|
||||
static final tokens = AppTokens.standard.copyWith(
|
||||
radiusBadge: 5,
|
||||
radiusThumb: 6,
|
||||
radiusCover: 10,
|
||||
radiusControl: 12,
|
||||
radiusCard: 12,
|
||||
radiusSheet: 32,
|
||||
coverMini: 38,
|
||||
headerExpandedTitleSize: 34,
|
||||
headerCollapsedTitleSize: 17,
|
||||
motionFast: const Duration(milliseconds: 180),
|
||||
motionMedium: const Duration(milliseconds: 220),
|
||||
motionSlow: const Duration(milliseconds: 380),
|
||||
);
|
||||
|
||||
// Theme construction includes seeded color generation and typography. Reuse
|
||||
// identical themes across routes; bound artwork-derived variants so browsing
|
||||
// many artists cannot grow the cache indefinitely. Platform is part of the
|
||||
// key because it controls fonts and route transitions.
|
||||
static final _themeCache =
|
||||
<(TargetPlatform, Brightness, Color?), ThemeData>{};
|
||||
|
||||
static ThemeData build(Brightness brightness, {Color? chromeSurface}) {
|
||||
final key = (defaultTargetPlatform, brightness, chromeSurface);
|
||||
final cached = _themeCache.remove(key);
|
||||
if (cached != null) {
|
||||
_themeCache[key] = cached;
|
||||
return cached;
|
||||
}
|
||||
final theme = _build(brightness, chromeSurface: chromeSurface);
|
||||
if (_themeCache.length >= 16) {
|
||||
_themeCache.remove(_themeCache.keys.first);
|
||||
}
|
||||
_themeCache[key] = theme;
|
||||
return theme;
|
||||
}
|
||||
|
||||
static ThemeData _build(Brightness brightness, {Color? chromeSurface}) {
|
||||
final dark = brightness == Brightness.dark;
|
||||
final accent = dark ? darkAccent : lightAccent;
|
||||
final foreground = dark ? Colors.white : Colors.black;
|
||||
final surface = dark ? Colors.black : Colors.white;
|
||||
final grouped = dark ? const Color(0xff1c1c1e) : const Color(0xfff2f2f7);
|
||||
final secondary = dark ? const Color(0xff98989f) : const Color(0xff6c6c70);
|
||||
final scheme =
|
||||
ColorScheme.fromSeed(
|
||||
seedColor: accent,
|
||||
brightness: brightness,
|
||||
).copyWith(
|
||||
primary: accent,
|
||||
onPrimary: Colors.white,
|
||||
primaryContainer: grouped,
|
||||
onPrimaryContainer: accent,
|
||||
secondary: accent,
|
||||
onSecondary: Colors.white,
|
||||
secondaryContainer: grouped,
|
||||
onSecondaryContainer: foreground,
|
||||
surface: surface,
|
||||
onSurface: foreground,
|
||||
surfaceContainerLowest: surface,
|
||||
surfaceContainerLow: grouped,
|
||||
surfaceContainer: grouped,
|
||||
surfaceContainerHigh: chromeSurface == null
|
||||
? (dark ? const Color(0xff2c2c2e) : grouped)
|
||||
: Color.lerp(chromeSurface, Colors.black, 0.16),
|
||||
surfaceContainerHighest: dark
|
||||
? const Color(0xff3a3a3c)
|
||||
: const Color(0xffe5e5ea),
|
||||
onSurfaceVariant: secondary,
|
||||
outline: secondary,
|
||||
outlineVariant: dark
|
||||
? const Color(0xff38383a)
|
||||
: const Color(0xffc6c6c8),
|
||||
surfaceTint: Colors.transparent,
|
||||
);
|
||||
// Apple platforms use the installed SF system faces. Inter is bundled for
|
||||
// other platforms; Cupertino's family names alone cannot provide SF there.
|
||||
final apple =
|
||||
defaultTargetPlatform == TargetPlatform.iOS ||
|
||||
defaultTargetPlatform == TargetPlatform.macOS;
|
||||
final type = CupertinoThemeData(brightness: brightness).textTheme;
|
||||
final text = type.textStyle.copyWith(
|
||||
inherit: true,
|
||||
color: foreground,
|
||||
fontFamily: apple ? type.textStyle.fontFamily : 'Inter',
|
||||
);
|
||||
final display = type.navLargeTitleTextStyle.copyWith(
|
||||
inherit: true,
|
||||
color: foreground,
|
||||
fontFamily: apple ? type.navLargeTitleTextStyle.fontFamily : 'Inter',
|
||||
);
|
||||
// Supply complete styles: replacing a Material role with a bare TextStyle
|
||||
// loses its system family and retains Material tracking in other roles.
|
||||
final typography = TextTheme(
|
||||
displayLarge: display.copyWith(fontSize: 57),
|
||||
displayMedium: display.copyWith(fontSize: 45),
|
||||
displaySmall: display.copyWith(fontSize: 36),
|
||||
headlineLarge: display,
|
||||
headlineMedium: display.copyWith(fontSize: 28, letterSpacing: 0.36),
|
||||
headlineSmall: display.copyWith(fontSize: 22, letterSpacing: 0.35),
|
||||
titleLarge: display.copyWith(
|
||||
fontSize: 20,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: 0.38,
|
||||
),
|
||||
titleMedium: text.copyWith(fontWeight: FontWeight.w500),
|
||||
titleSmall: text.copyWith(
|
||||
fontSize: 15,
|
||||
fontWeight: FontWeight.w600,
|
||||
letterSpacing: -0.23,
|
||||
),
|
||||
bodyLarge: text,
|
||||
bodyMedium: text.copyWith(fontSize: 15, letterSpacing: -0.23),
|
||||
bodySmall: text.copyWith(
|
||||
fontSize: 13,
|
||||
letterSpacing: -0.08,
|
||||
color: secondary,
|
||||
),
|
||||
labelLarge: text.copyWith(fontWeight: FontWeight.w600),
|
||||
labelMedium: text.copyWith(
|
||||
fontSize: 13,
|
||||
fontWeight: FontWeight.w500,
|
||||
letterSpacing: -0.08,
|
||||
),
|
||||
labelSmall: text.copyWith(
|
||||
fontSize: 11,
|
||||
fontWeight: FontWeight.w500,
|
||||
letterSpacing: -0.24,
|
||||
color: secondary,
|
||||
),
|
||||
);
|
||||
final base = ThemeData(
|
||||
useMaterial3: true,
|
||||
brightness: brightness,
|
||||
colorScheme: scheme,
|
||||
scaffoldBackgroundColor: surface,
|
||||
fontFamily: text.fontFamily,
|
||||
splashFactory: NoSplash.splashFactory,
|
||||
extensions: <ThemeExtension<dynamic>>[
|
||||
MornyeTheme(chromeSurface: chromeSurface),
|
||||
tokens,
|
||||
],
|
||||
);
|
||||
final controlShape = RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
);
|
||||
return base.copyWith(
|
||||
textTheme: typography,
|
||||
primaryTextTheme: typography.apply(
|
||||
bodyColor: scheme.onPrimary,
|
||||
displayColor: scheme.onPrimary,
|
||||
),
|
||||
pageTransitionsTheme: const PageTransitionsTheme(
|
||||
builders: {
|
||||
TargetPlatform.android: FadeForwardsPageTransitionsBuilder(),
|
||||
TargetPlatform.iOS: CupertinoPageTransitionsBuilder(),
|
||||
TargetPlatform.macOS: CupertinoPageTransitionsBuilder(),
|
||||
},
|
||||
),
|
||||
appBarTheme: AppBarTheme(
|
||||
backgroundColor: surface,
|
||||
foregroundColor: accent,
|
||||
elevation: 0,
|
||||
scrolledUnderElevation: 0,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
centerTitle: true,
|
||||
titleTextStyle: text.copyWith(fontWeight: FontWeight.w600),
|
||||
),
|
||||
cardTheme: CardThemeData(
|
||||
color: grouped,
|
||||
elevation: 0,
|
||||
shape: controlShape,
|
||||
),
|
||||
dividerTheme: DividerThemeData(
|
||||
color: scheme.outlineVariant,
|
||||
thickness: 0.5,
|
||||
space: 0.5,
|
||||
),
|
||||
listTileTheme: ListTileThemeData(
|
||||
iconColor: accent,
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 20, vertical: 4),
|
||||
shape: controlShape,
|
||||
),
|
||||
filledButtonTheme: FilledButtonThemeData(
|
||||
style: FilledButton.styleFrom(
|
||||
shape: controlShape,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 22, vertical: 12),
|
||||
),
|
||||
),
|
||||
textButtonTheme: TextButtonThemeData(
|
||||
style: TextButton.styleFrom(shape: controlShape),
|
||||
),
|
||||
outlinedButtonTheme: OutlinedButtonThemeData(
|
||||
style: OutlinedButton.styleFrom(shape: controlShape),
|
||||
),
|
||||
inputDecorationTheme: InputDecorationTheme(
|
||||
filled: true,
|
||||
fillColor: grouped,
|
||||
border: OutlineInputBorder(
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
borderSide: BorderSide.none,
|
||||
),
|
||||
),
|
||||
bottomSheetTheme: BottomSheetThemeData(
|
||||
backgroundColor: grouped,
|
||||
surfaceTintColor: Colors.transparent,
|
||||
constraints: const BoxConstraints(maxWidth: 640),
|
||||
shape: tokens.sheetShape,
|
||||
),
|
||||
dialogTheme: DialogThemeData(
|
||||
backgroundColor: grouped,
|
||||
shape: controlShape,
|
||||
),
|
||||
snackBarTheme: SnackBarThemeData(
|
||||
behavior: SnackBarBehavior.floating,
|
||||
backgroundColor: scheme.surfaceContainerHigh,
|
||||
contentTextStyle: text.copyWith(fontSize: 15, color: foreground),
|
||||
actionTextColor: accent,
|
||||
disabledActionTextColor: secondary,
|
||||
closeIconColor: foreground,
|
||||
elevation: 0,
|
||||
insetPadding: const EdgeInsets.fromLTRB(20, 8, 20, 12),
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(24),
|
||||
side: BorderSide(color: scheme.outlineVariant.withValues(alpha: 0.5)),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
MornyeTheme copyWith({Color? chromeSurface}) =>
|
||||
MornyeTheme(chromeSurface: chromeSurface ?? this.chromeSurface);
|
||||
|
||||
@override
|
||||
MornyeTheme lerp(covariant MornyeTheme? other, double t) => MornyeTheme(
|
||||
chromeSurface: Color.lerp(chromeSurface, other?.chromeSurface, t),
|
||||
);
|
||||
}
|
||||
|
||||
extension MornyeThemeContext on BuildContext {
|
||||
bool get isMornye => Theme.of(this).extension<MornyeTheme>() != null;
|
||||
}
|
||||
|
||||
/// Settings uses iOS's inset grouped surfaces while the music library keeps
|
||||
/// its artwork-led, plain background.
|
||||
class MornyeSettingsTheme extends StatelessWidget {
|
||||
const MornyeSettingsTheme({super.key, required this.child});
|
||||
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
if (!context.isMornye) return child;
|
||||
final theme = Theme.of(context);
|
||||
final dark = theme.brightness == Brightness.dark;
|
||||
final background = dark ? Colors.black : const Color(0xfff2f2f7);
|
||||
final group = dark ? const Color(0xff1c1c1e) : Colors.white;
|
||||
return Theme(
|
||||
data: theme.copyWith(
|
||||
scaffoldBackgroundColor: background,
|
||||
extensions: [
|
||||
for (final extension in theme.extensions.values)
|
||||
if (extension is AppTokens)
|
||||
extension.copyWith(radiusCard: 24)
|
||||
else
|
||||
extension,
|
||||
],
|
||||
colorScheme: theme.colorScheme.copyWith(
|
||||
surface: background,
|
||||
surfaceContainer: group,
|
||||
),
|
||||
appBarTheme: theme.appBarTheme.copyWith(backgroundColor: background),
|
||||
),
|
||||
child: ColoredBox(color: background, child: child),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user