v1.1.0: Major refactoring and improvements
This commit is contained in:
@@ -30,43 +30,44 @@ class IPTVProvider extends ChangeNotifier {
|
||||
List<XtreamEpisode> _seriesEpisodes = [];
|
||||
|
||||
String _selectedLiveCategory = '';
|
||||
String _selectedVodCategory = '';
|
||||
String _selectedCountry = '';
|
||||
String _selectedCategory = ''; // For special categories like "Fútbol Argentino"
|
||||
String _selectedCategory =
|
||||
''; // For special categories like "Fútbol Argentino"
|
||||
List<String> _countries = [];
|
||||
bool _isOrganizingCountries = false;
|
||||
XtreamSeries? _selectedSeries;
|
||||
|
||||
Map<String, String>? _categoryToCountryMapCache;
|
||||
List<XtreamStream>? _filteredLiveStreamsCache;
|
||||
String _filteredCountryCacheKey = '';
|
||||
String _filteredCategoryCacheKey = '';
|
||||
int _liveStreamsVersion = 0;
|
||||
int _filteredCacheVersion = -1;
|
||||
int _lastProgressUiUpdateMs = 0;
|
||||
|
||||
bool get isLoading => _isLoading;
|
||||
String? get error => _error;
|
||||
XtreamUserInfo? get userInfo => _userInfo;
|
||||
XtreamApiService get api => _api;
|
||||
int get loadedChannels => _loadedChannels;
|
||||
int get totalChannels => _totalChannels;
|
||||
double get loadingProgress => _totalChannels > 0 ? _loadedChannels / _totalChannels : 0.0;
|
||||
double get loadingProgress =>
|
||||
_totalChannels > 0 ? _loadedChannels / _totalChannels : 0.0;
|
||||
bool get isOrganizingCountries => _isOrganizingCountries;
|
||||
|
||||
|
||||
List<XtreamCategory> get liveCategories => _liveCategories;
|
||||
List<XtreamCategory> get vodCategories => _vodCategories;
|
||||
List<XtreamCategory> get seriesCategories => _seriesCategories;
|
||||
|
||||
|
||||
List<XtreamStream> get liveStreams => _liveStreams;
|
||||
List<XtreamStream> get vodStreams => _vodStreams;
|
||||
List<XtreamSeries> get seriesList => _seriesList;
|
||||
List<XtreamEpisode> get seriesEpisodes => _seriesEpisodes;
|
||||
|
||||
|
||||
String get selectedLiveCategory => _selectedLiveCategory;
|
||||
String get selectedCountry => _selectedCountry;
|
||||
String get selectedCategory => _selectedCategory;
|
||||
List<String> get countries {
|
||||
print('DEBUG: =========================================');
|
||||
print('DEBUG: countries getter called');
|
||||
print('DEBUG: _countries list length: ${_countries.length}');
|
||||
print('DEBUG: _countries list content: $_countries');
|
||||
print('DEBUG: _countries is empty: ${_countries.isEmpty}');
|
||||
print('DEBUG: =========================================');
|
||||
return _countries;
|
||||
}
|
||||
List<String> get countries => _countries;
|
||||
|
||||
/// Get display items for sidebar including special categories
|
||||
/// Returns a list of maps with 'name', 'type', and 'priority' for proper ordering
|
||||
@@ -76,27 +77,28 @@ class IPTVProvider extends ChangeNotifier {
|
||||
// Add all countries with their priority
|
||||
for (final country in _countries) {
|
||||
int priority = _getCountryPriority(country);
|
||||
items.add({
|
||||
'name': country,
|
||||
'type': 'country',
|
||||
'priority': priority,
|
||||
});
|
||||
items.add({'name': country, 'type': 'country', 'priority': priority});
|
||||
}
|
||||
|
||||
// Add special category: Fútbol Argentino (priority 2.5 - between Perú and other countries)
|
||||
// Only add if there are any Argentine football channels
|
||||
final hasArgentineFootball = _liveStreams.any((s) => _api.isArgentineFootballChannel(s.name));
|
||||
final hasArgentineFootball = _liveStreams.any(
|
||||
(s) => _api.isArgentineFootballChannel(s.name),
|
||||
);
|
||||
if (hasArgentineFootball) {
|
||||
items.add({
|
||||
'name': SpecialCategories.argentineFootball,
|
||||
'type': 'category',
|
||||
'priority': 2.5, // Between Perú (2) and other South American countries (3)
|
||||
'priority':
|
||||
2.5, // Between Perú (2) and other South American countries (3)
|
||||
});
|
||||
}
|
||||
|
||||
// Sort by priority, then alphabetically
|
||||
items.sort((a, b) {
|
||||
final priorityCompare = (a['priority'] as double).compareTo(b['priority'] as double);
|
||||
final priorityCompare = (a['priority'] as double).compareTo(
|
||||
b['priority'] as double,
|
||||
);
|
||||
if (priorityCompare != 0) return priorityCompare;
|
||||
return (a['name'] as String).compareTo(b['name'] as String);
|
||||
});
|
||||
@@ -126,8 +128,33 @@ class IPTVProvider extends ChangeNotifier {
|
||||
return 100; // Low priority for other countries
|
||||
}
|
||||
}
|
||||
|
||||
XtreamSeries? get selectedSeries => _selectedSeries;
|
||||
|
||||
void _invalidateLiveDerivedCaches() {
|
||||
_categoryToCountryMapCache = null;
|
||||
_filteredLiveStreamsCache = null;
|
||||
_filteredCacheVersion = -1;
|
||||
}
|
||||
|
||||
void _setLiveStreams(List<XtreamStream> streams, String categoryId) {
|
||||
_liveStreams = streams;
|
||||
_selectedLiveCategory = categoryId;
|
||||
_liveStreamsVersion++;
|
||||
_invalidateLiveDerivedCaches();
|
||||
}
|
||||
|
||||
void _notifyProgressUpdate() {
|
||||
final nowMs = DateTime.now().millisecondsSinceEpoch;
|
||||
final shouldUpdate =
|
||||
(nowMs - _lastProgressUiUpdateMs) >= 120 ||
|
||||
(_totalChannels > 0 && _loadedChannels >= _totalChannels);
|
||||
if (shouldUpdate) {
|
||||
_lastProgressUiUpdateMs = nowMs;
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> login(String server, String username, String password) async {
|
||||
_isLoading = true;
|
||||
_error = null;
|
||||
@@ -136,9 +163,9 @@ class IPTVProvider extends ChangeNotifier {
|
||||
try {
|
||||
_api.setCredentials(server, username, password);
|
||||
_userInfo = await _api.getUserInfo();
|
||||
|
||||
|
||||
// No automatic data loading on startup - data loads on demand only
|
||||
|
||||
|
||||
await _saveCredentials(server, username, password);
|
||||
} catch (e) {
|
||||
_error = e.toString();
|
||||
@@ -148,140 +175,144 @@ class IPTVProvider extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> _loadCategories() async {
|
||||
try {
|
||||
_liveCategories = await _api.getLiveCategories();
|
||||
_vodCategories = await _api.getVodCategories();
|
||||
_seriesCategories = await _api.getSeriesCategories();
|
||||
} catch (e) {
|
||||
_error = e.toString();
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> loadLiveStreams([String categoryId = '']) async {
|
||||
print('DEBUG: =========================================================');
|
||||
print('DEBUG: loadLiveStreams() START - API First Strategy');
|
||||
print('DEBUG: =========================================================');
|
||||
_isLoading = true;
|
||||
_isOrganizingCountries = false;
|
||||
_loadedChannels = 0;
|
||||
_totalChannels = 0;
|
||||
_countries = [];
|
||||
_lastProgressUiUpdateMs = 0;
|
||||
_invalidateLiveDerivedCaches();
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
// STEP 1: Load from API first (much faster than M3U)
|
||||
print('DEBUG: Attempting to load from API first...');
|
||||
|
||||
|
||||
try {
|
||||
_liveStreams = await _api.getLiveStreams(categoryId);
|
||||
_selectedLiveCategory = categoryId;
|
||||
_setLiveStreams(await _api.getLiveStreams(categoryId), categoryId);
|
||||
_totalChannels = _liveStreams.length;
|
||||
_loadedChannels = _liveStreams.length;
|
||||
print('DEBUG: API SUCCESS - Loaded ${_liveStreams.length} streams in < 5 seconds');
|
||||
|
||||
|
||||
if (_liveStreams.isEmpty) {
|
||||
throw Exception('API returned 0 streams');
|
||||
}
|
||||
} catch (apiError) {
|
||||
print('DEBUG: API failed: $apiError');
|
||||
print('DEBUG: Falling back to M3U...');
|
||||
|
||||
// Fallback to M3U only if API fails
|
||||
_liveStreams = await _api.getM3UStreams(
|
||||
onProgress: (loaded, total) {
|
||||
_loadedChannels = loaded;
|
||||
_totalChannels = total;
|
||||
print('DEBUG: M3U progress: $loaded of $total');
|
||||
notifyListeners();
|
||||
},
|
||||
_setLiveStreams(
|
||||
await _api.getM3UStreams(
|
||||
onProgress: (loaded, total) {
|
||||
_loadedChannels = loaded;
|
||||
_totalChannels = total;
|
||||
_notifyProgressUpdate();
|
||||
},
|
||||
),
|
||||
categoryId,
|
||||
);
|
||||
_selectedLiveCategory = categoryId;
|
||||
print('DEBUG: M3U FALLBACK - Loaded ${_liveStreams.length} streams');
|
||||
|
||||
|
||||
if (_liveStreams.isEmpty) {
|
||||
throw Exception('No channels available from API or M3U');
|
||||
}
|
||||
}
|
||||
|
||||
// STEP 2: Mark loading complete - channels ready to display
|
||||
print('DEBUG: === CHANNELS READY - Starting background country extraction ===');
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
|
||||
|
||||
// STEP 3: Extract countries in background (using optimized method)
|
||||
_extractCountriesInBackground();
|
||||
|
||||
} catch (e) {
|
||||
_error = e.toString();
|
||||
print('DEBUG: ERROR loading streams: $e');
|
||||
}
|
||||
|
||||
print('DEBUG: =========================================================');
|
||||
print('DEBUG: loadLiveStreams() END - Loaded ${_liveStreams.length} channels');
|
||||
print('DEBUG: =========================================================');
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
|
||||
/// Extract countries from streams in the background to avoid UI freezing
|
||||
void _extractCountriesInBackground() {
|
||||
if (_liveStreams.isEmpty) return;
|
||||
|
||||
|
||||
_isOrganizingCountries = true;
|
||||
notifyListeners();
|
||||
|
||||
print('DEBUG: Starting background country extraction from ${_liveStreams.length} streams...');
|
||||
|
||||
|
||||
// Use Future.microtask to schedule the extraction after the current frame
|
||||
Future.microtask(() {
|
||||
try {
|
||||
// Use optimized extraction (only sample 2000 channels for speed)
|
||||
_countries = _api.getCountriesOptimized(_liveStreams, maxChannelsToProcess: 2000);
|
||||
print('DEBUG: Countries extraction complete. Found ${_countries.length} countries');
|
||||
print('DEBUG: Countries list: $_countries');
|
||||
_countries = _api.getCountriesOptimized(
|
||||
_liveStreams,
|
||||
maxChannelsToProcess: 2000,
|
||||
);
|
||||
} catch (e) {
|
||||
print('DEBUG: Error extracting countries: $e');
|
||||
_countries = [];
|
||||
} finally {
|
||||
_isOrganizingCountries = false;
|
||||
print('DEBUG: === CHANNEL LOADING COMPLETE ===');
|
||||
notifyListeners();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Extract country names from live categories (format: "Country|XX")
|
||||
List<String> _extractCountriesFromCategories() {
|
||||
final countries = <String>{};
|
||||
for (final category in _liveCategories) {
|
||||
final countryName = category.name.split('|').first.trim();
|
||||
// Only add if it's a valid country (not a group title)
|
||||
if (countryName.isNotEmpty && !_isGroupTitle(countryName)) {
|
||||
countries.add(countryName);
|
||||
}
|
||||
}
|
||||
return countries.toList()..sort();
|
||||
}
|
||||
|
||||
/// Check if a string is a group title (not a country)
|
||||
bool _isGroupTitle(String name) {
|
||||
final normalized = name.toLowerCase().trim();
|
||||
final groupTitles = {
|
||||
'24/7', '24/7 ar', '24/7 in', '24/7-es', '24/7-de', '24/7-gr',
|
||||
'24/7-my', '24/7-pt', '24/7-ro', '24/7-tr', '24/7-latino',
|
||||
'vip', 'vip - pk', 'ppv', 'movies', 'cine', 'cine sd',
|
||||
'cine y serie', 'latino', 'general', 'music', 'religious',
|
||||
'bein', 'mbc', 'tod', 'osn', 'myhd', 'dstv', 'art',
|
||||
'icc-ca', 'icc-car', 'icc-dstv', 'icc-in', 'icc-nz',
|
||||
'icc-pk', 'icc-uk', 'xmas', 'sin', 'ezd', 'exyu', 'rot',
|
||||
'ar-kids', 'ar-sp', 'islam', 'bab', 'as', 'ei'
|
||||
'24/7',
|
||||
'24/7 ar',
|
||||
'24/7 in',
|
||||
'24/7-es',
|
||||
'24/7-de',
|
||||
'24/7-gr',
|
||||
'24/7-my',
|
||||
'24/7-pt',
|
||||
'24/7-ro',
|
||||
'24/7-tr',
|
||||
'24/7-latino',
|
||||
'vip',
|
||||
'vip - pk',
|
||||
'ppv',
|
||||
'movies',
|
||||
'cine',
|
||||
'cine sd',
|
||||
'cine y serie',
|
||||
'latino',
|
||||
'general',
|
||||
'music',
|
||||
'religious',
|
||||
'bein',
|
||||
'mbc',
|
||||
'tod',
|
||||
'osn',
|
||||
'myhd',
|
||||
'dstv',
|
||||
'art',
|
||||
'icc-ca',
|
||||
'icc-car',
|
||||
'icc-dstv',
|
||||
'icc-in',
|
||||
'icc-nz',
|
||||
'icc-pk',
|
||||
'icc-uk',
|
||||
'xmas',
|
||||
'sin',
|
||||
'ezd',
|
||||
'exyu',
|
||||
'rot',
|
||||
'ar-kids',
|
||||
'ar-sp',
|
||||
'islam',
|
||||
'bab',
|
||||
'as',
|
||||
'ei',
|
||||
};
|
||||
return groupTitles.contains(normalized);
|
||||
}
|
||||
|
||||
// Build a map from category ID to country name for API streams
|
||||
Map<String, String> _buildCategoryToCountryMap() {
|
||||
if (_categoryToCountryMapCache != null) {
|
||||
return _categoryToCountryMapCache!;
|
||||
}
|
||||
|
||||
final map = <String, String>{};
|
||||
for (final category in _liveCategories) {
|
||||
final countryName = category.name.split('|').first.trim();
|
||||
@@ -290,41 +321,68 @@ class IPTVProvider extends ChangeNotifier {
|
||||
map[category.id] = countryName;
|
||||
}
|
||||
}
|
||||
print('DEBUG: Built category map with ${map.length} entries');
|
||||
_categoryToCountryMapCache = map;
|
||||
return map;
|
||||
}
|
||||
|
||||
void filterByCountry(String country) {
|
||||
_selectedCountry = country.trim();
|
||||
final normalizedCountry = country.trim();
|
||||
if (_selectedCountry == normalizedCountry && _selectedCategory.isEmpty) {
|
||||
return;
|
||||
}
|
||||
_selectedCountry = normalizedCountry;
|
||||
_selectedCategory = ''; // Clear special category when country is selected
|
||||
print('DEBUG: Filter by country: "$_selectedCountry"');
|
||||
_filteredLiveStreamsCache = null;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
void filterByCategory(String category) {
|
||||
_selectedCategory = category.trim();
|
||||
final normalizedCategory = category.trim();
|
||||
if (_selectedCategory == normalizedCategory && _selectedCountry.isEmpty) {
|
||||
return;
|
||||
}
|
||||
_selectedCategory = normalizedCategory;
|
||||
_selectedCountry = ''; // Clear country when special category is selected
|
||||
print('DEBUG: Filter by category: "$_selectedCategory"');
|
||||
_filteredLiveStreamsCache = null;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
List<XtreamStream> get filteredLiveStreams {
|
||||
// If a special category is selected, filter by that
|
||||
if (_selectedCategory.isNotEmpty) {
|
||||
print('DEBUG: Filtering by special category: "$_selectedCategory"');
|
||||
return _api.filterByCategory(_liveStreams, _selectedCategory);
|
||||
final selectedCountry = _selectedCountry.trim();
|
||||
final selectedCategory = _selectedCategory.trim();
|
||||
if (_filteredLiveStreamsCache != null &&
|
||||
_filteredCacheVersion == _liveStreamsVersion &&
|
||||
_filteredCountryCacheKey == selectedCountry &&
|
||||
_filteredCategoryCacheKey == selectedCategory) {
|
||||
return _filteredLiveStreamsCache!;
|
||||
}
|
||||
|
||||
// Show all if empty or "Todos"/"All" selected
|
||||
final normalizedCountry = _selectedCountry.trim();
|
||||
if (normalizedCountry.isEmpty ||
|
||||
normalizedCountry.toLowerCase() == 'todos' ||
|
||||
normalizedCountry.toLowerCase() == 'all') {
|
||||
return _liveStreams;
|
||||
late final List<XtreamStream> result;
|
||||
|
||||
// If a special category is selected, filter by that
|
||||
if (selectedCategory.isNotEmpty) {
|
||||
result = _api.filterByCategory(_liveStreams, selectedCategory);
|
||||
} else if (selectedCountry.isEmpty ||
|
||||
selectedCountry.toLowerCase() == 'todos' ||
|
||||
selectedCountry.toLowerCase() == 'all') {
|
||||
result = _liveStreams;
|
||||
} else {
|
||||
// Build category map for API streams that don't have country in name
|
||||
final categoryMap = _buildCategoryToCountryMap();
|
||||
result = _api.filterByCountry(
|
||||
_liveStreams,
|
||||
selectedCountry,
|
||||
categoryToCountryMap: categoryMap,
|
||||
);
|
||||
}
|
||||
// Build category map for API streams that don't have country in name
|
||||
final categoryMap = _buildCategoryToCountryMap();
|
||||
return _api.filterByCountry(_liveStreams, _selectedCountry, categoryToCountryMap: categoryMap);
|
||||
|
||||
_filteredCountryCacheKey = selectedCountry;
|
||||
_filteredCategoryCacheKey = selectedCategory;
|
||||
_filteredCacheVersion = _liveStreamsVersion;
|
||||
_filteredLiveStreamsCache = identical(result, _liveStreams)
|
||||
? _liveStreams
|
||||
: List.unmodifiable(result);
|
||||
return _filteredLiveStreamsCache!;
|
||||
}
|
||||
|
||||
Future<void> loadVodStreams([String categoryId = '']) async {
|
||||
@@ -333,7 +391,6 @@ class IPTVProvider extends ChangeNotifier {
|
||||
|
||||
try {
|
||||
_vodStreams = await _api.getVodStreams(categoryId);
|
||||
_selectedVodCategory = categoryId;
|
||||
} catch (e) {
|
||||
_error = e.toString();
|
||||
}
|
||||
@@ -378,40 +435,36 @@ class IPTVProvider extends ChangeNotifier {
|
||||
_loadedChannels = 0;
|
||||
_totalChannels = 0;
|
||||
_countries = [];
|
||||
_lastProgressUiUpdateMs = 0;
|
||||
_invalidateLiveDerivedCaches();
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
// Try API first, then M3U fallback
|
||||
try {
|
||||
print('DEBUG: Attempting to reload from API...');
|
||||
_liveStreams = await _api.getLiveStreams('');
|
||||
_setLiveStreams(await _api.getLiveStreams(''), '');
|
||||
_totalChannels = _liveStreams.length;
|
||||
_loadedChannels = _liveStreams.length;
|
||||
print('DEBUG: API reload - Loaded ${_liveStreams.length} streams');
|
||||
} catch (apiError) {
|
||||
print('DEBUG: API reload failed: $apiError');
|
||||
print('DEBUG: Falling back to M3U...');
|
||||
|
||||
_liveStreams = await _api.getM3UStreams(
|
||||
onProgress: (loaded, total) {
|
||||
_loadedChannels = loaded;
|
||||
_totalChannels = total;
|
||||
print('DEBUG: M3U progress: $loaded of $total');
|
||||
notifyListeners();
|
||||
},
|
||||
_setLiveStreams(
|
||||
await _api.getM3UStreams(
|
||||
onProgress: (loaded, total) {
|
||||
_loadedChannels = loaded;
|
||||
_totalChannels = total;
|
||||
_notifyProgressUpdate();
|
||||
},
|
||||
),
|
||||
'',
|
||||
);
|
||||
print('DEBUG: M3U reload - Loaded ${_liveStreams.length} streams');
|
||||
}
|
||||
|
||||
|
||||
// Mark loading as complete - channels are ready to display
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
|
||||
|
||||
// Extract countries in background (optimized)
|
||||
_extractCountriesInBackground();
|
||||
|
||||
} catch (e) {
|
||||
print('DEBUG: Error reloading channels: $e');
|
||||
_error = 'Error al cargar canales: $e';
|
||||
_isLoading = false;
|
||||
_isOrganizingCountries = false;
|
||||
@@ -427,20 +480,20 @@ class IPTVProvider extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
|
||||
try {
|
||||
print('DEBUG: Starting M3U download and JSON conversion...');
|
||||
|
||||
// If we already have streams loaded, save those instead of downloading again
|
||||
if (_liveStreams.isNotEmpty) {
|
||||
print('DEBUG: Using already loaded ${_liveStreams.length} streams');
|
||||
|
||||
// Create M3U result from loaded streams
|
||||
final channels = _liveStreams.map((stream) => M3UChannel(
|
||||
name: stream.name,
|
||||
url: stream.url ?? '',
|
||||
groupTitle: stream.plot ?? 'Unknown',
|
||||
tvgLogo: stream.streamIcon,
|
||||
)).toList();
|
||||
|
||||
final channels = _liveStreams
|
||||
.map(
|
||||
(stream) => M3UChannel(
|
||||
name: stream.name,
|
||||
url: stream.url ?? '',
|
||||
groupTitle: stream.plot ?? 'Unknown',
|
||||
tvgLogo: stream.streamIcon,
|
||||
),
|
||||
)
|
||||
.toList();
|
||||
|
||||
final result = M3UDownloadResult(
|
||||
sourceUrl: '${_api.server}/get.php',
|
||||
downloadTime: DateTime.now(),
|
||||
@@ -448,40 +501,34 @@ class IPTVProvider extends ChangeNotifier {
|
||||
groupsCount: _groupChannelsByCountry(channels),
|
||||
channels: channels,
|
||||
);
|
||||
|
||||
|
||||
// Save as JSON file
|
||||
final filePath = await _api.saveM3UAsJson(result);
|
||||
print('DEBUG: Saved JSON to: $filePath');
|
||||
|
||||
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
|
||||
|
||||
return filePath;
|
||||
}
|
||||
|
||||
|
||||
// If no streams loaded, try to download
|
||||
print('DEBUG: No streams loaded, attempting download...');
|
||||
final result = await _api.downloadM3UAsJson();
|
||||
print('DEBUG: Downloaded ${result.totalChannels} channels from ${result.sourceUrl}');
|
||||
print('DEBUG: Groups found: ${result.groupsCount}');
|
||||
|
||||
|
||||
// Save as JSON file
|
||||
final filePath = await _api.saveM3UAsJson(result);
|
||||
print('DEBUG: Saved JSON to: $filePath');
|
||||
|
||||
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
|
||||
|
||||
return filePath;
|
||||
} catch (e) {
|
||||
print('DEBUG: Error downloading/saving M3U as JSON: $e');
|
||||
_error = 'Error al descargar playlist: $e';
|
||||
_isLoading = false;
|
||||
notifyListeners();
|
||||
throw Exception(_error);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Map<String, int> _groupChannelsByCountry(List<M3UChannel> channels) {
|
||||
final groups = <String, int>{};
|
||||
for (final channel in channels) {
|
||||
@@ -494,8 +541,6 @@ class IPTVProvider extends ChangeNotifier {
|
||||
/// Saves all loaded live channels as a text file for analysis
|
||||
Future<String> saveChannelsAsText() async {
|
||||
try {
|
||||
print('DEBUG: Saving ${_liveStreams.length} channels as text file');
|
||||
|
||||
// Build text content
|
||||
final buffer = StringBuffer();
|
||||
buffer.writeln('=== XSTREAM TV - LISTA DE CANALES ===');
|
||||
@@ -517,7 +562,8 @@ class IPTVProvider extends ChangeNotifier {
|
||||
country = countryFromName;
|
||||
}
|
||||
// If not found, try category mapping (API format)
|
||||
else if (stream.categoryId != null && categoryMap.containsKey(stream.categoryId)) {
|
||||
else if (stream.categoryId != null &&
|
||||
categoryMap.containsKey(stream.categoryId)) {
|
||||
country = categoryMap[stream.categoryId];
|
||||
}
|
||||
|
||||
@@ -549,13 +595,12 @@ class IPTVProvider extends ChangeNotifier {
|
||||
}
|
||||
|
||||
// Save to file
|
||||
final fileName = 'xstream_canales_${DateTime.now().millisecondsSinceEpoch}.txt';
|
||||
final fileName =
|
||||
'xstream_canales_${DateTime.now().millisecondsSinceEpoch}.txt';
|
||||
final filePath = await _api.saveTextFile(fileName, buffer.toString());
|
||||
|
||||
print('DEBUG: Saved channels list to: $filePath');
|
||||
return filePath;
|
||||
} catch (e) {
|
||||
print('DEBUG: Error saving channels as text: $e');
|
||||
throw Exception('Error al guardar lista: $e');
|
||||
}
|
||||
}
|
||||
@@ -565,7 +610,11 @@ class IPTVProvider extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> _saveCredentials(String server, String username, String password) async {
|
||||
Future<void> _saveCredentials(
|
||||
String server,
|
||||
String username,
|
||||
String password,
|
||||
) async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
await prefs.setString('server', server);
|
||||
await prefs.setString('username', username);
|
||||
@@ -599,8 +648,12 @@ class IPTVProvider extends ChangeNotifier {
|
||||
_vodStreams = [];
|
||||
_seriesList = [];
|
||||
_countries = [];
|
||||
_selectedLiveCategory = '';
|
||||
_selectedCountry = '';
|
||||
_selectedCategory = '';
|
||||
_isOrganizingCountries = false;
|
||||
_liveStreamsVersion = 0;
|
||||
_invalidateLiveDerivedCaches();
|
||||
notifyListeners();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user