perf(critical): Implement P0 optimization fixes - TiviMate parity

CRITICAL FIXES:
- P0-1: Fix EPG N+1 query storm with batch loading
  * Implement XtreamServiceMobile with getBatchEPG() method
  * Batch load EPG data instead of per-channel requests
  * Reduce 50-200 concurrent requests to 1 request per category
  * Add EPG cache with 1-hour TTL to prevent redundant calls

- P0-2: Remove destructive cache clearing on startup
  * Implement TTL-based cache invalidation
  * Preserve cache between app launches (6h channels, 1h EPG, 30m search)
  * Add HiveService.invalidateExpiredCache() method
  * Expected: -80% startup time on cold launches

- P0-3: Preserve tab state with IndexedStack
  * Replace switch statement with IndexedStack
  * Keep all tabs alive in widget tree
  * Preserve scroll position, search state, filters across tab switches
  * Expected: instant tab switching (<100ms)

HIGH PRIORITY FIXES:
- P1-1: Debounce search input (500ms)
  * Prevent per-keystroke full rebuilds
  * Add search debounce timer

- P1-3: Fix double FocusNode disposal crash
  * Remove duplicate focus node disposal in native_player_screen.dart
  * Prevents StateError on player exit

Expected impact: -75-80% perceived latency, instant tab switching, no OOM on startup.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
This commit is contained in:
GitHub CopilotandClaude Haiku 4.5 committed 2026-03-25 22:31:36 +01:00
1 parent 70c2241527
commit 93b8e5b337
8 files changed
+791 -41

No files matched your search

@@ -93,3 +93,35 @@ class LiveTvUiState {
/// Persistent index for mobile dashboard navigation
final mobileDashboardIndexProvider = StateProvider<int>((ref) => 0);
/// [P0-1 FIX] Batch EPG provider - Load all EPG data for multiple channels at once
/// This prevents the N+1 query problem where each channel card makes its own EPG request
class BatchEpgRequest {
final PlaylistConfig playlist;
final List<String> streamIds;
BatchEpgRequest({required this.playlist, required this.streamIds});
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is BatchEpgRequest &&
runtimeType == other.runtimeType &&
playlist == other.playlist &&
streamIds.toSet() == other.streamIds.toSet();
@override
int get hashCode => playlist.hashCode ^ streamIds.toSet().hashCode;
}
/// Load EPG data for multiple streams in one batch request
/// Cache is automatically handled by the service with 1-hour TTL
final mobileBatchEpgProvider =
FutureProvider.family<Map<String, ShortEpg>, BatchEpgRequest>(
(ref, request) async {
if (request.streamIds.isEmpty) return {};
final service =
await ref.watch(mobileXtreamServiceProvider(request.playlist).future);
return service.getBatchEPG(request.streamIds);
});