mirror of
https://github.com/R0m1k3/xtremflow.git
synced 2026-10-23 23:18:06 +02:00
fix(ui): prevent NaN division crash in TvChannelGrid and fix playlist silent failure
- tv_channel_grid.dart: guard screenWidth <= 0 / NaN / Infinite on first Flutter Web layout pass (previously caused NaN ~/ 225.03 crash cascade). Also use .clamp(1.0, infinity) on item width for belt-and-suspenders safety. Fix invalid 'padding.vertical as double?' cast in TvHorizontalList by using padding.resolve(TextDirection.ltr).top instead. - playlist_api_service.dart: remove silent catch-and-return-empty in getPlaylists(). Exceptions now propagate to the Riverpod FutureProvider so the UI shows the real error state (with Retry button) instead of a misleading 'No playlists available' when the API call actually failed (e.g., 401 Unauthorized or network error).
This commit is contained in:
1 parent
288bf9e6ee
commit
9755f51af5
2 files changed
+40
-28
No files matched your search
@@ -5,28 +5,29 @@ import '../models/playlist_config.dart';
|
|||||||
class PlaylistApiService {
|
class PlaylistApiService {
|
||||||
final ApiClient _api = ApiClient();
|
final ApiClient _api = ApiClient();
|
||||||
|
|
||||||
/// Get all playlists for current user
|
/// Get all playlists for current user.
|
||||||
|
///
|
||||||
|
/// Throws on API error so the Riverpod [FutureProvider] can expose the
|
||||||
|
/// [AsyncError] state — ensuring the UI shows a proper error with Retry,
|
||||||
|
/// instead of silently rendering an empty list.
|
||||||
Future<List<PlaylistConfig>> getPlaylists() async {
|
Future<List<PlaylistConfig>> getPlaylists() async {
|
||||||
try {
|
final response = await _api.get('/api/playlists');
|
||||||
final response = await _api.get('/api/playlists');
|
final data = response.data as Map<String, dynamic>;
|
||||||
final data = response.data as Map<String, dynamic>;
|
final playlistsData = data['playlists'] as List<dynamic>;
|
||||||
final playlistsData = data['playlists'] as List<dynamic>;
|
|
||||||
|
|
||||||
return playlistsData.map((p) {
|
return playlistsData.map((p) {
|
||||||
final playlist = p as Map<String, dynamic>;
|
final playlist = p as Map<String, dynamic>;
|
||||||
return PlaylistConfig(
|
return PlaylistConfig(
|
||||||
id: playlist['id'] as String,
|
id: playlist['id'] as String,
|
||||||
name: playlist['name'] as String,
|
name: playlist['name'] as String,
|
||||||
dns: playlist['serverUrl'] as String? ?? playlist['dns'] as String,
|
dns: playlist['serverUrl'] as String? ?? playlist['dns'] as String,
|
||||||
username: playlist['username'] as String,
|
username: playlist['username'] as String,
|
||||||
password: playlist['password'] as String,
|
password: playlist['password'] as String,
|
||||||
createdAt: DateTime.tryParse(playlist['createdAt'] as String? ?? '') ?? DateTime.now(),
|
createdAt:
|
||||||
);
|
DateTime.tryParse(playlist['createdAt'] as String? ?? '') ??
|
||||||
}).toList();
|
DateTime.now(),
|
||||||
} catch (e) {
|
);
|
||||||
print('Error fetching playlists: $e');
|
}).toList();
|
||||||
return [];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a new playlist
|
/// Create a new playlist
|
||||||
|
|||||||
@@ -30,8 +30,14 @@ class TvChannelGrid extends StatelessWidget {
|
|||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final screenWidth = MediaQuery.of(context).size.width;
|
final screenWidth = MediaQuery.of(context).size.width;
|
||||||
|
|
||||||
|
// Guard: during first Flutter Web layout pass, size may be 0 or NaN.
|
||||||
|
// LayoutBuilder will re-trigger build once the real size is known.
|
||||||
|
if (screenWidth <= 0 || screenWidth.isNaN || screenWidth.isInfinite) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
|
||||||
// Responsive column count
|
// Responsive column count
|
||||||
int columnCount;
|
final int columnCount;
|
||||||
if (screenWidth > 1920) {
|
if (screenWidth > 1920) {
|
||||||
columnCount = 6;
|
columnCount = 6;
|
||||||
} else if (screenWidth > 1600) {
|
} else if (screenWidth > 1600) {
|
||||||
@@ -44,9 +50,14 @@ class TvChannelGrid extends StatelessWidget {
|
|||||||
columnCount = 2;
|
columnCount = 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate horizontal padding safely
|
// Calculate horizontal padding safely using resolved EdgeInsets.
|
||||||
final paddingValue = padding.resolve(TextDirection.ltr);
|
final resolvedPadding = padding.resolve(TextDirection.ltr);
|
||||||
final horizontalPadding = paddingValue.left + paddingValue.right;
|
final horizontalPadding = resolvedPadding.left + resolvedPadding.right;
|
||||||
|
|
||||||
|
// Compute item width, clamped to avoid negative/NaN values.
|
||||||
|
final totalSpacing = horizontalSpacing * (columnCount - 1);
|
||||||
|
final availableWidth = screenWidth - horizontalPadding - totalSpacing;
|
||||||
|
final itemWidth = (availableWidth / columnCount).clamp(1.0, double.infinity);
|
||||||
|
|
||||||
return SingleChildScrollView(
|
return SingleChildScrollView(
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
@@ -58,9 +69,7 @@ class TvChannelGrid extends StatelessWidget {
|
|||||||
children: [
|
children: [
|
||||||
for (int i = 0; i < children.length; i++)
|
for (int i = 0; i < children.length; i++)
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: (screenWidth -
|
width: itemWidth,
|
||||||
horizontalPadding -
|
|
||||||
(horizontalSpacing * (columnCount - 1))) / columnCount,
|
|
||||||
child: children[i],
|
child: children[i],
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -117,7 +126,9 @@ class TvHorizontalList extends StatelessWidget {
|
|||||||
scrollDirection: Axis.horizontal,
|
scrollDirection: Axis.horizontal,
|
||||||
padding: EdgeInsets.symmetric(
|
padding: EdgeInsets.symmetric(
|
||||||
horizontal: 32,
|
horizontal: 32,
|
||||||
vertical: padding.vertical as double? ?? 0,
|
// Resolve EdgeInsetsGeometry to a concrete EdgeInsets before
|
||||||
|
// accessing vertical — casting EdgeInsetsGeometry to double throws.
|
||||||
|
vertical: padding.resolve(TextDirection.ltr).top,
|
||||||
),
|
),
|
||||||
itemCount: children.length,
|
itemCount: children.length,
|
||||||
separatorBuilder: (_, __) => SizedBox(width: spacing),
|
separatorBuilder: (_, __) => SizedBox(width: spacing),
|
||||||
|
|||||||
Reference in new issue
Block a user