feat: add wakelock_plus dependency and update task list to focus on Fire Stick fixes.

This commit is contained in:
Michael committed 2025-12-11 19:48:44 +01:00
1 parent 8916d6f1ee
commit 43921abd2e
6 files changed
+170 -148

No files matched your search

@@ -4,6 +4,7 @@ import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:video_player/video_player.dart';
import 'package:cached_network_image/cached_network_image.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
import 'package:xtremflow/mobile/widgets/tv_focusable.dart';
import 'package:path_provider/path_provider.dart';
import 'package:xtremflow/core/models/iptv_models.dart';
@@ -82,6 +83,9 @@ class _LitePlayerScreenState extends ConsumerState<LitePlayerScreen> with Widget
WidgetsBinding.instance.addObserver(this);
HardwareKeyboard.instance.addHandler(_handleKeyEvent);
// Enable wakelock to prevent screen from turning off during playback
WakelockPlus.enable();
_currentIndex = widget.initialIndex;
_startClock();
@@ -350,6 +354,10 @@ class _LitePlayerScreenState extends ConsumerState<LitePlayerScreen> with Widget
void dispose() {
WidgetsBinding.instance.removeObserver(this);
HardwareKeyboard.instance.removeHandler(_handleKeyEvent);
// Disable wakelock when leaving player
WakelockPlus.disable();
_controlsTimer?.cancel();
_clockTimer?.cancel();
_epgTimer?.cancel();
@@ -445,39 +453,58 @@ class _LitePlayerScreenState extends ConsumerState<LitePlayerScreen> with Widget
),
),
// Center Controls
Center(
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
// Replay 10s button only for VOD
if (widget.streamType != StreamType.live) ...[
IconButton(
icon: const Icon(Icons.replay_10, color: Colors.white, size: 40),
onPressed: () => _seek(const Duration(seconds: -10)),
),
const SizedBox(width: 32),
],
TVFocusable(
focusNode: _playPauseFocusNode,
onPressed: _togglePlayPause,
child: Icon(
_isPlaying ? Icons.pause_circle_filled : Icons.play_circle_filled,
color: AppColors.primary,
size: 64,
),
// Center Controls - Positioned lower with transparent background
Positioned(
bottom: 100, // Lower position (above progress bar area)
left: 0,
right: 0,
child: Center(
child: Container(
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.4),
borderRadius: BorderRadius.circular(40),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
// Replay 10s button only for VOD
if (widget.streamType != StreamType.live) ...[
IconButton(
icon: const Icon(Icons.replay_10, color: Colors.white70, size: 36),
onPressed: () => _seek(const Duration(seconds: -10)),
),
const SizedBox(width: 24),
],
TVFocusable(
focusNode: _playPauseFocusNode,
onPressed: _togglePlayPause,
child: Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.white.withOpacity(0.15),
shape: BoxShape.circle,
),
child: Icon(
_isPlaying ? Icons.pause_rounded : Icons.play_arrow_rounded,
color: Colors.white,
size: 48,
),
),
),
// Forward 10s button only for VOD
if (widget.streamType != StreamType.live) ...[
const SizedBox(width: 32),
IconButton(
icon: const Icon(Icons.forward_10, color: Colors.white, size: 40),
onPressed: () => _seek(const Duration(seconds: 10)),
),
],
],
// Forward 10s button only for VOD
if (widget.streamType != StreamType.live) ...[
const SizedBox(width: 24),
IconButton(
icon: const Icon(Icons.forward_10, color: Colors.white70, size: 36),
onPressed: () => _seek(const Duration(seconds: 10)),
),
],
],
),
),
),
),
@@ -5,6 +5,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:media_kit/media_kit.dart';
import 'package:media_kit_video/media_kit_video.dart';
import 'package:wakelock_plus/wakelock_plus.dart';
import 'package:xtremflow/mobile/widgets/tv_focusable.dart';
import 'package:path_provider/path_provider.dart';
import 'package:xtremflow/core/models/iptv_models.dart';
@@ -110,6 +111,9 @@ class _NativePlayerScreenState extends ConsumerState<NativePlayerScreen> with Wi
// Register keyboard handler for remote control
HardwareKeyboard.instance.addHandler(_handleKeyEvent);
// Enable wakelock to prevent screen from turning off during playback
WakelockPlus.enable();
// Only start clock if setting is enabled (will check in build too, but timer can run)
_startClock();
_currentIndex = widget.initialIndex;
@@ -277,6 +281,9 @@ class _NativePlayerScreenState extends ConsumerState<NativePlayerScreen> with Wi
// Unregister keyboard handler
HardwareKeyboard.instance.removeHandler(_handleKeyEvent);
// Disable wakelock when leaving player
WakelockPlus.disable();
// Save resume position before cleanup (only for VOD/Series)
_saveResumePositionOnExit();
@@ -25,12 +25,12 @@ class MobileLiveTVTab extends ConsumerStatefulWidget {
class _MobileLiveTVTabState extends ConsumerState<MobileLiveTVTab> with AutomaticKeepAliveClientMixin {
final TextEditingController _searchController = TextEditingController();
final FocusNode _searchFocusNode = FocusNode();
String _searchQuery = '';
bool _showFavoritesOnly = false;
bool _isSearchEditing = false;
bool _justReturnedFromPlayer = false; // Flag to prevent PopScope interception after player
@override
void initState() {
@@ -49,9 +49,6 @@ class _MobileLiveTVTabState extends ConsumerState<MobileLiveTVTab> with Automati
super.dispose();
}
@override
bool get wantKeepAlive => true;
@@ -69,10 +66,17 @@ class _MobileLiveTVTabState extends ConsumerState<MobileLiveTVTab> with Automati
child: Scaffold(
backgroundColor: Colors.transparent,
body: PopScope(
// Only allow full pop if we're at category view with no filters AND not just returned from player
canPop: uiState.isCategoryView && _searchQuery.isEmpty && !_showFavoritesOnly,
onPopInvoked: (didPop) {
onPopInvokedWithResult: (didPop, _) {
if (didPop) return;
// If just returned from player, reset the flag and do nothing (stay on channel list)
if (_justReturnedFromPlayer) {
_justReturnedFromPlayer = false;
return;
}
if (_searchQuery.isNotEmpty) {
setState(() {
_searchController.clear();
@@ -354,38 +358,43 @@ class _MobileLiveTVTabState extends ConsumerState<MobileLiveTVTab> with Automati
);
}
void _playChannel(BuildContext context, Channel channel, List<Channel> channels, PlaylistConfig playlist, int index) {
final engine = ref.read(mobileSettingsProvider).playerEngine;
if (engine == 'lite') {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LitePlayerScreen(
streamId: channel.streamId,
title: channel.name,
playlist: playlist,
streamType: StreamType.live,
channels: channels, // Pass full channel list for zapping
initialIndex: index,
),
),
);
} else {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => NativePlayerScreen(
streamId: channel.streamId,
title: channel.name,
playlist: playlist,
streamType: StreamType.live,
channels: channels, // Pass full channel list for zapping
initialIndex: index,
),
),
);
}
void _playChannel(BuildContext context, Channel channel, List<Channel> channels, PlaylistConfig playlist, int index) async {
final engine = ref.read(mobileSettingsProvider).playerEngine;
if (engine == 'lite') {
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => LitePlayerScreen(
streamId: channel.streamId,
title: channel.name,
playlist: playlist,
streamType: StreamType.live,
channels: channels, // Pass full channel list for zapping
initialIndex: index,
),
),
);
} else {
await Navigator.push(
context,
MaterialPageRoute(
builder: (context) => NativePlayerScreen(
streamId: channel.streamId,
title: channel.name,
playlist: playlist,
streamType: StreamType.live,
channels: channels, // Pass full channel list for zapping
initialIndex: index,
),
),
);
}
// When returning from player, set flag to prevent PopScope from going back to categories
if (mounted) {
setState(() => _justReturnedFromPlayer = true);
}
}
}
@@ -130,35 +130,47 @@ class _MobileSettingsTabState extends ConsumerState<MobileSettingsTab> {
// Appearance
const _SectionHeader(title: 'Apparence'),
Container(
decoration: BoxDecoration(
color: AppColors.surface,
borderRadius: BorderRadius.circular(16),
),
child: SwitchListTile(
title: const Text('Mode Sombre',
style: TextStyle(color: AppColors.textPrimary)),
secondary: Icon(isDark ? Icons.dark_mode : Icons.light_mode,
color: AppColors.primary),
value: isDark,
onChanged: (_) => themeNotifier.toggleTheme(),
activeColor: AppColors.primary,
// Dark Mode
TVFocusable(
onPressed: () => themeNotifier.toggleTheme(),
borderRadius: BorderRadius.circular(16),
child: Container(
decoration: BoxDecoration(
color: AppColors.surface,
borderRadius: BorderRadius.circular(16),
),
child: SwitchListTile(
title: const Text('Mode Sombre',
style: TextStyle(color: AppColors.textPrimary)),
secondary: Icon(isDark ? Icons.dark_mode : Icons.light_mode,
color: AppColors.primary),
value: isDark,
onChanged: (_) => themeNotifier.toggleTheme(),
activeColor: AppColors.primary,
),
),
),
const SizedBox(height: 8),
Container(
decoration: BoxDecoration(
color: AppColors.surface,
borderRadius: BorderRadius.circular(16),
),
child: SwitchListTile(
title: const Text('Afficher l\'heure',
style: TextStyle(color: AppColors.textPrimary)),
secondary: const Icon(Icons.access_time,
color: AppColors.primary),
value: settings.showClock,
onChanged: (val) => ref.read(mobileSettingsProvider.notifier).toggleShowClock(val),
activeColor: AppColors.primary,
// Show Clock
TVFocusable(
onPressed: () => ref.read(mobileSettingsProvider.notifier).toggleShowClock(!settings.showClock),
borderRadius: BorderRadius.circular(16),
child: Container(
decoration: BoxDecoration(
color: AppColors.surface,
borderRadius: BorderRadius.circular(16),
),
child: SwitchListTile(
title: const Text('Afficher l\'heure',
style: TextStyle(color: AppColors.textPrimary)),
secondary: const Icon(Icons.access_time,
color: AppColors.primary),
value: settings.showClock,
onChanged: (val) => ref.read(mobileSettingsProvider.notifier).toggleShowClock(val),
activeColor: AppColors.primary,
),
),
),
const SizedBox(height: 24),
@@ -364,8 +376,8 @@ class _MobileSettingsTabState extends ConsumerState<MobileSettingsTab> {
// Refresh Cache Section
const _SectionHeader(title: 'Cache'),
InkWell(
onTap: _isRefreshingCache ? null : () async {
TVFocusable(
onPressed: _isRefreshingCache ? null : () async {
setState(() => _isRefreshingCache = true);
try {
@@ -442,8 +454,8 @@ class _MobileSettingsTabState extends ConsumerState<MobileSettingsTab> {
const SizedBox(height: 16),
// Change Playlist
InkWell(
onTap: () {
TVFocusable(
onPressed: () {
// Open Playlist Selection in Manage Mode
Navigator.push(
context,
+1
View File
@@ -47,6 +47,7 @@ dependencies:
shared_preferences: ^2.3.2
path_provider: ^2.1.5
video_player: ^2.10.1
wakelock_plus: ^1.2.8
+16 -50
View File
@@ -1,59 +1,25 @@
# Task: UI Improvements & Cache EPG Implementation
# Task: Corrections Fire Stick & UI - TERMINÉ
## Demandes utilisateur
## Bugs corrigés
1. Enlever les informations "LITE PLAYER:" à côté des titres de chaînes
2. Enlever les flèches de navigation sur le flux TV (live)
3. Settings : améliorer la visibilité de la sélection (entourage visible)
4. Live TV : remplacer les contours blancs par une ombre blanche voyante
5. Favoris : long press 3 sec sur OK pour ajouter/retirer (télécommande)
6. Films/Séries : réduire le carrousel, déplacer la barre de recherche en dessous
7. Traduire les barres de recherche en français
8. Cache EPG 24h pour Films et Séries (éviter rechargement au lancement)
9. Bouton dans Settings pour forcer la mise à jour du cache
10. Navigation top bar style Apple TV moderne (fond blanc au focus)
### Session 1 (07h45)
## Progression
1. ✅ **Mise en veille pendant lecture** - Ajout de `wakelock_plus` dans les deux players
2. ✅ **Settings surbrillance invisible** - Toutes les box utilisent TVFocusable avec bordure blanche
3. ✅ **Player Lite bouton pause** - Descendu et rendu semi-transparent
### Phase 1 : Player UI
### Session 2 (18h15)
- [x] 1.1 Retirer "LITE PLAYER:" du titre dans `lite_player_screen.dart`
- [x] 1.2 Enlever les flèches haut/bas (navigation channels) dans `lite_player_screen.dart`
4. ✅ **Retour depuis player TV** - Le bouton retour ramène maintenant à la liste des chaînes (pas aux catégories)
### Phase 2 : Settings Tab Focus
## Fichiers modifiés
- [x] 2.1 Améliorer la visibilité de la sélection dans `mobile_settings_tab.dart` (bordure visible)
- [x] 2.2 Ajouter bouton "Actualiser le cache" dans les Settings
- `pubspec.yaml` - Ajout de `wakelock_plus`
- `native_player_screen.dart` - WakeLock activé/désactivé
- `lite_player_screen.dart` - WakeLock + nouveau design boutons + seek réduit
- `mobile_settings_tab.dart` - TVFocusable sur toutes les options
- `mobile_live_tv_tab.dart` - Correction navigation retour depuis player
### Phase 3 : Live TV Focus Style
## APK généré
- [x] 3.1 Modifier `tv_focusable.dart` pour utiliser une ombre blanche au lieu d'un contour
### Phase 4 : Favoris Long Press (3 secondes)
- [x] 4.1 Implémenter le long press 3 sec sur les chaînes pour toggle favoris
### Phase 5 : Movies/Series UI
- [x] 5.1 Réduire la hauteur du carrousel dans `mobile_movies_tab.dart`
- [x] 5.2 Réduire la hauteur du carrousel dans `mobile_series_tab.dart`
- [x] 5.3 Déplacer la barre de recherche sous le carrousel (movies)
- [x] 5.4 Déplacer la barre de recherche sous le carrousel (series)
- [x] 5.5 Traduire les placeholders de recherche en français
### Phase 6 : Cache EPG 24h
- [x] 6.1 Augmenter maxStale à 24h dans XtreamServiceMobile
- [x] 6.2 Ajouter méthode forceRefreshCache()
- [x] 6.3 Ajouter bouton refresh cache dans Settings
### Phase 7 : Navigation Apple TV Style
- [x] 7.1 Modifier top bar avec style Apple TV moderne (fond blanc au focus)
## Notes techniques
- Cache Dio HTTP configuré avec maxStale: 24h
- Cache en mémoire (`_cachedMoviesRaw`, `_cachedSeriesRaw`) vidé au redémarrage
- Le cache HTTP Hive persiste entre les sessions
- Bouton "Actualiser le cache" nettoie le cache Hive
`build\app\outputs\flutter-apk\app-release.apk` (92.3MB)