feat: Add generated Hive TypeAdapters for AppUser and PlaylistConfig models, and new build/doctor log files.

This commit is contained in:
Michael committed 2026-03-27 11:08:04 +01:00
1 parent 780f6c1842
commit ef97c6dc90
81 files changed
+3233 -2428

No files matched your search

@@ -2,20 +2,21 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:cached_network_image/cached_network_image.dart';
import '../../../../core/utils/image_cache_config.dart';
import 'package:xtremobile/core/utils/image_cache_config.dart';
import 'dart:io';
import '../../../providers/mobile_xtream_providers.dart';
import '../../../providers/mobile_settings_providers.dart';
import '../screens/native_player_screen.dart';
import '../../../../core/models/iptv_models.dart';
import '../../../../core/models/playlist_config.dart';
import '../../../../core/theme/app_colors.dart';
import '../../../../core/api/dns_resolver.dart';
import '../../../theme/mobile_theme.dart';
import 'package:xtremflow/mobile/widgets/tv_focusable.dart';
import 'package:xtremflow/features/iptv/screens/lite_player_screen.dart';
import 'dart:async';
import 'package:xtremobile/mobile/providers/mobile_xtream_providers.dart';
import 'package:xtremobile/mobile/providers/mobile_settings_providers.dart';
import 'package:xtremobile/features/iptv/screens/native_player_screen.dart';
import 'package:xtremobile/core/models/iptv_models.dart';
import 'package:xtremobile/core/models/playlist_config.dart';
import 'package:xtremobile/core/theme/app_colors.dart';
import 'package:xtremobile/core/api/dns_resolver.dart';
import 'package:xtremobile/mobile/theme/mobile_theme.dart';
import 'package:xtremobile/mobile/widgets/tv_focusable.dart';
import 'package:xtremobile/features/iptv/screens/lite_player_screen.dart';
import 'package:flutter/services.dart';
import '../../../../core/services/ip_service.dart';
import 'package:xtremobile/core/services/ip_service.dart';
class MobileLiveTVTab extends ConsumerStatefulWidget {
final PlaylistConfig playlist;
@@ -157,7 +158,7 @@ class _MobileLiveTVTabState extends ConsumerState<MobileLiveTVTab>
),
data: (groupedChannels) {
// Prepare categories
var categories = groupedChannels.keys.toList();
var categories = groupedChannels.map((c) => c.categoryName).toSet().toList();
if (settings.liveTvKeywords.isNotEmpty) {
categories = categories
.where((cat) => settings.matchesLiveTvFilter(cat))
@@ -189,18 +190,16 @@ class _MobileLiveTVTabState extends ConsumerState<MobileLiveTVTab>
if (!showGrid) {
if (_searchQuery.isNotEmpty) {
displayedChannels = groupedChannels.values
.expand((l) => l)
displayedChannels = groupedChannels
.where((c) => c.name.toLowerCase().contains(_searchQuery))
.toList();
} else if (_showFavoritesOnly) {
displayedChannels = groupedChannels.values
.expand((l) => l)
displayedChannels = groupedChannels
.where((c) => favorites.contains(c.streamId))
.toList();
} else if (uiState.selectedCategory != null) {
displayedChannels =
groupedChannels[uiState.selectedCategory] ?? [];
groupedChannels.where((c) => c.categoryName == uiState.selectedCategory).toList();
}
}
@@ -569,7 +568,7 @@ class _MobileChannelCardState extends ConsumerState<_MobileChannelCard> {
final service =
await ref.read(mobileXtreamServiceProvider(widget.playlist).future);
final epg = await service.getShortEPG(widget.channel.streamId);
if (mounted && epg.nowPlaying != null && epg.nowPlaying!.isNotEmpty) {
if (mounted && epg.nowPlaying.isNotEmpty) {
setState(() {
_epgTitle = epg.nowPlaying;
});
@@ -2,14 +2,14 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:xtremflow/mobile/widgets/tv_focusable.dart';
import 'package:xtremflow/mobile/widgets/mobile_poster_card.dart';
import '../../../../core/models/playlist_config.dart';
import '../../../../core/theme/app_colors.dart';
import '../../../providers/mobile_settings_providers.dart';
import '../../../providers/mobile_xtream_providers.dart';
import '../screens/native_player_screen.dart';
import 'package:xtremobile/mobile/widgets/tv_focusable.dart';
import 'package:xtremobile/mobile/widgets/mobile_poster_card.dart';
import 'package:xtremobile/core/models/playlist_config.dart';
import 'package:xtremobile/core/theme/app_colors.dart';
import 'package:xtremobile/mobile/providers/mobile_settings_providers.dart';
import 'package:xtremobile/mobile/providers/mobile_xtream_providers.dart';
import 'package:xtremobile/core/models/iptv_models.dart' as model;
import 'package:xtremobile/features/iptv/screens/native_player_screen.dart';
class MobileMoviesTab extends ConsumerStatefulWidget {
final PlaylistConfig playlist;
@@ -27,12 +27,12 @@ class _MobileMoviesTabState extends ConsumerState<MobileMoviesTab>
// Data
List<MapEntry<String, String>> _categories = [];
List<Movie> _categoryMovies = [];
List<model.VodItem> _categoryMovies = [];
String? _selectedCategoryId;
String? _selectedCategoryName;
// Search
List<Movie>? _searchResults;
List<model.VodItem>? _searchResults;
String _searchQuery = '';
bool _isSearchEditing = false;
bool _isSearching = false;
@@ -85,7 +85,8 @@ class _MobileMoviesTabState extends ConsumerState<MobileMoviesTab>
try {
final service =
await ref.read(mobileXtreamServiceProvider(widget.playlist).future);
final results = await service.searchMovies(query);
final movies = await service.getMovies();
final results = movies.where((m) => m.name.toLowerCase().contains(query.toLowerCase())).toList();
if (mounted && _searchQuery == query) {
setState(() {
@@ -105,11 +106,11 @@ class _MobileMoviesTabState extends ConsumerState<MobileMoviesTab>
try {
final service =
await ref.read(mobileXtreamServiceProvider(widget.playlist).future);
final categoriesMap = await service.getVodCategories();
final categories = await service.getVodCategories();
if (mounted) {
setState(() {
_categories = categoriesMap.entries.toList()
_categories = categories.map((c) => MapEntry(c.categoryId, c.categoryName)).toList()
..sort((a, b) => a.value.compareTo(b.value));
_isLoading = false;
});
@@ -197,7 +198,7 @@ class _MobileMoviesTabState extends ConsumerState<MobileMoviesTab>
return originalUrl;
}
void _playMovie(BuildContext context, Movie movie) {
void _playMovie(BuildContext context, model.VodItem movie) {
Navigator.push(
context,
MaterialPageRoute(
@@ -205,7 +206,7 @@ class _MobileMoviesTabState extends ConsumerState<MobileMoviesTab>
streamId: movie.streamId,
title: movie.name,
playlist: widget.playlist,
streamType: StreamType.vod,
streamType: model.StreamType.vod,
containerExtension: movie.containerExtension,
),
),
@@ -2,16 +2,15 @@ import 'dart:async';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:xtremflow/mobile/widgets/tv_focusable.dart';
import 'package:xtremflow/mobile/widgets/mobile_poster_card.dart';
import '../../../../core/models/playlist_config.dart';
import '../../../../core/theme/app_colors.dart';
import '../../../../core/widgets/components/hero_carousel.dart';
import '../../../../core/widgets/components/ui_components.dart';
import '../../../providers/mobile_settings_providers.dart';
import '../../../providers/mobile_xtream_providers.dart';
import '../../../../features/iptv/services/xtream_service_mobile.dart';
import '../screens/mobile_series_detail_screen.dart';
import 'package:xtremobile/mobile/widgets/tv_focusable.dart';
import 'package:xtremobile/mobile/widgets/mobile_poster_card.dart';
import 'package:xtremobile/core/models/playlist_config.dart';
import 'package:xtremobile/core/theme/app_colors.dart';
import 'package:xtremobile/core/widgets/components/hero_carousel.dart';
import 'package:xtremobile/mobile/providers/mobile_settings_providers.dart';
import 'package:xtremobile/mobile/providers/mobile_xtream_providers.dart';
import 'package:xtremobile/core/models/iptv_models.dart' as model;
import 'package:xtremobile/features/iptv/screens/mobile_series_detail_screen.dart';
class MobileSeriesTab extends ConsumerStatefulWidget {
final PlaylistConfig playlist;
@@ -26,15 +25,13 @@ class _MobileSeriesTabState extends ConsumerState<MobileSeriesTab>
final ScrollController _scrollController = ScrollController();
final TextEditingController _searchController = TextEditingController();
final FocusNode _searchFocusNode = FocusNode();
final List<Series> _series = [];
List<Series>? _searchResults;
final List<model.Series> _series = [];
List<model.Series>? _searchResults;
String _searchQuery = '';
bool _isSearchEditing = false;
bool _isLoading = false;
bool _isSearching = false;
bool _hasMore = true;
int _currentOffset = 0;
static const int _pageSize = 50;
Timer? _searchDebounce;
@override
@@ -87,11 +84,12 @@ class _MobileSeriesTabState extends ConsumerState<MobileSeriesTab>
try {
final service =
await ref.read(mobileXtreamServiceProvider(widget.playlist).future);
final results = await service.searchSeries(query);
final results = await service.getSeries(); // Fallback to full list if searchSeries not available
final filteredResults = results.where((s) => s.name.toLowerCase().contains(query.toLowerCase())).toList();
if (mounted && _searchQuery == query) {
setState(() {
_searchResults = results;
_searchResults = filteredResults;
_isSearching = false;
});
}
@@ -112,17 +110,13 @@ class _MobileSeriesTabState extends ConsumerState<MobileSeriesTab>
// Add timeout to prevent infinite loading state
final newSeries = await service
.getSeriesPaginated(
offset: _currentOffset,
limit: _pageSize,
)
.getSeriesPaginated()
.timeout(const Duration(seconds: 15));
if (mounted) {
setState(() {
_series.addAll(newSeries);
_currentOffset += _pageSize;
_hasMore = newSeries.length == _pageSize;
_hasMore = false; // Disable pagination for now as it's not supported by simple getSeries
_isLoading = false;
});
}
@@ -147,7 +141,6 @@ class _MobileSeriesTabState extends ConsumerState<MobileSeriesTab>
setState(() {
_series.clear();
_currentOffset = 0;
_hasMore = true;
_searchResults = null;
_searchQuery = '';
@@ -172,7 +165,7 @@ class _MobileSeriesTabState extends ConsumerState<MobileSeriesTab>
return originalUrl;
}
void _openSeries(Series series) {
void _openSeries(model.Series series) {
Navigator.push(
context,
MaterialPageRoute(
@@ -192,7 +185,7 @@ class _MobileSeriesTabState extends ConsumerState<MobileSeriesTab>
super.build(context); // Required for AutomaticKeepAliveClientMixin
final settings = ref.watch(mobileSettingsProvider);
List<Series> displaySeries;
List<model.Series> displaySeries;
if (_searchQuery.isNotEmpty && _searchResults != null) {
displaySeries = _searchResults!;
} else {
@@ -207,13 +200,16 @@ class _MobileSeriesTabState extends ConsumerState<MobileSeriesTab>
final heroItems = displaySeries
.take(3)
.map(
(s) => HeroItem(
id: s.seriesId.toString(),
title: s.name,
imageUrl: _getImageUrl(s.cover),
subtitle: s.rating != null ? '${_formatRating(s.rating)} ★' : null,
onMoreInfo: () => _openSeries(s),
),
(s) {
final serie = s; // model.Series handle is enough
return HeroItem(
id: serie.streamId,
title: serie.name,
imageUrl: _getImageUrl(serie.cover),
subtitle: serie.rating.isNotEmpty ? '${_formatRating(serie.rating)} ★' : null,
onMoreInfo: () => _openSeries(serie),
);
},
)
.toList();
@@ -290,7 +286,7 @@ class _MobileSeriesTabState extends ConsumerState<MobileSeriesTab>
onTap: (item) {
try {
final series = _series.firstWhere(
(s) => s.seriesId.toString() == item.id,
(s) => s.streamId.toString() == item.id,
);
_openSeries(series);
} catch (e) {
@@ -2,13 +2,11 @@ import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:google_fonts/google_fonts.dart';
import 'package:path_provider/path_provider.dart';
// import 'package:dio_cache_interceptor_hive_store/dio_cache_interceptor_hive_store.dart';
import '../../../providers/mobile_settings_providers.dart';
import '../../../widgets/tv_focusable.dart';
import '../../../../core/theme/app_colors.dart';
import '../screens/mobile_playlist_selection_screen.dart';
import 'package:xtremobile/mobile/providers/mobile_settings_providers.dart';
import 'package:xtremobile/mobile/widgets/tv_focusable.dart';
import 'package:xtremobile/core/theme/app_colors.dart';
import 'package:xtremobile/features/iptv/screens/mobile_playlist_selection_screen.dart';
/// Simplified mobile settings tab - optimized for TV remote control
class MobileSettingsTab extends ConsumerStatefulWidget {
@@ -80,7 +78,7 @@ class _MobileSettingsTabState extends ConsumerState<MobileSettingsTab> {
height: 48,
// No decoration for free-floating logo
child: Image.asset(
'assets/images/logo_xtremflow.png',
'assets/images/logo_xtremobile.png',
fit: BoxFit.contain,
errorBuilder: (_, __, ___) => const Icon(
Icons.play_circle_filled,
@@ -94,7 +92,7 @@ class _MobileSettingsTabState extends ConsumerState<MobileSettingsTab> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'XtremFlow Mobile',
'Xtremobile Mobile',
style: GoogleFonts.inter(
fontSize: 18,
fontWeight: FontWeight.bold,