feat: add mobile Xtream service, data providers, and UI widgets for Live TV and Movies.

This commit is contained in:
Michael committed 2026-03-27 15:15:44 +01:00
1 parent 527582e21b
commit a1b1363bc8
9 files changed
+875 -774

No files matched your search

@@ -396,29 +396,77 @@ class XtreamServiceMobile {
'password': _password,
'action': 'get_series',
},
options: Options(receiveTimeout: const Duration(seconds: 45)),
);
if (response.statusCode == 200 && response.data is List) {
return (response.data as List)
.map((e) => model.Series.fromJson(e))
.toList();
}
if (kDebugMode) {
print('⚠️ get_series returned unexpected status or type: ${response.statusCode}');
}
return [];
} catch (e) {
return [];
}
}
/// Search series
Future<List<model.Series>> searchSeries(String query) async {
final all = await getSeries();
return all
.where((s) => s.name.toLowerCase().contains(query.toLowerCase()))
.toList();
/// Get series paginated / filtered by category
Future<List<model.Series>> getSeriesPaginated({String? categoryId}) async {
try {
final queryParams = {
'username': _username,
'password': _password,
'action': 'get_series',
};
if (categoryId != null && categoryId.isNotEmpty) {
queryParams['category_id'] = categoryId;
}
final response = await _dio.get(
'$_baseUrl/player_api.php',
queryParameters: queryParams,
options: Options(receiveTimeout: const Duration(seconds: 30)),
);
if (response.statusCode == 200 && response.data is List) {
return (response.data as List)
.map((e) => model.Series.fromJson(e))
.toList();
}
return [];
} catch (e) {
if (kDebugMode) print('❌ Error loading series by category: $e');
return [];
}
}
/// Get series paginated
Future<List<model.Series>> getSeriesPaginated({int? categoryId}) async {
return getSeries(); // Simplified for now, can add filtering by categoryId later
/// Get series categories
Future<List<model.Category>> getSeriesCategories() async {
try {
final response = await _dio.get(
'$_baseUrl/player_api.php',
queryParameters: {
'username': _username,
'password': _password,
'action': 'get_series_categories',
},
options: Options(receiveTimeout: const Duration(seconds: 15)),
);
if (response.statusCode == 200 && response.data is List) {
return (response.data as List)
.map((e) => model.Category.fromJson(e))
.toList();
}
return [];
} catch (e) {
if (kDebugMode) print('❌ Error loading series categories: $e');
return [];
}
}
/// Get series info
@@ -482,6 +530,20 @@ class XtreamServiceMobile {
return '$_baseUrl/series/$_username/$_password/$streamId.$extension';
}
/// Search series (local filter on full list)
Future<List<model.Series>> searchSeries(String query) async {
try {
final allSeries = await getSeries();
if (query.isEmpty) return allSeries;
final normalizedQuery = query.toLowerCase();
return allSeries.where((s) => s.name.toLowerCase().contains(normalizedQuery)).toList();
} catch (e) {
if (kDebugMode) print('❌ Error searching series: $e');
return [];
}
}
/// Dispose service resources
void dispose() {
clearCache();