Fladder/lib/models/syncing/sync_item.dart
PartyDonut 5578076fdb
chore: Move to different iconsax library (#301)
Co-authored-by: PartyDonut <PartyDonut@users.noreply.github.com>
2025-04-05 23:36:48 +02:00

203 lines
7.2 KiB
Dart

import 'dart:convert';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:background_downloader/background_downloader.dart';
import 'package:iconsax_plus/iconsax_plus.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'package:path/path.dart';
import 'package:fladder/jellyfin/jellyfin_open_api.swagger.dart';
import 'package:fladder/models/item_base_model.dart';
import 'package:fladder/models/items/chapters_model.dart';
import 'package:fladder/models/items/images_models.dart';
import 'package:fladder/models/items/media_segments_model.dart';
import 'package:fladder/models/items/item_shared_models.dart';
import 'package:fladder/models/items/media_streams_model.dart';
import 'package:fladder/models/items/trick_play_model.dart';
import 'package:fladder/models/syncing/i_synced_item.dart';
import 'package:fladder/providers/sync/sync_provider_helpers.dart';
import 'package:fladder/providers/sync_provider.dart';
import 'package:fladder/util/localization_helper.dart';
part 'sync_item.freezed.dart';
@freezed
class SyncedItem with _$SyncedItem {
const SyncedItem._();
factory SyncedItem({
required String id,
@Default(false) bool syncing,
String? parentId,
required String userId,
String? path,
@Default(false) bool markedForDelete,
String? sortName,
int? fileSize,
String? videoFileName,
MediaSegmentsModel? mediaSegments,
TrickPlayModel? fTrickPlayModel,
ImagesData? fImages,
@Default([]) List<Chapter> fChapters,
@Default([]) List<SubStreamModel> subtitles,
@UserDataJsonSerializer() UserData? userData,
}) = _SyncItem;
static String trickPlayPath = "TrickPlay";
List<Chapter> get chapters => fChapters.map((e) => e.copyWith(imageUrl: joinAll({"$path", e.imageUrl}))).toList();
ImagesData? get images => fImages?.copyWith(
primary: () => fImages?.primary?.copyWith(path: joinAll(["$path", "${fImages?.primary?.path}"])),
logo: () => fImages?.logo?.copyWith(path: joinAll(["$path", "${fImages?.logo?.path}"])),
backDrop: () => fImages?.backDrop?.map((e) => e.copyWith(path: joinAll(["$path", (e.path)]))).toList(),
);
TrickPlayModel? get trickPlayModel => fTrickPlayModel?.copyWith(
images: fTrickPlayModel?.images
.map(
(trickPlayPath) => joinAll(["$path", trickPlayPath]),
)
.toList() ??
[]);
File get dataFile => File(joinAll(["$path", "data.json"]));
Directory get trickPlayDirectory => Directory(joinAll(["$path", trickPlayPath]));
File get videoFile => File(joinAll(["$path", "$videoFileName"]));
Directory get directory => Directory(path ?? "");
SyncStatus get status => switch (videoFile.existsSync()) {
true => SyncStatus.complete,
_ => SyncStatus.partially,
};
String? get taskId => task?.taskId;
bool get childHasTask => false;
double get totalProgress => 0.0;
bool get hasVideoFile => videoFileName?.isNotEmpty == true && (fileSize ?? 0) > 0;
TaskStatus get anyStatus {
return TaskStatus.notFound;
}
double get downloadProgress => 0.0;
TaskStatus get downloadStatus => TaskStatus.notFound;
DownloadTask? get task => null;
Future<bool> deleteDatFiles(Ref ref) async {
try {
await videoFile.delete();
await Directory(joinAll([directory.path, trickPlayPath])).delete(recursive: true);
} catch (e) {
return false;
}
return true;
}
List<SyncedItem> nestedChildren(WidgetRef ref) => ref.watch(syncChildrenProvider(this));
List<SyncedItem> getChildren(Ref ref) => ref.read(syncProvider.notifier).getChildren(this);
List<SyncedItem> getNestedChildren(Ref ref) => ref.read(syncProvider.notifier).getNestedChildren(this);
Future<int> get getDirSize async {
var files = await directory.list(recursive: true).toList();
var dirSize = files.fold(0, (int sum, file) => sum + file.statSync().size);
return dirSize;
}
ItemBaseModel? createItemModel(Ref ref) {
if (!dataFile.existsSync()) return null;
final BaseItemDto itemDto = BaseItemDto.fromJson(jsonDecode(dataFile.readAsStringSync()));
final itemModel = ItemBaseModel.fromBaseDto(itemDto, ref);
return itemModel.copyWith(
images: images,
userData: userData,
);
}
factory SyncedItem.fromIsar(ISyncedItem isarSyncedItem, String savePath) {
return SyncedItem(
id: isarSyncedItem.id,
parentId: isarSyncedItem.parentId,
userId: isarSyncedItem.userId ?? "",
sortName: isarSyncedItem.sortName,
syncing: isarSyncedItem.syncing,
path: joinAll([savePath, isarSyncedItem.path ?? ""]),
fileSize: isarSyncedItem.fileSize,
videoFileName: isarSyncedItem.videoFileName,
mediaSegments: isarSyncedItem.mediaSegments != null
? MediaSegmentsModel.fromJson(jsonDecode(isarSyncedItem.mediaSegments!))
: null,
fTrickPlayModel: isarSyncedItem.trickPlayModel != null
? TrickPlayModel.fromJson(jsonDecode(isarSyncedItem.trickPlayModel!))
: null,
fImages: isarSyncedItem.images != null ? ImagesData.fromJson(jsonDecode(isarSyncedItem.images!)) : null,
fChapters: isarSyncedItem.chapters
?.map(
(e) => Chapter.fromJson(jsonDecode(e)),
)
.toList() ??
[],
subtitles: isarSyncedItem.subtitles
?.map(
(e) => SubStreamModel.fromJson(jsonDecode(e)),
)
.toList() ??
[],
userData: isarSyncedItem.userData != null ? UserData.fromJson(jsonDecode(isarSyncedItem.userData!)) : null,
);
}
}
enum SyncStatus {
complete(
Color.fromARGB(255, 141, 214, 58),
IconsaxPlusLinear.tick_circle,
),
partially(
Color.fromARGB(255, 221, 135, 23),
IconsaxPlusLinear.more_circle,
),
;
const SyncStatus(this.color, this.icon);
final Color color;
String label(BuildContext context) {
return switch (this) {
SyncStatus.partially => context.localized.syncStatusPartially,
SyncStatus.complete => context.localized.syncStatusSynced,
};
}
final IconData icon;
}
extension StatusExtension on TaskStatus {
Color color(BuildContext context) => switch (this) {
TaskStatus.enqueued => Colors.blueAccent,
TaskStatus.running => Colors.limeAccent,
TaskStatus.complete => Colors.limeAccent,
TaskStatus.canceled || TaskStatus.notFound || TaskStatus.failed => Theme.of(context).colorScheme.error,
TaskStatus.waitingToRetry => Colors.yellowAccent,
TaskStatus.paused => Colors.orangeAccent,
};
String name(BuildContext context) => switch (this) {
TaskStatus.enqueued => context.localized.syncStatusEnqueued,
TaskStatus.running => context.localized.syncStatusRunning,
TaskStatus.complete => context.localized.syncStatusComplete,
TaskStatus.notFound => context.localized.syncStatusNotFound,
TaskStatus.failed => context.localized.syncStatusFailed,
TaskStatus.canceled => context.localized.syncStatusCanceled,
TaskStatus.waitingToRetry => context.localized.syncStatusWaitingToRetry,
TaskStatus.paused => context.localized.syncStatusPaused,
};
}