mirror of
https://github.com/R0m1k3/xtremflow.git
synced 2026-10-20 05:28:05 +02:00
feat: add initial Windows desktop platform support files and configuration.
This commit is contained in:
1 parent
57b7acbead
commit
8c3a143f46
38 files changed
+2084
-481
No files matched your search
@@ -88,8 +88,10 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
Future<void> _initializePlayer(
|
||||
{double? startTimeOverride, bool isChannelSwitch = false}) async {
|
||||
Future<void> _initializePlayer({
|
||||
double? startTimeOverride,
|
||||
bool isChannelSwitch = false,
|
||||
}) async {
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
_statusMessage = 'Loading...';
|
||||
@@ -116,7 +118,9 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
|
||||
service.getVodStreamUrl(currentStreamId, widget.containerExtension);
|
||||
} else if (widget.streamType == StreamType.series) {
|
||||
streamUrl = service.getSeriesStreamUrl(
|
||||
currentStreamId, widget.containerExtension);
|
||||
currentStreamId,
|
||||
widget.containerExtension,
|
||||
);
|
||||
}
|
||||
|
||||
// Store URL for Lite Player
|
||||
@@ -330,8 +334,10 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
|
||||
customBorder: const CircleBorder(),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(12),
|
||||
child: Icon(Icons.arrow_back_rounded,
|
||||
color: Colors.white),
|
||||
child: Icon(
|
||||
Icons.arrow_back_rounded,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -346,7 +352,7 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
|
||||
fontSize: 18,
|
||||
fontWeight: FontWeight.bold,
|
||||
shadows: [
|
||||
Shadow(blurRadius: 4, color: Colors.black)
|
||||
Shadow(blurRadius: 4, color: Colors.black),
|
||||
],
|
||||
),
|
||||
overflow: TextOverflow.ellipsis,
|
||||
@@ -394,8 +400,11 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
|
||||
customBorder: const CircleBorder(),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(14),
|
||||
child: Icon(Icons.keyboard_arrow_up,
|
||||
color: Colors.white, size: 28),
|
||||
child: Icon(
|
||||
Icons.keyboard_arrow_up,
|
||||
color: Colors.white,
|
||||
size: 28,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -409,8 +418,11 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
|
||||
customBorder: const CircleBorder(),
|
||||
child: const Padding(
|
||||
padding: EdgeInsets.all(14),
|
||||
child: Icon(Icons.keyboard_arrow_down,
|
||||
color: Colors.white, size: 28),
|
||||
child: Icon(
|
||||
Icons.keyboard_arrow_down,
|
||||
color: Colors.white,
|
||||
size: 28,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -495,8 +507,10 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
|
||||
color: Colors.white.withOpacity(0.1),
|
||||
shape: BoxShape.circle,
|
||||
),
|
||||
child: const Icon(Icons.arrow_back_rounded,
|
||||
color: Colors.white),
|
||||
child: const Icon(
|
||||
Icons.arrow_back_rounded,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 24),
|
||||
@@ -519,9 +533,10 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
|
||||
Text(
|
||||
'Live TV',
|
||||
style: GoogleFonts.inter(
|
||||
fontSize: 14,
|
||||
color: AppColors.live,
|
||||
fontWeight: FontWeight.bold),
|
||||
fontSize: 14,
|
||||
color: AppColors.live,
|
||||
fontWeight: FontWeight.bold,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -533,7 +548,8 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
|
||||
Center(
|
||||
child: _isLoading
|
||||
? const CircularProgressIndicator(
|
||||
color: Colors.white)
|
||||
color: Colors.white,
|
||||
)
|
||||
: TvFocusableCard(
|
||||
onTap: _togglePlayPause,
|
||||
borderRadius: 100,
|
||||
@@ -545,7 +561,8 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
|
||||
color: Colors.white.withOpacity(0.2),
|
||||
shape: BoxShape.circle,
|
||||
border: Border.all(
|
||||
color: Colors.white.withOpacity(0.3)),
|
||||
color: Colors.white.withOpacity(0.3),
|
||||
),
|
||||
),
|
||||
child: Icon(
|
||||
_isPlaying
|
||||
@@ -590,21 +607,27 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
|
||||
child: Row(
|
||||
children: [
|
||||
Text(
|
||||
_formatDuration(Duration(
|
||||
seconds:
|
||||
_currentPosition.toInt())),
|
||||
style: const TextStyle(
|
||||
color: Colors.white70)),
|
||||
_formatDuration(
|
||||
Duration(
|
||||
seconds: _currentPosition.toInt(),
|
||||
),
|
||||
),
|
||||
style: const TextStyle(
|
||||
color: Colors.white70,
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Expanded(
|
||||
child: SliderTheme(
|
||||
data: const SliderThemeData(
|
||||
trackHeight: 4,
|
||||
thumbShape: RoundSliderThumbShape(
|
||||
enabledThumbRadius: 8),
|
||||
enabledThumbRadius: 8,
|
||||
),
|
||||
overlayShape:
|
||||
RoundSliderOverlayShape(
|
||||
overlayRadius: 16),
|
||||
overlayRadius: 16,
|
||||
),
|
||||
activeTrackColor: AppColors.primary,
|
||||
inactiveTrackColor: Colors.white24,
|
||||
thumbColor: Colors.white,
|
||||
@@ -618,21 +641,25 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
|
||||
},
|
||||
onChanged: (value) {
|
||||
// Update UI immediately (optimistic update)
|
||||
setState(() =>
|
||||
_currentPosition = value);
|
||||
setState(
|
||||
() => _currentPosition = value,
|
||||
);
|
||||
},
|
||||
onChangeEnd: (value) {
|
||||
_sendMessage({
|
||||
'type': 'seek',
|
||||
'value': value
|
||||
'value': value,
|
||||
});
|
||||
// Small delay to prevent jitter from incoming messages
|
||||
Future.delayed(
|
||||
const Duration(
|
||||
milliseconds: 500), () {
|
||||
if (mounted)
|
||||
milliseconds: 500,
|
||||
), () {
|
||||
if (mounted) {
|
||||
setState(
|
||||
() => _isSeeking = false);
|
||||
() => _isSeeking = false,
|
||||
);
|
||||
}
|
||||
});
|
||||
},
|
||||
),
|
||||
@@ -640,10 +667,15 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
Text(
|
||||
_formatDuration(Duration(
|
||||
seconds: _totalDuration.toInt())),
|
||||
style: const TextStyle(
|
||||
color: Colors.white70)),
|
||||
_formatDuration(
|
||||
Duration(
|
||||
seconds: _totalDuration.toInt(),
|
||||
),
|
||||
),
|
||||
style: const TextStyle(
|
||||
color: Colors.white70,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
@@ -656,11 +688,14 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
|
||||
if (widget.streamType == StreamType.live &&
|
||||
widget.channels != null) ...[
|
||||
_buildTvControl(
|
||||
Icons.navigate_before_rounded,
|
||||
_previousChannel),
|
||||
Icons.navigate_before_rounded,
|
||||
_previousChannel,
|
||||
),
|
||||
const SizedBox(width: 32),
|
||||
_buildTvControl(Icons.navigate_next_rounded,
|
||||
_nextChannel),
|
||||
_buildTvControl(
|
||||
Icons.navigate_next_rounded,
|
||||
_nextChannel,
|
||||
),
|
||||
],
|
||||
|
||||
// Controls for VOD (Keep Skip buttons, they act as Prev/Next track if playlist or maybe seek)
|
||||
@@ -671,11 +706,15 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
|
||||
if (widget.streamType != StreamType.live &&
|
||||
widget.channels != null &&
|
||||
widget.channels!.isNotEmpty) ...[
|
||||
_buildTvControl(Icons.skip_previous_rounded,
|
||||
_previousChannel),
|
||||
_buildTvControl(
|
||||
Icons.skip_previous_rounded,
|
||||
_previousChannel,
|
||||
),
|
||||
const SizedBox(width: 32),
|
||||
_buildTvControl(
|
||||
Icons.skip_next_rounded, _nextChannel),
|
||||
Icons.skip_next_rounded,
|
||||
_nextChannel,
|
||||
),
|
||||
],
|
||||
|
||||
const Spacer(),
|
||||
@@ -688,10 +727,14 @@ class _PlayerScreenState extends ConsumerState<PlayerScreen> {
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
_buildTvControl(
|
||||
Icons.subtitles_rounded, () {}),
|
||||
Icons.subtitles_rounded,
|
||||
() {},
|
||||
),
|
||||
const SizedBox(width: 16),
|
||||
_buildTvControl(Icons.aspect_ratio_rounded,
|
||||
_toggleFullscreen),
|
||||
_buildTvControl(
|
||||
Icons.aspect_ratio_rounded,
|
||||
_toggleFullscreen,
|
||||
),
|
||||
],
|
||||
),
|
||||
],
|
||||
|
||||
@@ -27,7 +27,9 @@ class PlaylistSelectionScreen extends ConsumerWidget {
|
||||
title: Text(
|
||||
'Select Playlist',
|
||||
style: GoogleFonts.roboto(
|
||||
fontWeight: FontWeight.w600, color: Colors.white),
|
||||
fontWeight: FontWeight.w600,
|
||||
color: Colors.white,
|
||||
),
|
||||
),
|
||||
backgroundColor: Colors.transparent,
|
||||
elevation: 0,
|
||||
@@ -71,9 +73,13 @@ class PlaylistSelectionScreen extends ConsumerWidget {
|
||||
children: [
|
||||
Icon(Icons.error_outline, size: 64, color: Colors.red.shade400),
|
||||
const SizedBox(height: 16),
|
||||
Text('Error loading playlists',
|
||||
style: GoogleFonts.roboto(
|
||||
fontSize: 18, color: Colors.white70)),
|
||||
Text(
|
||||
'Error loading playlists',
|
||||
style: GoogleFonts.roboto(
|
||||
fontSize: 18,
|
||||
color: Colors.white70,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
ElevatedButton(
|
||||
onPressed: () => ref.refresh(playlistsProvider),
|
||||
@@ -118,7 +124,11 @@ class PlaylistSelectionScreen extends ConsumerWidget {
|
||||
|
||||
return GridView.builder(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
24, 100, 24, 24), // Top padding for transparent AppBar
|
||||
24,
|
||||
100,
|
||||
24,
|
||||
24,
|
||||
), // Top padding for transparent AppBar
|
||||
gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(
|
||||
crossAxisCount: 3,
|
||||
crossAxisSpacing: 24,
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
),
|
||||
);
|
||||
},
|
||||
),
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
|
||||
@@ -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),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
Reference in new issue
Block a user