fix(music): replace the Instant Mix faders with a wand icon
The three vertical faders read as an equalizer in a music context and are the vertical twin of the video player's settings icon. Use wand_stars, which names what the action produces and collides with no neighbouring affordance in the action bar or the music context menu. close #1629
This commit is contained in:
@@ -395,7 +395,7 @@ class MediaContextMenuState extends State<MediaContextMenu> {
|
|||||||
// reachable (capabilities stay truthy for offline servers).
|
// reachable (capabilities stay truthy for offline servers).
|
||||||
if (itemServerOnline && (mediaClient?.capabilities.instantMix ?? false)) {
|
if (itemServerOnline && (mediaClient?.capabilities.instantMix ?? false)) {
|
||||||
menuActions.add(
|
menuActions.add(
|
||||||
_MenuAction(value: 'music_instant_mix', icon: Symbols.instant_mix_rounded, label: t.music.instantMix),
|
_MenuAction(value: 'music_instant_mix', icon: Symbols.wand_stars_rounded, label: t.music.instantMix),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ List<FocusableAction> buildMusicActions({
|
|||||||
if (onInstantMix != null)
|
if (onInstantMix != null)
|
||||||
FocusableAction(
|
FocusableAction(
|
||||||
debugLabel: 'music_instant_mix',
|
debugLabel: 'music_instant_mix',
|
||||||
icon: Symbols.instant_mix_rounded,
|
icon: Symbols.wand_stars_rounded,
|
||||||
tooltip: t.music.instantMix,
|
tooltip: t.music.instantMix,
|
||||||
onPressed: onInstantMix,
|
onPressed: onInstantMix,
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import 'package:flutter/services.dart';
|
|||||||
import 'package:flutter_test/flutter_test.dart';
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:http/testing.dart';
|
import 'package:http/testing.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
import 'package:plezy/connection/connection.dart';
|
import 'package:plezy/connection/connection.dart';
|
||||||
import 'package:plezy/database/app_database.dart';
|
import 'package:plezy/database/app_database.dart';
|
||||||
import 'package:plezy/i18n/strings.g.dart';
|
import 'package:plezy/i18n/strings.g.dart';
|
||||||
@@ -556,6 +557,42 @@ void main() {
|
|||||||
expect(find.text('picker target'), findsOneWidget);
|
expect(find.text('picker target'), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('the music menu gives Instant Mix an icon it shares with no sibling row', (tester) async {
|
||||||
|
final track = testMediaItem(
|
||||||
|
id: 'track-1',
|
||||||
|
backend: MediaBackend.jellyfin,
|
||||||
|
kind: MediaKind.track,
|
||||||
|
title: 'Track',
|
||||||
|
parentId: 'album-1',
|
||||||
|
parentTitle: 'Album',
|
||||||
|
grandparentId: 'artist-1',
|
||||||
|
grandparentTitle: 'Artist',
|
||||||
|
serverId: 'srv-1',
|
||||||
|
);
|
||||||
|
final harness = await _pumpSiblingMusicMenu(tester, item: track, relatedItems: const []);
|
||||||
|
|
||||||
|
harness.menuKey.currentState!.showContextMenu(tester.element(find.text('mini-player menu target')));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(find.text(t.music.instantMix), findsOneWidget);
|
||||||
|
expect(find.byIcon(Symbols.wand_stars_rounded), findsOneWidget);
|
||||||
|
|
||||||
|
// #1629: the fader glyph reads as an equalizer, and the neighbouring
|
||||||
|
// rows must stay tellable apart at a glance on a TV.
|
||||||
|
expect(find.byIcon(Symbols.instant_mix_rounded), findsNothing);
|
||||||
|
expect(find.byIcon(Symbols.tune_rounded), findsNothing);
|
||||||
|
final musicIcons = tester
|
||||||
|
.widgetList<Icon>(find.byType(Icon))
|
||||||
|
.map((icon) => icon.icon)
|
||||||
|
.whereType<IconData>()
|
||||||
|
.toList();
|
||||||
|
expect(
|
||||||
|
musicIcons.where((icon) => icon == Symbols.wand_stars_rounded),
|
||||||
|
hasLength(1),
|
||||||
|
reason: 'Instant Mix must not reuse the glyph of Play, Play next, Add to queue, or Go to artist',
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('track album action uses the profile navigator from the sibling menu overlay', (tester) async {
|
testWidgets('track album action uses the profile navigator from the sibling menu overlay', (tester) async {
|
||||||
final track = testMediaItem(
|
final track = testMediaItem(
|
||||||
id: 'track-1',
|
id: 'track-1',
|
||||||
|
|||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:material_symbols_icons/symbols.dart';
|
||||||
|
import 'package:plezy/focus/focusable_action_bar.dart';
|
||||||
|
import 'package:plezy/i18n/strings.g.dart';
|
||||||
|
import 'package:plezy/theme/mono_theme.dart';
|
||||||
|
import 'package:plezy/widgets/music/music_actions.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
TestWidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
setUp(() => LocaleSettings.setLocaleSync(AppLocale.en));
|
||||||
|
|
||||||
|
testWidgets('Instant Mix renders a distinct icon and runs its callback when the server supports it', (tester) async {
|
||||||
|
var mixes = 0;
|
||||||
|
|
||||||
|
await tester.pumpWidget(_wrap(buildMusicActions(onPlay: () {}, onShuffle: () {}, onInstantMix: () => mixes++)));
|
||||||
|
|
||||||
|
final instantMix = find.byIcon(Symbols.wand_stars_rounded);
|
||||||
|
expect(instantMix, findsOneWidget);
|
||||||
|
expect(
|
||||||
|
find.ancestor(of: instantMix, matching: find.byTooltip(t.music.instantMix)),
|
||||||
|
findsOneWidget,
|
||||||
|
reason: 'the icon is the only affordance on TV, so it must carry the Instant Mix tooltip',
|
||||||
|
);
|
||||||
|
|
||||||
|
// #1629: the fader glyph reads as an equalizer in a music context, and is
|
||||||
|
// the vertical twin of the video player's settings icon.
|
||||||
|
expect(find.byIcon(Symbols.instant_mix_rounded), findsNothing);
|
||||||
|
expect(find.byIcon(Symbols.tune_rounded), findsNothing);
|
||||||
|
// It must also stay distinguishable from the shuffle button beside it.
|
||||||
|
expect(find.byIcon(Symbols.shuffle_rounded), findsOneWidget);
|
||||||
|
|
||||||
|
await tester.tap(instantMix);
|
||||||
|
await tester.pump();
|
||||||
|
|
||||||
|
expect(mixes, 1);
|
||||||
|
});
|
||||||
|
|
||||||
|
testWidgets('Instant Mix is absent when the server lacks the capability', (tester) async {
|
||||||
|
await tester.pumpWidget(_wrap(buildMusicActions(onPlay: () {}, onShuffle: () {})));
|
||||||
|
|
||||||
|
expect(find.byIcon(Symbols.wand_stars_rounded), findsNothing);
|
||||||
|
expect(find.byTooltip(t.music.instantMix), findsNothing);
|
||||||
|
expect(find.byIcon(Symbols.shuffle_rounded), findsOneWidget);
|
||||||
|
expect(find.text(t.common.play), findsOneWidget);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Widget _wrap(List<FocusableAction> actions) {
|
||||||
|
return TranslationProvider(
|
||||||
|
child: MaterialApp(
|
||||||
|
theme: monoTheme(dark: true),
|
||||||
|
home: Scaffold(
|
||||||
|
body: Center(child: FocusableActionBar(actions: actions)),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user