feat(appearance): add an optional system font

This commit is contained in:
zarzet
2026-09-27 04:36:28 +07:00
parent c89601b6dc
commit 9322e54739
12 changed files with 179 additions and 23 deletions
+2
View File
@@ -43,6 +43,8 @@
"appearanceAccentBlue": "Blue",
"appearanceAccentPurple": "Purple",
"appearanceAccentPink": "Pink",
"appearanceUseSystemFont": "Use system font",
"appearanceUseSystemFontSubtitle": "Use your device’s default typeface for app text.",
"@appearanceStyle": {
"description": "Appearance section for choosing the app visual style"
},
+2
View File
@@ -43,6 +43,8 @@
"appearanceAccentBlue": "Biru",
"appearanceAccentPurple": "Ungu",
"appearanceAccentPink": "Merah muda",
"appearanceUseSystemFont": "Gunakan font sistem",
"appearanceUseSystemFontSubtitle": "Gunakan jenis huruf bawaan perangkat untuk teks aplikasi.",
"@appearanceStyle": {
"description": "Bagian tampilan untuk memilih gaya visual aplikasi"
},
+11 -2
View File
@@ -6,6 +6,7 @@ const String kSeedColorKey = 'seed_color';
const String kUseAmoledKey = 'use_amoled';
const String kThemeStyleKey = 'theme_style';
const String kMornyeAccentKey = 'mornye_accent';
const String kUseSystemFontKey = 'use_system_font';
enum AppThemeStyle { material, mornye }
@@ -33,6 +34,7 @@ class ThemeSettings {
final bool useAmoled;
final AppThemeStyle style;
final MornyeAccent mornyeAccent;
final bool useSystemFont;
const ThemeSettings({
this.themeMode = ThemeMode.system,
@@ -41,6 +43,7 @@ class ThemeSettings {
this.useAmoled = false,
this.style = AppThemeStyle.material,
this.mornyeAccent = MornyeAccent.red,
this.useSystemFont = false,
});
Color get seedColor => Color(seedColorValue);
@@ -52,6 +55,7 @@ class ThemeSettings {
bool? useAmoled,
AppThemeStyle? style,
MornyeAccent? mornyeAccent,
bool? useSystemFont,
}) {
return ThemeSettings(
themeMode: themeMode ?? this.themeMode,
@@ -60,6 +64,7 @@ class ThemeSettings {
useAmoled: useAmoled ?? this.useAmoled,
style: style ?? this.style,
mornyeAccent: mornyeAccent ?? this.mornyeAccent,
useSystemFont: useSystemFont ?? this.useSystemFont,
);
}
@@ -70,6 +75,7 @@ class ThemeSettings {
kUseAmoledKey: useAmoled,
kThemeStyleKey: style.name,
kMornyeAccentKey: mornyeAccent.name,
kUseSystemFontKey: useSystemFont,
};
factory ThemeSettings.fromJson(Map<String, dynamic> json) {
@@ -80,6 +86,7 @@ class ThemeSettings {
useAmoled: json[kUseAmoledKey] as bool? ?? false,
style: themeStyleFromString(json[kThemeStyleKey] as String?),
mornyeAccent: mornyeAccentFromString(json[kMornyeAccentKey] as String?),
useSystemFont: json[kUseSystemFontKey] as bool? ?? false,
);
}
@@ -92,7 +99,8 @@ class ThemeSettings {
other.seedColorValue == seedColorValue &&
other.useAmoled == useAmoled &&
other.style == style &&
other.mornyeAccent == mornyeAccent;
other.mornyeAccent == mornyeAccent &&
other.useSystemFont == useSystemFont;
}
@override
@@ -102,7 +110,8 @@ class ThemeSettings {
seedColorValue.hashCode ^
useAmoled.hashCode ^
style.hashCode ^
mornyeAccent.hashCode;
mornyeAccent.hashCode ^
useSystemFont.hashCode;
}
ThemeMode themeModeFromString(String? value) {
+7
View File
@@ -15,6 +15,7 @@ ThemeSettings loadBootstrapThemeSettings(SharedPreferences prefs) {
useAmoled: prefs.getBool(kUseAmoledKey) ?? false,
style: themeStyleFromString(prefs.getString(kThemeStyleKey)),
mornyeAccent: mornyeAccentFromString(prefs.getString(kMornyeAccentKey)),
useSystemFont: prefs.getBool(kUseSystemFontKey) ?? false,
);
}
@@ -49,6 +50,7 @@ class ThemeNotifier extends Notifier<ThemeSettings> {
await prefs.setBool(kUseAmoledKey, state.useAmoled);
await prefs.setString(kThemeStyleKey, state.style.name);
await prefs.setString(kMornyeAccentKey, state.mornyeAccent.name);
await prefs.setBool(kUseSystemFontKey, state.useSystemFont);
} catch (e) {
debugPrint('Error saving theme settings: $e');
}
@@ -88,4 +90,9 @@ class ThemeNotifier extends Notifier<ThemeSettings> {
state = state.copyWith(mornyeAccent: accent);
await _saveToStorage();
}
Future<void> setUseSystemFont(bool value) async {
state = state.copyWith(useSystemFont: value);
await _saveToStorage();
}
}
@@ -177,6 +177,15 @@ class AppearanceSettingsPage extends ConsumerWidget {
SliverToBoxAdapter(
child: SettingsGroup(
children: [
SettingsSwitchItem(
icon: Icons.font_download_outlined,
title: context.l10n.appearanceUseSystemFont,
subtitle: context.l10n.appearanceUseSystemFontSubtitle,
value: themeSettings.useSystemFont,
onChanged: ref
.read(themeProvider.notifier)
.setUseSystemFont,
),
SettingsSwitchItem(
icon: Icons.animation,
title: context.l10n.appearanceHeroAnimations,
@@ -114,6 +114,18 @@ class SettingsSearchCatalog {
subtitle: l10n.appearanceAmoledDarkSubtitle,
keywords: const ['black theme', 'oled'],
),
SettingsSearchEntry(
icon: Icons.font_download_outlined,
title: l10n.appearanceUseSystemFont,
subtitle: l10n.appearanceUseSystemFontSubtitle,
keywords: const [
'font',
'typeface',
'typography',
'system font',
'huruf',
],
),
SettingsSearchEntry(
icon: Icons.animation,
title: l10n.appearanceHeroAnimations,
+10 -2
View File
@@ -325,13 +325,21 @@ class _ThemePicker extends ConsumerWidget {
padding: const EdgeInsets.only(bottom: 16),
child: Theme(
data: style == AppThemeStyle.mornye
? MornyeTheme.build(brightness, accent: settings.mornyeAccent)
? MornyeTheme.build(
brightness,
accent: settings.mornyeAccent,
useSystemFont: settings.useSystemFont,
)
: brightness == Brightness.dark
? AppTheme.dark(
seedColor: settings.seedColor,
isAmoled: settings.useAmoled,
useSystemFont: settings.useSystemFont,
)
: AppTheme.light(seedColor: settings.seedColor),
: AppTheme.light(
seedColor: settings.seedColor,
useSystemFont: settings.useSystemFont,
),
child: _ThemeOption(
style: style,
selected: settings.style == style,
+8 -3
View File
@@ -28,7 +28,11 @@ class AppTheme {
},
);
static ThemeData light({ColorScheme? dynamicScheme, Color? seedColor}) {
static ThemeData light({
ColorScheme? dynamicScheme,
Color? seedColor,
bool useSystemFont = false,
}) {
final scheme =
dynamicScheme ??
ColorScheme.fromSeed(
@@ -58,7 +62,7 @@ class AppTheme {
chipTheme: _chipTheme(scheme),
dividerTheme: _dividerTheme(scheme),
extensions: const <ThemeExtension<dynamic>>[AppTokens.standard],
fontFamily: 'Google Sans Flex',
fontFamily: useSystemFont ? null : 'Google Sans Flex',
);
}
@@ -66,6 +70,7 @@ class AppTheme {
ColorScheme? dynamicScheme,
Color? seedColor,
bool isAmoled = false,
bool useSystemFont = false,
}) {
final scheme =
dynamicScheme ??
@@ -97,7 +102,7 @@ class AppTheme {
chipTheme: _chipTheme(scheme),
dividerTheme: _dividerTheme(scheme),
extensions: const <ThemeExtension<dynamic>>[AppTokens.standard],
fontFamily: 'Google Sans Flex',
fontFamily: useSystemFont ? null : 'Google Sans Flex',
);
}
+15 -3
View File
@@ -19,8 +19,16 @@ class DynamicColorWrapper extends ConsumerWidget {
if (themeSettings.style == AppThemeStyle.mornye) {
return builder(
MornyeTheme.build(Brightness.light, accent: themeSettings.mornyeAccent),
MornyeTheme.build(Brightness.dark, accent: themeSettings.mornyeAccent),
MornyeTheme.build(
Brightness.light,
accent: themeSettings.mornyeAccent,
useSystemFont: themeSettings.useSystemFont,
),
MornyeTheme.build(
Brightness.dark,
accent: themeSettings.mornyeAccent,
useSystemFont: themeSettings.useSystemFont,
),
themeSettings.themeMode,
);
}
@@ -51,10 +59,14 @@ class DynamicColorWrapper extends ConsumerWidget {
darkScheme = _applyAmoledColors(darkScheme);
}
final lightTheme = AppTheme.light(dynamicScheme: lightScheme);
final lightTheme = AppTheme.light(
dynamicScheme: lightScheme,
useSystemFont: themeSettings.useSystemFont,
);
final darkTheme = AppTheme.dark(
dynamicScheme: darkScheme,
isAmoled: themeSettings.useAmoled,
useSystemFont: themeSettings.useSystemFont,
);
return builder(lightTheme, darkTheme, themeSettings.themeMode);
+41 -13
View File
@@ -9,10 +9,15 @@ import 'package:spotiflac_android/theme/app_tokens.dart';
/// in the Mornye project; keep this palette independent of wallpaper colors.
@immutable
class MornyeTheme extends ThemeExtension<MornyeTheme> {
const MornyeTheme({this.chromeSurface, this.accent = MornyeAccent.red});
const MornyeTheme({
this.chromeSurface,
this.accent = MornyeAccent.red,
this.useSystemFont = false,
});
final Color? chromeSurface;
final MornyeAccent accent;
final bool useSystemFont;
/// A single translucent fill for controls inside an existing glass surface.
static Color controlFill(BuildContext context, {bool enabled = true}) {
@@ -97,14 +102,21 @@ class MornyeTheme extends ThemeExtension<MornyeTheme> {
// 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?, MornyeAccent), ThemeData>{};
<(TargetPlatform, Brightness, Color?, MornyeAccent, bool), ThemeData>{};
static ThemeData build(
Brightness brightness, {
Color? chromeSurface,
MornyeAccent accent = MornyeAccent.red,
bool useSystemFont = false,
}) {
final key = (defaultTargetPlatform, brightness, chromeSurface, accent);
final key = (
defaultTargetPlatform,
brightness,
chromeSurface,
accent,
useSystemFont,
);
final cached = _themeCache.remove(key);
if (cached != null) {
_themeCache[key] = cached;
@@ -114,6 +126,7 @@ class MornyeTheme extends ThemeExtension<MornyeTheme> {
brightness,
chromeSurface: chromeSurface,
selectedAccent: accent,
useSystemFont: useSystemFont,
);
if (_themeCache.length >= 16) {
_themeCache.remove(_themeCache.keys.first);
@@ -134,6 +147,7 @@ class MornyeTheme extends ThemeExtension<MornyeTheme> {
brightness ?? theme.brightness,
chromeSurface: chromeSurface,
accent: preferences?.accent ?? MornyeAccent.red,
useSystemFont: preferences?.useSystemFont ?? false,
);
}
@@ -141,6 +155,7 @@ class MornyeTheme extends ThemeExtension<MornyeTheme> {
Brightness brightness, {
Color? chromeSurface,
required MornyeAccent selectedAccent,
required bool useSystemFont,
}) {
final dark = brightness == Brightness.dark;
final accent = accentColor(selectedAccent, brightness);
@@ -179,8 +194,8 @@ class MornyeTheme extends ThemeExtension<MornyeTheme> {
: 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.
// Cupertino's font proxies resolve to SF on Apple and the platform fallback
// elsewhere. Inter remains the default on other platforms unless opted out.
final apple =
defaultTargetPlatform == TargetPlatform.iOS ||
defaultTargetPlatform == TargetPlatform.macOS;
@@ -188,12 +203,14 @@ class MornyeTheme extends ThemeExtension<MornyeTheme> {
final text = type.textStyle.copyWith(
inherit: true,
color: foreground,
fontFamily: apple ? type.textStyle.fontFamily : 'Inter',
fontFamily: apple || useSystemFont ? type.textStyle.fontFamily : 'Inter',
);
final display = type.navLargeTitleTextStyle.copyWith(
inherit: true,
color: foreground,
fontFamily: apple ? type.navLargeTitleTextStyle.fontFamily : 'Inter',
fontFamily: apple || useSystemFont
? 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.
@@ -243,7 +260,11 @@ class MornyeTheme extends ThemeExtension<MornyeTheme> {
fontFamily: text.fontFamily,
splashFactory: NoSplash.splashFactory,
extensions: <ThemeExtension<dynamic>>[
MornyeTheme(chromeSurface: chromeSurface, accent: selectedAccent),
MornyeTheme(
chromeSurface: chromeSurface,
accent: selectedAccent,
useSystemFont: useSystemFont,
),
tokens,
],
);
@@ -335,16 +356,23 @@ class MornyeTheme extends ThemeExtension<MornyeTheme> {
}
@override
MornyeTheme copyWith({Color? chromeSurface, MornyeAccent? accent}) =>
MornyeTheme(
chromeSurface: chromeSurface ?? this.chromeSurface,
accent: accent ?? this.accent,
);
MornyeTheme copyWith({
Color? chromeSurface,
MornyeAccent? accent,
bool? useSystemFont,
}) => MornyeTheme(
chromeSurface: chromeSurface ?? this.chromeSurface,
accent: accent ?? this.accent,
useSystemFont: useSystemFont ?? this.useSystemFont,
);
@override
MornyeTheme lerp(covariant MornyeTheme? other, double t) => MornyeTheme(
chromeSurface: Color.lerp(chromeSurface, other?.chromeSurface, t),
accent: t < 0.5 ? accent : other?.accent ?? accent,
useSystemFont: t < 0.5
? useSystemFont
: other?.useSystemFont ?? useSystemFont,
);
}