Add live room preview thumbnails
This commit is contained in:
@@ -10,8 +10,10 @@ import 'player_page.dart';
|
||||
import 'my_stream_page.dart';
|
||||
|
||||
class HomePage extends StatefulWidget {
|
||||
const HomePage({super.key});
|
||||
|
||||
@override
|
||||
_HomePageState createState() => _HomePageState();
|
||||
State<HomePage> createState() => _HomePageState();
|
||||
}
|
||||
|
||||
class _HomePageState extends State<HomePage> {
|
||||
@@ -21,7 +23,7 @@ class _HomePageState extends State<HomePage> {
|
||||
Widget build(BuildContext context) {
|
||||
bool isWide = MediaQuery.of(context).size.width > 600;
|
||||
|
||||
final List<Widget> _pages = [
|
||||
final List<Widget> pages = [
|
||||
_ExploreView(onGoLive: () => setState(() => _selectedIndex = 1)),
|
||||
MyStreamPage(),
|
||||
SettingsPage(),
|
||||
@@ -51,7 +53,7 @@ class _HomePageState extends State<HomePage> {
|
||||
),
|
||||
],
|
||||
),
|
||||
Expanded(child: _pages[_selectedIndex]),
|
||||
Expanded(child: pages[_selectedIndex]),
|
||||
],
|
||||
),
|
||||
bottomNavigationBar: !isWide
|
||||
@@ -91,6 +93,8 @@ class _ExploreViewState extends State<_ExploreView> {
|
||||
List<dynamic> _activeRooms = [];
|
||||
bool _isLoading = false;
|
||||
Timer? _refreshTimer;
|
||||
String _thumbnailCacheBuster = DateTime.now().millisecondsSinceEpoch
|
||||
.toString();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
@@ -117,13 +121,20 @@ 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'] ?? [];
|
||||
_thumbnailCacheBuster = DateTime.now().millisecondsSinceEpoch
|
||||
.toString();
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
if (!isAuto && mounted)
|
||||
if (!isAuto && mounted) {
|
||||
ScaffoldMessenger.of(
|
||||
context,
|
||||
).showSnackBar(SnackBar(content: Text("Failed to load rooms")));
|
||||
}
|
||||
} finally {
|
||||
if (!isAuto && mounted) setState(() => _isLoading = false);
|
||||
}
|
||||
@@ -198,20 +209,21 @@ class _ExploreViewState extends State<_ExploreView> {
|
||||
}
|
||||
|
||||
Widget _buildRoomCard(dynamic room, SettingsProvider settings) {
|
||||
final roomId = room['room_id'].toString();
|
||||
return Card(
|
||||
elevation: 4,
|
||||
clipBehavior: Clip.antiAlias,
|
||||
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
final playbackUrl = settings.playbackUrl(room['room_id'].toString());
|
||||
final playbackUrl = settings.playbackUrl(roomId);
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (_) => PlayerPage(
|
||||
title: room['title'],
|
||||
playbackUrl: playbackUrl,
|
||||
roomId: room['room_id'].toString(),
|
||||
roomId: roomId,
|
||||
),
|
||||
),
|
||||
);
|
||||
@@ -224,16 +236,37 @@ class _ExploreViewState extends State<_ExploreView> {
|
||||
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,
|
||||
),
|
||||
),
|
||||
),
|
||||
settings.livePreviewThumbnailsEnabled
|
||||
? Image.network(
|
||||
settings.thumbnailUrl(
|
||||
roomId,
|
||||
cacheBuster: _thumbnailCacheBuster,
|
||||
),
|
||||
fit: BoxFit.cover,
|
||||
errorBuilder: (context, error, stackTrace) =>
|
||||
_buildRoomPreviewFallback(),
|
||||
loadingBuilder: (context, child, loadingProgress) {
|
||||
if (loadingProgress == null) {
|
||||
return child;
|
||||
}
|
||||
return Stack(
|
||||
fit: StackFit.expand,
|
||||
children: [
|
||||
_buildRoomPreviewFallback(),
|
||||
const Center(
|
||||
child: SizedBox(
|
||||
width: 22,
|
||||
height: 22,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
)
|
||||
: _buildRoomPreviewFallback(),
|
||||
Positioned(
|
||||
top: 8,
|
||||
left: 8,
|
||||
@@ -306,4 +339,17 @@ class _ExploreViewState extends State<_ExploreView> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildRoomPreviewFallback() {
|
||||
return Container(
|
||||
color: Theme.of(context).colorScheme.primaryContainer,
|
||||
child: Center(
|
||||
child: Icon(
|
||||
Icons.live_tv,
|
||||
size: 50,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ class SettingsPage extends StatefulWidget {
|
||||
const SettingsPage({super.key});
|
||||
|
||||
@override
|
||||
_SettingsPageState createState() => _SettingsPageState();
|
||||
State<SettingsPage> createState() => _SettingsPageState();
|
||||
}
|
||||
|
||||
class _SettingsPageState extends State<SettingsPage> {
|
||||
@@ -221,6 +221,18 @@ class _SettingsPageState extends State<SettingsPage> {
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
const SizedBox(height: 32),
|
||||
_buildSectionTitle("Explore"),
|
||||
const SizedBox(height: 8),
|
||||
SwitchListTile.adaptive(
|
||||
contentPadding: EdgeInsets.zero,
|
||||
title: const Text("Live Preview Thumbnails"),
|
||||
subtitle: const Text(
|
||||
"Show cached snapshot covers for live rooms when available.",
|
||||
),
|
||||
value: settings.livePreviewThumbnailsEnabled,
|
||||
onChanged: settings.setLivePreviewThumbnailsEnabled,
|
||||
),
|
||||
if (isAuthenticated) ...[
|
||||
const SizedBox(height: 32),
|
||||
_buildSectionTitle("Security"),
|
||||
|
||||
Reference in New Issue
Block a user