feat: add initial Windows desktop platform support files and configuration.

This commit is contained in:
Michael committed 2025-12-16 07:41:08 +01:00
1 parent 57b7acbead
commit 8c3a143f46
38 files changed
+2084 -481

No files matched your search

+75 -38
View File
@@ -66,8 +66,8 @@ class _LiveTVTabState extends ConsumerState<LiveTVTab>
body: channelsAsync.when(
loading: () => const ThemedLoading(),
error: (e, s) => Center(
child:
Text('Error: $e', style: const TextStyle(color: Colors.white))),
child: Text('Error: $e', style: const TextStyle(color: Colors.white)),
),
data: (groupedChannels) {
var categories = groupedChannels.keys.toList();
if (settings.liveTvKeywords.isNotEmpty) {
@@ -125,8 +125,10 @@ class _LiveTVTabState extends ConsumerState<LiveTVTab>
color: Colors.white.withOpacity(0.1),
shape: BoxShape.circle,
),
child: const Icon(Icons.arrow_back,
color: Colors.white),
child: const Icon(
Icons.arrow_back,
color: Colors.white,
),
),
),
const SizedBox(width: 16),
@@ -167,12 +169,17 @@ class _LiveTVTabState extends ConsumerState<LiveTVTab>
const Icon(Icons.search, color: Colors.white54),
border: InputBorder.none,
contentPadding: const EdgeInsets.symmetric(
horizontal: 16, vertical: 10),
horizontal: 16,
vertical: 10,
),
suffixIcon: _searchQuery.isNotEmpty
? IconButton(
icon: const Icon(Icons.clear,
color: Colors.white),
onPressed: _searchController.clear)
icon: const Icon(
Icons.clear,
color: Colors.white,
),
onPressed: _searchController.clear,
)
: null,
),
),
@@ -224,14 +231,18 @@ class _LiveTVTabState extends ConsumerState<LiveTVTab>
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.tv_off,
size: 64,
color: Colors.white.withOpacity(0.3)),
Icon(
Icons.tv_off,
size: 64,
color: Colors.white.withOpacity(0.3),
),
const SizedBox(height: 16),
Text(
'No channels found',
style: GoogleFonts.inter(
color: Colors.white54, fontSize: 18),
color: Colors.white54,
fontSize: 18,
),
),
],
),
@@ -247,7 +258,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,
@@ -300,8 +313,11 @@ class _LiveTVTabState extends ConsumerState<LiveTVTab>
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.folder_copy_outlined,
color: Colors.white.withOpacity(0.9), size: 32),
Icon(
Icons.folder_copy_outlined,
color: Colors.white.withOpacity(0.9),
size: 32,
),
const SizedBox(height: 12),
Padding(
padding: const EdgeInsets.symmetric(horizontal: 16),
@@ -321,7 +337,9 @@ class _LiveTVTabState extends ConsumerState<LiveTVTab>
Text(
'$count Channels',
style: GoogleFonts.inter(
fontSize: 13, color: Colors.white54),
fontSize: 13,
color: Colors.white54,
),
),
],
),
@@ -378,15 +396,19 @@ class _LiveTVTabState extends ConsumerState<LiveTVTab>
child: channel.streamIcon.isNotEmpty
? Image.network(
_getProxiedIconUrl(channel.streamIcon)!,
errorBuilder: (_, __, ___) => const Icon(Icons.tv,
color: Colors.grey, size: 40),
errorBuilder: (_, __, ___) => const Icon(
Icons.tv,
color: Colors.grey,
size: 40,
),
)
: Text(
channel.name.characters.first.toUpperCase(),
style: GoogleFonts.inter(
fontSize: 40,
fontWeight: FontWeight.bold,
color: Colors.black12),
fontSize: 40,
fontWeight: FontWeight.bold,
color: Colors.black12,
),
),
),
),
@@ -402,9 +424,10 @@ class _LiveTVTabState extends ConsumerState<LiveTVTab>
Text(
channel.name,
style: GoogleFonts.inter(
color: Colors.white,
fontSize: 13,
fontWeight: FontWeight.w500),
color: Colors.white,
fontSize: 13,
fontWeight: FontWeight.w500,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
),
@@ -414,17 +437,22 @@ class _LiveTVTabState extends ConsumerState<LiveTVTab>
final epgAsync = ref.watch(
epgByPlaylistProvider(
EpgRequestKey(
playlist: widget.playlist,
streamId: channel.streamId),
playlist: widget.playlist,
streamId: channel.streamId,
),
),
);
return epgAsync.when(
data: (epgList) {
if (epgList.isEmpty) {
return Text('No Info',
style: GoogleFonts.inter(
color: Colors.white54, fontSize: 10));
return Text(
'No Info',
style: GoogleFonts.inter(
color: Colors.white54,
fontSize: 10,
),
);
}
final now = DateTime.now();
final currentProgram = epgList.firstWhere(
@@ -444,19 +472,28 @@ class _LiveTVTabState extends ConsumerState<LiveTVTab>
return Text(
currentProgram.title,
style: GoogleFonts.inter(
color: const Color(0xFFFFD700),
fontSize: 10,
fontWeight: FontWeight.w600),
color: const Color(0xFFFFD700),
fontSize: 10,
fontWeight: FontWeight.w600,
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
);
},
loading: () => Text('...',
style: GoogleFonts.inter(
color: Colors.white24, fontSize: 10)),
error: (_, __) => Text('Err',
style: GoogleFonts.inter(
color: Colors.red, fontSize: 10)),
loading: () => Text(
'...',
style: GoogleFonts.inter(
color: Colors.white24,
fontSize: 10,
),
),
error: (_, __) => Text(
'Err',
style: GoogleFonts.inter(
color: Colors.red,
fontSize: 10,
),
),
);
},
),
+56 -26
View File
@@ -243,14 +243,19 @@ class _MoviesTabState extends ConsumerState<MoviesTab> {
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
children: [
const Icon(Icons.search,
size: 20, color: Colors.white54),
const Icon(
Icons.search,
size: 20,
color: Colors.white54,
),
const SizedBox(width: 8),
Expanded(
child: TextField(
controller: _searchController,
style: GoogleFonts.inter(
fontSize: 14, color: Colors.white),
fontSize: 14,
color: Colors.white,
),
decoration: InputDecoration(
hintText: 'Search movies...',
hintStyle:
@@ -265,18 +270,24 @@ class _MoviesTabState extends ConsumerState<MoviesTab> {
),
if (_isSearching)
const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(
strokeWidth: 2, color: Colors.white)),
width: 16,
height: 16,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.white,
),
),
if (_searchQuery.isNotEmpty)
GestureDetector(
onTap: () {
_searchController.clear();
_onSearchChanged('');
},
child: const Icon(Icons.close,
size: 16, color: Colors.white),
child: const Icon(
Icons.close,
size: 16,
color: Colors.white,
),
),
],
),
@@ -349,15 +360,23 @@ class _MoviesTabState extends ConsumerState<MoviesTab> {
errorBuilder: (ctx, err, stack) => Container(
color: AppColors.surfaceVariant,
child: const Center(
child: Icon(Icons.movie,
size: 48, color: Colors.white24)),
child: Icon(
Icons.movie,
size: 48,
color: Colors.white24,
),
),
),
)
: Container(
color: AppColors.surfaceVariant,
child: const Center(
child: Icon(Icons.movie,
size: 48, color: Colors.white24)),
child: Icon(
Icons.movie,
size: 48,
color: Colors.white24,
),
),
),
),
@@ -374,11 +393,12 @@ class _MoviesTabState extends ConsumerState<MoviesTab> {
end: Alignment.bottomCenter,
colors: [
Colors.transparent,
Colors.black.withOpacity(0.9)
Colors.black.withOpacity(0.9),
],
),
borderRadius: const BorderRadius.vertical(
bottom: Radius.circular(12)),
bottom: Radius.circular(12),
),
),
),
),
@@ -390,7 +410,9 @@ class _MoviesTabState extends ConsumerState<MoviesTab> {
right: 8,
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 6, vertical: 2),
horizontal: 6,
vertical: 2,
),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.7),
borderRadius: BorderRadius.circular(4),
@@ -399,15 +421,19 @@ class _MoviesTabState extends ConsumerState<MoviesTab> {
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.star,
size: 10, color: Colors.amber),
const Icon(
Icons.star,
size: 10,
color: Colors.amber,
),
const SizedBox(width: 4),
Text(
_formatRating(movie.rating)!,
style: GoogleFonts.inter(
fontSize: 10,
fontWeight: FontWeight.bold,
color: Colors.white),
fontSize: 10,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
],
),
@@ -431,7 +457,9 @@ class _MoviesTabState extends ConsumerState<MoviesTab> {
fontWeight: FontWeight.w600,
shadows: [
const Shadow(
color: Colors.black, blurRadius: 4)
color: Colors.black,
blurRadius: 4,
),
],
),
maxLines: 2,
@@ -443,9 +471,10 @@ class _MoviesTabState extends ConsumerState<MoviesTab> {
child: Text(
'WATCHED',
style: GoogleFonts.inter(
color: AppColors.primary,
fontSize: 10,
fontWeight: FontWeight.bold),
color: AppColors.primary,
fontSize: 10,
fontWeight: FontWeight.bold,
),
),
),
],
@@ -466,7 +495,8 @@ class _MoviesTabState extends ConsumerState<MoviesTab> {
child: Padding(
padding: EdgeInsets.all(32),
child: Center(
child: CircularProgressIndicator(color: Colors.white)),
child: CircularProgressIndicator(color: Colors.white),
),
),
),
+59 -29
View File
@@ -219,14 +219,19 @@ class _SeriesTabState extends ConsumerState<SeriesTab> {
padding: const EdgeInsets.symmetric(horizontal: 16),
child: Row(
children: [
const Icon(Icons.search,
size: 20, color: Colors.white54),
const Icon(
Icons.search,
size: 20,
color: Colors.white54,
),
const SizedBox(width: 8),
Expanded(
child: TextField(
controller: _searchController,
style: GoogleFonts.inter(
fontSize: 14, color: Colors.white),
fontSize: 14,
color: Colors.white,
),
decoration: InputDecoration(
hintText: 'Search series...',
hintStyle:
@@ -241,18 +246,24 @@ class _SeriesTabState extends ConsumerState<SeriesTab> {
),
if (_isSearching)
const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(
strokeWidth: 2, color: Colors.white)),
width: 16,
height: 16,
child: CircularProgressIndicator(
strokeWidth: 2,
color: Colors.white,
),
),
if (_searchQuery.isNotEmpty)
GestureDetector(
onTap: () {
_searchController.clear();
_onSearchChanged('');
},
child: const Icon(Icons.close,
size: 16, color: Colors.white),
child: const Icon(
Icons.close,
size: 16,
color: Colors.white,
),
),
],
),
@@ -281,10 +292,11 @@ class _SeriesTabState extends ConsumerState<SeriesTab> {
: null,
onMoreInfo: () {
final series = _series.firstWhere(
(element) =>
element.seriesId.toString() ==
s.seriesId.toString(),
orElse: () => s);
(element) =>
element.seriesId.toString() ==
s.seriesId.toString(),
orElse: () => s,
);
_openSeries(series);
},
),
@@ -292,8 +304,9 @@ class _SeriesTabState extends ConsumerState<SeriesTab> {
.toList(),
onTap: (item) {
final series = _series.firstWhere(
(element) => element.seriesId.toString() == item.id,
orElse: () => _series[0]);
(element) => element.seriesId.toString() == item.id,
orElse: () => _series[0],
);
_openSeries(series);
},
),
@@ -330,15 +343,23 @@ class _SeriesTabState extends ConsumerState<SeriesTab> {
errorBuilder: (ctx, err, stack) => Container(
color: AppColors.surfaceVariant,
child: const Center(
child: Icon(Icons.tv,
size: 48, color: Colors.white24)),
child: Icon(
Icons.tv,
size: 48,
color: Colors.white24,
),
),
),
)
: Container(
color: AppColors.surfaceVariant,
child: const Center(
child: Icon(Icons.tv,
size: 48, color: Colors.white24)),
child: Icon(
Icons.tv,
size: 48,
color: Colors.white24,
),
),
),
),
@@ -355,11 +376,12 @@ class _SeriesTabState extends ConsumerState<SeriesTab> {
end: Alignment.bottomCenter,
colors: [
Colors.transparent,
Colors.black.withOpacity(0.9)
Colors.black.withOpacity(0.9),
],
),
borderRadius: const BorderRadius.vertical(
bottom: Radius.circular(12)),
bottom: Radius.circular(12),
),
),
),
),
@@ -376,7 +398,8 @@ class _SeriesTabState extends ConsumerState<SeriesTab> {
fontSize: 13,
fontWeight: FontWeight.w600,
shadows: [
const Shadow(color: Colors.black, blurRadius: 4)
const Shadow(
color: Colors.black, blurRadius: 4),
],
),
maxLines: 2,
@@ -391,7 +414,9 @@ class _SeriesTabState extends ConsumerState<SeriesTab> {
right: 8,
child: Container(
padding: const EdgeInsets.symmetric(
horizontal: 6, vertical: 2),
horizontal: 6,
vertical: 2,
),
decoration: BoxDecoration(
color: Colors.black.withOpacity(0.7),
borderRadius: BorderRadius.circular(4),
@@ -400,15 +425,19 @@ class _SeriesTabState extends ConsumerState<SeriesTab> {
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.star,
size: 10, color: Colors.amber),
const Icon(
Icons.star,
size: 10,
color: Colors.amber,
),
const SizedBox(width: 4),
Text(
_formatRating(serie.rating)!,
style: GoogleFonts.inter(
fontSize: 10,
fontWeight: FontWeight.bold,
color: Colors.white),
fontSize: 10,
fontWeight: FontWeight.bold,
color: Colors.white,
),
),
],
),
@@ -429,7 +458,8 @@ class _SeriesTabState extends ConsumerState<SeriesTab> {
child: Padding(
padding: EdgeInsets.all(32),
child: Center(
child: CircularProgressIndicator(color: Colors.white)),
child: CircularProgressIndicator(color: Colors.white),
),
),
),
+43 -19
View File
@@ -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(),
@@ -252,10 +252,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(),