mirror of
https://github.com/R0m1k3/xtremobile.git
synced 2026-10-24 15:48:14 +02:00
feat: Add initial mobile dashboard screen with Xtream data and UI state providers.
This commit is contained in:
1 parent
3145af9e8d
commit
b249074db1
30 files changed
+1217
-582
No files matched your search
@@ -116,8 +116,10 @@ class _LiveTVTabState extends ConsumerState<LiveTVTab>
|
||||
_searchQuery.isEmpty &&
|
||||
!_showFavoritesOnly) ...[
|
||||
IconButton(
|
||||
icon: const Icon(Icons.arrow_back,
|
||||
color: AppColors.textPrimary),
|
||||
icon: const Icon(
|
||||
Icons.arrow_back,
|
||||
color: AppColors.textPrimary,
|
||||
),
|
||||
onPressed: () {
|
||||
setState(() {
|
||||
_selectedCategory = null;
|
||||
@@ -156,14 +158,19 @@ class _LiveTVTabState extends ConsumerState<LiveTVTab>
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.search,
|
||||
size: 20, color: AppColors.textSecondary),
|
||||
const Icon(
|
||||
Icons.search,
|
||||
size: 20,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _searchController,
|
||||
style: const TextStyle(
|
||||
fontSize: 14, color: Colors.white),
|
||||
fontSize: 14,
|
||||
color: Colors.white,
|
||||
),
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Rechercher...',
|
||||
hintStyle: TextStyle(color: Colors.white54),
|
||||
@@ -176,8 +183,11 @@ class _LiveTVTabState extends ConsumerState<LiveTVTab>
|
||||
if (_searchQuery.isNotEmpty)
|
||||
GestureDetector(
|
||||
onTap: () => _searchController.clear(),
|
||||
child: const Icon(Icons.close,
|
||||
size: 16, color: AppColors.textSecondary),
|
||||
child: const Icon(
|
||||
Icons.close,
|
||||
size: 16,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -250,7 +260,9 @@ class _LiveTVTabState extends ConsumerState<LiveTVTab>
|
||||
}
|
||||
|
||||
Widget _buildCategoryGrid(
|
||||
List<String> categories, Map<String, List<Channel>> groupedChannels) {
|
||||
List<String> categories,
|
||||
Map<String, List<Channel>> groupedChannels,
|
||||
) {
|
||||
final columns = ResponsiveLayout.value(
|
||||
context,
|
||||
mobile: 2,
|
||||
@@ -307,8 +319,11 @@ class _LiveTVTabState extends ConsumerState<LiveTVTab>
|
||||
color: AppColors.primary.withOpacity(0.1),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(Icons.folder_open,
|
||||
color: AppColors.primary, size: 32),
|
||||
child: const Icon(
|
||||
Icons.folder_open,
|
||||
color: AppColors.primary,
|
||||
size: 32,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
Text(
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import '../../../core/models/playlist_config.dart';
|
||||
import '../../../core/theme/app_colors.dart';
|
||||
import '../../../core/theme/app_theme.dart';
|
||||
@@ -238,14 +237,19 @@ class _MoviesTabState extends ConsumerState<MoviesTab> {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.search,
|
||||
size: 20, color: AppColors.textSecondary),
|
||||
const Icon(
|
||||
Icons.search,
|
||||
size: 20,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _searchController,
|
||||
style: const TextStyle(
|
||||
fontSize: 14, color: Colors.white),
|
||||
fontSize: 14,
|
||||
color: Colors.white,
|
||||
),
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Rechercher...',
|
||||
hintStyle: TextStyle(color: Colors.white54),
|
||||
@@ -260,17 +264,21 @@ class _MoviesTabState extends ConsumerState<MoviesTab> {
|
||||
),
|
||||
if (_isSearching)
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
height: 12,
|
||||
child: CircularProgressIndicator(strokeWidth: 2)),
|
||||
width: 12,
|
||||
height: 12,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
),
|
||||
if (_searchQuery.isNotEmpty)
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
_searchController.clear();
|
||||
_onSearchChanged('');
|
||||
},
|
||||
child: const Icon(Icons.close,
|
||||
size: 16, color: AppColors.textSecondary),
|
||||
child: const Icon(
|
||||
Icons.close,
|
||||
size: 16,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import 'dart:async';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:google_fonts/google_fonts.dart';
|
||||
import '../../../core/models/playlist_config.dart';
|
||||
import '../../../core/theme/app_colors.dart';
|
||||
import '../../../core/theme/app_theme.dart';
|
||||
@@ -230,14 +229,19 @@ class _SeriesTabState extends ConsumerState<SeriesTab> {
|
||||
padding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
child: Row(
|
||||
children: [
|
||||
const Icon(Icons.search,
|
||||
size: 20, color: AppColors.textSecondary),
|
||||
const Icon(
|
||||
Icons.search,
|
||||
size: 20,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: TextField(
|
||||
controller: _searchController,
|
||||
style: const TextStyle(
|
||||
fontSize: 14, color: Colors.white),
|
||||
fontSize: 14,
|
||||
color: Colors.white,
|
||||
),
|
||||
decoration: const InputDecoration(
|
||||
hintText: 'Rechercher...',
|
||||
hintStyle: TextStyle(color: Colors.white54),
|
||||
@@ -252,17 +256,21 @@ class _SeriesTabState extends ConsumerState<SeriesTab> {
|
||||
),
|
||||
if (_isSearching)
|
||||
const SizedBox(
|
||||
width: 12,
|
||||
height: 12,
|
||||
child: CircularProgressIndicator(strokeWidth: 2)),
|
||||
width: 12,
|
||||
height: 12,
|
||||
child: CircularProgressIndicator(strokeWidth: 2),
|
||||
),
|
||||
if (_searchQuery.isNotEmpty)
|
||||
GestureDetector(
|
||||
onTap: () {
|
||||
_searchController.clear();
|
||||
_onSearchChanged('');
|
||||
},
|
||||
child: const Icon(Icons.close,
|
||||
size: 16, color: AppColors.textSecondary),
|
||||
child: const Icon(
|
||||
Icons.close,
|
||||
size: 16,
|
||||
color: AppColors.textSecondary,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -280,8 +288,9 @@ class _SeriesTabState extends ConsumerState<SeriesTab> {
|
||||
child: HeroCarousel(
|
||||
items: heroItems,
|
||||
onTap: (item) {
|
||||
final series = _series.firstWhere((s) =>
|
||||
s.name == item.title); // Fallback by title if ID mismatch
|
||||
final series = _series.firstWhere(
|
||||
(s) => s.name == item.title,
|
||||
); // Fallback by title if ID mismatch
|
||||
_openSeries(series);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -96,8 +96,9 @@ class _SettingsTabState extends ConsumerState<SettingsTab>
|
||||
const Tab(icon: Icon(Icons.palette), text: 'Apparence'),
|
||||
if (currentUser?.isAdmin ?? false)
|
||||
const Tab(
|
||||
icon: Icon(Icons.admin_panel_settings),
|
||||
text: 'Administration'),
|
||||
icon: Icon(Icons.admin_panel_settings),
|
||||
text: 'Administration',
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -212,7 +213,9 @@ class _SettingsTabState extends ConsumerState<SettingsTab>
|
||||
title: Text(
|
||||
'Mode actuel',
|
||||
style: GoogleFonts.inter(
|
||||
fontWeight: FontWeight.w500, color: Colors.white),
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
subtitle: Text(
|
||||
isDark ? 'Thème sombre actif' : 'Thème clair actif',
|
||||
@@ -305,7 +308,10 @@ class _SettingsTabState extends ConsumerState<SettingsTab>
|
||||
}
|
||||
|
||||
Widget _buildFiltersTab(
|
||||
BuildContext context, dynamic currentUser, IptvSettings settings) {
|
||||
BuildContext context,
|
||||
dynamic currentUser,
|
||||
IptvSettings settings,
|
||||
) {
|
||||
return ListView(
|
||||
padding: const EdgeInsets.all(16),
|
||||
children: [
|
||||
@@ -326,13 +332,19 @@ class _SettingsTabState extends ConsumerState<SettingsTab>
|
||||
subtitle: Text(
|
||||
currentUser?.username ?? 'Unknown',
|
||||
style: GoogleFonts.roboto(
|
||||
fontWeight: FontWeight.w600, color: Colors.white),
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
trailing: currentUser?.isAdmin ?? false
|
||||
? Chip(
|
||||
label: Text('Admin',
|
||||
style: GoogleFonts.roboto(
|
||||
fontSize: 11, color: Colors.black)),
|
||||
label: Text(
|
||||
'Admin',
|
||||
style: GoogleFonts.roboto(
|
||||
fontSize: 11,
|
||||
color: Colors.black,
|
||||
),
|
||||
),
|
||||
backgroundColor: Colors.white,
|
||||
)
|
||||
: null,
|
||||
@@ -416,8 +428,10 @@ class _SettingsTabState extends ConsumerState<SettingsTab>
|
||||
),
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.playlist_play, color: Colors.white),
|
||||
title: Text('Changer de Playlist',
|
||||
style: GoogleFonts.roboto(color: Colors.white)),
|
||||
title: Text(
|
||||
'Changer de Playlist',
|
||||
style: GoogleFonts.roboto(color: Colors.white),
|
||||
),
|
||||
trailing: const Icon(Icons.chevron_right, color: Colors.white54),
|
||||
onTap: () => context.go('/playlists'),
|
||||
),
|
||||
@@ -433,8 +447,10 @@ class _SettingsTabState extends ConsumerState<SettingsTab>
|
||||
),
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.info_outline, color: Colors.white),
|
||||
title: Text('À propos',
|
||||
style: GoogleFonts.roboto(color: Colors.white)),
|
||||
title: Text(
|
||||
'À propos',
|
||||
style: GoogleFonts.roboto(color: Colors.white),
|
||||
),
|
||||
subtitle: Text(
|
||||
'XtremFlow IPTV v1.0.0',
|
||||
style: GoogleFonts.roboto(fontSize: 12, color: Colors.white54),
|
||||
@@ -452,8 +468,10 @@ class _SettingsTabState extends ConsumerState<SettingsTab>
|
||||
),
|
||||
child: ListTile(
|
||||
leading: const Icon(Icons.cleaning_services, color: Colors.orange),
|
||||
title: Text('Maintenance',
|
||||
style: GoogleFonts.roboto(color: Colors.white)),
|
||||
title: Text(
|
||||
'Maintenance',
|
||||
style: GoogleFonts.roboto(color: Colors.white),
|
||||
),
|
||||
subtitle: Text(
|
||||
'En cas de problèmes d\'affichage ou de mise à jour',
|
||||
style: GoogleFonts.roboto(fontSize: 12, color: Colors.white54),
|
||||
@@ -465,8 +483,10 @@ class _SettingsTabState extends ConsumerState<SettingsTab>
|
||||
context: context,
|
||||
builder: (context) => AlertDialog(
|
||||
backgroundColor: const Color(0xFF1E1E1E),
|
||||
title: const Text('Vider le cache ?',
|
||||
style: TextStyle(color: Colors.white)),
|
||||
title: const Text(
|
||||
'Vider le cache ?',
|
||||
style: TextStyle(color: Colors.white),
|
||||
),
|
||||
content: const Text(
|
||||
'Cette action va recharger l\'application et effacer les données temporaires.\n'
|
||||
'Vos réglages et favoris sont sauvegardés sur le serveur.',
|
||||
@@ -479,7 +499,8 @@ class _SettingsTabState extends ConsumerState<SettingsTab>
|
||||
),
|
||||
FilledButton(
|
||||
style: FilledButton.styleFrom(
|
||||
backgroundColor: Colors.orange),
|
||||
backgroundColor: Colors.orange,
|
||||
),
|
||||
onPressed: () {
|
||||
Navigator.pop(context);
|
||||
|
||||
@@ -580,8 +601,11 @@ class _SettingsTabState extends ConsumerState<SettingsTab>
|
||||
),
|
||||
suffixIcon: controller.text.isNotEmpty
|
||||
? IconButton(
|
||||
icon: const Icon(Icons.clear,
|
||||
size: 16, color: Colors.white54),
|
||||
icon: const Icon(
|
||||
Icons.clear,
|
||||
size: 16,
|
||||
color: Colors.white54,
|
||||
),
|
||||
onPressed: onClear,
|
||||
padding: EdgeInsets.zero,
|
||||
constraints: const BoxConstraints(),
|
||||
|
||||
@@ -248,10 +248,14 @@ class StreamingSettingsTab extends ConsumerWidget {
|
||||
required String Function(T) labelBuilder,
|
||||
}) {
|
||||
return ListTile(
|
||||
title: Text(title,
|
||||
style: GoogleFonts.roboto(fontSize: 14, color: Colors.white)),
|
||||
subtitle: Text(subtitle,
|
||||
style: GoogleFonts.roboto(fontSize: 12, color: Colors.white54)),
|
||||
title: Text(
|
||||
title,
|
||||
style: GoogleFonts.roboto(fontSize: 14, color: Colors.white),
|
||||
),
|
||||
subtitle: Text(
|
||||
subtitle,
|
||||
style: GoogleFonts.roboto(fontSize: 12, color: Colors.white54),
|
||||
),
|
||||
trailing: DropdownButton<T>(
|
||||
value: value,
|
||||
underline: const SizedBox(),
|
||||
|
||||
Reference in new issue
Block a user