import 'package:flutter/foundation.dart'; import 'package:freezed_annotation/freezed_annotation.dart'; part 'downloaded_series.freezed.dart'; part 'downloaded_series.g.dart'; /// Status of a download operation enum DownloadStatus { pending, downloading, complete, partial, failed; static DownloadStatus fromName(String name) { return DownloadStatus.values.firstWhere( (e) => e.name == name, orElse: () { debugPrint('Unknown DownloadStatus "$name", defaulting to pending'); return DownloadStatus.pending; }, ); } } /// Content type of a downloaded chapter enum ChapterContentType { epub, imageStream; static ChapterContentType fromName(String name) { return ChapterContentType.values.firstWhere( (e) => e.name == name, orElse: () { debugPrint('Unknown ChapterContentType "$name", defaulting to epub'); return ChapterContentType.epub; }, ); } } /// Represents a downloaded series for offline reading @freezed class DownloadedSeries with _$DownloadedSeries { const factory DownloadedSeries({ required String id, required String serverId, required String seriesFeedUrl, required String title, String? coverUrl, String? coverPath, String? thumbnailUrl, String? thumbnailPath, @Default(DownloadStatus.pending) DownloadStatus status, @Default(0) int totalChapters, @Default(0) int downloadedChaptersCount, DateTime? downloadedAt, required DateTime updatedAt, }) = _DownloadedSeries; const DownloadedSeries._(); factory DownloadedSeries.fromJson(Map json) => _$DownloadedSeriesFromJson(json); /// Create from database row factory DownloadedSeries.fromDatabase(Map row) { return DownloadedSeries( id: row['id'] as String, serverId: row['server_id'] as String, seriesFeedUrl: row['series_feed_url'] as String, title: row['title'] as String, coverUrl: row['cover_url'] as String?, coverPath: row['cover_path'] as String?, thumbnailUrl: row['thumbnail_url'] as String?, thumbnailPath: row['thumbnail_path'] as String?, status: DownloadStatus.fromName(row['status'] as String), totalChapters: row['total_chapters'] as int, downloadedChaptersCount: row['downloaded_chapters_count'] as int, downloadedAt: row['downloaded_at'] != null ? DateTime.fromMillisecondsSinceEpoch(row['downloaded_at'] as int) : null, updatedAt: DateTime.fromMillisecondsSinceEpoch(row['updated_at'] as int), ); } /// Whether the download is fully complete bool get isComplete => status == DownloadStatus.complete; /// Whether the download is in progress bool get isDownloading => status == DownloadStatus.downloading; /// Progress fraction (0.0 to 1.0) double get progress { if (totalChapters == 0) return 0.0; return (downloadedChaptersCount / totalChapters).clamp(0.0, 1.0); } } extension DownloadedSeriesX on DownloadedSeries { Map toDatabase() { return { 'id': id, 'server_id': serverId, 'series_feed_url': seriesFeedUrl, 'title': title, 'cover_url': coverUrl, 'cover_path': coverPath, 'thumbnail_url': thumbnailUrl, 'thumbnail_path': thumbnailPath, 'status': status.name, 'total_chapters': totalChapters, 'downloaded_chapters_count': downloadedChaptersCount, 'downloaded_at': downloadedAt?.millisecondsSinceEpoch, 'updated_at': updatedAt.millisecondsSinceEpoch, }; } }