Phase 3.5: Finalized UI polish, added Console, and fixed stale stream status bug
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user