feat: Enhance Apple TV UI components with modern design elements

- Updated ChannelCard widget to include live indicator, favorite button, and EPG information with smooth animations and skeleton loading for images.
- Introduced MobileTheme for touch-optimized layouts, adapting Apple TV design principles for mobile screens.
- Added TvChannelGrid for responsive channel layouts with flexible column counts and smooth animations.
- Created TvModernCard for displaying content with interactive hover states, progress indicators, and context menu support.
- Implemented TvTopNavBar and TvSideNav for improved navigation with glassmorphism effects and user-friendly interactions.
- Developed TvFloatingMenu for context-aware actions with smooth reveal animations.
This commit is contained in:
GitHub Copilot committed 2026-03-26 11:09:40 +01:00
1 parent 2c4572191d
commit 2ca0c8bf1a
14 files changed
+4578 -201

No files matched your search

+183 -24
View File
@@ -1,7 +1,19 @@
import 'dart:ui';
import 'package:flutter/material.dart';
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
/// - Premium border styling
/// - Shadow system
/// - Smooth animations
class GlassContainer extends StatelessWidget {
final Widget child;
final double? width;
@@ -9,9 +21,14 @@ class GlassContainer extends StatelessWidget {
final EdgeInsetsGeometry padding;
final EdgeInsetsGeometry? margin;
final double borderRadius;
final double opacity;
final double? opacity;
final Color? backgroundColor;
final Color? borderColor;
final bool border;
final bool hasBorder;
final double borderWidth;
final bool showShadow;
final double blur;
final bool showGradient;
const GlassContainer({
super.key,
@@ -21,13 +38,23 @@ class GlassContainer extends StatelessWidget {
this.padding = const EdgeInsets.all(16),
this.margin,
this.borderRadius = 16.0,
this.opacity = 0.6,
this.opacity,
this.backgroundColor,
this.borderColor,
this.border = true,
this.hasBorder = true,
this.borderWidth = 1.0,
this.showShadow = true,
this.blur = 15.0,
this.showGradient = true,
});
@override
Widget build(BuildContext context) {
final theme = Theme.of(context);
final finalOpacity = opacity ?? 0.08;
final finalBgColor = backgroundColor ?? AppColors.glassBackground;
final finalBorderColor = borderColor ?? AppColors.glassBorder;
return Container(
width: width,
height: height,
@@ -35,32 +62,45 @@ class GlassContainer extends StatelessWidget {
child: ClipRRect(
borderRadius: BorderRadius.circular(borderRadius),
child: BackdropFilter(
filter: ImageFilter.blur(sigmaX: 15, sigmaY: 15),
filter: ImageFilter.blur(sigmaX: blur, sigmaY: blur),
child: Container(
padding: padding,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(borderRadius),
border: border
? Border.all(
color: borderColor ?? Colors.white.withOpacity(0.1),
width: 1,
// Subtle gradient overlay for depth
gradient: showGradient
? LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
finalBgColor.withOpacity(finalOpacity * 1.2),
finalBgColor.withOpacity(finalOpacity * 0.6),
],
)
: null,
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
AppColors.surface.withOpacity(opacity),
AppColors.surface.withOpacity(opacity * 0.5),
],
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.2),
blurRadius: 20,
spreadRadius: -5,
),
],
border: hasBorder
? Border.all(
color: finalBorderColor,
width: borderWidth,
)
: null,
// Premium shadow
boxShadow: showShadow
? [
BoxShadow(
color: Colors.black.withOpacity(0.15),
blurRadius: 24,
spreadRadius: -6,
offset: const Offset(0, 8),
),
BoxShadow(
color: Colors.black.withOpacity(0.08),
blurRadius: 12,
spreadRadius: -3,
offset: const Offset(0, 4),
),
]
: [],
),
child: child,
),
@@ -69,3 +109,122 @@ class GlassContainer extends StatelessWidget {
);
}
}
/// Premium Glass Card - Full-featured variant
class GlassCard extends StatefulWidget {
final Widget child;
final VoidCallback? onTap;
final VoidCallback? onLongPress;
final EdgeInsetsGeometry padding;
final EdgeInsetsGeometry? margin;
final double borderRadius;
final bool interactive;
final bool isLoading;
const GlassCard({
super.key,
required this.child,
this.onTap,
this.onLongPress,
this.padding = const EdgeInsets.all(16),
this.margin,
this.borderRadius = 16.0,
this.interactive = false,
this.isLoading = false,
});
@override
State<GlassCard> createState() => _GlassCardState();
}
class _GlassCardState extends State<GlassCard>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _scaleAnimation;
late Animation<double> _opacityAnimation;
@override
void initState() {
super.initState();
_controller = AnimationController(
vsync: this,
duration: AppTheme.durationMd,
);
_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));
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
void _onPointerDown(PointerDownEvent event) {
if (widget.interactive && widget.onTap != null) {
_controller.forward();
}
}
void _onPointerUp(PointerUpEvent event) {
_controller.reverse();
}
@override
Widget build(BuildContext context) {
Widget content = GlassContainer(
borderRadius: widget.borderRadius,
padding: widget.padding,
margin: widget.margin,
child: widget.child,
);
if (widget.interactive && widget.onTap != null) {
content = MouseRegion(
cursor: SystemMouseCursors.click,
child: GestureDetector(
onTap: widget.onTap,
onLongPress: widget.onLongPress,
onTapDown: (_) => _onPointerDown(PointerDownEvent(
position: Offset.zero,
)),
onTapUp: (_) => _onPointerUp(PointerUpEvent(
position: Offset.zero,
)),
onTapCancel: () => _controller.reverse(),
child: ScaleTransition(
scale: _scaleAnimation,
child: Opacity(
opacity: _opacityAnimation.value,
child: content,
),
),
),
);
}
if (widget.isLoading) {
content = Stack(
children: [
Opacity(opacity: 0.5, child: content),
Center(
child: SizedBox(
width: 40,
height: 40,
child: CircularProgressIndicator(
color: AppColors.primary,
strokeWidth: 2,
),
),
),
],
);
}
return content;
}
}