1 Commits
main ... v1.1.0

Author SHA1 Message Date
7911af217f v1.1.0: Major refactoring and improvements 2026-02-25 23:57:26 -03:00
3 changed files with 68 additions and 127 deletions

View File

@@ -3,7 +3,6 @@ import 'package:provider/provider.dart';
import 'package:intl/intl.dart'; import 'package:intl/intl.dart';
import '../services/iptv_provider.dart'; import '../services/iptv_provider.dart';
import '../models/xtream_models.dart'; import '../models/xtream_models.dart';
import '../utils/channel_name_formatter.dart';
import 'player_screen.dart'; import 'player_screen.dart';
import '../widgets/simple_countries_sidebar.dart'; import '../widgets/simple_countries_sidebar.dart';
@@ -551,10 +550,7 @@ class _ContentListScreenState extends State<ContentListScreen> {
void _loadContent() { void _loadContent() {
final provider = context.read<IPTVProvider>(); final provider = context.read<IPTVProvider>();
if (widget.type == ContentType.live) { if (widget.type == ContentType.live) {
// Keep live list in memory while app is running. provider.loadLiveStreams(_selectedCountry ?? '');
if (provider.liveStreams.isEmpty) {
provider.loadLiveStreams(_selectedCountry ?? '');
}
} else if (widget.type == ContentType.movies) { } else if (widget.type == ContentType.movies) {
provider.loadVodStreams(); provider.loadVodStreams();
} else { } else {
@@ -615,7 +611,6 @@ class _ContentListScreenState extends State<ContentListScreen> {
: (_isMediumScreen ? 300.0 : 250.0); : (_isMediumScreen ? 300.0 : 250.0);
final searchHeight = _isLargeScreen ? 56.0 : 44.0; final searchHeight = _isLargeScreen ? 56.0 : 44.0;
final iconSize = _isLargeScreen ? 32.0 : 24.0; final iconSize = _isLargeScreen ? 32.0 : 24.0;
final showSearch = widget.type != ContentType.live;
return Container( return Container(
padding: EdgeInsets.all(_headerPadding), padding: EdgeInsets.all(_headerPadding),
child: Row( child: Row(
@@ -636,61 +631,60 @@ class _ContentListScreenState extends State<ContentListScreen> {
), ),
), ),
const Spacer(), const Spacer(),
if (showSearch) SizedBox(
SizedBox( width: searchWidth,
width: searchWidth, height: searchHeight,
height: searchHeight, child: TextField(
child: TextField( controller: _searchController,
controller: _searchController, style: TextStyle(
style: TextStyle( color: Colors.white,
color: Colors.white, fontSize: _isLargeScreen ? 18 : 14,
),
decoration: InputDecoration(
hintText: 'Buscar...',
hintStyle: TextStyle(
color: Colors.grey,
fontSize: _isLargeScreen ? 18 : 14, fontSize: _isLargeScreen ? 18 : 14,
), ),
decoration: InputDecoration( prefixIcon: Icon(
hintText: 'Buscar...', Icons.search,
hintStyle: TextStyle( color: Colors.grey,
color: Colors.grey, size: _isLargeScreen ? 28 : 20,
fontSize: _isLargeScreen ? 18 : 14, ),
), suffixIcon: _searchQuery.isNotEmpty
prefixIcon: Icon( ? IconButton(
Icons.search, icon: Icon(
color: Colors.grey, Icons.clear,
size: _isLargeScreen ? 28 : 20, color: Colors.grey,
), size: _isLargeScreen ? 28 : 20,
suffixIcon: _searchQuery.isNotEmpty ),
? IconButton( onPressed: () {
icon: Icon( _searchController.clear();
Icons.clear, setState(() {
color: Colors.grey, _searchQuery = '';
size: _isLargeScreen ? 28 : 20, _lastSearchResults = null;
), });
onPressed: () { },
_searchController.clear(); )
setState(() { : null,
_searchQuery = ''; filled: true,
_lastSearchResults = null; fillColor: Colors.grey[900],
}); border: OutlineInputBorder(
}, borderRadius: BorderRadius.circular(8),
) borderSide: BorderSide.none,
: null, ),
filled: true, contentPadding: EdgeInsets.symmetric(
fillColor: Colors.grey[900], horizontal: 16,
border: OutlineInputBorder( vertical: _isLargeScreen ? 16 : 12,
borderRadius: BorderRadius.circular(8),
borderSide: BorderSide.none,
),
contentPadding: EdgeInsets.symmetric(
horizontal: 16,
vertical: _isLargeScreen ? 16 : 12,
),
), ),
onChanged: (value) {
setState(() {
_searchQuery = value;
});
},
), ),
onChanged: (value) {
setState(() {
_searchQuery = value;
});
},
), ),
),
], ],
), ),
); );
@@ -986,25 +980,18 @@ class _ChannelCardState extends State<_ChannelCard> {
), ),
), ),
Positioned( Positioned(
bottom: padding,
left: padding, left: padding,
right: padding, right: padding,
bottom: padding, child: Text(
child: SizedBox( widget.stream.name,
height: textSize * 2.8, style: TextStyle(
child: Center( color: Colors.white,
child: Text( fontSize: textSize,
ChannelNameFormatter.forDisplay(widget.stream.name), fontWeight: FontWeight.w500,
style: TextStyle(
color: Colors.white,
fontSize: textSize,
fontWeight: FontWeight.w600,
height: 1.15,
),
maxLines: 2,
textAlign: TextAlign.center,
overflow: TextOverflow.ellipsis,
),
), ),
maxLines: 2,
overflow: TextOverflow.ellipsis,
), ),
), ),
if (widget.stream.rating != null) if (widget.stream.rating != null)

View File

@@ -102,6 +102,15 @@ class _PlayerScreenState extends State<PlayerScreen> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Scaffold( return Scaffold(
backgroundColor: Colors.black, backgroundColor: Colors.black,
appBar: AppBar(
backgroundColor: Colors.transparent,
elevation: 0,
title: Text(
widget.stream.name,
style: const TextStyle(color: Colors.white),
),
iconTheme: const IconThemeData(color: Colors.white),
),
body: Center( body: Center(
child: _isLoading child: _isLoading
? const CircularProgressIndicator(color: Colors.red) ? const CircularProgressIndicator(color: Colors.red)

View File

@@ -1,55 +0,0 @@
class ChannelNameFormatter {
static final Map<String, String> _cache = <String, String>{};
static const Set<String> _qualityTokens = <String>{
'SD',
'HD',
'FHD',
'UHD',
'4K',
'8K',
'HDR',
'HEVC',
'H264',
'H265',
'FULLHD',
};
static String forDisplay(String rawName) {
if (rawName.isEmpty) return rawName;
final cached = _cache[rawName];
if (cached != null) return cached;
var display = rawName.trim();
final pipeIndex = display.indexOf('|');
if (pipeIndex >= 0 && pipeIndex < display.length - 1) {
display = display.substring(pipeIndex + 1).trim();
}
display = display.replaceFirst(RegExp(r'^[\-\\—:]+'), '').trim();
if (display.isNotEmpty) {
final parts = display.split(RegExp(r'\s+')).toList(growable: true);
while (parts.isNotEmpty && _isQualityToken(parts.last)) {
parts.removeLast();
}
display = parts.join(' ').trim();
}
if (display.isEmpty) {
display = rawName.trim();
}
if (_cache.length > 50000) {
_cache.clear();
}
_cache[rawName] = display;
return display;
}
static bool _isQualityToken(String token) {
final normalized = token.toUpperCase().replaceAll(RegExp(r'[^A-Z0-9]'), '');
return _qualityTokens.contains(normalized);
}
}