mirror of
https://github.com/R0m1k3/xtremobile.git
synced 2026-10-18 12:49:17 +02:00
feat: Add generated Hive TypeAdapters for AppUser and PlaylistConfig models, and new build/doctor log files.
This commit is contained in:
1 parent
780f6c1842
commit
ef97c6dc90
81 files changed
+3233
-2428
No files matched your search
@@ -52,6 +52,9 @@ class ApiClient {
|
||||
return _token;
|
||||
}
|
||||
|
||||
/// Get underlying Dio instance for direct use
|
||||
Dio get dio => _dio;
|
||||
|
||||
/// Restore token from SharedPreferences
|
||||
Future<void> restoreToken() async {
|
||||
final prefs = await SharedPreferences.getInstance();
|
||||
|
||||
@@ -139,14 +139,6 @@ class UnifiedDnsService {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Clear DNS cache
|
||||
void clearCache() {
|
||||
_cache.clear();
|
||||
if (kDebugMode) {
|
||||
debugPrint('🗑️ DNS cache cleared');
|
||||
}
|
||||
}
|
||||
|
||||
/// Get cache stats for debugging
|
||||
Map<String, dynamic> getCacheStats() {
|
||||
final expiredCount = _cache.values.where((e) => e.isExpired).length;
|
||||
@@ -158,14 +150,22 @@ class UnifiedDnsService {
|
||||
};
|
||||
}
|
||||
|
||||
/// Cache entry with TTL
|
||||
static class _DnsCacheEntry {
|
||||
final String ip;
|
||||
final DateTime timestamp;
|
||||
|
||||
_DnsCacheEntry(this.ip) : timestamp = DateTime.now();
|
||||
|
||||
bool get isExpired =>
|
||||
DateTime.now().difference(timestamp).inSeconds > _cacheTtlSeconds;
|
||||
/// Clear DNS cache
|
||||
void clearCache() {
|
||||
_cache.clear();
|
||||
if (kDebugMode) {
|
||||
debugPrint('🗑️ DNS cache cleared');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Cache entry with TTL
|
||||
class _DnsCacheEntry {
|
||||
final String ip;
|
||||
final DateTime timestamp;
|
||||
|
||||
_DnsCacheEntry(this.ip) : timestamp = DateTime.now();
|
||||
|
||||
bool get isExpired =>
|
||||
DateTime.now().difference(timestamp).inSeconds > 3600;
|
||||
}
|
||||
@@ -1,4 +1,7 @@
|
||||
import 'dart:convert';
|
||||
|
||||
enum StreamType { live, vod, series }
|
||||
|
||||
/// Channel model for Live TV
|
||||
class Channel {
|
||||
final String streamId;
|
||||
@@ -101,6 +104,7 @@ class Series {
|
||||
final String plot;
|
||||
final String categoryId;
|
||||
final String categoryName;
|
||||
final String rating;
|
||||
|
||||
const Series({
|
||||
required this.seriesId,
|
||||
@@ -109,6 +113,7 @@ class Series {
|
||||
this.plot = '',
|
||||
required this.categoryId,
|
||||
required this.categoryName,
|
||||
this.rating = '0',
|
||||
});
|
||||
|
||||
factory Series.fromJson(Map<String, dynamic> json) {
|
||||
@@ -119,6 +124,7 @@ class Series {
|
||||
plot: json['plot']?.toString() ?? '',
|
||||
categoryId: json['category_id']?.toString() ?? '',
|
||||
categoryName: json['category_name']?.toString() ?? '',
|
||||
rating: json['rating']?.toString() ?? '0',
|
||||
);
|
||||
}
|
||||
|
||||
@@ -130,8 +136,14 @@ class Series {
|
||||
'plot': plot,
|
||||
'category_id': categoryId,
|
||||
'category_name': categoryName,
|
||||
'rating': rating,
|
||||
};
|
||||
}
|
||||
|
||||
/// UI Compatibility getters
|
||||
String get streamId => seriesId;
|
||||
String get coverUrl => cover;
|
||||
String get title => name;
|
||||
}
|
||||
|
||||
/// EPG (Electronic Program Guide) entry
|
||||
@@ -192,9 +204,152 @@ class EpgEntry {
|
||||
final totalDuration = endTime.difference(startTime).inSeconds;
|
||||
final elapsed = now.difference(startTime).inSeconds;
|
||||
|
||||
if (totalDuration == 0) return 0.0;
|
||||
return elapsed / totalDuration;
|
||||
} catch (e) {
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Category model
|
||||
class Category {
|
||||
final String categoryId;
|
||||
final String categoryName;
|
||||
final int parentId;
|
||||
|
||||
const Category({
|
||||
required this.categoryId,
|
||||
required this.categoryName,
|
||||
this.parentId = 0,
|
||||
});
|
||||
|
||||
factory Category.fromJson(Map<String, dynamic> json) {
|
||||
return Category(
|
||||
categoryId: json['category_id']?.toString() ?? '',
|
||||
categoryName: json['category_name']?.toString() ?? '',
|
||||
parentId: int.tryParse(json['parent_id']?.toString() ?? '0') ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'category_id': categoryId,
|
||||
'category_name': categoryName,
|
||||
'parent_id': parentId,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Series Episode model
|
||||
class Episode {
|
||||
final String id;
|
||||
final String title;
|
||||
final String containerExtension;
|
||||
final String season;
|
||||
final int episodeNum;
|
||||
final int duration;
|
||||
|
||||
const Episode({
|
||||
required this.id,
|
||||
required this.title,
|
||||
this.containerExtension = 'mp4',
|
||||
this.season = '1',
|
||||
this.episodeNum = 1,
|
||||
this.duration = 0,
|
||||
});
|
||||
|
||||
/// UI Compatibility getters
|
||||
String get streamId => id;
|
||||
int? get durationSecs => duration;
|
||||
int get seasonNum => int.tryParse(season) ?? 1;
|
||||
|
||||
factory Episode.fromJson(Map<String, dynamic> json) {
|
||||
return Episode(
|
||||
id: json['id']?.toString() ?? '',
|
||||
title: json['title']?.toString() ?? '',
|
||||
containerExtension: json['container_extension']?.toString() ?? 'mp4',
|
||||
season: json['season']?.toString() ?? '1',
|
||||
episodeNum: int.tryParse(json['episode_num']?.toString() ?? '1') ?? 1,
|
||||
duration: int.tryParse(json['duration']?.toString() ?? '0') ?? 0,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'id': id,
|
||||
'title': title,
|
||||
'container_extension': containerExtension,
|
||||
'season': season,
|
||||
'episode_num': episodeNum,
|
||||
'duration': duration,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/// Detailed Series Info
|
||||
class SeriesInfo {
|
||||
final Series series;
|
||||
final Map<String, List<Episode>> episodes;
|
||||
|
||||
const SeriesInfo({
|
||||
required this.series,
|
||||
required this.episodes,
|
||||
});
|
||||
|
||||
/// UI Compatibility getters
|
||||
String? get coverUrl => series.cover;
|
||||
String get title => series.name;
|
||||
String? get rating => series.rating;
|
||||
String? get plot => series.plot;
|
||||
}
|
||||
|
||||
/// Short EPG model
|
||||
class ShortEPG {
|
||||
final String id;
|
||||
final String title;
|
||||
final String start;
|
||||
final String end;
|
||||
final String description;
|
||||
|
||||
const ShortEPG({
|
||||
required this.id,
|
||||
required this.title,
|
||||
required this.start,
|
||||
required this.end,
|
||||
this.description = '',
|
||||
});
|
||||
|
||||
factory ShortEPG.fromJson(Map<String, dynamic> json) {
|
||||
return ShortEPG(
|
||||
id: json['id']?.toString() ?? '',
|
||||
title: json['title']?.toString() ?? '',
|
||||
start: json['start']?.toString() ?? '',
|
||||
end: json['end']?.toString() ?? '',
|
||||
description: json['description']?.toString() ?? '',
|
||||
);
|
||||
}
|
||||
|
||||
/// Get progress percentage (compatibility with UI expectations)
|
||||
double get progress {
|
||||
try {
|
||||
final startTime = DateTime.parse(start);
|
||||
final endTime = DateTime.parse(end);
|
||||
final now = DateTime.now();
|
||||
if (now.isBefore(startTime)) return 0.0;
|
||||
if (now.isAfter(endTime)) return 1.0;
|
||||
final total = endTime.difference(startTime).inSeconds;
|
||||
final elapsed = now.difference(startTime).inSeconds;
|
||||
if (total == 0) return 0.0;
|
||||
return elapsed / total;
|
||||
} catch (_) {
|
||||
return 0.0;
|
||||
}
|
||||
}
|
||||
|
||||
/// Get current program title
|
||||
String get nowPlaying => title;
|
||||
|
||||
/// Get next program info (mocked or from data if available)
|
||||
String get nextPlaying => "Next Program";
|
||||
}
|
||||
@@ -40,8 +40,8 @@ class StreamResolverPattern {
|
||||
_performDnsResolution(streamUrl).timeout(timeout, onTimeout: () => false),
|
||||
], eagerError: false).catchError((_) => [false, false]);
|
||||
|
||||
final isValid = results[0] as bool;
|
||||
final dnsResolved = results[1] as bool;
|
||||
final isValid = results[0];
|
||||
final dnsResolved = results[1];
|
||||
|
||||
if (!isValid) {
|
||||
return StreamResolverPattern(
|
||||
|
||||
@@ -2,15 +2,18 @@ import 'package:flutter/material.dart';
|
||||
import 'package:media_kit/media_kit.dart';
|
||||
import 'package:media_kit_video/media_kit_video.dart';
|
||||
|
||||
/// Video player selection and wrapper pattern
|
||||
/// Abstracts between MediaKit and other video players
|
||||
/// Handles fallback logic and player initialization
|
||||
/// Enum for supported players
|
||||
enum PlayerType { mediakit, native }
|
||||
|
||||
/// Video player selection and wrapper pattern
|
||||
/// Abstracts between MediaKit and other video players
|
||||
/// Handles fallback logic and player initialization
|
||||
class VideoPlayerWrapper {
|
||||
static const _availablePlayers = ['mediakit'];
|
||||
|
||||
/// Enum for supported players
|
||||
enum PlayerType { mediakit, native }
|
||||
|
||||
/// Get the preferred player type based on stream and platform
|
||||
static PlayerType getPreferredPlayer({
|
||||
required String streamUrl,
|
||||
@@ -50,7 +53,7 @@ class VideoPlayerWrapper {
|
||||
}) {
|
||||
final player = Player(
|
||||
configuration: PlayerConfiguration(
|
||||
logLevel: enableLogs ? MPVLogLevel.debug : MPVLogLevel.no,
|
||||
logLevel: enableLogs ? MPVLogLevel.debug : MPVLogLevel.error,
|
||||
),
|
||||
);
|
||||
return player;
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
/// Solution: Specify memCacheWidth/Height for display size, limiting disk cache.
|
||||
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart';
|
||||
|
||||
/// Standard cache manager with 200MB size limit
|
||||
@@ -18,8 +19,6 @@ class AppCacheManager {
|
||||
'xtremflow_image_cache',
|
||||
stalePeriod: const Duration(days: 7),
|
||||
maxNrOfCacheObjects: 500,
|
||||
diskUsagePercentageHigh: 95,
|
||||
diskUsagePercentageGoal: 80,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user