feat: add EPG TV guide, season passes (auto-recording) and unified recordings/EPG tab with 3 sub-tabs

This commit is contained in:
Michael committed 2026-03-09 08:21:48 +01:00
1 parent 4762b4edf8
commit 50a6998c09
7 files changed
+1271 -248

No files matched your search

+60
View File
@@ -98,6 +98,20 @@ class AppDatabase {
)
''');
// Season Passes table (enregistrements répétés intelligents)
_db.execute('''
CREATE TABLE IF NOT EXISTS season_passes (
id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
show_title TEXT NOT NULL,
channel_id TEXT NOT NULL,
stream_url TEXT NOT NULL,
enabled INTEGER DEFAULT 1,
created_at TEXT DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE
)
''');
// Indexes
_db.execute(
'CREATE INDEX IF NOT EXISTS idx_sessions_token ON sessions(token)',
@@ -108,6 +122,9 @@ class AppDatabase {
_db.execute(
'CREATE INDEX IF NOT EXISTS idx_playlists_user ON playlists(user_id)',
);
_db.execute(
'CREATE INDEX IF NOT EXISTS idx_recordings_status ON tv_recordings(status)',
);
}
/// Seed default admin user if no users exist
@@ -510,6 +527,49 @@ class AppDatabase {
_db.execute('DELETE FROM tv_recordings WHERE id = ?', [id]);
}
// ==================== Season Passes ====================
/// Créer un Season Pass (enregistrement répété par titre d'émission)
Map<String, dynamic> createSeasonPass({
required String userId,
required String showTitle,
required String channelId,
required String streamUrl,
}) {
final id = _uuid.v4();
final now = DateTime.now().toIso8601String();
_db.execute(
'INSERT INTO season_passes (id, user_id, show_title, channel_id, stream_url, created_at) VALUES (?, ?, ?, ?, ?, ?)',
[id, userId, showTitle, channelId, streamUrl, now],
);
return {'id': id, 'user_id': userId, 'show_title': showTitle, 'channel_id': channelId, 'stream_url': streamUrl, 'enabled': 1, 'created_at': now};
}
/// Lister tous les Season Passes
List<Map<String, dynamic>> getAllSeasonPasses() {
final result = _db.select('SELECT * FROM season_passes WHERE enabled = 1 ORDER BY created_at DESC');
return result.map((r) => Map<String, dynamic>.from(r)).toList();
}
/// Supprimer un Season Pass
void deleteSeasonPass(String id) {
_db.execute('DELETE FROM season_passes WHERE id = ?', [id]);
}
/// Vérifier si un enregistrement existe déjà pour ce titre (déduplication)
/// Retourne true si un enregistrement non-échoué avec ce titre existe pour cetteémission programméeà la même heure
bool existsRecordingForEpisode(String title, DateTime startTime) {
// Normaliser le titre pour la comparaison (insensible casse, sans espaces doubles)
final result = _db.select(
'''SELECT COUNT(*) as cnt FROM tv_recordings
WHERE LOWER(title) = LOWER(?)
AND start_time = ?
AND status NOT IN ('failed')''',
[title, startTime.toUtc().toIso8601String()],
);
return (result.first['cnt'] as int) > 0;
}
/// Close database connection
void close() {
_db.dispose();