mirror of
https://github.com/R0m1k3/xtremobile.git
synced 2026-10-23 07:08:16 +02:00
feat: add AppColors class defining an Apple TV-inspired color palette and theme schemes.
This commit is contained in:
1 parent
32af7fd920
commit
9cc0f5eacb
10 files changed
+549
-246
No files matched your search
@@ -437,7 +437,7 @@ class _LitePlayerScreenState extends ConsumerState<LitePlayerScreen> with Widget
|
||||
),
|
||||
),
|
||||
),
|
||||
Text('LITE PLAYER: $title', style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
|
||||
Text(title, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold)),
|
||||
const Spacer(),
|
||||
Text(_currentTime, style: const TextStyle(color: Colors.white70)),
|
||||
],
|
||||
@@ -450,13 +450,8 @@ class _LitePlayerScreenState extends ConsumerState<LitePlayerScreen> with Widget
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
if (widget.streamType == StreamType.live) ...[
|
||||
IconButton(
|
||||
icon: const Icon(Icons.keyboard_arrow_up, color: Colors.white, size: 40),
|
||||
onPressed: _playPrevious,
|
||||
),
|
||||
const SizedBox(width: 32),
|
||||
] else ...[
|
||||
// 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)),
|
||||
@@ -474,13 +469,8 @@ class _LitePlayerScreenState extends ConsumerState<LitePlayerScreen> with Widget
|
||||
),
|
||||
),
|
||||
|
||||
if (widget.streamType == StreamType.live) ...[
|
||||
const SizedBox(width: 32),
|
||||
IconButton(
|
||||
icon: const Icon(Icons.keyboard_arrow_down, color: Colors.white, size: 40),
|
||||
onPressed: _playNext,
|
||||
),
|
||||
] else ...[
|
||||
// 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),
|
||||
|
||||
@@ -441,6 +441,9 @@ class _MobileChannelTileState extends ConsumerState<_MobileChannelTile> {
|
||||
|
||||
return TVFocusable(
|
||||
onPressed: widget.onTap,
|
||||
onLongPress: () {
|
||||
ref.read(mobileFavoritesProvider.notifier).toggle(widget.channel.streamId);
|
||||
},
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(8),
|
||||
@@ -500,18 +503,12 @@ class _MobileChannelTileState extends ConsumerState<_MobileChannelTile> {
|
||||
],
|
||||
),
|
||||
),
|
||||
// Favorite Button
|
||||
IconButton(
|
||||
icon: Icon(
|
||||
isFavorite ? Icons.favorite : Icons.favorite_border,
|
||||
color: isFavorite ? Colors.red : AppColors.textSecondary,
|
||||
// Favorite Indicator (no button, just icon)
|
||||
if (isFavorite)
|
||||
const Padding(
|
||||
padding: EdgeInsets.only(right: 8),
|
||||
child: Icon(Icons.favorite, color: Colors.red, size: 20),
|
||||
),
|
||||
onPressed: () {
|
||||
ref.read(mobileFavoritesProvider.notifier).toggle(widget.channel.streamId);
|
||||
},
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: const BoxConstraints(minWidth: 40, minHeight: 40),
|
||||
),
|
||||
// Play Icon
|
||||
const Icon(Icons.play_circle_outline, color: AppColors.primary),
|
||||
],
|
||||
|
||||
@@ -238,17 +238,40 @@ class _MobileMoviesTabState extends ConsumerState<MobileMoviesTab> with Automati
|
||||
controller: _scrollController,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
// Header & Search
|
||||
// Hero Section (reduced height, moved above search)
|
||||
if (_searchQuery.isEmpty && heroItems.isNotEmpty)
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 8, bottom: 8),
|
||||
child: SizedBox(
|
||||
height: 180, // Reduced from 250
|
||||
child: HeroCarousel(
|
||||
items: heroItems,
|
||||
onTap: (item) {
|
||||
try {
|
||||
final movie = _movies.firstWhere((element) => element.streamId == item.id);
|
||||
_playMovie(context, movie);
|
||||
} catch (e) {
|
||||
// Fallback ignored
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Search Bar (moved below carousel)
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: TVFocusable(
|
||||
onPressed: () {
|
||||
setState(() => _isSearchEditing = true);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_searchFocusNode.requestFocus();
|
||||
});
|
||||
},
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Container(
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
@@ -272,7 +295,7 @@ class _MobileMoviesTabState extends ConsumerState<MobileMoviesTab> with Automati
|
||||
readOnly: !_isSearchEditing,
|
||||
style: const TextStyle(fontSize: 14, color: AppColors.textPrimary),
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Search Movies',
|
||||
hintText: 'Rechercher un film...',
|
||||
border: InputBorder.none,
|
||||
isDense: true,
|
||||
contentPadding: EdgeInsets.only(bottom: 11),
|
||||
@@ -298,28 +321,6 @@ class _MobileMoviesTabState extends ConsumerState<MobileMoviesTab> with Automati
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Hero Section
|
||||
if (_searchQuery.isEmpty && heroItems.isNotEmpty)
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16),
|
||||
child: SizedBox(
|
||||
height: 250,
|
||||
child: HeroCarousel(
|
||||
items: heroItems,
|
||||
onTap: (item) {
|
||||
try {
|
||||
final movie = _movies.firstWhere((element) => element.streamId == item.id);
|
||||
_playMovie(context, movie);
|
||||
} catch (e) {
|
||||
// Fallback ignored
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Grid
|
||||
SliverPadding(
|
||||
|
||||
@@ -214,17 +214,40 @@ class _MobileSeriesTabState extends ConsumerState<MobileSeriesTab> with Automati
|
||||
controller: _scrollController,
|
||||
physics: const AlwaysScrollableScrollPhysics(),
|
||||
slivers: [
|
||||
// Header & Search
|
||||
// Hero Section (reduced height, moved above search)
|
||||
if (_searchQuery.isEmpty && heroItems.isNotEmpty)
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(top: 8, bottom: 8),
|
||||
child: SizedBox(
|
||||
height: 180, // Reduced from 250
|
||||
child: HeroCarousel(
|
||||
items: heroItems,
|
||||
onTap: (item) {
|
||||
try {
|
||||
final series = _series.firstWhere((s) => s.seriesId.toString() == item.id);
|
||||
_openSeries(series);
|
||||
} catch (e) {
|
||||
// Fallback logic not critical
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Search Bar (moved below carousel)
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(16),
|
||||
child: GestureDetector(
|
||||
onTap: () {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
|
||||
child: TVFocusable(
|
||||
onPressed: () {
|
||||
setState(() => _isSearchEditing = true);
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
_searchFocusNode.requestFocus();
|
||||
});
|
||||
},
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Container(
|
||||
height: 40,
|
||||
decoration: BoxDecoration(
|
||||
@@ -248,7 +271,7 @@ class _MobileSeriesTabState extends ConsumerState<MobileSeriesTab> with Automati
|
||||
readOnly: !_isSearchEditing,
|
||||
style: const TextStyle(fontSize: 14, color: AppColors.textPrimary),
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Search Series',
|
||||
hintText: 'Rechercher une série...',
|
||||
border: InputBorder.none,
|
||||
isDense: true,
|
||||
contentPadding: EdgeInsets.only(bottom: 11),
|
||||
@@ -274,32 +297,6 @@ class _MobileSeriesTabState extends ConsumerState<MobileSeriesTab> with Automati
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Hero Section
|
||||
if (_searchQuery.isEmpty && heroItems.isNotEmpty)
|
||||
SliverToBoxAdapter(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.only(bottom: 16),
|
||||
child: SizedBox(
|
||||
height: 250,
|
||||
child: HeroCarousel(
|
||||
items: heroItems,
|
||||
onTap: (item) {
|
||||
// Find series by title fallback if ID logic differs (Series ID is int usually, Hero is Str)
|
||||
// But here we rely on passing the correct string ID or using the closure directly if possible.
|
||||
// HeroCarousel onTap returns HeroItem.
|
||||
// We must map it back.
|
||||
try {
|
||||
final series = _series.firstWhere((s) => s.seriesId.toString() == item.id);
|
||||
_openSeries(series);
|
||||
} catch (e) {
|
||||
// Fallback logic not critical for now, assume ID matches
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
// Grid
|
||||
SliverPadding(
|
||||
|
||||
@@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:dio_cache_interceptor_hive_store/dio_cache_interceptor_hive_store.dart';
|
||||
import '../../../providers/mobile_settings_providers.dart';
|
||||
import '../../../../core/providers/theme_provider.dart';
|
||||
import '../../../../core/theme/app_colors.dart';
|
||||
@@ -20,6 +22,7 @@ class _MobileSettingsTabState extends ConsumerState<MobileSettingsTab> {
|
||||
late TextEditingController _moviesController;
|
||||
late TextEditingController _seriesController;
|
||||
bool _initialized = false;
|
||||
bool _isRefreshingCache = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -161,105 +164,143 @@ class _MobileSettingsTabState extends ConsumerState<MobileSettingsTab> {
|
||||
|
||||
// Playback Settings
|
||||
const _SectionHeader(title: 'Lecture Vidéo'),
|
||||
Container(
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.surface,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
|
||||
// Player Engine
|
||||
TVFocusable(
|
||||
onPressed: () {},
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.surface,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.rocket_launch, color: AppColors.primary),
|
||||
title: const Text('Moteur Vidéo',
|
||||
style: TextStyle(color: AppColors.textPrimary)),
|
||||
subtitle: Text(
|
||||
settings.playerEngine == 'ultra' ? 'Ultra (MPV) - Puissant' : 'Lite (ExoPlayer) - Léger',
|
||||
style: const TextStyle(color: AppColors.textSecondary),
|
||||
),
|
||||
trailing: DropdownButton<String>(
|
||||
value: settings.playerEngine,
|
||||
dropdownColor: AppColors.surface,
|
||||
underline: const SizedBox(),
|
||||
icon: const Icon(Icons.arrow_drop_down, color: AppColors.primary),
|
||||
items: const [
|
||||
DropdownMenuItem(value: 'ultra', child: Text('Ultra', style: TextStyle(color: AppColors.textPrimary))),
|
||||
DropdownMenuItem(value: 'lite', child: Text('Lite', style: TextStyle(color: AppColors.textPrimary))),
|
||||
],
|
||||
onChanged: (val) {
|
||||
if (val != null) {
|
||||
ref.read(mobileSettingsProvider.notifier).setPlayerEngine(val);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
child: Column(
|
||||
children: [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.rocket_launch, color: AppColors.primary),
|
||||
title: const Text('Moteur Vidéo',
|
||||
style: TextStyle(color: AppColors.textPrimary)),
|
||||
subtitle: Text(
|
||||
settings.playerEngine == 'ultra' ? 'Ultra (MPV) - Puissant' : 'Lite (ExoPlayer) - Léger',
|
||||
style: const TextStyle(color: AppColors.textSecondary),
|
||||
),
|
||||
trailing: DropdownButton<String>(
|
||||
value: settings.playerEngine,
|
||||
dropdownColor: AppColors.surface,
|
||||
underline: const SizedBox(),
|
||||
icon: const Icon(Icons.arrow_drop_down, color: AppColors.primary),
|
||||
items: const [
|
||||
DropdownMenuItem(value: 'ultra', child: Text('Ultra', style: TextStyle(color: AppColors.textPrimary))),
|
||||
DropdownMenuItem(value: 'lite', child: Text('Lite', style: TextStyle(color: AppColors.textPrimary))),
|
||||
],
|
||||
onChanged: (val) {
|
||||
if (val != null) {
|
||||
ref.read(mobileSettingsProvider.notifier).setPlayerEngine(val);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Debug Stats
|
||||
TVFocusable(
|
||||
onPressed: () => ref.read(mobileSettingsProvider.notifier).toggleShowDebugStats(!settings.showDebugStats),
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.surface,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: SwitchListTile(
|
||||
title: const Text('Stats pour Nerds',
|
||||
style: TextStyle(color: AppColors.textPrimary)),
|
||||
subtitle: const Text('Infos techniques (FPS, Buffer)',
|
||||
style: TextStyle(color: AppColors.textSecondary, fontSize: 12)),
|
||||
secondary: const Icon(Icons.bug_report, color: AppColors.primary),
|
||||
value: settings.showDebugStats,
|
||||
onChanged: (val) => ref.read(mobileSettingsProvider.notifier).toggleShowDebugStats(val),
|
||||
activeColor: AppColors.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Decoder Mode
|
||||
TVFocusable(
|
||||
onPressed: () {},
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.surface,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.memory, color: AppColors.primary),
|
||||
title: const Text('Décodeur Vidéo',
|
||||
style: TextStyle(color: AppColors.textPrimary)),
|
||||
subtitle: Text(
|
||||
settings.decoderMode == 'auto' ? 'Automatique (Recommandé)' :
|
||||
settings.decoderMode == 'mediacodec' ? 'Matériel (Hardware)' : 'Logiciel (Software)',
|
||||
style: const TextStyle(color: AppColors.textSecondary),
|
||||
),
|
||||
Divider(height: 1, color: AppColors.textSecondary.withOpacity(0.2)),
|
||||
SwitchListTile(
|
||||
title: const Text('Stats pour Nerds',
|
||||
style: TextStyle(color: AppColors.textPrimary)),
|
||||
subtitle: const Text('Infos techniques (FPS, Buffer)',
|
||||
style: TextStyle(color: AppColors.textSecondary, fontSize: 12)),
|
||||
secondary: const Icon(Icons.bug_report, color: AppColors.primary),
|
||||
value: settings.showDebugStats,
|
||||
onChanged: (val) => ref.read(mobileSettingsProvider.notifier).toggleShowDebugStats(val),
|
||||
activeColor: AppColors.primary,
|
||||
trailing: DropdownButton<String>(
|
||||
value: settings.decoderMode,
|
||||
dropdownColor: AppColors.surface,
|
||||
underline: const SizedBox(),
|
||||
icon: const Icon(Icons.arrow_drop_down, color: AppColors.primary),
|
||||
items: const [
|
||||
DropdownMenuItem(value: 'auto', child: Text('Auto', style: TextStyle(color: AppColors.textPrimary))),
|
||||
DropdownMenuItem(value: 'mediacodec', child: Text('Hardware', style: TextStyle(color: AppColors.textPrimary))),
|
||||
DropdownMenuItem(value: 'no', child: Text('Software', style: TextStyle(color: AppColors.textPrimary))),
|
||||
],
|
||||
onChanged: (val) {
|
||||
if (val != null) {
|
||||
ref.read(mobileSettingsProvider.notifier).setDecoderMode(val);
|
||||
}
|
||||
},
|
||||
),
|
||||
Divider(height: 1, color: AppColors.textSecondary.withOpacity(0.2)),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.memory, color: AppColors.primary),
|
||||
title: const Text('Décodeur Vidéo',
|
||||
style: TextStyle(color: AppColors.textPrimary)),
|
||||
subtitle: Text(
|
||||
settings.decoderMode == 'auto' ? 'Automatique (Recommandé)' :
|
||||
settings.decoderMode == 'mediacodec' ? 'Matériel (Hardware)' : 'Logiciel (Software)',
|
||||
style: const TextStyle(color: AppColors.textSecondary),
|
||||
),
|
||||
trailing: DropdownButton<String>(
|
||||
value: settings.decoderMode,
|
||||
dropdownColor: AppColors.surface,
|
||||
underline: const SizedBox(),
|
||||
icon: const Icon(Icons.arrow_drop_down, color: AppColors.primary),
|
||||
items: const [
|
||||
DropdownMenuItem(value: 'auto', child: Text('Auto', style: TextStyle(color: AppColors.textPrimary))),
|
||||
DropdownMenuItem(value: 'mediacodec', child: Text('Hardware', style: TextStyle(color: AppColors.textPrimary))),
|
||||
DropdownMenuItem(value: 'no', child: Text('Software', style: TextStyle(color: AppColors.textPrimary))),
|
||||
],
|
||||
onChanged: (val) {
|
||||
if (val != null) {
|
||||
ref.read(mobileSettingsProvider.notifier).setDecoderMode(val);
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
|
||||
// Buffer Duration
|
||||
TVFocusable(
|
||||
onPressed: () {},
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.surface,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
),
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.speed, color: AppColors.primary),
|
||||
title: const Text('Mise en mémoire tampon (Cache)',
|
||||
style: TextStyle(color: AppColors.textPrimary)),
|
||||
subtitle: Text(
|
||||
settings.bufferDuration == 0
|
||||
? 'Auto (Défaut)'
|
||||
: '${settings.bufferDuration} secondes',
|
||||
style: const TextStyle(color: AppColors.textSecondary),
|
||||
),
|
||||
Divider(height: 1, color: AppColors.textSecondary.withOpacity(0.2)),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.speed, color: AppColors.primary),
|
||||
title: const Text('Mise en mémoire tampon (Cache)',
|
||||
style: TextStyle(color: AppColors.textPrimary)),
|
||||
subtitle: Text(
|
||||
settings.bufferDuration == 0
|
||||
? 'Auto (Défaut)'
|
||||
: '${settings.bufferDuration} secondes',
|
||||
style: const TextStyle(color: AppColors.textSecondary),
|
||||
),
|
||||
trailing: DropdownButton<int>(
|
||||
value: settings.bufferDuration,
|
||||
dropdownColor: AppColors.surface,
|
||||
underline: const SizedBox(),
|
||||
icon: const Icon(Icons.arrow_drop_down, color: AppColors.primary),
|
||||
items: const [
|
||||
DropdownMenuItem(value: 0, child: Text('Auto', style: TextStyle(color: AppColors.textPrimary))),
|
||||
DropdownMenuItem(value: 15, child: Text('15s', style: TextStyle(color: AppColors.textPrimary))),
|
||||
DropdownMenuItem(value: 30, child: Text('30s', style: TextStyle(color: AppColors.textPrimary))),
|
||||
DropdownMenuItem(value: 60, child: Text('60s', style: TextStyle(color: AppColors.textPrimary))),
|
||||
],
|
||||
onChanged: (val) {
|
||||
if (val != null) {
|
||||
ref.read(mobileSettingsProvider.notifier).setBufferDuration(val);
|
||||
}
|
||||
},
|
||||
),
|
||||
trailing: DropdownButton<int>(
|
||||
value: settings.bufferDuration,
|
||||
dropdownColor: AppColors.surface,
|
||||
underline: const SizedBox(),
|
||||
icon: const Icon(Icons.arrow_drop_down, color: AppColors.primary),
|
||||
items: const [
|
||||
DropdownMenuItem(value: 0, child: Text('Auto', style: TextStyle(color: AppColors.textPrimary))),
|
||||
DropdownMenuItem(value: 15, child: Text('15s', style: TextStyle(color: AppColors.textPrimary))),
|
||||
DropdownMenuItem(value: 30, child: Text('30s', style: TextStyle(color: AppColors.textPrimary))),
|
||||
DropdownMenuItem(value: 60, child: Text('60s', style: TextStyle(color: AppColors.textPrimary))),
|
||||
],
|
||||
onChanged: (val) {
|
||||
if (val != null) {
|
||||
ref.read(mobileSettingsProvider.notifier).setBufferDuration(val);
|
||||
}
|
||||
},
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
@@ -320,6 +361,85 @@ class _MobileSettingsTabState extends ConsumerState<MobileSettingsTab> {
|
||||
),
|
||||
const SizedBox(height: 24),
|
||||
|
||||
// Refresh Cache Section
|
||||
const _SectionHeader(title: 'Cache'),
|
||||
InkWell(
|
||||
onTap: _isRefreshingCache ? null : () async {
|
||||
setState(() => _isRefreshingCache = true);
|
||||
|
||||
try {
|
||||
// Clear the Hive HTTP cache directly
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
final cacheStore = HiveCacheStore(dir.path);
|
||||
await cacheStore.clean();
|
||||
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
const SnackBar(
|
||||
content: Text('Cache actualisé ! Relancez l\'application pour charger les nouvelles données.'),
|
||||
backgroundColor: Colors.green,
|
||||
duration: Duration(seconds: 3),
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text('Erreur: $e'),
|
||||
backgroundColor: Colors.red,
|
||||
),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
if (mounted) setState(() => _isRefreshingCache = false);
|
||||
}
|
||||
},
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.all(16),
|
||||
decoration: BoxDecoration(
|
||||
color: AppColors.surface,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
_isRefreshingCache
|
||||
? const SizedBox(
|
||||
width: 24,
|
||||
height: 24,
|
||||
child: CircularProgressIndicator(strokeWidth: 2)
|
||||
)
|
||||
: const Icon(Icons.refresh, color: AppColors.primary),
|
||||
const SizedBox(width: 16),
|
||||
const Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
'Actualiser le cache',
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 2),
|
||||
Text(
|
||||
'Recharge les films, séries et EPG (cache 24h)',
|
||||
style: TextStyle(
|
||||
fontSize: 12,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
|
||||
// Change Playlist
|
||||
InkWell(
|
||||
onTap: () {
|
||||
|
||||
Reference in new issue
Block a user