feat: add mobile-optimized IPTV player with HLS and MpegTS support

This commit is contained in:
Michael committed 2026-03-29 10:19:36 +02:00
1 parent 3c9a9c7be7
commit fa6ae90d8d
38 files changed
+1280 -895

No files matched your search

+17 -13
View File
@@ -4,10 +4,10 @@ import '../theme/app_colors.dart';
import '../theme/app_theme.dart';
/// Modern Apple TV Glass Container
///
///
/// Sophisticated glassmorphism with premium blur effects,
/// configurable transparency, and elegant borders.
///
///
/// Features:
/// - Backdrop blur (iOS-style glass effect)
/// - Subtle gradient overlay
@@ -151,11 +151,11 @@ class _GlassCardState extends State<GlassCard>
duration: AppTheme.durationMd,
);
_scaleAnimation = Tween<double>(begin: 1.0, end: 1.04)
.animate(CurvedAnimation(parent: _controller, curve: AppTheme.curveDefault));
_scaleAnimation = Tween<double>(begin: 1.0, end: 1.04).animate(
CurvedAnimation(parent: _controller, curve: AppTheme.curveDefault));
_opacityAnimation = Tween<double>(begin: 1.0, end: 0.8)
.animate(CurvedAnimation(parent: _controller, curve: AppTheme.curveDefault));
_opacityAnimation = Tween<double>(begin: 1.0, end: 0.8).animate(
CurvedAnimation(parent: _controller, curve: AppTheme.curveDefault));
}
@override
@@ -189,12 +189,16 @@ class _GlassCardState extends State<GlassCard>
child: GestureDetector(
onTap: widget.onTap,
onLongPress: widget.onLongPress,
onTapDown: (_) => _onPointerDown(PointerDownEvent(
position: Offset.zero,
)),
onTapUp: (_) => _onPointerUp(PointerUpEvent(
position: Offset.zero,
)),
onTapDown: (_) => _onPointerDown(
const PointerDownEvent(
position: Offset.zero,
),
),
onTapUp: (_) => _onPointerUp(
const PointerUpEvent(
position: Offset.zero,
),
),
onTapCancel: () => _controller.reverse(),
child: ScaleTransition(
scale: _scaleAnimation,
@@ -211,7 +215,7 @@ class _GlassCardState extends State<GlassCard>
content = Stack(
children: [
Opacity(opacity: 0.5, child: content),
Center(
const Center(
child: SizedBox(
width: 40,
height: 40,
+8 -6
View File
@@ -285,7 +285,8 @@ class _HeroCarouselState extends State<HeroCarousel>
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.play_arrow, color: AppColors.textPrimary),
const Icon(Icons.play_arrow,
color: AppColors.textPrimary),
const SizedBox(width: 8),
Text(
'Play',
@@ -320,8 +321,10 @@ class _HeroCarouselState extends State<HeroCarousel>
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.info_outline,
color: AppColors.textPrimary),
const Icon(
Icons.info_outline,
color: AppColors.textPrimary,
),
const SizedBox(width: 8),
Text(
'More Info',
@@ -361,9 +364,8 @@ class _HeroCarouselState extends State<HeroCarousel>
width: _currentIndex == index ? 32 : 8,
height: 8,
decoration: BoxDecoration(
color: _currentIndex == index
? AppColors.primary
: AppColors.border,
color:
_currentIndex == index ? AppColors.primary : AppColors.border,
borderRadius: BorderRadius.circular(4),
),
),
+3 -3
View File
@@ -17,15 +17,15 @@ class ThemedLoading extends StatelessWidget {
],
),
),
child: Center(
child: const Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const CircularProgressIndicator(
CircularProgressIndicator(
color: AppColors.primary,
strokeWidth: 3,
),
const SizedBox(height: 16),
SizedBox(height: 16),
Text(
'Loading...',
style: TextStyle(
+3 -3
View File
@@ -1,9 +1,8 @@
import 'package:flutter/material.dart';
import '../theme/app_colors.dart';
import '../theme/app_theme.dart';
/// Apple TV Modern Channel Grid
///
///
/// Responsive grid layout for channels with:
/// - Flexible column count based on screen size
/// - Smooth animations
@@ -57,7 +56,8 @@ class TvChannelGrid extends StatelessWidget {
// Compute item width, clamped to avoid negative/NaN values.
final totalSpacing = horizontalSpacing * (columnCount - 1);
final availableWidth = screenWidth - horizontalPadding - totalSpacing;
final itemWidth = (availableWidth / columnCount).clamp(1.0, double.infinity);
final itemWidth =
(availableWidth / columnCount).clamp(1.0, double.infinity);
return SingleChildScrollView(
controller: scrollController,
+12 -10
View File
@@ -6,7 +6,7 @@ import '../theme/app_theme.dart';
import 'glass_container.dart';
/// Apple TV Modern Content Card
///
///
/// Premium card for displaying movies, shows, or other content with:
/// - Poster image with skeleton loading
/// - Title and metadata
@@ -149,19 +149,19 @@ class _TvModernCardState extends State<TvModernCard>
right: 0,
child: Container(
height: 3,
decoration: BoxDecoration(
decoration: const BoxDecoration(
borderRadius: BorderRadius.vertical(
bottom: Radius.circular(AppTheme.radiusLg),
),
),
child: ClipRRect(
borderRadius: BorderRadius.vertical(
borderRadius: const BorderRadius.vertical(
bottom: Radius.circular(AppTheme.radiusLg),
),
child: LinearProgressIndicator(
value: widget.progress,
backgroundColor: AppColors.surfaceVariant,
valueColor: AlwaysStoppedAnimation(
valueColor: const AlwaysStoppedAnimation(
AppColors.primary,
),
minHeight: 3,
@@ -171,7 +171,8 @@ class _TvModernCardState extends State<TvModernCard>
),
// Action buttons (on hover)
if (_isHovered && (widget.onPlayTap != null || widget.onMoreTap != null))
if (_isHovered &&
(widget.onPlayTap != null || widget.onMoreTap != null))
Center(
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
@@ -182,7 +183,8 @@ class _TvModernCardState extends State<TvModernCard>
label: 'Play',
onTap: widget.onPlayTap!,
),
if (widget.onPlayTap != null && widget.onMoreTap != null)
if (widget.onPlayTap != null &&
widget.onMoreTap != null)
const SizedBox(width: 16),
if (widget.onMoreTap != null)
_buildActionButton(
@@ -219,7 +221,7 @@ class _TvModernCardState extends State<TvModernCard>
Row(
children: [
if (widget.rating != null) ...[
Icon(
const Icon(
Icons.star,
size: 14,
color: AppColors.ratingGold,
@@ -258,7 +260,7 @@ class _TvModernCardState extends State<TvModernCard>
if (widget.imageUrl == null || widget.imageUrl!.isEmpty) {
return Container(
color: AppColors.surface,
child: Center(
child: const Center(
child: Icon(
Icons.image_outlined,
color: AppColors.textSecondary,
@@ -273,7 +275,7 @@ class _TvModernCardState extends State<TvModernCard>
fit: BoxFit.cover,
placeholder: (context, url) => Container(
color: AppColors.shimmer,
child: Center(
child: const Center(
child: SizedBox(
width: 40,
height: 40,
@@ -286,7 +288,7 @@ class _TvModernCardState extends State<TvModernCard>
),
errorWidget: (context, url, error) => Container(
color: AppColors.surface,
child: Center(
child: const Center(
child: Icon(
Icons.broken_image_outlined,
color: AppColors.textSecondary,