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

+13
View File
@@ -74,6 +74,19 @@
"appearanceGlassClaritySubtitle": "Adjust transparency in Mornye menus and navigation. Text contrast is preserved.",
"appearanceGlassTinted": "Tinted",
"appearanceGlassClear": "Clear",
"appearanceGlassClarityBlurOff": "Glass is opaque on this device. Turn on “{setting}” to adjust clarity.",
"@appearanceGlassClarityBlurOff": {
"description": "Shown under the disabled glass clarity slider when the device renders Mornye glass without blur. The placeholder is the name of the force-blur toggle.",
"placeholders": {
"setting": {
"type": "String"
}
}
},
"appearanceGlassClarityHighContrast": "Glass stays opaque while the system’s high contrast setting is on.",
"@appearanceGlassClarityHighContrast": {
"description": "Shown under the disabled glass clarity slider while the OS high contrast accessibility setting is on"
},
"appearanceUseSystemFont": "Use system font",
"appearanceUseSystemFontSubtitle": "Use your device’s default typeface for app text.",
"@appearanceStyle": {
+2
View File
@@ -74,6 +74,8 @@
"appearanceGlassClaritySubtitle": "Atur transparansi menu dan navigasi Mornye. Kontras tulisan tetap dijaga.",
"appearanceGlassTinted": "Pekat",
"appearanceGlassClear": "Bening",
"appearanceGlassClarityBlurOff": "Kaca dibuat pekat di perangkat ini. Aktifkan “{setting}” untuk mengatur kebeningan.",
"appearanceGlassClarityHighContrast": "Kaca tetap pekat selama pengaturan kontras tinggi sistem aktif.",
"appearanceUseSystemFont": "Gunakan font sistem",
"appearanceUseSystemFontSubtitle": "Gunakan jenis huruf bawaan perangkat untuk teks aplikasi.",
"@appearanceStyle": {
@@ -3,6 +3,7 @@ import 'package:spotiflac_android/widgets/app_bottom_sheet.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:spotiflac_android/l10n/l10n.dart';
import 'package:spotiflac_android/constants/language_choices.dart';
import 'package:spotiflac_android/providers/runtime_profile_provider.dart';
import 'package:spotiflac_android/providers/settings_provider.dart';
import 'package:spotiflac_android/providers/theme_provider.dart';
import 'package:spotiflac_android/models/theme_settings.dart';
@@ -493,6 +494,10 @@ class _MornyeGlassClaritySlider extends ConsumerWidget {
final notifier = ref.read(themeProvider.notifier);
final theme = Theme.of(context);
final l10n = context.l10n;
// Flat devices and high contrast draw every glass surface opaque, so the
// slider would change nothing visible. Keep the stored value untouched.
final highContrast = MediaQuery.highContrastOf(context);
final adjustable = !highContrast && ref.watch(mornyeBlurEnabledProvider);
return Padding(
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
child: Column(
@@ -504,6 +509,20 @@ class _MornyeGlassClaritySlider extends ConsumerWidget {
l10n.appearanceGlassClaritySubtitle,
style: theme.textTheme.bodySmall,
),
if (!adjustable) ...[
const SizedBox(height: 8),
Text(
highContrast
? l10n.appearanceGlassClarityHighContrast
: l10n.appearanceGlassClarityBlurOff(
l10n.appearanceForceBlur,
),
key: const ValueKey('mornye-glass-clarity-unavailable'),
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
],
const SizedBox(height: 16),
ExcludeSemantics(
child: ClipRRect(
@@ -553,9 +572,11 @@ class _MornyeGlassClaritySlider extends ConsumerWidget {
value: clarity,
divisions: 20,
label: '${(clarity * 100).round()}%',
onChanged: (value) =>
notifier.setMornyeGlassClarity(value, persist: false),
onChangeEnd: notifier.setMornyeGlassClarity,
onChanged: adjustable
? (value) =>
notifier.setMornyeGlassClarity(value, persist: false)
: null,
onChangeEnd: adjustable ? notifier.setMornyeGlassClarity : null,
),
),
Row(
+5 -5
View File
@@ -88,11 +88,11 @@ class MornyeBottomBar extends ConsumerWidget {
);
final liquidGlass = ref.watch(mornyeLiquidGlassProvider);
// Animated glass tabs already reserve 8px above their visible capsule.
final glassTabs =
blurEnabled &&
liquidGlass &&
!MediaQuery.disableAnimationsOf(context) &&
!MediaQuery.highContrastOf(context);
final glassTabs = MornyeTabBar.usesLiquidGlass(
context,
blurEnabled: blurEnabled,
liquidGlass: liquidGlass,
);
final tabGap = glassTabs ? 0.0 : 8.0;
// These contents do not depend on animation progress. Retain their widget
// instances so folding only updates size/opacity wrappers each frame.
+23 -5
View File
@@ -27,8 +27,11 @@ class MornyeSegmentedControl extends ConsumerWidget {
!MediaQuery.highContrastOf(context) &&
ref.watch(mornyeBlurEnabledProvider);
final animate = !MediaQuery.disableAnimationsOf(context);
// At 0% clarity the material is opaque; drop the shader pill as
// MornyeGlass drops its lens.
final clear = MornyeTheme.glassClarityOf(context) > 0;
final height = MediaQuery.textScalerOf(context).scale(15) + 36;
if (!blur || !animate || !ref.watch(mornyeLiquidGlassProvider)) {
if (!blur || !animate || !clear || !ref.watch(mornyeLiquidGlassProvider)) {
// The Impeller tab bar ignores its pill mode and always runs its shader
// passes. Without blur or motion, draw the resting segments directly.
return _plainSegments(context, blur: blur, height: height);
@@ -614,6 +617,20 @@ class MornyeTabBar extends StatelessWidget {
/// Shader capsule and travelling pill; otherwise the frosted row below.
final bool liquidGlass;
/// Whether the bar renders the shader capsule. Overlays aligned with its
/// icons must use the same decision. At 0% clarity the material is opaque,
/// so the shader pill is dropped as MornyeGlass drops its lens.
static bool usesLiquidGlass(
BuildContext context, {
required bool blurEnabled,
required bool liquidGlass,
}) =>
blurEnabled &&
liquidGlass &&
MornyeTheme.glassClarityOf(context) > 0 &&
!MediaQuery.disableAnimationsOf(context) &&
!MediaQuery.highContrastOf(context);
final List<NavigationDestination> destinations;
final int selectedIndex;
final ValueChanged<int> onSelected;
@@ -628,10 +645,11 @@ class MornyeTabBar extends StatelessWidget {
alpha: scheme.brightness == Brightness.dark ? 0.12 : 0.08,
);
final inactiveIconColor = scheme.onSurface;
if (blurEnabled &&
liquidGlass &&
!MediaQuery.disableAnimationsOf(context) &&
!MediaQuery.highContrastOf(context)) {
if (usesLiquidGlass(
context,
blurEnabled: blurEnabled,
liquidGlass: liquidGlass,
)) {
return LayoutBuilder(
builder: (context, constraints) => SizedBox(
// Leave room above and below for the travelling pill to lift and