fix(livetv): keep the favorites filter narrow while favorites reload
Only the load that commits a favorites set writes the loaded flag, so a refresh keeps the previous set authoritative. Clearing it up front widened the guide to the full lineup for the whole round-trip and moved the D-pad cursor when it collapsed back.
This commit is contained in:
@@ -82,6 +82,9 @@ class _LiveTvScreenState extends State<LiveTvScreen>
|
|||||||
int _favoritesLoadGeneration = 0;
|
int _favoritesLoadGeneration = 0;
|
||||||
Future<void>? _favoritesLoadFuture;
|
Future<void>? _favoritesLoadFuture;
|
||||||
final SerialFutureQueue _favoritesMutationQueue = SerialFutureQueue();
|
final SerialFutureQueue _favoritesMutationQueue = SerialFutureQueue();
|
||||||
|
|
||||||
|
/// True while [_favoriteChannels] holds an authoritative set. A refresh keeps the previous set live until the new
|
||||||
|
/// one commits, so the favorites filter never widens mid-load.
|
||||||
bool _favoritesLoaded = false;
|
bool _favoritesLoaded = false;
|
||||||
bool _favoritesWritable = false;
|
bool _favoritesWritable = false;
|
||||||
|
|
||||||
@@ -423,7 +426,6 @@ class _LiveTvScreenState extends State<LiveTvScreen>
|
|||||||
|
|
||||||
Future<void> _loadFavorites(MultiServerProvider multiServer) async {
|
Future<void> _loadFavorites(MultiServerProvider multiServer) async {
|
||||||
final loadGeneration = ++_favoritesLoadGeneration;
|
final loadGeneration = ++_favoritesLoadGeneration;
|
||||||
_favoritesLoaded = false;
|
|
||||||
_favoritesWritable = false;
|
_favoritesWritable = false;
|
||||||
final previousStoreBySource = Map<String, String>.of(_favoriteStoreBySource);
|
final previousStoreBySource = Map<String, String>.of(_favoriteStoreBySource);
|
||||||
final scopeByLiveServer = Map<String, _FavoriteScope>.of(_favoriteScopeByLiveServer);
|
final scopeByLiveServer = Map<String, _FavoriteScope>.of(_favoriteScopeByLiveServer);
|
||||||
|
|||||||
@@ -52,6 +52,30 @@ void main() {
|
|||||||
expect(_guideChannels(tester), isEmpty);
|
expect(_guideChannels(tester), isEmpty);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('refresh keeps the favorites filter narrow while favorites reload', (tester) async {
|
||||||
|
final harness = await _pumpLiveTvScreen(tester, channelKeys: const ['channel-a', 'channel-b']);
|
||||||
|
addTearDown(() async {
|
||||||
|
await tester.pumpWidget(const SizedBox.shrink());
|
||||||
|
harness.dispose();
|
||||||
|
});
|
||||||
|
|
||||||
|
final favorite = FavoriteChannel(id: 'channel-a', source: 'server://server-a/provider-a');
|
||||||
|
harness.liveTv.favorites.complete([favorite]);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(_guideChannels(tester).map((channel) => channel.key), ['channel-a']);
|
||||||
|
|
||||||
|
await tester.tap(find.byIcon(Symbols.refresh_rounded));
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(_guideChannels(tester).map((channel) => channel.key), ['channel-a']);
|
||||||
|
|
||||||
|
harness.liveTv.favorites.complete([favorite]);
|
||||||
|
await tester.pumpAndSettle();
|
||||||
|
|
||||||
|
expect(_guideChannels(tester).map((channel) => channel.key), ['channel-a']);
|
||||||
|
});
|
||||||
|
|
||||||
testWidgets('favorite read failure preserves raw Guide channels', (tester) async {
|
testWidgets('favorite read failure preserves raw Guide channels', (tester) async {
|
||||||
final harness = await _pumpLiveTvScreen(tester);
|
final harness = await _pumpLiveTvScreen(tester);
|
||||||
addTearDown(() async {
|
addTearDown(() async {
|
||||||
@@ -144,8 +168,8 @@ void main() {
|
|||||||
|
|
||||||
List<LiveTvChannel> _guideChannels(WidgetTester tester) => tester.widget<GuideTab>(find.byType(GuideTab)).channels;
|
List<LiveTvChannel> _guideChannels(WidgetTester tester) => tester.widget<GuideTab>(find.byType(GuideTab)).channels;
|
||||||
|
|
||||||
Future<_LiveTvHarness> _pumpLiveTvScreen(WidgetTester tester) async {
|
Future<_LiveTvHarness> _pumpLiveTvScreen(WidgetTester tester, {List<String>? channelKeys}) async {
|
||||||
final liveTv = _FakeLiveTvSupport();
|
final liveTv = _FakeLiveTvSupport(channelKeys: channelKeys);
|
||||||
final client = _FakeMediaServerClient(liveTv);
|
final client = _FakeMediaServerClient(liveTv);
|
||||||
final manager = MultiServerManager()..debugRegisterClientForTesting(client);
|
final manager = MultiServerManager()..debugRegisterClientForTesting(client);
|
||||||
final provider = testMultiServerProvider(manager);
|
final provider = testMultiServerProvider(manager);
|
||||||
@@ -207,11 +231,26 @@ class _FakeMediaServerClient implements MediaServerClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _FakeLiveTvSupport implements LiveTvSupport {
|
class _FakeLiveTvSupport implements LiveTvSupport {
|
||||||
_FakeLiveTvSupport({this.serverId = 'server-a', this.storeKey = 'test-store'});
|
_FakeLiveTvSupport({this.serverId = 'server-a', this.storeKey = 'test-store', List<String>? channelKeys})
|
||||||
|
: channelKeys = channelKeys ?? [serverId == 'server-a' ? 'channel-a' : 'channel-$serverId'];
|
||||||
|
|
||||||
final String serverId;
|
final String serverId;
|
||||||
final String storeKey;
|
final String storeKey;
|
||||||
final Completer<List<FavoriteChannel>> favorites = Completer<List<FavoriteChannel>>();
|
final List<String> channelKeys;
|
||||||
|
final List<Completer<List<FavoriteChannel>>> _favoriteRequests = [];
|
||||||
|
int _servedFavoriteRequests = 0;
|
||||||
|
|
||||||
|
Completer<List<FavoriteChannel>> get favorites {
|
||||||
|
if (_favoriteRequests.length > _servedFavoriteRequests) {
|
||||||
|
return _favoriteRequests[_servedFavoriteRequests];
|
||||||
|
}
|
||||||
|
if (_servedFavoriteRequests > 0 && !_favoriteRequests[_servedFavoriteRequests - 1].isCompleted) {
|
||||||
|
return _favoriteRequests[_servedFavoriteRequests - 1];
|
||||||
|
}
|
||||||
|
final request = Completer<List<FavoriteChannel>>();
|
||||||
|
_favoriteRequests.add(request);
|
||||||
|
return request;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
LiveTvDvrSupport? get dvr => null;
|
LiveTvDvrSupport? get dvr => null;
|
||||||
@@ -227,18 +266,24 @@ class _FakeLiveTvSupport implements LiveTvSupport {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<LiveTvChannel>> fetchChannels({String? lineup}) async => [
|
Future<List<LiveTvChannel>> fetchChannels({String? lineup}) async => [
|
||||||
LiveTvChannel(
|
for (final key in channelKeys)
|
||||||
key: serverId == 'server-a' ? 'channel-a' : 'channel-$serverId',
|
LiveTvChannel(
|
||||||
title: serverId == 'server-a' ? 'Unique Channel A' : 'Unique Channel $serverId',
|
key: key,
|
||||||
serverId: serverId,
|
title: key == 'channel-a' ? 'Unique Channel A' : 'Unique Channel $key',
|
||||||
),
|
serverId: serverId,
|
||||||
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<LiveTvProgram>> fetchSchedule({DateTime? from, DateTime? to}) async => const [];
|
Future<List<LiveTvProgram>> fetchSchedule({DateTime? from, DateTime? to}) async => const [];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<List<FavoriteChannel>> fetchFavoriteChannels() => favorites.future;
|
Future<List<FavoriteChannel>> fetchFavoriteChannels() {
|
||||||
|
if (_favoriteRequests.length == _servedFavoriteRequests) {
|
||||||
|
_favoriteRequests.add(Completer<List<FavoriteChannel>>());
|
||||||
|
}
|
||||||
|
return _favoriteRequests[_servedFavoriteRequests++].future;
|
||||||
|
}
|
||||||
|
|
||||||
final List<Object> writeFailures = [];
|
final List<Object> writeFailures = [];
|
||||||
final List<List<FavoriteChannel>> writes = [];
|
final List<List<FavoriteChannel>> writes = [];
|
||||||
|
|||||||
Reference in New Issue
Block a user