Phase 3.5: Finalized UI polish, added Console, and fixed stale stream status bug

This commit is contained in:
2026-03-18 11:37:16 +08:00
parent 38bc9526b2
commit d05ec7ccdf
6 changed files with 223 additions and 97 deletions

View File

@@ -17,49 +17,40 @@ class HomePage extends StatefulWidget {
class _HomePageState extends State<HomePage> {
int _selectedIndex = 0;
// 根据当前选择的索引返回对应的页面
final List<Widget> _pages = [
_ExploreView(),
MyStreamPage(),
SettingsPage(),
];
@override
Widget build(BuildContext context) {
// 检查是否为桌面/宽屏环境
bool isWide = MediaQuery.of(context).size.width > 600;
final List<Widget> _pages = [
_ExploreView(onGoLive: () => setState(() => _selectedIndex = 1)),
MyStreamPage(),
SettingsPage(),
];
return Scaffold(
body: Row(
children: [
// 桌面端侧边导航
if (isWide)
NavigationRail(
selectedIndex: _selectedIndex,
onDestinationSelected: (int index) {
setState(() => _selectedIndex = index);
},
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('Stream')),
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);
},
onDestinationSelected: (int index) => setState(() => _selectedIndex = index),
destinations: const [
NavigationDestination(icon: Icon(Icons.explore), label: 'Explore'),
NavigationDestination(icon: Icon(Icons.videocam), label: 'Stream'),
NavigationDestination(icon: Icon(Icons.videocam), label: 'Console'),
NavigationDestination(icon: Icon(Icons.settings), label: 'Settings'),
],
)
@@ -68,8 +59,10 @@ class _HomePageState extends State<HomePage> {
}
}
// 将原本的直播列表逻辑封装为一个内部 View
class _ExploreView extends StatefulWidget {
final VoidCallback onGoLive;
const _ExploreView({required this.onGoLive});
@override
_ExploreViewState createState() => _ExploreViewState();
}
@@ -96,7 +89,6 @@ class _ExploreViewState extends State<_ExploreView> {
Future<void> _refreshRooms({bool isAuto = false}) async {
if (!isAuto && mounted) setState(() => _isLoading = true);
final settings = context.read<SettingsProvider>();
final auth = context.read<AuthProvider>();
final api = ApiService(settings, auth.token);
@@ -105,14 +97,10 @@ class _ExploreViewState extends State<_ExploreView> {
final response = await api.getActiveRooms();
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
if (mounted) {
setState(() => _activeRooms = data['active_rooms'] ?? []);
}
if (mounted) setState(() => _activeRooms = data['active_rooms'] ?? []);
}
} catch (e) {
if (!isAuto && mounted) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Failed to load rooms")));
}
if (!isAuto && mounted) ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Failed to load rooms")));
} finally {
if (!isAuto && mounted) setState(() => _isLoading = false);
}
@@ -124,16 +112,17 @@ class _ExploreViewState extends State<_ExploreView> {
return Scaffold(
appBar: AppBar(
title: Text("Hightube Live", style: TextStyle(fontWeight: FontWeight.bold)),
centerTitle: true,
title: Text("Explore", style: TextStyle(fontWeight: FontWeight.bold)),
actions: [
IconButton(icon: Icon(Icons.refresh), onPressed: () => _refreshRooms()),
IconButton(
icon: Icon(Icons.logout),
onPressed: () => context.read<AuthProvider>().logout(),
),
IconButton(icon: Icon(Icons.logout), onPressed: () => context.read<AuthProvider>().logout()),
],
),
floatingActionButton: FloatingActionButton.extended(
onPressed: widget.onGoLive,
label: Text("Go Live"),
icon: Icon(Icons.videocam),
),
body: RefreshIndicator(
onRefresh: _refreshRooms,
child: _isLoading && _activeRooms.isEmpty
@@ -178,15 +167,7 @@ class _ExploreViewState extends State<_ExploreView> {
child: InkWell(
onTap: () {
final rtmpUrl = "${settings.rtmpUrl}/${room['room_id']}";
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => PlayerPage(
title: room['title'],
rtmpUrl: rtmpUrl,
),
),
);
Navigator.push(context, MaterialPageRoute(builder: (_) => PlayerPage(title: room['title'], rtmpUrl: rtmpUrl)));
},
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,