import 'package:flutter/material.dart'; import 'package:just_audio_background/just_audio_background.dart'; import 'app_theme_business_object.dart'; import 'live_info_business_object.dart'; import 'live_info_panel.dart'; import 'streaming_audio_business_object.dart'; Future main() async { WidgetsFlutterBinding.ensureInitialized(); await JustAudioBackground.init( androidNotificationChannelId: 'com.kryzgoflutter.audio.channel', androidNotificationChannelName: 'KRYZ Audio Playback', androidNotificationOngoing: true, ); runApp(const MyApp()); } class MyApp extends StatefulWidget { const MyApp({super.key}); @override State createState() => _MyAppState(); } class _MyAppState extends State { final StreamingAudioBusinessObject _audio = StreamingAudioBusinessObject(); @override void dispose() { _audio.dispose(); super.dispose(); } @override Widget build(BuildContext context) { return MaterialApp( title: 'Split Screen Player', theme: AppThemeBusinessObject.lightTheme(), darkTheme: AppThemeBusinessObject.darkTheme(), themeMode: ThemeMode.system, home: MainPage(audio: _audio), debugShowCheckedModeBanner: false, ); } } class MainPage extends StatefulWidget { const MainPage({super.key, required this.audio}); final StreamingAudioBusinessObject audio; @override State createState() => _MainPageState(); } class _MainPageState extends State { StreamingAudioBusinessObject get _audio => widget.audio; final LiveInfoBusinessObject _liveInfo = LiveInfoBusinessObject(); static const List _intervalOptions = [5, 10, 15, 30, 60]; String _playbackStatusLabel() { switch (_audio.playbackStatus) { case StreamingPlaybackStatus.stopped: return 'Stopped'; case StreamingPlaybackStatus.buffering: return 'Buffering...'; case StreamingPlaybackStatus.playing: return 'Playing'; } } @override void initState() { super.initState(); _audio.addListener(_handleAudioStateChanged); _liveInfo.addListener(_handleLiveInfoChanged); _liveInfo.start(); } void _handleAudioStateChanged() { if (!mounted) { return; } setState(() { // Rebuild when playback state changes from the player. }); } void _handleLiveInfoChanged() { if (!mounted) { return; } setState(() { // Rebuild when live metadata updates from the polling API. }); } Future _play() async { await _audio.play(); } Future _stop() async { await _audio.stop(); } @override void dispose() { _audio.removeListener(_handleAudioStateChanged); _liveInfo.removeListener(_handleLiveInfoChanged); _liveInfo.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final themeColors = AppThemeBusinessObject.colorsFor(context); final theme = Theme.of(context); return Scaffold( body: DecoratedBox( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ themeColors.scaffoldGradientStart, themeColors.scaffoldGradientEnd, ], ), ), child: SafeArea( child: Padding( padding: const EdgeInsets.all(12), child: Column( children: [ Container( width: double.infinity, padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: themeColors.shellSurface, borderRadius: BorderRadius.circular(24), border: Border.all(color: themeColors.shellBorder), boxShadow: [ BoxShadow( color: themeColors.controlsShadow, blurRadius: 18, offset: const Offset(0, 10), ), ], ), child: Row( children: [ ClipRRect( borderRadius: BorderRadius.circular(18), child: Image.asset( 'assets/icon/app_icon.png', width: 72, height: 72, fit: BoxFit.cover, ), ), const SizedBox(width: 14), Expanded( child: Text( 'KRYZ Go!', textAlign: TextAlign.center, style: theme.textTheme.headlineMedium?.copyWith( fontWeight: FontWeight.w900, letterSpacing: 0.4, color: themeColors.statusText, ), ), ), const SizedBox(width: 86), ], ), ), const SizedBox(height: 12), Expanded( child: LiveInfoPanel( liveInfo: _liveInfo, themeColors: themeColors, intervalOptions: _intervalOptions, ), ), const SizedBox(height: 12), Container( width: double.infinity, padding: const EdgeInsets.symmetric( horizontal: 16, vertical: 14, ), decoration: BoxDecoration( borderRadius: BorderRadius.circular(24), gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [ themeColors.controlsGradientStart, themeColors.controlsGradientEnd, ], ), boxShadow: [ BoxShadow( color: themeColors.controlsShadow, blurRadius: 14, offset: const Offset(0, 6), ), ], ), child: Wrap( alignment: WrapAlignment.center, crossAxisAlignment: WrapCrossAlignment.center, spacing: 12, runSpacing: 12, children: [ ElevatedButton.icon( onPressed: _audio.isStopped ? _play : null, icon: const Icon(Icons.play_arrow_rounded), label: const Text('Play'), ), ElevatedButton.icon( onPressed: _audio.isStopped ? null : _stop, icon: const Icon(Icons.stop_rounded), label: const Text('Stop'), ), Text( _playbackStatusLabel(), style: theme.textTheme.titleSmall?.copyWith( fontWeight: FontWeight.w700, color: themeColors.statusText, ), ), ], ), ), ], ), ), ), ), ); } }