Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 7911af217f |
@@ -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)
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user