import 'dart:async'; import 'dart:convert'; import 'package:flutter/material.dart'; import 'package:provider/provider.dart'; import '../providers/auth_provider.dart'; import '../providers/settings_provider.dart'; import '../services/api_service.dart'; import 'settings_page.dart'; import 'player_page.dart'; import 'my_stream_page.dart'; class HomePage extends StatefulWidget { @override _HomePageState createState() => _HomePageState(); } class _HomePageState extends State { int _selectedIndex = 0; @override Widget build(BuildContext context) { bool isWide = MediaQuery.of(context).size.width > 600; final List _pages = [ _ExploreView(onGoLive: () => setState(() => _selectedIndex = 1)), MyStreamPage(), SettingsPage(), ]; return Scaffold( body: Row( children: [ if (isWide) NavigationRail( selectedIndex: _selectedIndex, onDestinationSelected: (int index) => setState(() => _selectedIndex = index), labelType: NavigationRailLabelType.all, destinations: const [ NavigationRailDestination(icon: Icon(Icons.explore), label: Text('Explore')), NavigationRailDestination(icon: Icon(Icons.videocam), label: Text('Console')), NavigationRailDestination(icon: Icon(Icons.settings), label: Text('Settings')), ], ), Expanded(child: _pages[_selectedIndex]), ], ), bottomNavigationBar: !isWide ? NavigationBar( selectedIndex: _selectedIndex, onDestinationSelected: (int index) => setState(() => _selectedIndex = index), destinations: const [ NavigationDestination(icon: Icon(Icons.explore), label: 'Explore'), NavigationDestination(icon: Icon(Icons.videocam), label: 'Console'), NavigationDestination(icon: Icon(Icons.settings), label: 'Settings'), ], ) : null, ); } } class _ExploreView extends StatefulWidget { final VoidCallback onGoLive; const _ExploreView({required this.onGoLive}); @override _ExploreViewState createState() => _ExploreViewState(); } class _ExploreViewState extends State<_ExploreView> { List _activeRooms = []; bool _isLoading = false; Timer? _refreshTimer; @override void initState() { super.initState(); _refreshRooms(); _refreshTimer = Timer.periodic(Duration(seconds: 10), (timer) { if (mounted) _refreshRooms(isAuto: true); }); } @override void dispose() { _refreshTimer?.cancel(); super.dispose(); } Future _refreshRooms({bool isAuto = false}) async { if (!isAuto && mounted) setState(() => _isLoading = true); final settings = context.read(); final auth = context.read(); final api = ApiService(settings, auth.token); try { final response = await api.getActiveRooms(); if (response.statusCode == 200) { final data = jsonDecode(response.body); if (mounted) setState(() => _activeRooms = data['active_rooms'] ?? []); } } catch (e) { if (!isAuto && mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Failed to load rooms"))); } finally { if (!isAuto && mounted) setState(() => _isLoading = false); } } @override Widget build(BuildContext context) { final settings = context.watch(); return Scaffold( appBar: AppBar( title: Text("Explore", style: TextStyle(fontWeight: FontWeight.bold)), actions: [ IconButton(icon: Icon(Icons.refresh), onPressed: () => _refreshRooms()), IconButton(icon: Icon(Icons.logout), onPressed: () => context.read().logout()), ], ), floatingActionButton: FloatingActionButton.extended( onPressed: widget.onGoLive, label: Text("Go Live"), icon: Icon(Icons.videocam), ), body: RefreshIndicator( onRefresh: _refreshRooms, child: _isLoading && _activeRooms.isEmpty ? Center(child: CircularProgressIndicator()) : _activeRooms.isEmpty ? ListView(children: [ Padding( padding: EdgeInsets.only(top: 100), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ Icon(Icons.live_tv_outlined, size: 80, color: Colors.grey), SizedBox(height: 16), Text("No active rooms. Be the first!", style: TextStyle(color: Colors.grey, fontSize: 16)), ], ), ) ]) : GridView.builder( padding: EdgeInsets.all(12), gridDelegate: SliverGridDelegateWithMaxCrossAxisExtent( maxCrossAxisExtent: 400, childAspectRatio: 1.2, crossAxisSpacing: 12, mainAxisSpacing: 12, ), itemCount: _activeRooms.length, itemBuilder: (context, index) { final room = _activeRooms[index]; return _buildRoomCard(room, settings); }, ), ), ); } Widget _buildRoomCard(dynamic room, SettingsProvider settings) { return Card( elevation: 4, clipBehavior: Clip.antiAlias, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), child: InkWell( onTap: () { final rtmpUrl = "${settings.rtmpUrl}/${room['room_id']}"; Navigator.push(context, MaterialPageRoute(builder: (_) => PlayerPage(title: room['title'], rtmpUrl: rtmpUrl))); }, child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Expanded( flex: 3, child: Stack( fit: StackFit.expand, children: [ Container( color: Theme.of(context).colorScheme.primaryContainer, child: Center(child: Icon(Icons.live_tv, size: 50, color: Theme.of(context).colorScheme.primary)), ), Positioned( top: 8, left: 8, child: Container( padding: EdgeInsets.symmetric(horizontal: 8, vertical: 4), decoration: BoxDecoration(color: Colors.red, borderRadius: BorderRadius.circular(4)), child: Row(children: [ Icon(Icons.circle, size: 8, color: Colors.white), SizedBox(width: 4), Text("LIVE", style: TextStyle(color: Colors.white, fontSize: 10, fontWeight: FontWeight.bold)), ]), ), ), ], ), ), Expanded( flex: 1, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 8), child: Row( children: [ CircleAvatar(radius: 16, child: Text(room['user_id'].toString().substring(0, 1))), SizedBox(width: 12), Expanded( child: Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(room['title'], maxLines: 1, overflow: TextOverflow.ellipsis, style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14)), Text("Host ID: ${room['user_id']}", style: TextStyle(fontSize: 12, color: Colors.grey)), ], ), ), ], ), ), ), ], ), ), ); } }