perf(player): rebuild preview buttons only on status changes

This commit is contained in:
zarzet committed 2026-09-26 23:41:58 +07:00
1 parent abfd8df5fb
commit 7a26f3beac
2 files changed
+96 -5

No files matched your search

+13 -5
View File
@@ -39,8 +39,12 @@ class PreviewButton extends ConsumerWidget {
// When the built-in player is currently on this track, mirror and control // When the built-in player is currently on this track, mirror and control
// it (consistent with the mini player) rather than the preview snippet. // it (consistent with the mini player) rather than the preview snippet.
final mainItem = ref.watch(currentMediaItemProvider).value; final isCurrentMainTrack = ref.watch(
if (_isCurrentMainTrack(mainItem)) { currentMediaItemProvider.select(
(item) => _isCurrentMainTrack(item.value),
),
);
if (isCurrentMainTrack) {
final isPlaying = ref.watch(playbackPlayingProvider); final isPlaying = ref.watch(playbackPlayingProvider);
return IconButton( return IconButton(
iconSize: size, iconSize: size,
@@ -62,9 +66,13 @@ class PreviewButton extends ConsumerWidget {
if (!track.hasPreview) return const SizedBox.shrink(); if (!track.hasPreview) return const SizedBox.shrink();
final previewState = ref.watch(previewPlayerProvider); final status = ref.watch(
final isActive = previewState.isActiveUrl(track.previewUrl); previewPlayerProvider.select(
final status = isActive ? previewState.status : PreviewStatus.idle; (state) => state.isActiveUrl(track.previewUrl)
? state.status
: PreviewStatus.idle,
),
);
final Widget icon; final Widget icon;
final String tooltip; final String tooltip;
+83
View File
@@ -7,6 +7,7 @@ import 'package:spotiflac_android/models/settings.dart';
import 'package:spotiflac_android/models/track.dart'; import 'package:spotiflac_android/models/track.dart';
import 'package:spotiflac_android/providers/library_collections_provider.dart'; import 'package:spotiflac_android/providers/library_collections_provider.dart';
import 'package:spotiflac_android/providers/music_player_provider.dart'; import 'package:spotiflac_android/providers/music_player_provider.dart';
import 'package:spotiflac_android/providers/preview_player_provider.dart';
import 'package:spotiflac_android/providers/settings_provider.dart'; import 'package:spotiflac_android/providers/settings_provider.dart';
import 'package:spotiflac_android/widgets/preview_button.dart'; import 'package:spotiflac_android/widgets/preview_button.dart';
import 'package:spotiflac_android/widgets/track_collection_quick_actions.dart'; import 'package:spotiflac_android/widgets/track_collection_quick_actions.dart';
@@ -79,6 +80,69 @@ void main() {
expectCenteredHitbox(tester, Icons.play_circle_fill_rounded); expectCenteredHitbox(tester, Icons.play_circle_fill_rounded);
}); });
testWidgets('preview progress does not rebuild track buttons', (
tester,
) async {
final player = _TestPreviewPlayer();
var builds = 0;
await tester.pumpWidget(
ProviderScope(
overrides: [
currentMediaItemProvider.overrideWith((ref) => Stream.value(null)),
previewPlayerProvider.overrideWith(() => player),
],
child: MaterialApp(
localizationsDelegates: AppLocalizations.localizationsDelegates,
supportedLocales: AppLocalizations.supportedLocales,
home: Scaffold(
body: _CountingPreviewButton(track: track, onBuild: () => builds++),
),
),
),
);
await tester.pumpAndSettle();
player.emit(
PreviewPlayerState(
activeUrl: track.previewUrl,
status: PreviewStatus.playing,
),
);
await tester.pump();
expect(find.byIcon(Icons.pause_circle_filled_rounded), findsOneWidget);
final playingBuilds = builds;
for (var second = 1; second <= 3; second++) {
player.emit(
PreviewPlayerState(
activeUrl: track.previewUrl,
status: PreviewStatus.playing,
position: Duration(seconds: second),
duration: const Duration(seconds: 30),
),
);
await tester.pump();
}
expect(builds, playingBuilds);
player.emit(
PreviewPlayerState(
activeUrl: track.previewUrl,
status: PreviewStatus.paused,
),
);
await tester.pump();
expect(find.byIcon(Icons.play_circle_fill_rounded), findsOneWidget);
expect(builds, playingBuilds + 1);
player.emit(
const PreviewPlayerState(
activeUrl: 'https://example.com/another-preview.mp3',
status: PreviewStatus.playing,
),
);
await tester.pump();
expect(find.byIcon(Icons.play_circle_outline_rounded), findsOneWidget);
});
testWidgets('overflow menu icon is centered in its adjacent hitbox', ( testWidgets('overflow menu icon is centered in its adjacent hitbox', (
tester, tester,
) async { ) async {
@@ -157,6 +221,25 @@ class _TestSettingsNotifier extends SettingsNotifier {
AppSettings build() => const AppSettings(); AppSettings build() => const AppSettings();
} }
class _TestPreviewPlayer extends PreviewPlayerController {
@override
PreviewPlayerState build() => const PreviewPlayerState();
void emit(PreviewPlayerState next) => state = next;
}
class _CountingPreviewButton extends PreviewButton {
const _CountingPreviewButton({required super.track, required this.onBuild});
final VoidCallback onBuild;
@override
Widget build(BuildContext context, WidgetRef ref) {
onBuild();
return super.build(context, ref);
}
}
class _TestLibraryCollectionsNotifier extends LibraryCollectionsNotifier { class _TestLibraryCollectionsNotifier extends LibraryCollectionsNotifier {
@override @override
LibraryCollectionsState build() => LibraryCollectionsState(isLoaded: true); LibraryCollectionsState build() => LibraryCollectionsState(isLoaded: true);