mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-10-21 23:11:45 +02:00
feat(appearance): make Mornye glass outlines optional
This commit is contained in:
1 parent
1b9c35f322
commit
1cae561e6c
14 files changed
+204
-34
No files matched your search
@@ -146,7 +146,13 @@ void main() {
|
||||
await tester.pump();
|
||||
}
|
||||
|
||||
for (final clarity in [0.6, 1.0]) {
|
||||
for (final (brightness, clarity, outlines) in [
|
||||
(Brightness.dark, 0.6, true),
|
||||
(Brightness.dark, 1.0, true),
|
||||
(Brightness.light, 0.75, true),
|
||||
(Brightness.light, 0.75, false),
|
||||
(Brightness.dark, 0.75, false),
|
||||
]) {
|
||||
chrome.expand();
|
||||
await tester.pumpWidget(
|
||||
ProviderScope(
|
||||
@@ -162,16 +168,22 @@ void main() {
|
||||
mornyeGlassLevelProvider.overrideWithValue(MornyeGlassLevel.liquid),
|
||||
],
|
||||
child: MaterialApp(
|
||||
theme: MornyeTheme.build(Brightness.dark, glassClarity: clarity),
|
||||
theme: MornyeTheme.build(
|
||||
brightness,
|
||||
glassClarity: clarity,
|
||||
glassOutlines: outlines,
|
||||
),
|
||||
locale: const Locale('en'),
|
||||
localizationsDelegates: AppLocalizations.localizationsDelegates,
|
||||
supportedLocales: AppLocalizations.supportedLocales,
|
||||
home: Scaffold(
|
||||
extendBody: true,
|
||||
body: flatBackdrop
|
||||
? const ColoredBox(
|
||||
color: Colors.black,
|
||||
child: SizedBox.expand(),
|
||||
? ColoredBox(
|
||||
color: brightness == Brightness.dark
|
||||
? Colors.black
|
||||
: Colors.white,
|
||||
child: const SizedBox.expand(),
|
||||
)
|
||||
: const CustomPaint(
|
||||
painter: _Pattern(),
|
||||
@@ -229,7 +241,15 @@ void main() {
|
||||
}
|
||||
expect(find.byType(MiniPlayer), findsOneWidget);
|
||||
final player = tester.state(find.byType(MiniPlayer));
|
||||
final prefix = 'clarity_${(clarity * 100).round()}';
|
||||
final prefix =
|
||||
'${brightness.name}_clarity_${(clarity * 100).round()}_outlines_$outlines';
|
||||
final lenses = tester.widgetList<LiquidGlassLens>(
|
||||
find.byType(LiquidGlassLens),
|
||||
);
|
||||
expect(lenses, isNotEmpty);
|
||||
for (final lens in lenses) {
|
||||
expect(lens.style.shape?.borderWidth, outlines ? 0.75 : 0);
|
||||
}
|
||||
await capture('${prefix}_expanded');
|
||||
await halfway(true);
|
||||
await capture('${prefix}_folding_half');
|
||||
@@ -331,6 +351,15 @@ void main() {
|
||||
await touch.cancel();
|
||||
}
|
||||
await wait(300);
|
||||
// Live emulator frames can lag behind the release animation's duration.
|
||||
for (
|
||||
var attempt = 0;
|
||||
capsule() != restingCapsule && attempt < 20;
|
||||
attempt++
|
||||
) {
|
||||
await wait(100);
|
||||
await tester.pump();
|
||||
}
|
||||
expect(capsule(), restingCapsule);
|
||||
expect(foreground(), restingForeground);
|
||||
expect(tester.state(find.byType(MiniPlayer)), same(player));
|
||||
@@ -339,7 +368,7 @@ void main() {
|
||||
binding.reportData!['mornye_glass_visual'] = {
|
||||
'scope':
|
||||
'Production MornyeBottomBar/MiniPlayer over '
|
||||
'${flatBackdrop ? 'flat black' : 'generated stripes/grid'}; '
|
||||
'${flatBackdrop ? 'flat light/dark surfaces' : 'generated stripes/grid'}; '
|
||||
'paused in-memory playback and settings, no persistent data or network.',
|
||||
'captureSurface':
|
||||
'${Platform.isAndroid ? 'Android converted Flutter image view' : 'iOS window'}; '
|
||||
|
||||
@@ -114,6 +114,8 @@
|
||||
"appearanceAccentPurple": "Purple",
|
||||
"appearanceAccentPink": "Pink",
|
||||
"appearanceGlassClarity": "Glass clarity",
|
||||
"appearanceGlassOutlines": "Glass outlines",
|
||||
"appearanceGlassOutlinesSubtitle": "Show decorative edges around Mornye glass. Turning this off keeps blur and refraction.",
|
||||
"appearanceGlassClaritySubtitle": "Adjust transparency in Mornye menus and navigation. Text contrast is preserved.",
|
||||
"appearanceGlassTinted": "Tinted",
|
||||
"appearanceGlassClear": "Clear",
|
||||
|
||||
@@ -198,6 +198,8 @@
|
||||
"appearanceAccentPurple": "Ungu",
|
||||
"appearanceAccentPink": "Merah muda",
|
||||
"appearanceGlassClarity": "Kebeningan kaca",
|
||||
"appearanceGlassOutlines": "Garis tepi kaca",
|
||||
"appearanceGlassOutlinesSubtitle": "Tampilkan garis dekoratif di tepi kaca Mornye. Mematikannya tetap mempertahankan blur dan pembiasan.",
|
||||
"appearanceGlassClaritySubtitle": "Atur kebeningan kaca menu dan navigasi Mornye. Kontras teks tetap terjaga.",
|
||||
"appearanceGlassTinted": "Berwarna",
|
||||
"appearanceGlassClear": "Bening",
|
||||
|
||||
@@ -8,6 +8,7 @@ const String kThemeStyleKey = 'theme_style';
|
||||
const String kMornyeAccentKey = 'mornye_accent';
|
||||
const String kUseSystemFontKey = 'use_system_font';
|
||||
const String kMornyeGlassClarityKey = 'mornye_glass_clarity';
|
||||
const String kMornyeGlassOutlinesKey = 'mornye_glass_outlines';
|
||||
const double kDefaultMornyeGlassClarity = 0.75;
|
||||
|
||||
double normalizeMornyeGlassClarity(num? value) =>
|
||||
@@ -43,6 +44,7 @@ class ThemeSettings {
|
||||
final MornyeAccent mornyeAccent;
|
||||
final bool useSystemFont;
|
||||
final double mornyeGlassClarity;
|
||||
final bool mornyeGlassOutlines;
|
||||
|
||||
const ThemeSettings({
|
||||
this.themeMode = ThemeMode.system,
|
||||
@@ -53,6 +55,7 @@ class ThemeSettings {
|
||||
this.mornyeAccent = MornyeAccent.red,
|
||||
this.useSystemFont = false,
|
||||
this.mornyeGlassClarity = kDefaultMornyeGlassClarity,
|
||||
this.mornyeGlassOutlines = true,
|
||||
});
|
||||
|
||||
Color get seedColor => Color(seedColorValue);
|
||||
@@ -66,6 +69,7 @@ class ThemeSettings {
|
||||
MornyeAccent? mornyeAccent,
|
||||
bool? useSystemFont,
|
||||
double? mornyeGlassClarity,
|
||||
bool? mornyeGlassOutlines,
|
||||
}) {
|
||||
return ThemeSettings(
|
||||
themeMode: themeMode ?? this.themeMode,
|
||||
@@ -78,6 +82,7 @@ class ThemeSettings {
|
||||
mornyeGlassClarity: normalizeMornyeGlassClarity(
|
||||
mornyeGlassClarity ?? this.mornyeGlassClarity,
|
||||
),
|
||||
mornyeGlassOutlines: mornyeGlassOutlines ?? this.mornyeGlassOutlines,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -90,6 +95,7 @@ class ThemeSettings {
|
||||
kMornyeAccentKey: mornyeAccent.name,
|
||||
kUseSystemFontKey: useSystemFont,
|
||||
kMornyeGlassClarityKey: mornyeGlassClarity,
|
||||
kMornyeGlassOutlinesKey: mornyeGlassOutlines,
|
||||
};
|
||||
|
||||
factory ThemeSettings.fromJson(Map<String, dynamic> json) {
|
||||
@@ -104,6 +110,7 @@ class ThemeSettings {
|
||||
mornyeGlassClarity: normalizeMornyeGlassClarity(
|
||||
json[kMornyeGlassClarityKey] as num?,
|
||||
),
|
||||
mornyeGlassOutlines: json[kMornyeGlassOutlinesKey] as bool? ?? true,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -118,7 +125,8 @@ class ThemeSettings {
|
||||
other.style == style &&
|
||||
other.mornyeAccent == mornyeAccent &&
|
||||
other.useSystemFont == useSystemFont &&
|
||||
other.mornyeGlassClarity == mornyeGlassClarity;
|
||||
other.mornyeGlassClarity == mornyeGlassClarity &&
|
||||
other.mornyeGlassOutlines == mornyeGlassOutlines;
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -130,7 +138,8 @@ class ThemeSettings {
|
||||
style.hashCode ^
|
||||
mornyeAccent.hashCode ^
|
||||
useSystemFont.hashCode ^
|
||||
mornyeGlassClarity.hashCode;
|
||||
mornyeGlassClarity.hashCode ^
|
||||
mornyeGlassOutlines.hashCode;
|
||||
}
|
||||
|
||||
ThemeMode themeModeFromString(String? value) {
|
||||
|
||||
@@ -19,6 +19,7 @@ ThemeSettings loadBootstrapThemeSettings(SharedPreferences prefs) {
|
||||
mornyeGlassClarity: normalizeMornyeGlassClarity(
|
||||
prefs.getDouble(kMornyeGlassClarityKey),
|
||||
),
|
||||
mornyeGlassOutlines: prefs.getBool(kMornyeGlassOutlinesKey) ?? true,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -80,6 +81,10 @@ class ThemeNotifier extends Notifier<ThemeSettings> {
|
||||
kMornyeGlassClarityKey,
|
||||
snapshot.mornyeGlassClarity,
|
||||
);
|
||||
await prefs.setBool(
|
||||
kMornyeGlassOutlinesKey,
|
||||
snapshot.mornyeGlassOutlines,
|
||||
);
|
||||
} catch (e) {
|
||||
debugPrint('Error saving theme settings: $e');
|
||||
}
|
||||
@@ -127,4 +132,9 @@ class ThemeNotifier extends Notifier<ThemeSettings> {
|
||||
_editState((current) => current.copyWith(mornyeGlassClarity: value));
|
||||
if (persist) await _saveToStorage();
|
||||
}
|
||||
|
||||
Future<void> setMornyeGlassOutlines(bool value) async {
|
||||
_editState((current) => current.copyWith(mornyeGlassOutlines: value));
|
||||
await _saveToStorage();
|
||||
}
|
||||
}
|
||||
@@ -113,6 +113,17 @@ class AppearanceSettingsPage extends ConsumerWidget {
|
||||
.setMornyeAccent,
|
||||
),
|
||||
const _MornyeGlassClaritySlider(),
|
||||
SettingsSwitchItem(
|
||||
key: const ValueKey('mornye-glass-outlines'),
|
||||
icon: Icons.rounded_corner,
|
||||
title: context.l10n.appearanceGlassOutlines,
|
||||
subtitle: context.l10n.appearanceGlassOutlinesSubtitle,
|
||||
value: themeSettings.mornyeGlassOutlines,
|
||||
onChanged: ref
|
||||
.read(themeProvider.notifier)
|
||||
.setMornyeGlassOutlines,
|
||||
showDivider: false,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
||||
@@ -89,6 +89,18 @@ class SettingsSearchCatalog {
|
||||
),
|
||||
],
|
||||
appearance = [
|
||||
SettingsSearchEntry(
|
||||
icon: Icons.rounded_corner,
|
||||
title: l10n.appearanceGlassOutlines,
|
||||
subtitle: l10n.appearanceGlassOutlinesSubtitle,
|
||||
keywords: const [
|
||||
'mornye',
|
||||
'liquid glass',
|
||||
'border',
|
||||
'rim',
|
||||
'outline',
|
||||
],
|
||||
),
|
||||
SettingsSearchEntry(
|
||||
icon: Icons.wallpaper,
|
||||
title: l10n.appearanceDynamicColor,
|
||||
|
||||
@@ -330,6 +330,7 @@ class _ThemePicker extends ConsumerWidget {
|
||||
accent: settings.mornyeAccent,
|
||||
useSystemFont: settings.useSystemFont,
|
||||
glassClarity: settings.mornyeGlassClarity,
|
||||
glassOutlines: settings.mornyeGlassOutlines,
|
||||
)
|
||||
: brightness == Brightness.dark
|
||||
? AppTheme.dark(
|
||||
|
||||
@@ -24,12 +24,14 @@ class DynamicColorWrapper extends ConsumerWidget {
|
||||
accent: themeSettings.mornyeAccent,
|
||||
useSystemFont: themeSettings.useSystemFont,
|
||||
glassClarity: themeSettings.mornyeGlassClarity,
|
||||
glassOutlines: themeSettings.mornyeGlassOutlines,
|
||||
),
|
||||
MornyeTheme.build(
|
||||
Brightness.dark,
|
||||
accent: themeSettings.mornyeAccent,
|
||||
useSystemFont: themeSettings.useSystemFont,
|
||||
glassClarity: themeSettings.mornyeGlassClarity,
|
||||
glassOutlines: themeSettings.mornyeGlassOutlines,
|
||||
),
|
||||
themeSettings.themeMode,
|
||||
);
|
||||
|
||||
@@ -14,17 +14,22 @@ class MornyeTheme extends ThemeExtension<MornyeTheme> {
|
||||
this.accent = MornyeAccent.red,
|
||||
this.useSystemFont = false,
|
||||
this.glassClarity = kDefaultMornyeGlassClarity,
|
||||
this.glassOutlines = true,
|
||||
});
|
||||
|
||||
final Color? chromeSurface;
|
||||
final MornyeAccent accent;
|
||||
final bool useSystemFont;
|
||||
final double glassClarity;
|
||||
final bool glassOutlines;
|
||||
|
||||
static double glassClarityOf(BuildContext context) =>
|
||||
Theme.of(context).extension<MornyeTheme>()?.glassClarity ??
|
||||
kDefaultMornyeGlassClarity;
|
||||
|
||||
static bool glassOutlinesOf(BuildContext context) =>
|
||||
Theme.of(context).extension<MornyeTheme>()?.glassOutlines ?? true;
|
||||
|
||||
/// A single translucent fill for controls inside an existing glass surface.
|
||||
static Color controlFill(BuildContext context, {bool enabled = true}) {
|
||||
final theme = Theme.of(context);
|
||||
@@ -121,6 +126,7 @@ class MornyeTheme extends ThemeExtension<MornyeTheme> {
|
||||
MornyeAccent accent = MornyeAccent.red,
|
||||
bool useSystemFont = false,
|
||||
double glassClarity = kDefaultMornyeGlassClarity,
|
||||
bool glassOutlines = true,
|
||||
}) {
|
||||
final key = (
|
||||
defaultTargetPlatform,
|
||||
@@ -132,7 +138,7 @@ class MornyeTheme extends ThemeExtension<MornyeTheme> {
|
||||
final cached = _themeCache.remove(key);
|
||||
if (cached != null) {
|
||||
_themeCache[key] = cached;
|
||||
return _withGlassClarity(cached, glassClarity);
|
||||
return _withGlassPreferences(cached, glassClarity, glassOutlines);
|
||||
}
|
||||
final theme = _build(
|
||||
brightness,
|
||||
@@ -144,19 +150,27 @@ class MornyeTheme extends ThemeExtension<MornyeTheme> {
|
||||
_themeCache.remove(_themeCache.keys.first);
|
||||
}
|
||||
_themeCache[key] = theme;
|
||||
return _withGlassClarity(theme, glassClarity);
|
||||
return _withGlassPreferences(theme, glassClarity, glassOutlines);
|
||||
}
|
||||
|
||||
// Moving the clarity slider only changes the material, not typography or
|
||||
// seeded colors. Keep the expensive base theme cached independently.
|
||||
static ThemeData _withGlassClarity(ThemeData theme, double value) {
|
||||
static ThemeData _withGlassPreferences(
|
||||
ThemeData theme,
|
||||
double value,
|
||||
bool outlines,
|
||||
) {
|
||||
final clarity = normalizeMornyeGlassClarity(value);
|
||||
if (theme.extension<MornyeTheme>()!.glassClarity == clarity) return theme;
|
||||
final preferences = theme.extension<MornyeTheme>()!;
|
||||
if (preferences.glassClarity == clarity &&
|
||||
preferences.glassOutlines == outlines) {
|
||||
return theme;
|
||||
}
|
||||
return theme.copyWith(
|
||||
extensions: [
|
||||
for (final extension in theme.extensions.values)
|
||||
if (extension is MornyeTheme)
|
||||
extension.copyWith(glassClarity: clarity)
|
||||
extension.copyWith(glassClarity: clarity, glassOutlines: outlines)
|
||||
else
|
||||
extension,
|
||||
],
|
||||
@@ -177,6 +191,7 @@ class MornyeTheme extends ThemeExtension<MornyeTheme> {
|
||||
accent: preferences?.accent ?? MornyeAccent.red,
|
||||
useSystemFont: preferences?.useSystemFont ?? false,
|
||||
glassClarity: preferences?.glassClarity ?? kDefaultMornyeGlassClarity,
|
||||
glassOutlines: preferences?.glassOutlines ?? true,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -394,11 +409,13 @@ class MornyeTheme extends ThemeExtension<MornyeTheme> {
|
||||
MornyeAccent? accent,
|
||||
bool? useSystemFont,
|
||||
double? glassClarity,
|
||||
bool? glassOutlines,
|
||||
}) => MornyeTheme(
|
||||
chromeSurface: chromeSurface ?? this.chromeSurface,
|
||||
accent: accent ?? this.accent,
|
||||
useSystemFont: useSystemFont ?? this.useSystemFont,
|
||||
glassClarity: glassClarity ?? this.glassClarity,
|
||||
glassOutlines: glassOutlines ?? this.glassOutlines,
|
||||
);
|
||||
|
||||
@override
|
||||
@@ -411,6 +428,9 @@ class MornyeTheme extends ThemeExtension<MornyeTheme> {
|
||||
glassClarity:
|
||||
glassClarity +
|
||||
((other?.glassClarity ?? glassClarity) - glassClarity) * t,
|
||||
glassOutlines: t < 0.5
|
||||
? glassOutlines
|
||||
: other?.glassOutlines ?? glassOutlines,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -184,6 +184,7 @@ class MornyeGlass extends ConsumerWidget {
|
||||
clarity > 0 &&
|
||||
!MediaQuery.highContrastOf(context);
|
||||
final dark = scheme.brightness == Brightness.dark;
|
||||
final outlines = MornyeTheme.glassOutlinesOf(context);
|
||||
final shape = BorderRadius.vertical(
|
||||
top: firstInGroup ? Radius.circular(radius) : Radius.zero,
|
||||
bottom: lastInGroup ? Radius.circular(radius) : Radius.zero,
|
||||
@@ -280,13 +281,18 @@ class MornyeGlass extends ConsumerWidget {
|
||||
: null,
|
||||
),
|
||||
child: CustomPaint(
|
||||
foregroundPainter: _MornyeGlassRim(
|
||||
shape: shape,
|
||||
firstInGroup: firstInGroup,
|
||||
lastInGroup: lastInGroup,
|
||||
illuminated: translucent && (!useLens || highlightBorder),
|
||||
emphasized: highlightBorder,
|
||||
),
|
||||
// The optical lens already draws its own rim. Avoid a second dark
|
||||
// contact stroke on top, particularly visible against light pages.
|
||||
foregroundPainter: outlines && (!useLens || highlightBorder)
|
||||
? _MornyeGlassRim(
|
||||
shape: shape,
|
||||
firstInGroup: firstInGroup,
|
||||
lastInGroup: lastInGroup,
|
||||
illuminated: translucent && (!useLens || highlightBorder),
|
||||
emphasized: highlightBorder,
|
||||
dark: dark,
|
||||
)
|
||||
: null,
|
||||
child: ClipRRect(
|
||||
borderRadius: shape,
|
||||
child: useLens
|
||||
@@ -324,6 +330,7 @@ class _MornyeGlassRim extends CustomPainter {
|
||||
required this.lastInGroup,
|
||||
required this.illuminated,
|
||||
required this.emphasized,
|
||||
required this.dark,
|
||||
});
|
||||
|
||||
final BorderRadius shape;
|
||||
@@ -331,6 +338,7 @@ class _MornyeGlassRim extends CustomPainter {
|
||||
final bool lastInGroup;
|
||||
final bool illuminated;
|
||||
final bool emphasized;
|
||||
final bool dark;
|
||||
|
||||
@override
|
||||
void paint(Canvas canvas, Size size) {
|
||||
@@ -352,7 +360,7 @@ class _MornyeGlassRim extends CustomPainter {
|
||||
..style = PaintingStyle.stroke
|
||||
..strokeWidth = 0.5
|
||||
// A thin contact edge must not paint a shadow under the whole glass.
|
||||
..color = Colors.black.withValues(alpha: 0.5),
|
||||
..color = Colors.black.withValues(alpha: dark ? 0.5 : 0.16),
|
||||
);
|
||||
if (illuminated) {
|
||||
canvas.drawRRect(
|
||||
@@ -387,7 +395,8 @@ class _MornyeGlassRim extends CustomPainter {
|
||||
firstInGroup != oldDelegate.firstInGroup ||
|
||||
lastInGroup != oldDelegate.lastInGroup ||
|
||||
illuminated != oldDelegate.illuminated ||
|
||||
emphasized != oldDelegate.emphasized;
|
||||
emphasized != oldDelegate.emphasized ||
|
||||
dark != oldDelegate.dark;
|
||||
}
|
||||
|
||||
/// A searchable category uses the same material as the navigation capsule.
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:liquid_glass_easy/liquid_glass_easy.dart';
|
||||
import 'package:spotiflac_android/theme/mornye_theme.dart';
|
||||
|
||||
/// A bounded package lens without a captured-screen view or a touch ticker.
|
||||
class MornyeLiquidBackdrop extends StatelessWidget {
|
||||
@@ -28,14 +29,16 @@ class MornyeLiquidBackdrop extends StatelessWidget {
|
||||
Widget build(BuildContext context) {
|
||||
if (interaction && progress <= 0) return child;
|
||||
final effect = interaction ? progress : 1.0;
|
||||
final outlines = MornyeTheme.glassOutlinesOf(context);
|
||||
final dark = Theme.of(context).brightness == Brightness.dark;
|
||||
return LiquidGlassLens(
|
||||
style: LiquidGlassStyle(
|
||||
shape: LiquidGlassShape.roundedRectangle(
|
||||
cornerRadius: borderRadius.topLeft.x,
|
||||
borderWidth: 0.75,
|
||||
borderWidth: outlines ? 0.75 : 0,
|
||||
lightDirection: 0,
|
||||
lightIntensity: 0.25 * effect,
|
||||
lightColor: Colors.white,
|
||||
lightIntensity: outlines ? (dark ? 0.25 : 0.12) * effect : 0,
|
||||
lightColor: dark ? Colors.white : const Color(0x88ffffff),
|
||||
borderType: const OpticalBorder(
|
||||
ambientIntensity: 0.05,
|
||||
lightSpread: 0.2,
|
||||
|
||||
@@ -5,6 +5,7 @@ import 'dart:ui' as ui;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:liquid_glass_easy/liquid_glass_easy.dart';
|
||||
import 'package:spotiflac_android/theme/mornye_theme.dart';
|
||||
import 'package:spotiflac_android/widgets/mornye_liquid_backdrop.dart';
|
||||
|
||||
Future<ui.FragmentProgram>? _selectionMaskProgram;
|
||||
@@ -247,10 +248,14 @@ class _MornyeSelectionPillState extends State<MornyeSelectionPill>
|
||||
: 0.08,
|
||||
),
|
||||
borderRadius: BorderRadius.circular(_pillRadius),
|
||||
border: Border.all(
|
||||
color: scheme.onSurface.withValues(alpha: 0.05),
|
||||
width: 0.5,
|
||||
),
|
||||
border: MornyeTheme.glassOutlinesOf(context)
|
||||
? Border.all(
|
||||
color: scheme.onSurface.withValues(
|
||||
alpha: 0.05,
|
||||
),
|
||||
width: 0.5,
|
||||
)
|
||||
: null,
|
||||
boxShadow: [
|
||||
BoxShadow(
|
||||
color: Colors.black.withValues(alpha: 0.08),
|
||||
@@ -408,10 +413,14 @@ class _MornyeSelectionPillState extends State<MornyeSelectionPill>
|
||||
? 0.24
|
||||
: 0.08),
|
||||
),
|
||||
border: Border.all(
|
||||
color: Colors.white.withValues(alpha: 0.6 * progress),
|
||||
width: 0.75,
|
||||
),
|
||||
border: MornyeTheme.glassOutlinesOf(context)
|
||||
? Border.all(
|
||||
color: Colors.white.withValues(
|
||||
alpha: 0.6 * progress,
|
||||
),
|
||||
width: 0.75,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
child: const SizedBox.expand(),
|
||||
),
|
||||
|
||||
@@ -11,6 +11,19 @@ import 'package:spotiflac_android/theme/dynamic_color_wrapper.dart';
|
||||
import 'package:spotiflac_android/theme/mornye_theme.dart';
|
||||
|
||||
void main() {
|
||||
test(
|
||||
'old themes retain outlines and new themes serialize the preference',
|
||||
() {
|
||||
expect(ThemeSettings.fromJson({}).mornyeGlassOutlines, isTrue);
|
||||
const settings = ThemeSettings(mornyeGlassOutlines: false);
|
||||
expect(ThemeSettings.fromJson(settings.toJson()), settings);
|
||||
expect(
|
||||
settings.copyWith(mornyeGlassClarity: 1).mornyeGlassOutlines,
|
||||
isFalse,
|
||||
);
|
||||
expect(settings, isNot(const ThemeSettings()));
|
||||
},
|
||||
);
|
||||
test('glass clarity restores safely from old and invalid settings', () {
|
||||
expect(
|
||||
ThemeSettings.fromJson({}).mornyeGlassClarity,
|
||||
@@ -90,6 +103,44 @@ void main() {
|
||||
}
|
||||
|
||||
for (final mode in [ThemeMode.light, ThemeMode.dark]) {
|
||||
testWidgets(
|
||||
'glass outlines toggle survives restart and local themes ($mode)',
|
||||
(tester) async {
|
||||
SharedPreferences.setMockInitialValues({
|
||||
kThemeStyleKey: 'mornye',
|
||||
kThemeModeKey: mode.name,
|
||||
});
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await openSettings(tester, prefs);
|
||||
final toggle = find.byKey(const ValueKey('mornye-glass-outlines'));
|
||||
await tester.ensureVisible(toggle);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Glass outlines'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(prefs.getBool(kMornyeGlassOutlinesKey), isFalse);
|
||||
final context = tester.element(toggle);
|
||||
expect(MornyeTheme.glassOutlinesOf(context), isFalse);
|
||||
final local = MornyeTheme.fromContext(
|
||||
context,
|
||||
brightness: Brightness.dark,
|
||||
);
|
||||
expect(local.extension<MornyeTheme>()!.glassOutlines, isFalse);
|
||||
expect(
|
||||
local.extension<MornyeTheme>()!.glassClarity,
|
||||
kDefaultMornyeGlassClarity,
|
||||
);
|
||||
await tester.pumpWidget(const SizedBox());
|
||||
await openSettings(tester, prefs);
|
||||
expect(MornyeTheme.glassOutlinesOf(tester.element(toggle)), isFalse);
|
||||
await tester.ensureVisible(toggle);
|
||||
await tester.pumpAndSettle();
|
||||
await tester.tap(find.text('Glass outlines'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(prefs.getBool(kMornyeGlassOutlinesKey), isTrue);
|
||||
expect(tester.takeException(), isNull);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('glass clarity previews live and survives restart ($mode)', (
|
||||
tester,
|
||||
) async {
|
||||
|
||||
Reference in new issue
Block a user