fix(mornye): respect glass clarity and accessibility limits

This commit is contained in:
zarzet committed 2026-10-01 00:42:15 +07:00
1 parent 7a7dd99eab
commit a2974acee0
7 files changed
+218 -15

No files matched your search

+82 -2
View File
@@ -4,6 +4,7 @@ import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:spotiflac_android/l10n/l10n.dart';
import 'package:spotiflac_android/models/theme_settings.dart';
import 'package:spotiflac_android/providers/runtime_profile_provider.dart';
import 'package:spotiflac_android/providers/theme_provider.dart';
import 'package:spotiflac_android/screens/settings/appearance_settings_page.dart';
import 'package:spotiflac_android/theme/dynamic_color_wrapper.dart';
@@ -58,8 +59,9 @@ void main() {
Future<void> openSettings(
WidgetTester tester,
SharedPreferences prefs,
) async {
SharedPreferences prefs, {
bool lowEnd = false,
}) async {
tester.view.physicalSize = const Size(390, 1000);
tester.view.devicePixelRatio = 1;
addTearDown(tester.view.reset);
@@ -69,6 +71,8 @@ void main() {
initialThemeSettingsProvider.overrideWithValue(
loadBootstrapThemeSettings(prefs),
),
// Low-end Android without forced blur renders flat, opaque glass.
if (lowEnd) lowEndDeviceProvider.overrideWithValue(true),
],
child: DynamicColorWrapper(
builder: (light, dark, mode) => MaterialApp(
@@ -123,6 +127,82 @@ void main() {
expect(tester.takeException(), isNull);
});
testWidgets('glass clarity waits for blur on flat devices ($mode)', (
tester,
) async {
SharedPreferences.setMockInitialValues({
kThemeStyleKey: 'mornye',
kThemeModeKey: mode.name,
});
final prefs = await SharedPreferences.getInstance();
await openSettings(tester, prefs, lowEnd: true);
final slider = find.byKey(const ValueKey('mornye-glass-clarity'));
final hint = find.byKey(
const ValueKey('mornye-glass-clarity-unavailable'),
);
await tester.ensureVisible(slider);
await tester.pumpAndSettle();
expect(tester.widget<Slider>(slider).onChanged, isNull);
expect(
tester.widget<Text>(hint).data,
'Glass is opaque on this device. Turn on '
'“Always use blur effects” to adjust clarity.',
);
await tester.drag(slider, const Offset(160, 0));
await tester.pumpAndSettle();
expect(tester.widget<Slider>(slider).value, kDefaultMornyeGlassClarity);
expect(prefs.getDouble(kMornyeGlassClarityKey), isNull);
final forceBlur = find.text('Always use blur effects');
await tester.scrollUntilVisible(
forceBlur,
200,
scrollable: find.byType(Scrollable).first,
);
await tester.tap(forceBlur);
await tester.pumpAndSettle();
// The lazily built slider scrolled away; bring it back.
await tester.scrollUntilVisible(
slider,
-200,
scrollable: find.byType(Scrollable).first,
);
await tester.pumpAndSettle();
expect(hint, findsNothing);
expect(tester.widget<Slider>(slider).onChanged, isNotNull);
await tester.drag(slider, const Offset(160, 0));
await tester.pumpAndSettle();
expect(prefs.getDouble(kMornyeGlassClarityKey), 1);
expect(tester.takeException(), isNull);
});
testWidgets('glass clarity explains high contrast ($mode)', (tester) async {
tester.platformDispatcher.accessibilityFeaturesTestValue =
const FakeAccessibilityFeatures(highContrast: true);
addTearDown(
tester.platformDispatcher.clearAccessibilityFeaturesTestValue,
);
SharedPreferences.setMockInitialValues({
kThemeStyleKey: 'mornye',
kThemeModeKey: mode.name,
});
final prefs = await SharedPreferences.getInstance();
await openSettings(tester, prefs);
final slider = find.byKey(const ValueKey('mornye-glass-clarity'));
await tester.ensureVisible(slider);
await tester.pumpAndSettle();
expect(tester.widget<Slider>(slider).onChanged, isNull);
expect(
tester
.widget<Text>(
find.byKey(const ValueKey('mornye-glass-clarity-unavailable')),
)
.data,
'Glass stays opaque while the system’s high contrast setting is on.',
);
expect(tester.takeException(), isNull);
});
testWidgets('Mornye accent updates live and survives restart ($mode)', (
tester,
) async {
+69
View File
@@ -114,6 +114,75 @@ void main() {
expect(tester.takeException(), isNull);
});
for (final clarity in [0.0, 0.5]) {
testWidgets('liquid tab and segment pills follow clarity ($clarity)', (
tester,
) async {
late BuildContext context;
await tester.pumpWidget(
ProviderScope(
child: MaterialApp(
theme: MornyeTheme.build(Brightness.dark, glassClarity: clarity),
home: Scaffold(
body: Builder(
builder: (builderContext) {
context = builderContext;
return Column(
children: [
MornyeSegmentedControl(
labels: const ['One', 'Two'],
selectedIndex: 0,
onChanged: (_) {},
),
MornyeTabBar(
destinations: const [
NavigationDestination(
icon: Icon(Icons.home),
label: 'Home',
),
NavigationDestination(
icon: Icon(Icons.music_note),
label: 'Library',
),
],
selectedIndex: 0,
onSelected: (_) {},
blurEnabled: true,
),
],
);
},
),
),
),
),
);
await tester.pump();
final liquid = clarity > 0;
// Overlays aligned with the bar share this decision.
expect(
MornyeTabBar.usesLiquidGlass(
context,
blurEnabled: true,
liquidGlass: true,
),
liquid,
);
// At 0% the opaque material drops the shader pills, like MornyeGlass
// drops its lens; the resting segments and tabs remain usable.
expect(find.byType(LiquidGlassTabBar), findsNWidgets(liquid ? 2 : 0));
expect(
find.byType(LiquidGlassLens),
liquid ? findsWidgets : findsNothing,
);
// The shader bar paints its labels in more than one layer.
expect(find.text('Two'), findsWidgets);
expect(find.text('Library'), findsWidgets);
expect(tester.takeException(), isNull);
});
}
testWidgets('liquid level keeps the shader glass', (tester) async {
await tester.pumpWidget(
ProviderScope(