From a89f78696091c232f4420cea9ad285a86d10c38c Mon Sep 17 00:00:00 2001 From: zarzet <42882290+zarzet@users.noreply.github.com> Date: Sun, 6 Sep 2026 14:04:05 +0700 Subject: [PATCH] perf(ui): retain platform link lookup across rebuilds Keep the platform lookup Future in widget state and replace it only when track identifiers change. Theme rebuilds retain loaded content without another native call. Add widget regressions for theme and identifier changes. --- lib/widgets/open_on_platform_sheet.dart | 36 ++++++++++++--- test/open_on_platform_sheet_test.dart | 60 +++++++++++++++++++++++++ 2 files changed, 91 insertions(+), 5 deletions(-) create mode 100644 test/open_on_platform_sheet_test.dart diff --git a/lib/widgets/open_on_platform_sheet.dart b/lib/widgets/open_on_platform_sheet.dart index 471075f8..5af332db 100644 --- a/lib/widgets/open_on_platform_sheet.dart +++ b/lib/widgets/open_on_platform_sheet.dart @@ -9,7 +9,7 @@ import 'package:url_launcher/url_launcher.dart'; /// Bottom sheet listing every streaming platform song.link resolves for a /// track. Data-driven from the generic platform-link map so the app core /// stays service-agnostic; tapping a row opens the link externally. -class OpenOnPlatformSheet extends StatelessWidget { +class OpenOnPlatformSheet extends StatefulWidget { final String spotifyId; final String isrc; @@ -31,6 +31,35 @@ class OpenOnPlatformSheet extends StatelessWidget { ); } + @override + State createState() => _OpenOnPlatformSheetState(); +} + +class _OpenOnPlatformSheetState extends State { + late Future> _links; + + @override + void initState() { + super.initState(); + _loadLinks(); + } + + @override + void didUpdateWidget(OpenOnPlatformSheet oldWidget) { + super.didUpdateWidget(oldWidget); + if (oldWidget.spotifyId != widget.spotifyId || + oldWidget.isrc != widget.isrc) { + _loadLinks(); + } + } + + void _loadLinks() { + _links = PlatformBridge.getTrackPlatformLinks( + spotifyId: widget.spotifyId, + isrc: widget.isrc, + ); + } + static const Map _platformNames = { 'spotify': 'Spotify', 'appleMusic': 'Apple Music', @@ -83,10 +112,7 @@ class OpenOnPlatformSheet extends StatelessWidget { Widget build(BuildContext context) { final colorScheme = Theme.of(context).colorScheme; return FutureBuilder>( - future: PlatformBridge.getTrackPlatformLinks( - spotifyId: spotifyId, - isrc: isrc, - ), + future: _links, builder: (context, snapshot) { if (snapshot.connectionState != ConnectionState.done) { return const Padding( diff --git a/test/open_on_platform_sheet_test.dart b/test/open_on_platform_sheet_test.dart new file mode 100644 index 00000000..76e83f89 --- /dev/null +++ b/test/open_on_platform_sheet_test.dart @@ -0,0 +1,60 @@ +import 'dart:async'; +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:spotiflac_android/l10n/app_localizations.dart'; +import 'package:spotiflac_android/widgets/open_on_platform_sheet.dart'; + +void main() { + TestWidgetsFlutterBinding.ensureInitialized(); + testWidgets( + 'theme rebuild retains completed lookup; identifiers replace it', + (tester) async { + const channel = MethodChannel('com.zarz.spotiflac/backend'); + final calls = []; + final pending = Completer>(); + tester.binding.defaultBinaryMessenger.setMockMethodCallHandler(channel, ( + call, + ) async { + calls.add(call); + if (calls.length == 1) { + return { + 'platforms': {'spotify': 'https://example.test/one'}, + }; + } + return pending.future; + }); + addTearDown( + () => tester.binding.defaultBinaryMessenger.setMockMethodCallHandler( + channel, + null, + ), + ); + Widget app(Brightness brightness, String id) => MaterialApp( + theme: ThemeData(brightness: brightness), + localizationsDelegates: AppLocalizations.localizationsDelegates, + supportedLocales: AppLocalizations.supportedLocales, + home: Scaffold(body: OpenOnPlatformSheet(spotifyId: id)), + ); + await tester.pumpWidget(app(Brightness.light, 'one')); + await tester.pumpAndSettle(); + expect(find.text('Spotify'), findsOneWidget); + await tester.pumpWidget(app(Brightness.dark, 'one')); + await tester.pumpAndSettle(); + expect(calls.length, 1); + expect(find.text('Spotify'), findsOneWidget); + expect(find.byType(CircularProgressIndicator), findsNothing); + await tester.pumpWidget(app(Brightness.dark, 'two')); + await tester.pump(); + expect(calls.length, 2); + expect(calls.last.arguments, {'spotify_id': 'two', 'isrc': ''}); + expect(find.byType(CircularProgressIndicator), findsOneWidget); + pending.complete({ + 'platforms': {'appleMusic': 'https://example.test/two'}, + }); + await tester.pumpAndSettle(); + expect(find.text('Apple Music'), findsOneWidget); + expect(find.text('Spotify'), findsNothing); + }, + ); +}