Add Android quick streaming and logout confirmation

This commit is contained in:
2026-04-22 11:44:15 +08:00
parent b07f243c88
commit c5b7451fc6
8 changed files with 672 additions and 63 deletions

View File

@@ -140,6 +140,32 @@ class _ExploreViewState extends State<_ExploreView> {
}
}
Future<void> _confirmLogout() async {
final confirmed = await showDialog<bool>(
context: context,
builder: (context) {
return AlertDialog(
title: const Text('Confirm Logout'),
content: const Text('Are you sure you want to log out now?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('Cancel'),
),
FilledButton(
onPressed: () => Navigator.pop(context, true),
child: const Text('Logout'),
),
],
);
},
);
if (confirmed == true && mounted) {
await context.read<AuthProvider>().logout();
}
}
@override
Widget build(BuildContext context) {
final settings = context.watch<SettingsProvider>();
@@ -152,10 +178,7 @@ class _ExploreViewState extends State<_ExploreView> {
icon: Icon(Icons.refresh),
onPressed: () => _refreshRooms(),
),
IconButton(
icon: Icon(Icons.logout),
onPressed: () => context.read<AuthProvider>().logout(),
),
IconButton(icon: Icon(Icons.logout), onPressed: _confirmLogout),
],
),
floatingActionButton: FloatingActionButton.extended(

View File

@@ -5,10 +5,13 @@ import 'package:flutter/services.dart';
import '../providers/auth_provider.dart';
import '../providers/settings_provider.dart';
import '../services/api_service.dart';
import '../widgets/android_quick_stream_panel.dart';
class MyStreamPage extends StatefulWidget {
const MyStreamPage({super.key});
@override
_MyStreamPageState createState() => _MyStreamPageState();
State<MyStreamPage> createState() => _MyStreamPageState();
}
class _MyStreamPageState extends State<MyStreamPage> {
@@ -29,13 +32,23 @@ class _MyStreamPageState extends State<MyStreamPage> {
try {
final response = await api.getMyRoom();
if (!mounted) {
return;
}
if (response.statusCode == 200) {
setState(() => _roomInfo = jsonDecode(response.body));
}
} catch (e) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Failed to fetch room info")));
if (!mounted) {
return;
}
ScaffoldMessenger.of(
context,
).showSnackBar(SnackBar(content: Text("Failed to fetch room info")));
} finally {
setState(() => _isLoading = false);
if (mounted) {
setState(() => _isLoading = false);
}
}
}
@@ -48,59 +61,76 @@ class _MyStreamPageState extends State<MyStreamPage> {
body: _isLoading
? Center(child: CircularProgressIndicator())
: _roomInfo == null
? Center(child: Text("No room info found."))
: SingleChildScrollView(
padding: EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildInfoCard(
title: "Room Title",
value: _roomInfo!['title'],
icon: Icons.edit,
onTap: () {
// TODO: Implement title update API later
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Title editing coming soon!")));
},
),
SizedBox(height: 20),
_buildInfoCard(
title: "RTMP Server URL",
value: settings.rtmpUrl,
icon: Icons.copy,
onTap: () {
Clipboard.setData(ClipboardData(text: settings.rtmpUrl));
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Server URL copied to clipboard")));
},
),
SizedBox(height: 20),
_buildInfoCard(
title: "Stream Key (Keep Secret!)",
value: _roomInfo!['stream_key'],
icon: Icons.copy,
isSecret: true,
onTap: () {
Clipboard.setData(ClipboardData(text: _roomInfo!['stream_key']));
ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text("Stream Key copied to clipboard")));
},
),
SizedBox(height: 30),
Center(
child: Column(
children: [
Icon(Icons.info_outline, color: Colors.grey),
SizedBox(height: 8),
Text(
"Use OBS or other tools to stream to this address.",
textAlign: TextAlign.center,
style: TextStyle(color: Colors.grey, fontSize: 12),
),
],
),
),
],
? Center(child: Text("No room info found."))
: SingleChildScrollView(
padding: EdgeInsets.all(20),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
_buildInfoCard(
title: "Room Title",
value: _roomInfo!['title'],
icon: Icons.edit,
onTap: () {
// TODO: Implement title update API later
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text("Title editing coming soon!")),
);
},
),
),
SizedBox(height: 20),
_buildInfoCard(
title: "RTMP Server URL",
value: settings.rtmpUrl,
icon: Icons.copy,
onTap: () {
Clipboard.setData(ClipboardData(text: settings.rtmpUrl));
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("Server URL copied to clipboard"),
),
);
},
),
SizedBox(height: 20),
_buildInfoCard(
title: "Stream Key (Keep Secret!)",
value: _roomInfo!['stream_key'],
icon: Icons.copy,
isSecret: true,
onTap: () {
Clipboard.setData(
ClipboardData(text: _roomInfo!['stream_key']),
);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text("Stream Key copied to clipboard"),
),
);
},
),
SizedBox(height: 24),
AndroidQuickStreamPanel(
rtmpBaseUrl: settings.rtmpUrl,
streamKey: _roomInfo!['stream_key'],
),
SizedBox(height: 30),
Center(
child: Column(
children: [
Icon(Icons.info_outline, color: Colors.grey),
SizedBox(height: 8),
Text(
"Use OBS or other tools to stream to this address.",
textAlign: TextAlign.center,
style: TextStyle(color: Colors.grey, fontSize: 12),
),
],
),
),
],
),
),
);
}

View File

@@ -88,6 +88,32 @@ class _SettingsPageState extends State<SettingsPage> {
}
}
Future<void> _confirmLogout(AuthProvider auth) async {
final confirmed = await showDialog<bool>(
context: context,
builder: (context) {
return AlertDialog(
title: const Text("Confirm Logout"),
content: const Text("Are you sure you want to log out now?"),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text("Cancel"),
),
FilledButton(
onPressed: () => Navigator.pop(context, true),
child: const Text("Logout"),
),
],
);
},
);
if (confirmed == true && mounted) {
await auth.logout();
}
}
@override
Widget build(BuildContext context) {
final auth = context.watch<AuthProvider>();
@@ -278,7 +304,7 @@ class _SettingsPageState extends State<SettingsPage> {
SizedBox(
width: double.infinity,
child: FilledButton.tonalIcon(
onPressed: auth.logout,
onPressed: () => _confirmLogout(auth),
icon: const Icon(Icons.logout),
label: const Text("Logout"),
style: FilledButton.styleFrom(