mirror of
https://github.com/gabehf/Fladder.git
synced 2026-03-07 21:48:14 -08:00
feature: Re-implemented syncing
This commit is contained in:
parent
c5c7f71b84
commit
86ff355e21
51 changed files with 3067 additions and 1147 deletions
|
|
@ -1242,5 +1242,47 @@
|
||||||
"notificationDownloadingDownloading": "Downloading",
|
"notificationDownloadingDownloading": "Downloading",
|
||||||
"notificationDownloadingPaused": "Download paused",
|
"notificationDownloadingPaused": "Download paused",
|
||||||
"notificationDownloadingFinished": "Download finished",
|
"notificationDownloadingFinished": "Download finished",
|
||||||
"notificationDownloadingError": "Download error"
|
"notificationDownloadingError": "Download error",
|
||||||
|
"syncAllItemsTitle": "Sync all items from {itemName}?",
|
||||||
|
"@syncAllItemsTitle": {
|
||||||
|
"description": "syncAllItemsFrom",
|
||||||
|
"placeholders": {
|
||||||
|
"itemName": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"syncAllItemsDesc": "This will sync ({itemCount}) items from '{itemName}' to your device.\nThis can take a while depending on the amount of items.",
|
||||||
|
"@syncAllItemsDesc": {
|
||||||
|
"description": "syncAllitemsFromDesc",
|
||||||
|
"placeholders": {
|
||||||
|
"itemName": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"itemCount": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"syncDeleteAllItemsTitle": "Delete all synced items from {itemName}?",
|
||||||
|
"@syncDeleteAllItemsTitle": {
|
||||||
|
"description": "syncDeleteAllitemsFrom",
|
||||||
|
"placeholders": {
|
||||||
|
"itemName": {
|
||||||
|
"type": "String"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"syncDeleteAllItemsDesc": "This will delete all synced items from '{itemName}'.\nThis is permanent and you will need to re-sync ({itemCount}) files.",
|
||||||
|
"@syncDeleteAllItemsDesc": {
|
||||||
|
"description": "syncDeleteAllitemsFromDesc",
|
||||||
|
"placeholders": {
|
||||||
|
"itemName": {
|
||||||
|
"type": "String"
|
||||||
|
},
|
||||||
|
"itemCount": {
|
||||||
|
"type": "int"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -8,7 +8,6 @@ import 'package:flutter/services.dart';
|
||||||
|
|
||||||
import 'package:dynamic_color/dynamic_color.dart';
|
import 'package:dynamic_color/dynamic_color.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:isar/isar.dart';
|
|
||||||
import 'package:package_info_plus/package_info_plus.dart';
|
import 'package:package_info_plus/package_info_plus.dart';
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
|
@ -20,7 +19,6 @@ import 'package:window_manager/window_manager.dart';
|
||||||
import 'package:fladder/l10n/generated/app_localizations.dart';
|
import 'package:fladder/l10n/generated/app_localizations.dart';
|
||||||
import 'package:fladder/models/account_model.dart';
|
import 'package:fladder/models/account_model.dart';
|
||||||
import 'package:fladder/models/settings/arguments_model.dart';
|
import 'package:fladder/models/settings/arguments_model.dart';
|
||||||
import 'package:fladder/models/syncing/i_synced_item.dart';
|
|
||||||
import 'package:fladder/providers/arguments_provider.dart';
|
import 'package:fladder/providers/arguments_provider.dart';
|
||||||
import 'package:fladder/providers/crash_log_provider.dart';
|
import 'package:fladder/providers/crash_log_provider.dart';
|
||||||
import 'package:fladder/providers/settings/client_settings_provider.dart';
|
import 'package:fladder/providers/settings/client_settings_provider.dart';
|
||||||
|
|
@ -100,12 +98,6 @@ void main(List<String> args) async {
|
||||||
argumentsStateProvider.overrideWith((ref) => ArgumentsModel.fromArguments(args)),
|
argumentsStateProvider.overrideWith((ref) => ArgumentsModel.fromArguments(args)),
|
||||||
syncProvider.overrideWith((ref) => SyncNotifier(
|
syncProvider.overrideWith((ref) => SyncNotifier(
|
||||||
ref,
|
ref,
|
||||||
!kIsWeb
|
|
||||||
? Isar.open(
|
|
||||||
schemas: [ISyncedItemSchema],
|
|
||||||
directory: isarPath.path,
|
|
||||||
)
|
|
||||||
: null,
|
|
||||||
applicationDirectory,
|
applicationDirectory,
|
||||||
))
|
))
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -45,6 +45,7 @@ class EpisodeModel extends ItemStreamModel with EpisodeModelMappable {
|
||||||
final String? seriesName;
|
final String? seriesName;
|
||||||
final int season;
|
final int season;
|
||||||
final int episode;
|
final int episode;
|
||||||
|
final int? episodeEnd;
|
||||||
final List<Chapter> chapters;
|
final List<Chapter> chapters;
|
||||||
final ItemLocation? location;
|
final ItemLocation? location;
|
||||||
final DateTime? dateAired;
|
final DateTime? dateAired;
|
||||||
|
|
@ -52,6 +53,7 @@ class EpisodeModel extends ItemStreamModel with EpisodeModelMappable {
|
||||||
required this.seriesName,
|
required this.seriesName,
|
||||||
required this.season,
|
required this.season,
|
||||||
required this.episode,
|
required this.episode,
|
||||||
|
required this.episodeEnd,
|
||||||
this.chapters = const [],
|
this.chapters = const [],
|
||||||
this.location,
|
this.location,
|
||||||
this.dateAired,
|
this.dateAired,
|
||||||
|
|
@ -134,12 +136,26 @@ class EpisodeModel extends ItemStreamModel with EpisodeModelMappable {
|
||||||
String seasonAnnotation(BuildContext context) => context.localized.season(1)[0];
|
String seasonAnnotation(BuildContext context) => context.localized.season(1)[0];
|
||||||
String episodeAnnotation(BuildContext context) => context.localized.episode(1)[0];
|
String episodeAnnotation(BuildContext context) => context.localized.episode(1)[0];
|
||||||
|
|
||||||
|
int get episodeCount {
|
||||||
|
if (episodeEnd != null && episodeEnd! > episode) {
|
||||||
|
return episodeEnd! - episode + 1;
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
String get episodeRange {
|
||||||
|
if (episodeEnd != null && episodeEnd! > episode) {
|
||||||
|
return "$episode-${episodeEnd!}";
|
||||||
|
}
|
||||||
|
return episode.toString();
|
||||||
|
}
|
||||||
|
|
||||||
String seasonEpisodeLabel(BuildContext context) {
|
String seasonEpisodeLabel(BuildContext context) {
|
||||||
return "${seasonAnnotation(context)}$season - ${episodeAnnotation(context)}$episode";
|
return "${seasonAnnotation(context)}$season - ${episodeAnnotation(context)}$episodeRange";
|
||||||
}
|
}
|
||||||
|
|
||||||
String seasonEpisodeLabelFull(BuildContext context) {
|
String seasonEpisodeLabelFull(BuildContext context) {
|
||||||
return "${context.localized.season(1)} $season - ${context.localized.episode(1)} $episode";
|
return "${context.localized.season(1)} $season - ${context.localized.episode(episodeCount)} $episodeRange";
|
||||||
}
|
}
|
||||||
|
|
||||||
String episodeLabel(BuildContext context) {
|
String episodeLabel(BuildContext context) {
|
||||||
|
|
@ -147,7 +163,7 @@ class EpisodeModel extends ItemStreamModel with EpisodeModelMappable {
|
||||||
}
|
}
|
||||||
|
|
||||||
String get fullName {
|
String get fullName {
|
||||||
return "$episode. $subText";
|
return "$episodeRange. $subText";
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -169,6 +185,7 @@ class EpisodeModel extends ItemStreamModel with EpisodeModelMappable {
|
||||||
primaryRatio: item.primaryImageAspectRatio,
|
primaryRatio: item.primaryImageAspectRatio,
|
||||||
season: item.parentIndexNumber ?? 0,
|
season: item.parentIndexNumber ?? 0,
|
||||||
episode: item.indexNumber ?? 0,
|
episode: item.indexNumber ?? 0,
|
||||||
|
episodeEnd: item.indexNumberEnd,
|
||||||
location: ItemLocation.fromDto(item.locationType),
|
location: ItemLocation.fromDto(item.locationType),
|
||||||
parentImages: ImagesData.fromBaseItemParent(item, ref),
|
parentImages: ImagesData.fromBaseItemParent(item, ref),
|
||||||
canDelete: item.canDelete,
|
canDelete: item.canDelete,
|
||||||
|
|
|
||||||
|
|
@ -31,6 +31,9 @@ class EpisodeModelMapper extends SubClassMapperBase<EpisodeModel> {
|
||||||
static int _$episode(EpisodeModel v) => v.episode;
|
static int _$episode(EpisodeModel v) => v.episode;
|
||||||
static const Field<EpisodeModel, int> _f$episode =
|
static const Field<EpisodeModel, int> _f$episode =
|
||||||
Field('episode', _$episode);
|
Field('episode', _$episode);
|
||||||
|
static int? _$episodeEnd(EpisodeModel v) => v.episodeEnd;
|
||||||
|
static const Field<EpisodeModel, int> _f$episodeEnd =
|
||||||
|
Field('episodeEnd', _$episodeEnd);
|
||||||
static List<Chapter> _$chapters(EpisodeModel v) => v.chapters;
|
static List<Chapter> _$chapters(EpisodeModel v) => v.chapters;
|
||||||
static const Field<EpisodeModel, List<Chapter>> _f$chapters =
|
static const Field<EpisodeModel, List<Chapter>> _f$chapters =
|
||||||
Field('chapters', _$chapters, opt: true, def: const []);
|
Field('chapters', _$chapters, opt: true, def: const []);
|
||||||
|
|
@ -86,6 +89,7 @@ class EpisodeModelMapper extends SubClassMapperBase<EpisodeModel> {
|
||||||
#seriesName: _f$seriesName,
|
#seriesName: _f$seriesName,
|
||||||
#season: _f$season,
|
#season: _f$season,
|
||||||
#episode: _f$episode,
|
#episode: _f$episode,
|
||||||
|
#episodeEnd: _f$episodeEnd,
|
||||||
#chapters: _f$chapters,
|
#chapters: _f$chapters,
|
||||||
#location: _f$location,
|
#location: _f$location,
|
||||||
#dateAired: _f$dateAired,
|
#dateAired: _f$dateAired,
|
||||||
|
|
@ -120,6 +124,7 @@ class EpisodeModelMapper extends SubClassMapperBase<EpisodeModel> {
|
||||||
seriesName: data.dec(_f$seriesName),
|
seriesName: data.dec(_f$seriesName),
|
||||||
season: data.dec(_f$season),
|
season: data.dec(_f$season),
|
||||||
episode: data.dec(_f$episode),
|
episode: data.dec(_f$episode),
|
||||||
|
episodeEnd: data.dec(_f$episodeEnd),
|
||||||
chapters: data.dec(_f$chapters),
|
chapters: data.dec(_f$chapters),
|
||||||
location: data.dec(_f$location),
|
location: data.dec(_f$location),
|
||||||
dateAired: data.dec(_f$dateAired),
|
dateAired: data.dec(_f$dateAired),
|
||||||
|
|
@ -166,6 +171,7 @@ abstract class EpisodeModelCopyWith<$R, $In extends EpisodeModel, $Out>
|
||||||
{String? seriesName,
|
{String? seriesName,
|
||||||
int? season,
|
int? season,
|
||||||
int? episode,
|
int? episode,
|
||||||
|
int? episodeEnd,
|
||||||
List<Chapter>? chapters,
|
List<Chapter>? chapters,
|
||||||
ItemLocation? location,
|
ItemLocation? location,
|
||||||
DateTime? dateAired,
|
DateTime? dateAired,
|
||||||
|
|
@ -209,6 +215,7 @@ class _EpisodeModelCopyWithImpl<$R, $Out>
|
||||||
{Object? seriesName = $none,
|
{Object? seriesName = $none,
|
||||||
int? season,
|
int? season,
|
||||||
int? episode,
|
int? episode,
|
||||||
|
Object? episodeEnd = $none,
|
||||||
List<Chapter>? chapters,
|
List<Chapter>? chapters,
|
||||||
Object? location = $none,
|
Object? location = $none,
|
||||||
Object? dateAired = $none,
|
Object? dateAired = $none,
|
||||||
|
|
@ -230,6 +237,7 @@ class _EpisodeModelCopyWithImpl<$R, $Out>
|
||||||
if (seriesName != $none) #seriesName: seriesName,
|
if (seriesName != $none) #seriesName: seriesName,
|
||||||
if (season != null) #season: season,
|
if (season != null) #season: season,
|
||||||
if (episode != null) #episode: episode,
|
if (episode != null) #episode: episode,
|
||||||
|
if (episodeEnd != $none) #episodeEnd: episodeEnd,
|
||||||
if (chapters != null) #chapters: chapters,
|
if (chapters != null) #chapters: chapters,
|
||||||
if (location != $none) #location: location,
|
if (location != $none) #location: location,
|
||||||
if (dateAired != $none) #dateAired: dateAired,
|
if (dateAired != $none) #dateAired: dateAired,
|
||||||
|
|
@ -253,6 +261,7 @@ class _EpisodeModelCopyWithImpl<$R, $Out>
|
||||||
seriesName: data.get(#seriesName, or: $value.seriesName),
|
seriesName: data.get(#seriesName, or: $value.seriesName),
|
||||||
season: data.get(#season, or: $value.season),
|
season: data.get(#season, or: $value.season),
|
||||||
episode: data.get(#episode, or: $value.episode),
|
episode: data.get(#episode, or: $value.episode),
|
||||||
|
episodeEnd: data.get(#episodeEnd, or: $value.episodeEnd),
|
||||||
chapters: data.get(#chapters, or: $value.chapters),
|
chapters: data.get(#chapters, or: $value.chapters),
|
||||||
location: data.get(#location, or: $value.location),
|
location: data.get(#location, or: $value.location),
|
||||||
dateAired: data.get(#dateAired, or: $value.dateAired),
|
dateAired: data.get(#dateAired, or: $value.dateAired),
|
||||||
|
|
|
||||||
|
|
@ -50,6 +50,21 @@ class UserData with UserDataMappable {
|
||||||
|
|
||||||
Duration get playBackPosition => Duration(milliseconds: playbackPositionTicks ~/ 10000);
|
Duration get playBackPosition => Duration(milliseconds: playbackPositionTicks ~/ 10000);
|
||||||
|
|
||||||
|
static UserData? determineLastUserData(List<UserData?> data) {
|
||||||
|
return data.where((data) => data != null).reduce((a, b) {
|
||||||
|
final aDate = a?.lastPlayed;
|
||||||
|
final bDate = b?.lastPlayed;
|
||||||
|
|
||||||
|
if (aDate != null && bDate != null) {
|
||||||
|
return aDate.isAfter(bDate) ? a : b;
|
||||||
|
} else if (aDate != null) {
|
||||||
|
return a;
|
||||||
|
} else {
|
||||||
|
return b;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
factory UserData.fromMap(Map<String, dynamic> map) => UserDataMapper.fromMap(map);
|
factory UserData.fromMap(Map<String, dynamic> map) => UserDataMapper.fromMap(map);
|
||||||
factory UserData.fromJson(String json) => UserDataMapper.fromJson(json);
|
factory UserData.fromJson(String json) => UserDataMapper.fromJson(json);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -75,7 +75,7 @@ class SeasonModel extends ItemBaseModel with SeasonModelMappable {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool get syncAble => true;
|
bool get syncAble => episodes.isNotEmpty && episodes.any((element) => element.syncAble);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ImagesData? get getPosters => images ?? parentImages;
|
ImagesData? get getPosters => images ?? parentImages;
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ import 'dart:developer';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:background_downloader/background_downloader.dart';
|
||||||
import 'package:chopper/chopper.dart';
|
import 'package:chopper/chopper.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
@ -26,7 +27,6 @@ import 'package:fladder/profiles/default_profile.dart';
|
||||||
import 'package:fladder/providers/api_provider.dart';
|
import 'package:fladder/providers/api_provider.dart';
|
||||||
import 'package:fladder/providers/service_provider.dart';
|
import 'package:fladder/providers/service_provider.dart';
|
||||||
import 'package:fladder/providers/settings/video_player_settings_provider.dart';
|
import 'package:fladder/providers/settings/video_player_settings_provider.dart';
|
||||||
import 'package:fladder/providers/sync/sync_provider_helpers.dart';
|
|
||||||
import 'package:fladder/providers/sync_provider.dart';
|
import 'package:fladder/providers/sync_provider.dart';
|
||||||
import 'package:fladder/providers/user_provider.dart';
|
import 'package:fladder/providers/user_provider.dart';
|
||||||
import 'package:fladder/providers/video_player_provider.dart';
|
import 'package:fladder/providers/video_player_provider.dart';
|
||||||
|
|
@ -129,7 +129,7 @@ class PlaybackModelHelper {
|
||||||
await _createOfflinePlaybackModel(
|
await _createOfflinePlaybackModel(
|
||||||
newItem,
|
newItem,
|
||||||
null,
|
null,
|
||||||
ref.read(syncProvider.notifier).getSyncedItem(newItem),
|
await ref.read(syncProvider.notifier).getSyncedItem(newItem),
|
||||||
oldModel: currentModel,
|
oldModel: currentModel,
|
||||||
);
|
);
|
||||||
if (newModel == null) return null;
|
if (newModel == null) return null;
|
||||||
|
|
@ -143,10 +143,10 @@ class PlaybackModelHelper {
|
||||||
SyncedItem? syncedItem, {
|
SyncedItem? syncedItem, {
|
||||||
PlaybackModel? oldModel,
|
PlaybackModel? oldModel,
|
||||||
}) async {
|
}) async {
|
||||||
final ItemBaseModel? syncedItemModel = ref.read(syncProvider.notifier).getItem(syncedItem);
|
final ItemBaseModel? syncedItemModel = syncedItem?.itemModel;
|
||||||
if (syncedItemModel == null || syncedItem == null || !syncedItem.dataFile.existsSync()) return null;
|
if (syncedItemModel == null || syncedItem == null || !syncedItem.dataFile.existsSync()) return null;
|
||||||
|
|
||||||
final children = ref.read(syncChildrenProvider(syncedItem));
|
final children = await ref.read(syncProvider.notifier).getChildren(syncedItem);
|
||||||
final syncedItems = children.where((element) => element.videoFile.existsSync()).toList();
|
final syncedItems = children.where((element) => element.videoFile.existsSync()).toList();
|
||||||
final itemQueue = syncedItems.map((e) => e.createItemModel(ref));
|
final itemQueue = syncedItems.map((e) => e.createItemModel(ref));
|
||||||
|
|
||||||
|
|
@ -174,6 +174,7 @@ class PlaybackModelHelper {
|
||||||
final userId = ref.read(userProvider)?.id;
|
final userId = ref.read(userProvider)?.id;
|
||||||
if (userId?.isEmpty == true) return null;
|
if (userId?.isEmpty == true) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
final queue = oldModel?.queue ?? libraryQueue ?? await collectQueue(item);
|
final queue = oldModel?.queue ?? libraryQueue ?? await collectQueue(item);
|
||||||
|
|
||||||
final firstItemToPlay = switch (item) {
|
final firstItemToPlay = switch (item) {
|
||||||
|
|
@ -187,9 +188,9 @@ class PlaybackModelHelper {
|
||||||
|
|
||||||
if (fullItem == null) return null;
|
if (fullItem == null) return null;
|
||||||
|
|
||||||
SyncedItem? syncedItem = ref.read(syncProvider.notifier).getSyncedItem(fullItem);
|
SyncedItem? syncedItem = await ref.read(syncProvider.notifier).getSyncedItem(fullItem);
|
||||||
|
|
||||||
final firstItemIsSynced = syncedItem != null && syncedItem.status == SyncStatus.complete;
|
final firstItemIsSynced = syncedItem != null && syncedItem.status == TaskStatus.complete;
|
||||||
|
|
||||||
final options = {
|
final options = {
|
||||||
PlaybackType.directStream,
|
PlaybackType.directStream,
|
||||||
|
|
@ -236,6 +237,19 @@ class PlaybackModelHelper {
|
||||||
syncedItem,
|
syncedItem,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
} catch (e) {
|
||||||
|
log(e.toString());
|
||||||
|
SyncedItem? syncedItem = await ref.read(syncProvider.notifier).getSyncedItem(item);
|
||||||
|
if (syncedItem != null) {
|
||||||
|
return await _createOfflinePlaybackModel(
|
||||||
|
item,
|
||||||
|
item.streamModel,
|
||||||
|
syncedItem,
|
||||||
|
oldModel: oldModel,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<PlaybackModel?> _createServerPlaybackModel(
|
Future<PlaybackModel?> _createServerPlaybackModel(
|
||||||
|
|
|
||||||
193
lib/models/syncing/database_item.dart
Normal file
193
lib/models/syncing/database_item.dart
Normal file
|
|
@ -0,0 +1,193 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:drift_flutter/drift_flutter.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:path_provider/path_provider.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/item_shared_models.dart';
|
||||||
|
import 'package:fladder/models/items/media_segments_model.dart';
|
||||||
|
import 'package:fladder/models/items/media_streams_model.dart';
|
||||||
|
import 'package:fladder/models/items/trick_play_model.dart';
|
||||||
|
import 'package:fladder/models/syncing/sync_item.dart';
|
||||||
|
import 'package:fladder/providers/user_provider.dart';
|
||||||
|
|
||||||
|
part 'database_item.g.dart';
|
||||||
|
|
||||||
|
@TableIndex(name: 'database_id', columns: {#id})
|
||||||
|
class DatabaseItems extends Table {
|
||||||
|
TextColumn get userId => text()();
|
||||||
|
TextColumn get id => text().withLength(min: 1)();
|
||||||
|
BoolColumn get syncing => boolean()();
|
||||||
|
TextColumn get sortName => text().nullable()();
|
||||||
|
TextColumn get parentId => text().nullable()();
|
||||||
|
TextColumn get path => text().nullable()();
|
||||||
|
IntColumn get fileSize => integer().nullable()();
|
||||||
|
TextColumn get videoFileName => text().nullable()();
|
||||||
|
TextColumn get trickPlayModel => text().nullable()();
|
||||||
|
TextColumn get mediaSegments => text().nullable()();
|
||||||
|
TextColumn get images => text().nullable()();
|
||||||
|
TextColumn get chapters => text().nullable()();
|
||||||
|
TextColumn get subtitles => text().nullable()();
|
||||||
|
TextColumn get userData => text().nullable()();
|
||||||
|
|
||||||
|
@override
|
||||||
|
Set<Column<Object>> get primaryKey => {id};
|
||||||
|
}
|
||||||
|
|
||||||
|
@DriftDatabase(tables: [DatabaseItems])
|
||||||
|
class AppDatabase extends _$AppDatabase {
|
||||||
|
AppDatabase(this.ref, [QueryExecutor? executor]) : super(executor ?? _openConnection());
|
||||||
|
|
||||||
|
final Ref ref;
|
||||||
|
|
||||||
|
String get userId => ref.read(userProvider.select((value) => value?.id ?? ""));
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get schemaVersion => 1;
|
||||||
|
|
||||||
|
Future<void> clearDatabase() {
|
||||||
|
return transaction(() async {
|
||||||
|
for (final table in allTables) {
|
||||||
|
await delete(table).go();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Selectable<SyncedItem> getItem(String id) =>
|
||||||
|
(select(databaseItems)..where((tbl) => tbl.id.equals(id) & tbl.userId.equals(userId))).map(databaseConverter);
|
||||||
|
|
||||||
|
Selectable<SyncedItem> getParent(String id) =>
|
||||||
|
(select(databaseItems)..where((tbl) => tbl.parentId.equals(id) & tbl.userId.equals(userId)))
|
||||||
|
.map(databaseConverter);
|
||||||
|
|
||||||
|
Selectable<SyncedItem> get getParentItems =>
|
||||||
|
((select(databaseItems)..where((tbl) => (tbl.parentId.isNull() & tbl.userId.equals(userId))))
|
||||||
|
..orderBy([(t) => OrderingTerm(expression: t.sortName)]))
|
||||||
|
.map(databaseConverter);
|
||||||
|
|
||||||
|
Selectable<SyncedItem> getChildren(String parentId) =>
|
||||||
|
((select(databaseItems)..where((tbl) => (tbl.parentId.equals(parentId) & tbl.userId.equals(userId))))
|
||||||
|
..orderBy([(t) => OrderingTerm(expression: t.sortName)]))
|
||||||
|
.map(databaseConverter);
|
||||||
|
|
||||||
|
Future<int> insertItem(SyncedItem item) async {
|
||||||
|
final itemExists = await getItem(item.id).getSingleOrNull();
|
||||||
|
if (itemExists != null) {
|
||||||
|
return (update(databaseItems)..where((tbl) => tbl.id.equals(item.id) & tbl.userId.equals(userId)))
|
||||||
|
.write(toDataBaseItem(item));
|
||||||
|
} else {
|
||||||
|
return into(databaseItems).insert(toDataBaseItem(item));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<List<SyncedItem>> getNestedChildren(SyncedItem root) async {
|
||||||
|
final itemType = root.createItemModel(ref)?.type;
|
||||||
|
|
||||||
|
if (itemType == null) return [];
|
||||||
|
|
||||||
|
final int maxDepth = switch (itemType) {
|
||||||
|
FladderItemType.episode => 0,
|
||||||
|
FladderItemType.movie => 0,
|
||||||
|
FladderItemType.season => 1,
|
||||||
|
FladderItemType.series => 2,
|
||||||
|
_ => 1,
|
||||||
|
};
|
||||||
|
|
||||||
|
final all = <SyncedItem>[];
|
||||||
|
List<SyncedItem> toProcess = [root];
|
||||||
|
|
||||||
|
if (maxDepth == 0) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = 0; i < maxDepth; i++) {
|
||||||
|
final futures = toProcess.map((item) => getChildren(item.id).get());
|
||||||
|
final resultsList = await Future.wait(futures);
|
||||||
|
|
||||||
|
final children = resultsList.expand((r) => r).toList();
|
||||||
|
|
||||||
|
if (children.isEmpty) break;
|
||||||
|
|
||||||
|
all.addAll(children);
|
||||||
|
toProcess = children;
|
||||||
|
}
|
||||||
|
|
||||||
|
return all;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> insertMultipleEntries(List<SyncedItem> items) async {
|
||||||
|
await batch((batch) {
|
||||||
|
batch.insertAll(
|
||||||
|
databaseItems,
|
||||||
|
items.map(toDataBaseItem),
|
||||||
|
mode: InsertMode.insertOrReplace,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> deleteAllItems(List<SyncedItem> items) async => await batch((batch) {
|
||||||
|
batch.deleteWhere(databaseItems, (tbl) => tbl.id.isIn(items.map((e) => e.id)));
|
||||||
|
});
|
||||||
|
|
||||||
|
DatabaseItemsCompanion toDataBaseItem(SyncedItem item) {
|
||||||
|
return DatabaseItemsCompanion(
|
||||||
|
id: Value(item.id),
|
||||||
|
parentId: Value(item.parentId),
|
||||||
|
syncing: Value(item.syncing),
|
||||||
|
userId: Value(userId),
|
||||||
|
path: Value(item.path),
|
||||||
|
fileSize: Value(item.fileSize),
|
||||||
|
sortName: Value(item.sortName),
|
||||||
|
videoFileName: Value(item.videoFileName),
|
||||||
|
trickPlayModel: Value(item.fTrickPlayModel != null ? jsonEncode(item.fTrickPlayModel?.toJson()) : null),
|
||||||
|
mediaSegments: Value(item.mediaSegments != null ? jsonEncode(item.mediaSegments?.toJson()) : null),
|
||||||
|
images: Value(item.fImages != null ? jsonEncode(item.fImages?.toJson()) : null),
|
||||||
|
chapters: Value(jsonEncode(item.fChapters.map((e) => e.toJson()).toList())),
|
||||||
|
subtitles: Value(jsonEncode(item.subtitles.map((e) => e.toJson()).toList())),
|
||||||
|
userData: Value(item.userData != null ? jsonEncode(item.userData?.toJson()) : null),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
SyncedItem databaseConverter(DatabaseItem dataItem) {
|
||||||
|
final syncedItem = SyncedItem(
|
||||||
|
id: dataItem.id,
|
||||||
|
userId: dataItem.userId,
|
||||||
|
parentId: dataItem.parentId,
|
||||||
|
sortName: dataItem.sortName,
|
||||||
|
syncing: dataItem.syncing,
|
||||||
|
path: dataItem.path,
|
||||||
|
fileSize: dataItem.fileSize,
|
||||||
|
videoFileName: dataItem.videoFileName,
|
||||||
|
fTrickPlayModel:
|
||||||
|
dataItem.trickPlayModel != null ? TrickPlayModel.fromJson(jsonDecode(dataItem.trickPlayModel!)) : null,
|
||||||
|
mediaSegments:
|
||||||
|
dataItem.mediaSegments != null ? MediaSegmentsModel.fromJson(jsonDecode(dataItem.mediaSegments!)) : null,
|
||||||
|
fImages: dataItem.images != null ? ImagesData.fromJson(jsonDecode(dataItem.images!)) : null,
|
||||||
|
fChapters: (dataItem.chapters != null && dataItem.chapters!.isNotEmpty)
|
||||||
|
? (jsonDecode(dataItem.chapters!) as List).map((e) => Chapter.fromJson(e)).toList()
|
||||||
|
: [],
|
||||||
|
subtitles: (dataItem.subtitles != null && dataItem.subtitles!.isNotEmpty)
|
||||||
|
? (jsonDecode(dataItem.subtitles!) as List).map((e) => SubStreamModel.fromJson(e)).toList()
|
||||||
|
: [],
|
||||||
|
userData: dataItem.userData != null ? UserData.fromJson(jsonDecode(dataItem.userData!)) : null,
|
||||||
|
);
|
||||||
|
|
||||||
|
return syncedItem.copyWith(
|
||||||
|
itemModel: syncedItem.createItemModel(ref),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static QueryExecutor _openConnection() {
|
||||||
|
return driftDatabase(
|
||||||
|
name: 'syncedDatabase',
|
||||||
|
native: const DriftNativeOptions(
|
||||||
|
databaseDirectory: getApplicationSupportDirectory,
|
||||||
|
),
|
||||||
|
// If you need web support, see https://drift.simonbinder.eu/platforms/web/
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
1035
lib/models/syncing/database_item.g.dart
Normal file
1035
lib/models/syncing/database_item.g.dart
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -6,24 +6,6 @@ import 'package:fladder/models/syncing/sync_item.dart';
|
||||||
|
|
||||||
part 'i_synced_item.g.dart';
|
part 'i_synced_item.g.dart';
|
||||||
|
|
||||||
// extension IsarExtensions on String? {
|
|
||||||
// int get fastHash {
|
|
||||||
// if (this == null) return 0;
|
|
||||||
// var hash = 0xcbf29ce484222325;
|
|
||||||
|
|
||||||
// var i = 0;
|
|
||||||
// while (i < this!.length) {
|
|
||||||
// final codeUnit = this!.codeUnitAt(i++);
|
|
||||||
// hash ^= codeUnit >> 8;
|
|
||||||
// hash *= 0x100000001b3;
|
|
||||||
// hash ^= codeUnit & 0xFF;
|
|
||||||
// hash *= 0x100000001b3;
|
|
||||||
// }
|
|
||||||
|
|
||||||
// return hash;
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
@collection
|
@collection
|
||||||
class ISyncedItem {
|
class ISyncedItem {
|
||||||
String? userId;
|
String? userId;
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ import 'package:fladder/models/items/media_segments_model.dart';
|
||||||
import 'package:fladder/models/items/media_streams_model.dart';
|
import 'package:fladder/models/items/media_streams_model.dart';
|
||||||
import 'package:fladder/models/items/trick_play_model.dart';
|
import 'package:fladder/models/items/trick_play_model.dart';
|
||||||
import 'package:fladder/models/syncing/i_synced_item.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/providers/sync_provider.dart';
|
||||||
import 'package:fladder/util/localization_helper.dart';
|
import 'package:fladder/util/localization_helper.dart';
|
||||||
|
|
||||||
|
|
@ -44,6 +43,8 @@ class SyncedItem with _$SyncedItem {
|
||||||
@Default([]) List<Chapter> fChapters,
|
@Default([]) List<Chapter> fChapters,
|
||||||
@Default([]) List<SubStreamModel> subtitles,
|
@Default([]) List<SubStreamModel> subtitles,
|
||||||
@UserDataJsonSerializer() UserData? userData,
|
@UserDataJsonSerializer() UserData? userData,
|
||||||
|
// ignore: invalid_annotation_target
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false) ItemBaseModel? itemModel,
|
||||||
}) = _SyncItem;
|
}) = _SyncItem;
|
||||||
|
|
||||||
static String trickPlayPath = "TrickPlay";
|
static String trickPlayPath = "TrickPlay";
|
||||||
|
|
@ -70,9 +71,9 @@ class SyncedItem with _$SyncedItem {
|
||||||
File get videoFile => File(joinAll(["$path", "$videoFileName"]));
|
File get videoFile => File(joinAll(["$path", "$videoFileName"]));
|
||||||
Directory get directory => Directory(path ?? "");
|
Directory get directory => Directory(path ?? "");
|
||||||
|
|
||||||
SyncStatus get status => switch (videoFile.existsSync()) {
|
TaskStatus get status => switch (videoFile.existsSync()) {
|
||||||
true => SyncStatus.complete,
|
true => TaskStatus.complete,
|
||||||
_ => SyncStatus.partially,
|
_ => TaskStatus.notFound,
|
||||||
};
|
};
|
||||||
|
|
||||||
String? get taskId => task?.taskId;
|
String? get taskId => task?.taskId;
|
||||||
|
|
@ -103,10 +104,9 @@ class SyncedItem with _$SyncedItem {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<SyncedItem> nestedChildren(WidgetRef ref) => ref.watch(syncChildrenProvider(this));
|
Future<List<SyncedItem>> getChildren(Ref ref) async => await ref.read(syncProvider.notifier).getChildren(this);
|
||||||
|
Future<List<SyncedItem>> getNestedChildren(Ref ref) async =>
|
||||||
List<SyncedItem> getChildren(Ref ref) => ref.read(syncProvider.notifier).getChildren(this);
|
await ref.read(syncProvider.notifier).getNestedChildren(this);
|
||||||
List<SyncedItem> getNestedChildren(Ref ref) => ref.read(syncProvider.notifier).getNestedChildren(this);
|
|
||||||
|
|
||||||
Future<int> get getDirSize async {
|
Future<int> get getDirSize async {
|
||||||
var files = await directory.list(recursive: true).toList();
|
var files = await directory.list(recursive: true).toList();
|
||||||
|
|
@ -158,44 +158,45 @@ class SyncedItem with _$SyncedItem {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
enum SyncStatus {
|
extension StatusExtension on TaskStatus {
|
||||||
complete(
|
IconData get icon => switch (this) {
|
||||||
Color.fromARGB(255, 141, 214, 58),
|
TaskStatus.enqueued => IconsaxPlusLinear.calendar_circle,
|
||||||
IconsaxPlusLinear.tick_circle,
|
TaskStatus.running => IconsaxPlusLinear.arrow_down_1,
|
||||||
),
|
TaskStatus.complete => IconsaxPlusLinear.tick_circle,
|
||||||
partially(
|
TaskStatus.notFound => IconsaxPlusLinear.warning_2,
|
||||||
Color.fromARGB(255, 221, 135, 23),
|
TaskStatus.failed => IconsaxPlusLinear.tag_cross,
|
||||||
IconsaxPlusLinear.more_circle,
|
TaskStatus.canceled => IconsaxPlusLinear.tag_cross,
|
||||||
),
|
TaskStatus.waitingToRetry => IconsaxPlusLinear.clock,
|
||||||
;
|
TaskStatus.paused => IconsaxPlusLinear.pause_circle,
|
||||||
|
};
|
||||||
|
|
||||||
const SyncStatus(this.color, this.icon);
|
Color color(BuildContext context) {
|
||||||
|
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
|
||||||
final Color color;
|
return isDarkMode
|
||||||
String label(BuildContext context) {
|
? switch (this) {
|
||||||
return switch (this) {
|
TaskStatus.enqueued => Colors.blueAccent,
|
||||||
SyncStatus.partially => context.localized.syncStatusPartially,
|
TaskStatus.running => Colors.greenAccent,
|
||||||
SyncStatus.complete => context.localized.syncStatusSynced,
|
TaskStatus.complete => Colors.limeAccent,
|
||||||
|
TaskStatus.notFound => const Color.fromARGB(255, 221, 135, 23),
|
||||||
|
TaskStatus.canceled || TaskStatus.failed => Theme.of(context).colorScheme.error,
|
||||||
|
TaskStatus.waitingToRetry => Colors.yellowAccent,
|
||||||
|
TaskStatus.paused => Colors.tealAccent,
|
||||||
|
}
|
||||||
|
: switch (this) {
|
||||||
|
TaskStatus.enqueued => Colors.blue,
|
||||||
|
TaskStatus.running => Colors.green,
|
||||||
|
TaskStatus.complete => Colors.lime,
|
||||||
|
TaskStatus.notFound => const Color.fromARGB(255, 221, 135, 23),
|
||||||
|
TaskStatus.canceled || TaskStatus.failed => Theme.of(context).colorScheme.error,
|
||||||
|
TaskStatus.waitingToRetry => Colors.yellow,
|
||||||
|
TaskStatus.paused => Colors.teal,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
String name(BuildContext context) => switch (this) {
|
||||||
TaskStatus.enqueued => context.localized.syncStatusEnqueued,
|
TaskStatus.enqueued => context.localized.syncStatusEnqueued,
|
||||||
TaskStatus.running => context.localized.syncStatusRunning,
|
TaskStatus.running => context.localized.syncStatusRunning,
|
||||||
TaskStatus.complete => context.localized.syncStatusComplete,
|
TaskStatus.complete => context.localized.syncStatusSynced,
|
||||||
TaskStatus.notFound => context.localized.syncStatusNotFound,
|
TaskStatus.notFound => context.localized.syncStatusNotFound,
|
||||||
TaskStatus.failed => context.localized.syncStatusFailed,
|
TaskStatus.failed => context.localized.syncStatusFailed,
|
||||||
TaskStatus.canceled => context.localized.syncStatusCanceled,
|
TaskStatus.canceled => context.localized.syncStatusCanceled,
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,10 @@ mixin _$SyncedItem {
|
||||||
List<Chapter> get fChapters => throw _privateConstructorUsedError;
|
List<Chapter> get fChapters => throw _privateConstructorUsedError;
|
||||||
List<SubStreamModel> get subtitles => throw _privateConstructorUsedError;
|
List<SubStreamModel> get subtitles => throw _privateConstructorUsedError;
|
||||||
@UserDataJsonSerializer()
|
@UserDataJsonSerializer()
|
||||||
UserData? get userData => throw _privateConstructorUsedError;
|
UserData? get userData =>
|
||||||
|
throw _privateConstructorUsedError; // ignore: invalid_annotation_target
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
ItemBaseModel? get itemModel => throw _privateConstructorUsedError;
|
||||||
|
|
||||||
/// Create a copy of SyncedItem
|
/// Create a copy of SyncedItem
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
|
@ -61,7 +64,9 @@ abstract class $SyncedItemCopyWith<$Res> {
|
||||||
ImagesData? fImages,
|
ImagesData? fImages,
|
||||||
List<Chapter> fChapters,
|
List<Chapter> fChapters,
|
||||||
List<SubStreamModel> subtitles,
|
List<SubStreamModel> subtitles,
|
||||||
@UserDataJsonSerializer() UserData? userData});
|
@UserDataJsonSerializer() UserData? userData,
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
ItemBaseModel? itemModel});
|
||||||
|
|
||||||
$TrickPlayModelCopyWith<$Res>? get fTrickPlayModel;
|
$TrickPlayModelCopyWith<$Res>? get fTrickPlayModel;
|
||||||
}
|
}
|
||||||
|
|
@ -96,6 +101,7 @@ class _$SyncedItemCopyWithImpl<$Res, $Val extends SyncedItem>
|
||||||
Object? fChapters = null,
|
Object? fChapters = null,
|
||||||
Object? subtitles = null,
|
Object? subtitles = null,
|
||||||
Object? userData = freezed,
|
Object? userData = freezed,
|
||||||
|
Object? itemModel = freezed,
|
||||||
}) {
|
}) {
|
||||||
return _then(_value.copyWith(
|
return _then(_value.copyWith(
|
||||||
id: null == id
|
id: null == id
|
||||||
|
|
@ -158,6 +164,10 @@ class _$SyncedItemCopyWithImpl<$Res, $Val extends SyncedItem>
|
||||||
? _value.userData
|
? _value.userData
|
||||||
: userData // ignore: cast_nullable_to_non_nullable
|
: userData // ignore: cast_nullable_to_non_nullable
|
||||||
as UserData?,
|
as UserData?,
|
||||||
|
itemModel: freezed == itemModel
|
||||||
|
? _value.itemModel
|
||||||
|
: itemModel // ignore: cast_nullable_to_non_nullable
|
||||||
|
as ItemBaseModel?,
|
||||||
) as $Val);
|
) as $Val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -199,7 +209,9 @@ abstract class _$$SyncItemImplCopyWith<$Res>
|
||||||
ImagesData? fImages,
|
ImagesData? fImages,
|
||||||
List<Chapter> fChapters,
|
List<Chapter> fChapters,
|
||||||
List<SubStreamModel> subtitles,
|
List<SubStreamModel> subtitles,
|
||||||
@UserDataJsonSerializer() UserData? userData});
|
@UserDataJsonSerializer() UserData? userData,
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
ItemBaseModel? itemModel});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
$TrickPlayModelCopyWith<$Res>? get fTrickPlayModel;
|
$TrickPlayModelCopyWith<$Res>? get fTrickPlayModel;
|
||||||
|
|
@ -233,6 +245,7 @@ class __$$SyncItemImplCopyWithImpl<$Res>
|
||||||
Object? fChapters = null,
|
Object? fChapters = null,
|
||||||
Object? subtitles = null,
|
Object? subtitles = null,
|
||||||
Object? userData = freezed,
|
Object? userData = freezed,
|
||||||
|
Object? itemModel = freezed,
|
||||||
}) {
|
}) {
|
||||||
return _then(_$SyncItemImpl(
|
return _then(_$SyncItemImpl(
|
||||||
id: null == id
|
id: null == id
|
||||||
|
|
@ -295,6 +308,10 @@ class __$$SyncItemImplCopyWithImpl<$Res>
|
||||||
? _value.userData
|
? _value.userData
|
||||||
: userData // ignore: cast_nullable_to_non_nullable
|
: userData // ignore: cast_nullable_to_non_nullable
|
||||||
as UserData?,
|
as UserData?,
|
||||||
|
itemModel: freezed == itemModel
|
||||||
|
? _value.itemModel
|
||||||
|
: itemModel // ignore: cast_nullable_to_non_nullable
|
||||||
|
as ItemBaseModel?,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -317,7 +334,8 @@ class _$SyncItemImpl extends _SyncItem {
|
||||||
this.fImages,
|
this.fImages,
|
||||||
final List<Chapter> fChapters = const [],
|
final List<Chapter> fChapters = const [],
|
||||||
final List<SubStreamModel> subtitles = const [],
|
final List<SubStreamModel> subtitles = const [],
|
||||||
@UserDataJsonSerializer() this.userData})
|
@UserDataJsonSerializer() this.userData,
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false) this.itemModel})
|
||||||
: _fChapters = fChapters,
|
: _fChapters = fChapters,
|
||||||
_subtitles = subtitles,
|
_subtitles = subtitles,
|
||||||
super._();
|
super._();
|
||||||
|
|
@ -369,10 +387,14 @@ class _$SyncItemImpl extends _SyncItem {
|
||||||
@override
|
@override
|
||||||
@UserDataJsonSerializer()
|
@UserDataJsonSerializer()
|
||||||
final UserData? userData;
|
final UserData? userData;
|
||||||
|
// ignore: invalid_annotation_target
|
||||||
|
@override
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
final ItemBaseModel? itemModel;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
return 'SyncedItem(id: $id, syncing: $syncing, parentId: $parentId, userId: $userId, path: $path, markedForDelete: $markedForDelete, sortName: $sortName, fileSize: $fileSize, videoFileName: $videoFileName, mediaSegments: $mediaSegments, fTrickPlayModel: $fTrickPlayModel, fImages: $fImages, fChapters: $fChapters, subtitles: $subtitles, userData: $userData)';
|
return 'SyncedItem(id: $id, syncing: $syncing, parentId: $parentId, userId: $userId, path: $path, markedForDelete: $markedForDelete, sortName: $sortName, fileSize: $fileSize, videoFileName: $videoFileName, mediaSegments: $mediaSegments, fTrickPlayModel: $fTrickPlayModel, fImages: $fImages, fChapters: $fChapters, subtitles: $subtitles, userData: $userData, itemModel: $itemModel)';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Create a copy of SyncedItem
|
/// Create a copy of SyncedItem
|
||||||
|
|
@ -400,7 +422,9 @@ abstract class _SyncItem extends SyncedItem {
|
||||||
final ImagesData? fImages,
|
final ImagesData? fImages,
|
||||||
final List<Chapter> fChapters,
|
final List<Chapter> fChapters,
|
||||||
final List<SubStreamModel> subtitles,
|
final List<SubStreamModel> subtitles,
|
||||||
@UserDataJsonSerializer() final UserData? userData}) = _$SyncItemImpl;
|
@UserDataJsonSerializer() final UserData? userData,
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
final ItemBaseModel? itemModel}) = _$SyncItemImpl;
|
||||||
_SyncItem._() : super._();
|
_SyncItem._() : super._();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|
@ -433,7 +457,10 @@ abstract class _SyncItem extends SyncedItem {
|
||||||
List<SubStreamModel> get subtitles;
|
List<SubStreamModel> get subtitles;
|
||||||
@override
|
@override
|
||||||
@UserDataJsonSerializer()
|
@UserDataJsonSerializer()
|
||||||
UserData? get userData;
|
UserData? get userData; // ignore: invalid_annotation_target
|
||||||
|
@override
|
||||||
|
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||||
|
ItemBaseModel? get itemModel;
|
||||||
|
|
||||||
/// Create a copy of SyncedItem
|
/// Create a copy of SyncedItem
|
||||||
/// with the given fields replaced by the non-null parameter values.
|
/// with the given fields replaced by the non-null parameter values.
|
||||||
|
|
|
||||||
|
|
@ -72,20 +72,16 @@ class EpisodeDetailsProvider extends StateNotifier<EpisodeDetailModel> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _tryToCreateOfflineState(ItemBaseModel item) {
|
Future<void> _tryToCreateOfflineState(ItemBaseModel item) async {
|
||||||
final syncNotifier = ref.read(syncProvider.notifier);
|
final syncNotifier = ref.read(syncProvider.notifier);
|
||||||
final episodeModel = syncNotifier.getSyncedItem(item)?.createItemModel(ref) as EpisodeModel?;
|
final episodeModel = (await syncNotifier.getSyncedItem(item))?.itemModel as EpisodeModel?;
|
||||||
if (episodeModel == null) return;
|
if (episodeModel == null) return;
|
||||||
final seriesSyncedItem = syncNotifier.getSyncedItem(episodeModel.parentBaseModel);
|
final seriesSyncedItem = await syncNotifier.getSyncedItem(episodeModel.parentBaseModel);
|
||||||
if (seriesSyncedItem == null) return;
|
if (seriesSyncedItem == null) return;
|
||||||
final seriesModel = seriesSyncedItem.createItemModel(ref) as SeriesModel?;
|
final seriesModel = seriesSyncedItem.itemModel as SeriesModel?;
|
||||||
if (seriesModel == null) return;
|
if (seriesModel == null) return;
|
||||||
final episodes = syncNotifier
|
final episodes = (await syncNotifier.getNestedChildren(seriesSyncedItem))
|
||||||
.getNestedChildren(seriesSyncedItem)
|
.map((e) => e.itemModel)
|
||||||
.map(
|
|
||||||
(e) => e.createItemModel(ref),
|
|
||||||
)
|
|
||||||
.nonNulls
|
|
||||||
.whereType<EpisodeModel>()
|
.whereType<EpisodeModel>()
|
||||||
.toList();
|
.toList();
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
|
|
|
||||||
|
|
@ -35,11 +35,11 @@ class MovieDetails extends _$MovieDetails {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _tryToCreateOfflineState(ItemBaseModel item) {
|
void _tryToCreateOfflineState(ItemBaseModel item) async {
|
||||||
final syncNotifier = ref.read(syncProvider.notifier);
|
final syncNotifier = ref.read(syncProvider.notifier);
|
||||||
final syncedItem = syncNotifier.getParentItem(item.id);
|
final syncedItem = await syncNotifier.getParentItem(item.id);
|
||||||
if (syncedItem == null) return;
|
if (syncedItem == null) return;
|
||||||
final movieModel = syncedItem.createItemModel(ref) as MovieModel;
|
final movieModel = syncedItem.itemModel as MovieModel?;
|
||||||
state = movieModel;
|
state = movieModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ part of 'movies_details_provider.dart';
|
||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$movieDetailsHash() => r'872ea61464ef8493c7e6c559c526377f1c8f6a6d';
|
String _$movieDetailsHash() => r'a9d8d2eeb7fa37652f25c1820b5e346efeeb59fc';
|
||||||
|
|
||||||
/// Copied from Dart SDK
|
/// Copied from Dart SDK
|
||||||
class _SystemHash {
|
class _SystemHash {
|
||||||
|
|
|
||||||
|
|
@ -29,7 +29,12 @@ class SeasonDetailsNotifier extends StateNotifier<SeasonModel?> {
|
||||||
seriesId: newState?.seriesId ?? "",
|
seriesId: newState?.seriesId ?? "",
|
||||||
seasonId: newState?.id,
|
seasonId: newState?.id,
|
||||||
season: newState?.season,
|
season: newState?.season,
|
||||||
fields: [ItemFields.overview],
|
fields: [
|
||||||
|
ItemFields.overview,
|
||||||
|
ItemFields.candelete,
|
||||||
|
ItemFields.candownload,
|
||||||
|
ItemFields.parentid,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
newState = newState?.copyWith(episodes: EpisodeModel.episodesFromDto(episodes.body?.items, ref).toList());
|
newState = newState?.copyWith(episodes: EpisodeModel.episodesFromDto(episodes.body?.items, ref).toList());
|
||||||
state = newState;
|
state = newState;
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,7 @@ class SeriesDetailViewNotifier extends StateNotifier<SeriesModel?> {
|
||||||
ItemFields.mediasources,
|
ItemFields.mediasources,
|
||||||
ItemFields.overview,
|
ItemFields.overview,
|
||||||
ItemFields.candownload,
|
ItemFields.candownload,
|
||||||
|
ItemFields.childcount,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
final newEpisodes = EpisodeModel.episodesFromDto(
|
final newEpisodes = EpisodeModel.episodesFromDto(
|
||||||
|
|
@ -45,10 +46,19 @@ class SeriesDetailViewNotifier extends StateNotifier<SeriesModel?> {
|
||||||
ref,
|
ref,
|
||||||
);
|
);
|
||||||
final episodesCanDownload = newEpisodes.any((episode) => episode.canDownload == true);
|
final episodesCanDownload = newEpisodes.any((episode) => episode.canDownload == true);
|
||||||
final seasons = await api.showsSeriesIdSeasonsGet(seriesId: seriesModel.id);
|
final seasons = await api.showsSeriesIdSeasonsGet(seriesId: seriesModel.id, fields: [
|
||||||
|
ItemFields.mediastreams,
|
||||||
|
ItemFields.mediasources,
|
||||||
|
ItemFields.overview,
|
||||||
|
ItemFields.candownload,
|
||||||
|
ItemFields.childcount,
|
||||||
|
]);
|
||||||
newState = newState.copyWith(
|
newState = newState.copyWith(
|
||||||
seasons: SeasonModel.seasonsFromDto(seasons.body?.items, ref)
|
seasons: SeasonModel.seasonsFromDto(seasons.body?.items, ref)
|
||||||
.map((element) => element.copyWith(canDownload: true))
|
.map((element) => element.copyWith(
|
||||||
|
canDownload: true,
|
||||||
|
episodes: newEpisodes.where((episode) => episode.season == element.season).toList(),
|
||||||
|
))
|
||||||
.toList(),
|
.toList(),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -68,20 +78,16 @@ class SeriesDetailViewNotifier extends StateNotifier<SeriesModel?> {
|
||||||
|
|
||||||
Future<void> _tryToCreateOfflineState(ItemBaseModel series) async {
|
Future<void> _tryToCreateOfflineState(ItemBaseModel series) async {
|
||||||
final syncNotifier = ref.read(syncProvider.notifier);
|
final syncNotifier = ref.read(syncProvider.notifier);
|
||||||
final syncedItem = syncNotifier.getSyncedItem(series);
|
final syncedItem = await syncNotifier.getSyncedItem(series);
|
||||||
if (syncedItem == null) return;
|
if (syncedItem == null) return;
|
||||||
final seriesModel = syncedItem.createItemModel(ref) as SeriesModel;
|
final seriesModel = syncedItem.itemModel as SeriesModel;
|
||||||
final allChildren = syncedItem
|
final allChildren = (await syncedItem.getNestedChildren(ref)).map((e) => e.itemModel).toList();
|
||||||
.getNestedChildren(ref)
|
if (mounted) {
|
||||||
.map(
|
|
||||||
(e) => e.createItemModel(ref),
|
|
||||||
)
|
|
||||||
.nonNulls
|
|
||||||
.toList();
|
|
||||||
state = seriesModel.copyWith(
|
state = seriesModel.copyWith(
|
||||||
availableEpisodes: allChildren.whereType<EpisodeModel>().toList(),
|
availableEpisodes: allChildren.whereType<EpisodeModel>().toList(),
|
||||||
seasons: allChildren.whereType<SeasonModel>().toList(),
|
seasons: allChildren.whereType<SeasonModel>().toList(),
|
||||||
);
|
);
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -536,7 +536,10 @@ class JellyService {
|
||||||
return api.showsSeriesIdEpisodesGet(
|
return api.showsSeriesIdEpisodesGet(
|
||||||
seriesId: seriesId,
|
seriesId: seriesId,
|
||||||
userId: account?.id,
|
userId: account?.id,
|
||||||
fields: fields,
|
fields: [
|
||||||
|
...?fields,
|
||||||
|
ItemFields.parentid,
|
||||||
|
],
|
||||||
isMissing: isMissing,
|
isMissing: isMissing,
|
||||||
limit: limit,
|
limit: limit,
|
||||||
sortBy: sortBy,
|
sortBy: sortBy,
|
||||||
|
|
@ -694,7 +697,10 @@ class JellyService {
|
||||||
seriesId: seriesId,
|
seriesId: seriesId,
|
||||||
isMissing: isMissing,
|
isMissing: isMissing,
|
||||||
enableUserData: enableUserData,
|
enableUserData: enableUserData,
|
||||||
fields: fields,
|
fields: [
|
||||||
|
...?fields,
|
||||||
|
ItemFields.parentid,
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
Future<Response<QueryFilters>> itemsFilters2Get({
|
Future<Response<QueryFilters>> itemsFilters2Get({
|
||||||
|
|
@ -903,7 +909,7 @@ class JellyService {
|
||||||
|
|
||||||
Future<Response<dynamic>> deleteItem(String itemId) => api.itemsItemIdDelete(itemId: itemId);
|
Future<Response<dynamic>> deleteItem(String itemId) => api.itemsItemIdDelete(itemId: itemId);
|
||||||
|
|
||||||
Future<UserConfiguration?> _updateUserConfiguration(UserConfiguration newUserConfiguration) async {
|
Future<UserConfiguration?> _updateUserConfiguration(UserConfiguration newUserConfiguration) async {
|
||||||
if (account?.id == null) return null;
|
if (account?.id == null) return null;
|
||||||
|
|
||||||
final response = await api.usersConfigurationPost(
|
final response = await api.usersConfigurationPost(
|
||||||
|
|
@ -915,9 +921,9 @@ Future<UserConfiguration?> _updateUserConfiguration(UserConfiguration newUserCon
|
||||||
return newUserConfiguration;
|
return newUserConfiguration;
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<UserConfiguration?> updateRememberAudioSelections() {
|
Future<UserConfiguration?> updateRememberAudioSelections() {
|
||||||
final currentUserConfiguration = account?.userConfiguration;
|
final currentUserConfiguration = account?.userConfiguration;
|
||||||
if (currentUserConfiguration == null) return Future.value(null);
|
if (currentUserConfiguration == null) return Future.value(null);
|
||||||
|
|
||||||
|
|
@ -925,9 +931,9 @@ Future<UserConfiguration?> updateRememberAudioSelections() {
|
||||||
rememberAudioSelections: !(currentUserConfiguration.rememberAudioSelections ?? false),
|
rememberAudioSelections: !(currentUserConfiguration.rememberAudioSelections ?? false),
|
||||||
);
|
);
|
||||||
return _updateUserConfiguration(updated);
|
return _updateUserConfiguration(updated);
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<UserConfiguration?> updateRememberSubtitleSelections() {
|
Future<UserConfiguration?> updateRememberSubtitleSelections() {
|
||||||
final current = account?.userConfiguration;
|
final current = account?.userConfiguration;
|
||||||
if (current == null) return Future.value(null);
|
if (current == null) return Future.value(null);
|
||||||
|
|
||||||
|
|
@ -935,5 +941,5 @@ Future<UserConfiguration?> updateRememberSubtitleSelections() {
|
||||||
rememberSubtitleSelections: !(current.rememberSubtitleSelections ?? false),
|
rememberSubtitleSelections: !(current.rememberSubtitleSelections ?? false),
|
||||||
);
|
);
|
||||||
return _updateUserConfiguration(updated);
|
return _updateUserConfiguration(updated);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,43 +1,34 @@
|
||||||
import 'package:isar/isar.dart';
|
import 'package:background_downloader/background_downloader.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
|
import 'package:fladder/models/item_base_model.dart';
|
||||||
import 'package:fladder/models/syncing/download_stream.dart';
|
import 'package:fladder/models/syncing/download_stream.dart';
|
||||||
import 'package:fladder/models/syncing/i_synced_item.dart';
|
|
||||||
import 'package:fladder/models/syncing/sync_item.dart';
|
import 'package:fladder/models/syncing/sync_item.dart';
|
||||||
import 'package:fladder/providers/sync_provider.dart';
|
import 'package:fladder/providers/sync_provider.dart';
|
||||||
|
|
||||||
part 'sync_provider_helpers.g.dart';
|
part 'sync_provider_helpers.g.dart';
|
||||||
|
|
||||||
@riverpod
|
@riverpod
|
||||||
class SyncChildren extends _$SyncChildren {
|
Stream<SyncedItem?> syncedItem(Ref ref, ItemBaseModel? item) {
|
||||||
|
final id = item?.id;
|
||||||
|
if (id == null || id.isEmpty) {
|
||||||
|
return Stream.value(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ref.watch(syncProvider.notifier).db.getItem(id).watchSingleOrNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@riverpod
|
||||||
|
class SyncedChildren extends _$SyncedChildren {
|
||||||
@override
|
@override
|
||||||
List<SyncedItem> build(SyncedItem root) {
|
FutureOr<List<SyncedItem>> build(SyncedItem item) => ref.read(syncProvider.notifier).getChildren(item);
|
||||||
final isar = ref.watch(syncProvider.notifier).isar;
|
}
|
||||||
final syncPath = ref.read(syncProvider.notifier).syncPath ?? "";
|
|
||||||
|
|
||||||
if (isar == null) return [];
|
@riverpod
|
||||||
|
class SyncedNestedChildren extends _$SyncedNestedChildren {
|
||||||
final all = <SyncedItem>[];
|
@override
|
||||||
List<SyncedItem> toProcess = [root];
|
FutureOr<List<SyncedItem>> build(SyncedItem item) => ref.read(syncProvider.notifier).getNestedChildren(item);
|
||||||
|
|
||||||
while (toProcess.isNotEmpty) {
|
|
||||||
final parentIds = toProcess.map((e) => e.id).toList();
|
|
||||||
|
|
||||||
final children = <ISyncedItem>[];
|
|
||||||
for (final id in parentIds) {
|
|
||||||
final results = isar.iSyncedItems.where().parentIdEqualTo(id).sortBySortName().findAll();
|
|
||||||
children.addAll(results);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (children.isEmpty) break;
|
|
||||||
|
|
||||||
final wrapped = children.map((e) => SyncedItem.fromIsar(e, syncPath)).toList();
|
|
||||||
all.addAll(wrapped);
|
|
||||||
toProcess = wrapped;
|
|
||||||
}
|
|
||||||
|
|
||||||
return all;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@riverpod
|
@riverpod
|
||||||
|
|
@ -55,46 +46,30 @@ class SyncDownloadStatus extends _$SyncDownloadStatus {
|
||||||
int downloadCount = 0;
|
int downloadCount = 0;
|
||||||
double fullProgress = mainStream.hasDownload ? mainStream.progress : 0.0;
|
double fullProgress = mainStream.hasDownload ? mainStream.progress : 0.0;
|
||||||
|
|
||||||
|
int fullySyncedChildren = 0;
|
||||||
|
|
||||||
for (var i = 0; i < nestedChildren.length; i++) {
|
for (var i = 0; i < nestedChildren.length; i++) {
|
||||||
final childItem = nestedChildren[i];
|
final childItem = nestedChildren[i];
|
||||||
final downloadStream = ref.read(downloadTasksProvider(childItem.id));
|
final downloadStream = ref.read(downloadTasksProvider(childItem.id));
|
||||||
|
if (childItem.videoFile.existsSync()) {
|
||||||
|
fullySyncedChildren++;
|
||||||
|
}
|
||||||
if (downloadStream.hasDownload) {
|
if (downloadStream.hasDownload) {
|
||||||
downloadCount++;
|
downloadCount++;
|
||||||
fullProgress += downloadStream.progress;
|
fullProgress += downloadStream.progress;
|
||||||
mainStream = mainStream.copyWith(status: downloadStream.status);
|
mainStream = mainStream.copyWith(
|
||||||
}
|
status: mainStream.status != TaskStatus.running ? downloadStream.status : mainStream.status,
|
||||||
}
|
|
||||||
|
|
||||||
return mainStream.copyWith(
|
|
||||||
progress: fullProgress / downloadCount.clamp(1, double.infinity).toInt(),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
@riverpod
|
|
||||||
class SyncStatuses extends _$SyncStatuses {
|
|
||||||
@override
|
|
||||||
FutureOr<SyncStatus> build(SyncedItem arg, List<SyncedItem>? children) async {
|
|
||||||
final nestedChildren = children;
|
|
||||||
|
|
||||||
ref.watch(downloadTasksProvider(arg.id));
|
|
||||||
if (nestedChildren != null) {
|
|
||||||
for (var element in nestedChildren) {
|
|
||||||
ref.watch(downloadTasksProvider(element.id));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (var i = 0; i < nestedChildren.length; i++) {
|
int syncAbleChildren = nestedChildren.where((element) => element.hasVideoFile).length;
|
||||||
final item = nestedChildren[i];
|
|
||||||
if (item.hasVideoFile && !await item.videoFile.exists()) {
|
|
||||||
return SyncStatus.partially;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (arg.hasVideoFile && !await arg.videoFile.exists()) {
|
var fullySynced = nestedChildren.isNotEmpty ? fullySyncedChildren == syncAbleChildren : arg.videoFile.existsSync();
|
||||||
return SyncStatus.partially;
|
return mainStream.copyWith(
|
||||||
}
|
status: fullySynced ? TaskStatus.complete : mainStream.status,
|
||||||
return SyncStatus.complete;
|
progress: fullProgress / downloadCount.clamp(1, double.infinity).toInt(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -112,9 +87,11 @@ class SyncSize extends _$SyncSize {
|
||||||
ref.watch(downloadTasksProvider(element.id));
|
ref.watch(downloadTasksProvider(element.id));
|
||||||
}
|
}
|
||||||
for (var element in nestedChildren) {
|
for (var element in nestedChildren) {
|
||||||
|
if (element.videoFile.existsSync()) {
|
||||||
size += element.fileSize ?? 0;
|
size += element.fileSize ?? 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ part of 'sync_provider_helpers.dart';
|
||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$syncChildrenHash() => r'c5a90d630d49f59ad4fbaacb5154f1205799f5ab';
|
String _$syncedItemHash() => r'7b1178ba78529ebf65425aa4cb8b239a28c7914b';
|
||||||
|
|
||||||
/// Copied from Dart SDK
|
/// Copied from Dart SDK
|
||||||
class _SystemHash {
|
class _SystemHash {
|
||||||
|
|
@ -29,39 +29,30 @@ class _SystemHash {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class _$SyncChildren
|
/// See also [syncedItem].
|
||||||
extends BuildlessAutoDisposeNotifier<List<SyncedItem>> {
|
@ProviderFor(syncedItem)
|
||||||
late final SyncedItem root;
|
const syncedItemProvider = SyncedItemFamily();
|
||||||
|
|
||||||
List<SyncedItem> build(
|
/// See also [syncedItem].
|
||||||
SyncedItem root,
|
class SyncedItemFamily extends Family<AsyncValue<SyncedItem?>> {
|
||||||
);
|
/// See also [syncedItem].
|
||||||
}
|
const SyncedItemFamily();
|
||||||
|
|
||||||
/// See also [SyncChildren].
|
/// See also [syncedItem].
|
||||||
@ProviderFor(SyncChildren)
|
SyncedItemProvider call(
|
||||||
const syncChildrenProvider = SyncChildrenFamily();
|
ItemBaseModel? item,
|
||||||
|
|
||||||
/// See also [SyncChildren].
|
|
||||||
class SyncChildrenFamily extends Family<List<SyncedItem>> {
|
|
||||||
/// See also [SyncChildren].
|
|
||||||
const SyncChildrenFamily();
|
|
||||||
|
|
||||||
/// See also [SyncChildren].
|
|
||||||
SyncChildrenProvider call(
|
|
||||||
SyncedItem root,
|
|
||||||
) {
|
) {
|
||||||
return SyncChildrenProvider(
|
return SyncedItemProvider(
|
||||||
root,
|
item,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
SyncChildrenProvider getProviderOverride(
|
SyncedItemProvider getProviderOverride(
|
||||||
covariant SyncChildrenProvider provider,
|
covariant SyncedItemProvider provider,
|
||||||
) {
|
) {
|
||||||
return call(
|
return call(
|
||||||
provider.root,
|
provider.item,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -77,81 +68,75 @@ class SyncChildrenFamily extends Family<List<SyncedItem>> {
|
||||||
_allTransitiveDependencies;
|
_allTransitiveDependencies;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String? get name => r'syncChildrenProvider';
|
String? get name => r'syncedItemProvider';
|
||||||
}
|
}
|
||||||
|
|
||||||
/// See also [SyncChildren].
|
/// See also [syncedItem].
|
||||||
class SyncChildrenProvider
|
class SyncedItemProvider extends AutoDisposeStreamProvider<SyncedItem?> {
|
||||||
extends AutoDisposeNotifierProviderImpl<SyncChildren, List<SyncedItem>> {
|
/// See also [syncedItem].
|
||||||
/// See also [SyncChildren].
|
SyncedItemProvider(
|
||||||
SyncChildrenProvider(
|
ItemBaseModel? item,
|
||||||
SyncedItem root,
|
|
||||||
) : this._internal(
|
) : this._internal(
|
||||||
() => SyncChildren()..root = root,
|
(ref) => syncedItem(
|
||||||
from: syncChildrenProvider,
|
ref as SyncedItemRef,
|
||||||
name: r'syncChildrenProvider',
|
item,
|
||||||
|
),
|
||||||
|
from: syncedItemProvider,
|
||||||
|
name: r'syncedItemProvider',
|
||||||
debugGetCreateSourceHash:
|
debugGetCreateSourceHash:
|
||||||
const bool.fromEnvironment('dart.vm.product')
|
const bool.fromEnvironment('dart.vm.product')
|
||||||
? null
|
? null
|
||||||
: _$syncChildrenHash,
|
: _$syncedItemHash,
|
||||||
dependencies: SyncChildrenFamily._dependencies,
|
dependencies: SyncedItemFamily._dependencies,
|
||||||
allTransitiveDependencies:
|
allTransitiveDependencies:
|
||||||
SyncChildrenFamily._allTransitiveDependencies,
|
SyncedItemFamily._allTransitiveDependencies,
|
||||||
root: root,
|
item: item,
|
||||||
);
|
);
|
||||||
|
|
||||||
SyncChildrenProvider._internal(
|
SyncedItemProvider._internal(
|
||||||
super._createNotifier, {
|
super._createNotifier, {
|
||||||
required super.name,
|
required super.name,
|
||||||
required super.dependencies,
|
required super.dependencies,
|
||||||
required super.allTransitiveDependencies,
|
required super.allTransitiveDependencies,
|
||||||
required super.debugGetCreateSourceHash,
|
required super.debugGetCreateSourceHash,
|
||||||
required super.from,
|
required super.from,
|
||||||
required this.root,
|
required this.item,
|
||||||
}) : super.internal();
|
}) : super.internal();
|
||||||
|
|
||||||
final SyncedItem root;
|
final ItemBaseModel? item;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<SyncedItem> runNotifierBuild(
|
Override overrideWith(
|
||||||
covariant SyncChildren notifier,
|
Stream<SyncedItem?> Function(SyncedItemRef provider) create,
|
||||||
) {
|
) {
|
||||||
return notifier.build(
|
|
||||||
root,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Override overrideWith(SyncChildren Function() create) {
|
|
||||||
return ProviderOverride(
|
return ProviderOverride(
|
||||||
origin: this,
|
origin: this,
|
||||||
override: SyncChildrenProvider._internal(
|
override: SyncedItemProvider._internal(
|
||||||
() => create()..root = root,
|
(ref) => create(ref as SyncedItemRef),
|
||||||
from: from,
|
from: from,
|
||||||
name: null,
|
name: null,
|
||||||
dependencies: null,
|
dependencies: null,
|
||||||
allTransitiveDependencies: null,
|
allTransitiveDependencies: null,
|
||||||
debugGetCreateSourceHash: null,
|
debugGetCreateSourceHash: null,
|
||||||
root: root,
|
item: item,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
AutoDisposeNotifierProviderElement<SyncChildren, List<SyncedItem>>
|
AutoDisposeStreamProviderElement<SyncedItem?> createElement() {
|
||||||
createElement() {
|
return _SyncedItemProviderElement(this);
|
||||||
return _SyncChildrenProviderElement(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return other is SyncChildrenProvider && other.root == root;
|
return other is SyncedItemProvider && other.item == item;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode {
|
int get hashCode {
|
||||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||||
hash = _SystemHash.combine(hash, root.hashCode);
|
hash = _SystemHash.combine(hash, item.hashCode);
|
||||||
|
|
||||||
return _SystemHash.finish(hash);
|
return _SystemHash.finish(hash);
|
||||||
}
|
}
|
||||||
|
|
@ -159,22 +144,316 @@ class SyncChildrenProvider
|
||||||
|
|
||||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
// ignore: unused_element
|
// ignore: unused_element
|
||||||
mixin SyncChildrenRef on AutoDisposeNotifierProviderRef<List<SyncedItem>> {
|
mixin SyncedItemRef on AutoDisposeStreamProviderRef<SyncedItem?> {
|
||||||
/// The parameter `root` of this provider.
|
/// The parameter `item` of this provider.
|
||||||
SyncedItem get root;
|
ItemBaseModel? get item;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SyncChildrenProviderElement
|
class _SyncedItemProviderElement
|
||||||
extends AutoDisposeNotifierProviderElement<SyncChildren, List<SyncedItem>>
|
extends AutoDisposeStreamProviderElement<SyncedItem?> with SyncedItemRef {
|
||||||
with SyncChildrenRef {
|
_SyncedItemProviderElement(super.provider);
|
||||||
_SyncChildrenProviderElement(super.provider);
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
SyncedItem get root => (origin as SyncChildrenProvider).root;
|
ItemBaseModel? get item => (origin as SyncedItemProvider).item;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$syncedChildrenHash() => r'2b6ce1611750785060df6317ce0ea25e2dc0aeb4';
|
||||||
|
|
||||||
|
abstract class _$SyncedChildren
|
||||||
|
extends BuildlessAutoDisposeAsyncNotifier<List<SyncedItem>> {
|
||||||
|
late final SyncedItem item;
|
||||||
|
|
||||||
|
FutureOr<List<SyncedItem>> build(
|
||||||
|
SyncedItem item,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [SyncedChildren].
|
||||||
|
@ProviderFor(SyncedChildren)
|
||||||
|
const syncedChildrenProvider = SyncedChildrenFamily();
|
||||||
|
|
||||||
|
/// See also [SyncedChildren].
|
||||||
|
class SyncedChildrenFamily extends Family<AsyncValue<List<SyncedItem>>> {
|
||||||
|
/// See also [SyncedChildren].
|
||||||
|
const SyncedChildrenFamily();
|
||||||
|
|
||||||
|
/// See also [SyncedChildren].
|
||||||
|
SyncedChildrenProvider call(
|
||||||
|
SyncedItem item,
|
||||||
|
) {
|
||||||
|
return SyncedChildrenProvider(
|
||||||
|
item,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
SyncedChildrenProvider getProviderOverride(
|
||||||
|
covariant SyncedChildrenProvider provider,
|
||||||
|
) {
|
||||||
|
return call(
|
||||||
|
provider.item,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||||
|
|
||||||
|
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||||
|
_allTransitiveDependencies;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get name => r'syncedChildrenProvider';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [SyncedChildren].
|
||||||
|
class SyncedChildrenProvider extends AutoDisposeAsyncNotifierProviderImpl<
|
||||||
|
SyncedChildren, List<SyncedItem>> {
|
||||||
|
/// See also [SyncedChildren].
|
||||||
|
SyncedChildrenProvider(
|
||||||
|
SyncedItem item,
|
||||||
|
) : this._internal(
|
||||||
|
() => SyncedChildren()..item = item,
|
||||||
|
from: syncedChildrenProvider,
|
||||||
|
name: r'syncedChildrenProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$syncedChildrenHash,
|
||||||
|
dependencies: SyncedChildrenFamily._dependencies,
|
||||||
|
allTransitiveDependencies:
|
||||||
|
SyncedChildrenFamily._allTransitiveDependencies,
|
||||||
|
item: item,
|
||||||
|
);
|
||||||
|
|
||||||
|
SyncedChildrenProvider._internal(
|
||||||
|
super._createNotifier, {
|
||||||
|
required super.name,
|
||||||
|
required super.dependencies,
|
||||||
|
required super.allTransitiveDependencies,
|
||||||
|
required super.debugGetCreateSourceHash,
|
||||||
|
required super.from,
|
||||||
|
required this.item,
|
||||||
|
}) : super.internal();
|
||||||
|
|
||||||
|
final SyncedItem item;
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<List<SyncedItem>> runNotifierBuild(
|
||||||
|
covariant SyncedChildren notifier,
|
||||||
|
) {
|
||||||
|
return notifier.build(
|
||||||
|
item,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Override overrideWith(SyncedChildren Function() create) {
|
||||||
|
return ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
override: SyncedChildrenProvider._internal(
|
||||||
|
() => create()..item = item,
|
||||||
|
from: from,
|
||||||
|
name: null,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
debugGetCreateSourceHash: null,
|
||||||
|
item: item,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
AutoDisposeAsyncNotifierProviderElement<SyncedChildren, List<SyncedItem>>
|
||||||
|
createElement() {
|
||||||
|
return _SyncedChildrenProviderElement(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return other is SyncedChildrenProvider && other.item == item;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||||
|
hash = _SystemHash.combine(hash, item.hashCode);
|
||||||
|
|
||||||
|
return _SystemHash.finish(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
|
mixin SyncedChildrenRef
|
||||||
|
on AutoDisposeAsyncNotifierProviderRef<List<SyncedItem>> {
|
||||||
|
/// The parameter `item` of this provider.
|
||||||
|
SyncedItem get item;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SyncedChildrenProviderElement
|
||||||
|
extends AutoDisposeAsyncNotifierProviderElement<SyncedChildren,
|
||||||
|
List<SyncedItem>> with SyncedChildrenRef {
|
||||||
|
_SyncedChildrenProviderElement(super.provider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
SyncedItem get item => (origin as SyncedChildrenProvider).item;
|
||||||
|
}
|
||||||
|
|
||||||
|
String _$syncedNestedChildrenHash() =>
|
||||||
|
r'ea8dd0e694efa6d6ec0c73d699b5fb3e933f9322';
|
||||||
|
|
||||||
|
abstract class _$SyncedNestedChildren
|
||||||
|
extends BuildlessAutoDisposeAsyncNotifier<List<SyncedItem>> {
|
||||||
|
late final SyncedItem item;
|
||||||
|
|
||||||
|
FutureOr<List<SyncedItem>> build(
|
||||||
|
SyncedItem item,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [SyncedNestedChildren].
|
||||||
|
@ProviderFor(SyncedNestedChildren)
|
||||||
|
const syncedNestedChildrenProvider = SyncedNestedChildrenFamily();
|
||||||
|
|
||||||
|
/// See also [SyncedNestedChildren].
|
||||||
|
class SyncedNestedChildrenFamily extends Family<AsyncValue<List<SyncedItem>>> {
|
||||||
|
/// See also [SyncedNestedChildren].
|
||||||
|
const SyncedNestedChildrenFamily();
|
||||||
|
|
||||||
|
/// See also [SyncedNestedChildren].
|
||||||
|
SyncedNestedChildrenProvider call(
|
||||||
|
SyncedItem item,
|
||||||
|
) {
|
||||||
|
return SyncedNestedChildrenProvider(
|
||||||
|
item,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
SyncedNestedChildrenProvider getProviderOverride(
|
||||||
|
covariant SyncedNestedChildrenProvider provider,
|
||||||
|
) {
|
||||||
|
return call(
|
||||||
|
provider.item,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
||||||
|
|
||||||
|
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
||||||
|
_allTransitiveDependencies;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? get name => r'syncedNestedChildrenProvider';
|
||||||
|
}
|
||||||
|
|
||||||
|
/// See also [SyncedNestedChildren].
|
||||||
|
class SyncedNestedChildrenProvider extends AutoDisposeAsyncNotifierProviderImpl<
|
||||||
|
SyncedNestedChildren, List<SyncedItem>> {
|
||||||
|
/// See also [SyncedNestedChildren].
|
||||||
|
SyncedNestedChildrenProvider(
|
||||||
|
SyncedItem item,
|
||||||
|
) : this._internal(
|
||||||
|
() => SyncedNestedChildren()..item = item,
|
||||||
|
from: syncedNestedChildrenProvider,
|
||||||
|
name: r'syncedNestedChildrenProvider',
|
||||||
|
debugGetCreateSourceHash:
|
||||||
|
const bool.fromEnvironment('dart.vm.product')
|
||||||
|
? null
|
||||||
|
: _$syncedNestedChildrenHash,
|
||||||
|
dependencies: SyncedNestedChildrenFamily._dependencies,
|
||||||
|
allTransitiveDependencies:
|
||||||
|
SyncedNestedChildrenFamily._allTransitiveDependencies,
|
||||||
|
item: item,
|
||||||
|
);
|
||||||
|
|
||||||
|
SyncedNestedChildrenProvider._internal(
|
||||||
|
super._createNotifier, {
|
||||||
|
required super.name,
|
||||||
|
required super.dependencies,
|
||||||
|
required super.allTransitiveDependencies,
|
||||||
|
required super.debugGetCreateSourceHash,
|
||||||
|
required super.from,
|
||||||
|
required this.item,
|
||||||
|
}) : super.internal();
|
||||||
|
|
||||||
|
final SyncedItem item;
|
||||||
|
|
||||||
|
@override
|
||||||
|
FutureOr<List<SyncedItem>> runNotifierBuild(
|
||||||
|
covariant SyncedNestedChildren notifier,
|
||||||
|
) {
|
||||||
|
return notifier.build(
|
||||||
|
item,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Override overrideWith(SyncedNestedChildren Function() create) {
|
||||||
|
return ProviderOverride(
|
||||||
|
origin: this,
|
||||||
|
override: SyncedNestedChildrenProvider._internal(
|
||||||
|
() => create()..item = item,
|
||||||
|
from: from,
|
||||||
|
name: null,
|
||||||
|
dependencies: null,
|
||||||
|
allTransitiveDependencies: null,
|
||||||
|
debugGetCreateSourceHash: null,
|
||||||
|
item: item,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
AutoDisposeAsyncNotifierProviderElement<SyncedNestedChildren,
|
||||||
|
List<SyncedItem>> createElement() {
|
||||||
|
return _SyncedNestedChildrenProviderElement(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) {
|
||||||
|
return other is SyncedNestedChildrenProvider && other.item == item;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode {
|
||||||
|
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
||||||
|
hash = _SystemHash.combine(hash, item.hashCode);
|
||||||
|
|
||||||
|
return _SystemHash.finish(hash);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
||||||
|
// ignore: unused_element
|
||||||
|
mixin SyncedNestedChildrenRef
|
||||||
|
on AutoDisposeAsyncNotifierProviderRef<List<SyncedItem>> {
|
||||||
|
/// The parameter `item` of this provider.
|
||||||
|
SyncedItem get item;
|
||||||
|
}
|
||||||
|
|
||||||
|
class _SyncedNestedChildrenProviderElement
|
||||||
|
extends AutoDisposeAsyncNotifierProviderElement<SyncedNestedChildren,
|
||||||
|
List<SyncedItem>> with SyncedNestedChildrenRef {
|
||||||
|
_SyncedNestedChildrenProviderElement(super.provider);
|
||||||
|
|
||||||
|
@override
|
||||||
|
SyncedItem get item => (origin as SyncedNestedChildrenProvider).item;
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$syncDownloadStatusHash() =>
|
String _$syncDownloadStatusHash() =>
|
||||||
r'1036352200e1138b4ef70e524c0baf13bb9cd452';
|
r'6ee039e094f1e007ebaeb20ae63430be829cdeb7';
|
||||||
|
|
||||||
abstract class _$SyncDownloadStatus
|
abstract class _$SyncDownloadStatus
|
||||||
extends BuildlessAutoDisposeNotifier<DownloadStream?> {
|
extends BuildlessAutoDisposeNotifier<DownloadStream?> {
|
||||||
|
|
@ -344,176 +623,7 @@ class _SyncDownloadStatusProviderElement
|
||||||
(origin as SyncDownloadStatusProvider).children;
|
(origin as SyncDownloadStatusProvider).children;
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$syncStatusesHash() => r'64a3499fc7b7bbdbd6594b1eec76cf42a119a041';
|
String _$syncSizeHash() => r'eeb6ab8dc1fdf5696c06e53f04a0e54ad68c6748';
|
||||||
|
|
||||||
abstract class _$SyncStatuses
|
|
||||||
extends BuildlessAutoDisposeAsyncNotifier<SyncStatus> {
|
|
||||||
late final SyncedItem arg;
|
|
||||||
late final List<SyncedItem>? children;
|
|
||||||
|
|
||||||
FutureOr<SyncStatus> build(
|
|
||||||
SyncedItem arg,
|
|
||||||
List<SyncedItem>? children,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// See also [SyncStatuses].
|
|
||||||
@ProviderFor(SyncStatuses)
|
|
||||||
const syncStatusesProvider = SyncStatusesFamily();
|
|
||||||
|
|
||||||
/// See also [SyncStatuses].
|
|
||||||
class SyncStatusesFamily extends Family<AsyncValue<SyncStatus>> {
|
|
||||||
/// See also [SyncStatuses].
|
|
||||||
const SyncStatusesFamily();
|
|
||||||
|
|
||||||
/// See also [SyncStatuses].
|
|
||||||
SyncStatusesProvider call(
|
|
||||||
SyncedItem arg,
|
|
||||||
List<SyncedItem>? children,
|
|
||||||
) {
|
|
||||||
return SyncStatusesProvider(
|
|
||||||
arg,
|
|
||||||
children,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
SyncStatusesProvider getProviderOverride(
|
|
||||||
covariant SyncStatusesProvider provider,
|
|
||||||
) {
|
|
||||||
return call(
|
|
||||||
provider.arg,
|
|
||||||
provider.children,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
static const Iterable<ProviderOrFamily>? _dependencies = null;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Iterable<ProviderOrFamily>? get dependencies => _dependencies;
|
|
||||||
|
|
||||||
static const Iterable<ProviderOrFamily>? _allTransitiveDependencies = null;
|
|
||||||
|
|
||||||
@override
|
|
||||||
Iterable<ProviderOrFamily>? get allTransitiveDependencies =>
|
|
||||||
_allTransitiveDependencies;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String? get name => r'syncStatusesProvider';
|
|
||||||
}
|
|
||||||
|
|
||||||
/// See also [SyncStatuses].
|
|
||||||
class SyncStatusesProvider
|
|
||||||
extends AutoDisposeAsyncNotifierProviderImpl<SyncStatuses, SyncStatus> {
|
|
||||||
/// See also [SyncStatuses].
|
|
||||||
SyncStatusesProvider(
|
|
||||||
SyncedItem arg,
|
|
||||||
List<SyncedItem>? children,
|
|
||||||
) : this._internal(
|
|
||||||
() => SyncStatuses()
|
|
||||||
..arg = arg
|
|
||||||
..children = children,
|
|
||||||
from: syncStatusesProvider,
|
|
||||||
name: r'syncStatusesProvider',
|
|
||||||
debugGetCreateSourceHash:
|
|
||||||
const bool.fromEnvironment('dart.vm.product')
|
|
||||||
? null
|
|
||||||
: _$syncStatusesHash,
|
|
||||||
dependencies: SyncStatusesFamily._dependencies,
|
|
||||||
allTransitiveDependencies:
|
|
||||||
SyncStatusesFamily._allTransitiveDependencies,
|
|
||||||
arg: arg,
|
|
||||||
children: children,
|
|
||||||
);
|
|
||||||
|
|
||||||
SyncStatusesProvider._internal(
|
|
||||||
super._createNotifier, {
|
|
||||||
required super.name,
|
|
||||||
required super.dependencies,
|
|
||||||
required super.allTransitiveDependencies,
|
|
||||||
required super.debugGetCreateSourceHash,
|
|
||||||
required super.from,
|
|
||||||
required this.arg,
|
|
||||||
required this.children,
|
|
||||||
}) : super.internal();
|
|
||||||
|
|
||||||
final SyncedItem arg;
|
|
||||||
final List<SyncedItem>? children;
|
|
||||||
|
|
||||||
@override
|
|
||||||
FutureOr<SyncStatus> runNotifierBuild(
|
|
||||||
covariant SyncStatuses notifier,
|
|
||||||
) {
|
|
||||||
return notifier.build(
|
|
||||||
arg,
|
|
||||||
children,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
Override overrideWith(SyncStatuses Function() create) {
|
|
||||||
return ProviderOverride(
|
|
||||||
origin: this,
|
|
||||||
override: SyncStatusesProvider._internal(
|
|
||||||
() => create()
|
|
||||||
..arg = arg
|
|
||||||
..children = children,
|
|
||||||
from: from,
|
|
||||||
name: null,
|
|
||||||
dependencies: null,
|
|
||||||
allTransitiveDependencies: null,
|
|
||||||
debugGetCreateSourceHash: null,
|
|
||||||
arg: arg,
|
|
||||||
children: children,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
AutoDisposeAsyncNotifierProviderElement<SyncStatuses, SyncStatus>
|
|
||||||
createElement() {
|
|
||||||
return _SyncStatusesProviderElement(this);
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
bool operator ==(Object other) {
|
|
||||||
return other is SyncStatusesProvider &&
|
|
||||||
other.arg == arg &&
|
|
||||||
other.children == children;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
|
||||||
int get hashCode {
|
|
||||||
var hash = _SystemHash.combine(0, runtimeType.hashCode);
|
|
||||||
hash = _SystemHash.combine(hash, arg.hashCode);
|
|
||||||
hash = _SystemHash.combine(hash, children.hashCode);
|
|
||||||
|
|
||||||
return _SystemHash.finish(hash);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Deprecated('Will be removed in 3.0. Use Ref instead')
|
|
||||||
// ignore: unused_element
|
|
||||||
mixin SyncStatusesRef on AutoDisposeAsyncNotifierProviderRef<SyncStatus> {
|
|
||||||
/// The parameter `arg` of this provider.
|
|
||||||
SyncedItem get arg;
|
|
||||||
|
|
||||||
/// The parameter `children` of this provider.
|
|
||||||
List<SyncedItem>? get children;
|
|
||||||
}
|
|
||||||
|
|
||||||
class _SyncStatusesProviderElement
|
|
||||||
extends AutoDisposeAsyncNotifierProviderElement<SyncStatuses, SyncStatus>
|
|
||||||
with SyncStatusesRef {
|
|
||||||
_SyncStatusesProviderElement(super.provider);
|
|
||||||
|
|
||||||
@override
|
|
||||||
SyncedItem get arg => (origin as SyncStatusesProvider).arg;
|
|
||||||
@override
|
|
||||||
List<SyncedItem>? get children => (origin as SyncStatusesProvider).children;
|
|
||||||
}
|
|
||||||
|
|
||||||
String _$syncSizeHash() => r'81797ecc4a6f600691b6f1fe0c16bae0228ec920';
|
|
||||||
|
|
||||||
abstract class _$SyncSize extends BuildlessAutoDisposeNotifier<int?> {
|
abstract class _$SyncSize extends BuildlessAutoDisposeNotifier<int?> {
|
||||||
late final SyncedItem arg;
|
late final SyncedItem arg;
|
||||||
|
|
|
||||||
|
|
@ -8,10 +8,10 @@ import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:background_downloader/background_downloader.dart';
|
import 'package:background_downloader/background_downloader.dart';
|
||||||
import 'package:collection/collection.dart';
|
import 'package:collection/collection.dart';
|
||||||
|
import 'package:drift_db_viewer/drift_db_viewer.dart';
|
||||||
import 'package:file_picker/file_picker.dart';
|
import 'package:file_picker/file_picker.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:http/http.dart' as http;
|
import 'package:http/http.dart' as http;
|
||||||
import 'package:isar/isar.dart';
|
|
||||||
import 'package:path/path.dart' as path;
|
import 'package:path/path.dart' as path;
|
||||||
import 'package:path_provider/path_provider.dart';
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
|
||||||
|
|
@ -20,13 +20,14 @@ import 'package:fladder/models/item_base_model.dart';
|
||||||
import 'package:fladder/models/items/chapters_model.dart';
|
import 'package:fladder/models/items/chapters_model.dart';
|
||||||
import 'package:fladder/models/items/episode_model.dart';
|
import 'package:fladder/models/items/episode_model.dart';
|
||||||
import 'package:fladder/models/items/images_models.dart';
|
import 'package:fladder/models/items/images_models.dart';
|
||||||
|
import 'package:fladder/models/items/item_shared_models.dart';
|
||||||
import 'package:fladder/models/items/media_streams_model.dart';
|
import 'package:fladder/models/items/media_streams_model.dart';
|
||||||
import 'package:fladder/models/items/movie_model.dart';
|
import 'package:fladder/models/items/movie_model.dart';
|
||||||
import 'package:fladder/models/items/season_model.dart';
|
import 'package:fladder/models/items/season_model.dart';
|
||||||
import 'package:fladder/models/items/series_model.dart';
|
import 'package:fladder/models/items/series_model.dart';
|
||||||
import 'package:fladder/models/items/trick_play_model.dart';
|
import 'package:fladder/models/items/trick_play_model.dart';
|
||||||
|
import 'package:fladder/models/syncing/database_item.dart';
|
||||||
import 'package:fladder/models/syncing/download_stream.dart';
|
import 'package:fladder/models/syncing/download_stream.dart';
|
||||||
import 'package:fladder/models/syncing/i_synced_item.dart';
|
|
||||||
import 'package:fladder/models/syncing/sync_item.dart';
|
import 'package:fladder/models/syncing/sync_item.dart';
|
||||||
import 'package:fladder/models/syncing/sync_settings_model.dart';
|
import 'package:fladder/models/syncing/sync_settings_model.dart';
|
||||||
import 'package:fladder/models/video_stream_model.dart';
|
import 'package:fladder/models/video_stream_model.dart';
|
||||||
|
|
@ -38,16 +39,27 @@ import 'package:fladder/providers/sync/background_download_provider.dart';
|
||||||
import 'package:fladder/providers/user_provider.dart';
|
import 'package:fladder/providers/user_provider.dart';
|
||||||
import 'package:fladder/screens/shared/fladder_snackbar.dart';
|
import 'package:fladder/screens/shared/fladder_snackbar.dart';
|
||||||
import 'package:fladder/util/localization_helper.dart';
|
import 'package:fladder/util/localization_helper.dart';
|
||||||
|
import 'package:fladder/util/migration/isar_drift_migration.dart';
|
||||||
|
|
||||||
final syncProvider = StateNotifierProvider<SyncNotifier, SyncSettingsModel>((ref) => throw UnimplementedError());
|
final syncProvider = StateNotifierProvider<SyncNotifier, SyncSettingsModel>((ref) => throw UnimplementedError());
|
||||||
|
|
||||||
final downloadTasksProvider = StateProvider.family<DownloadStream, String>((ref, id) => DownloadStream.empty());
|
final downloadTasksProvider = StateProvider.family<DownloadStream, String>((ref, id) => DownloadStream.empty());
|
||||||
|
|
||||||
class SyncNotifier extends StateNotifier<SyncSettingsModel> {
|
class SyncNotifier extends StateNotifier<SyncSettingsModel> {
|
||||||
SyncNotifier(this.ref, this.isar, this.mobileDirectory) : super(SyncSettingsModel()) {
|
SyncNotifier(this.ref, this.mobileDirectory) : super(SyncSettingsModel()) {
|
||||||
_init();
|
_init();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final Ref ref;
|
||||||
|
late final db = AppDatabase(ref);
|
||||||
|
final Directory mobileDirectory;
|
||||||
|
final String subPath = "Synced";
|
||||||
|
|
||||||
|
void migrateFromIsar() async {
|
||||||
|
await isarMigration(ref, db, mainDirectory.path);
|
||||||
|
_initializeQueryStream();
|
||||||
|
}
|
||||||
|
|
||||||
void _init() {
|
void _init() {
|
||||||
cleanupTemporaryFiles();
|
cleanupTemporaryFiles();
|
||||||
ref.listen(
|
ref.listen(
|
||||||
|
|
@ -55,35 +67,29 @@ class SyncNotifier extends StateNotifier<SyncSettingsModel> {
|
||||||
(previous, next) {
|
(previous, next) {
|
||||||
if (previous?.id != next?.id) {
|
if (previous?.id != next?.id) {
|
||||||
if (next?.id != null) {
|
if (next?.id != null) {
|
||||||
_initializeQueryStream(next?.id ?? "");
|
_initializeQueryStream();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
final userId = ref.read(userProvider)?.id;
|
_initializeQueryStream();
|
||||||
if (userId != null) {
|
|
||||||
_initializeQueryStream(userId);
|
migrateFromIsar();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void _initializeQueryStream(String userId) {
|
void _initializeQueryStream() async {
|
||||||
|
final userId = ref.read(userProvider)?.id;
|
||||||
|
if (userId == null) return;
|
||||||
_subscription?.cancel();
|
_subscription?.cancel();
|
||||||
|
|
||||||
final queryStream = getParentSyncItems
|
final queryStream = db.getParentItems.watch();
|
||||||
?.userIdEqualTo(userId)
|
|
||||||
.watch()
|
|
||||||
.asyncMap((event) => event.map((e) => SyncedItem.fromIsar(e, syncPath ?? "")).toList());
|
|
||||||
|
|
||||||
final initItems = getParentSyncItems
|
final initItems = await db.getParentItems.get();
|
||||||
?.userIdEqualTo(userId)
|
|
||||||
.findAll()
|
|
||||||
.mapIndexed((index, element) => SyncedItem.fromIsar(element, syncPath ?? ""))
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
state = state.copyWith(items: initItems ?? []);
|
state = state.copyWith(items: initItems);
|
||||||
|
|
||||||
_subscription = queryStream?.listen((items) {
|
_subscription = queryStream.listen((items) {
|
||||||
state = state.copyWith(items: items);
|
state = state.copyWith(items: items);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
@ -117,15 +123,8 @@ class SyncNotifier extends StateNotifier<SyncSettingsModel> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final Ref ref;
|
|
||||||
final Isar? isar;
|
|
||||||
final Directory mobileDirectory;
|
|
||||||
final String subPath = "Synced";
|
|
||||||
|
|
||||||
StreamSubscription<List<SyncedItem>>? _subscription;
|
StreamSubscription<List<SyncedItem>>? _subscription;
|
||||||
|
|
||||||
IsarCollection<String, ISyncedItem>? get syncedItems => isar?.iSyncedItems;
|
|
||||||
|
|
||||||
late final JellyService api = ref.read(jellyApiProvider);
|
late final JellyService api = ref.read(jellyApiProvider);
|
||||||
|
|
||||||
String? get _savePath => !kIsWeb && (Platform.isWindows || Platform.isLinux || Platform.isMacOS)
|
String? get _savePath => !kIsWeb && (Platform.isWindows || Platform.isLinux || Platform.isMacOS)
|
||||||
|
|
@ -151,9 +150,6 @@ class SyncNotifier extends StateNotifier<SyncSettingsModel> {
|
||||||
|
|
||||||
String? get syncPath => saveDirectory?.path;
|
String? get syncPath => saveDirectory?.path;
|
||||||
|
|
||||||
QueryBuilder<ISyncedItem, ISyncedItem, QAfterFilterCondition>? get getParentSyncItems =>
|
|
||||||
syncedItems?.where().parentIdIsNull();
|
|
||||||
|
|
||||||
Future<int> get directorySize async {
|
Future<int> get directorySize async {
|
||||||
if (saveDirectory == null) return 0;
|
if (saveDirectory == null) return 0;
|
||||||
var files = await saveDirectory!.list(recursive: true).toList();
|
var files = await saveDirectory!.list(recursive: true).toList();
|
||||||
|
|
@ -168,59 +164,62 @@ class SyncNotifier extends StateNotifier<SyncSettingsModel> {
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> refresh() async {
|
Future<void> refresh() async {
|
||||||
state = state.copyWith(
|
state = state.copyWith(items: (await db.getParentItems.get()));
|
||||||
items: (await getParentSyncItems?.userIdEqualTo(ref.read(userProvider)?.id).findAllAsync())
|
|
||||||
?.map((e) => SyncedItem.fromIsar(e, syncPath ?? ""))
|
|
||||||
.toList() ??
|
|
||||||
[]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
List<SyncedItem> getNestedChildren(SyncedItem item) {
|
Future<List<SyncedItem>> getNestedChildren(SyncedItem item) async => db.getNestedChildren(item);
|
||||||
final allChildren = <SyncedItem>[];
|
|
||||||
List<SyncedItem> toProcess = [item];
|
|
||||||
while (toProcess.isNotEmpty) {
|
|
||||||
final currentLevel = toProcess.map(
|
|
||||||
(parent) {
|
|
||||||
final children = syncedItems?.where().parentIdEqualTo(parent.id).sortBySortName().findAll();
|
|
||||||
return children?.map((e) => SyncedItem.fromIsar(e, ref.read(syncProvider.notifier).syncPath ?? "")) ??
|
|
||||||
<SyncedItem>[];
|
|
||||||
},
|
|
||||||
);
|
|
||||||
allChildren.addAll(currentLevel.expand((list) => list));
|
|
||||||
toProcess = currentLevel.expand((list) => list).toList();
|
|
||||||
}
|
|
||||||
return allChildren;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<SyncedItem> getChildren(SyncedItem item) {
|
Future<List<SyncedItem>> getChildren(SyncedItem root) async => await db.getChildren(root.id).get();
|
||||||
return (syncedItems?.where().parentIdEqualTo(item.id).sortBySortName().findAll())
|
|
||||||
?.map(
|
|
||||||
(e) => SyncedItem.fromIsar(e, syncPath ?? ""),
|
|
||||||
)
|
|
||||||
.toList() ??
|
|
||||||
[];
|
|
||||||
}
|
|
||||||
|
|
||||||
SyncedItem? getSyncedItem(ItemBaseModel? item) {
|
Future<SyncedItem?> getSyncedItem(ItemBaseModel? item) async {
|
||||||
final id = item?.id;
|
final id = item?.id;
|
||||||
if (id == null) return null;
|
if (id == null) return null;
|
||||||
final newItem = syncedItems?.get(id);
|
return await db.getItem(id).getSingleOrNull();
|
||||||
if (newItem == null) return null;
|
|
||||||
return SyncedItem.fromIsar(newItem, syncPath ?? "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
SyncedItem? getParentItem(String id) {
|
Future<SyncedItem?> getParentItem(String id) async => await db.getParent(id).getSingleOrNull();
|
||||||
ISyncedItem? newItem = syncedItems?.get(id);
|
|
||||||
while (newItem?.parentId != null) {
|
Future<SyncedItem> refreshSyncItem(SyncedItem item) async {
|
||||||
newItem = syncedItems?.get(newItem!.parentId!);
|
List<SyncedItem> itemsToSync = await getNestedChildren(item);
|
||||||
|
|
||||||
|
itemsToSync = [item, ...itemsToSync];
|
||||||
|
|
||||||
|
SyncedItem parentItem = item;
|
||||||
|
|
||||||
|
List<SyncedItem> newItems = [];
|
||||||
|
|
||||||
|
for (var i = 0; i < itemsToSync.length; i++) {
|
||||||
|
final itemToSync = itemsToSync[i];
|
||||||
|
final itemResponse = await api.usersUserIdItemsItemIdGetBaseItem(
|
||||||
|
itemId: itemToSync.id,
|
||||||
|
);
|
||||||
|
|
||||||
|
final itemModel = ItemBaseModel.fromBaseDto(itemResponse.bodyOrThrow, ref);
|
||||||
|
|
||||||
|
final syncedParent = await db.getItem(itemToSync.parentId ?? "").getSingleOrNull();
|
||||||
|
|
||||||
|
SyncedItem newSyncedItem = await _syncItemData(syncedParent, itemModel, itemResponse.bodyOrThrow);
|
||||||
|
|
||||||
|
final updatedItem = itemToSync.copyWith(
|
||||||
|
itemModel: newSyncedItem.createItemModel(ref),
|
||||||
|
sortName: newSyncedItem.sortName,
|
||||||
|
syncing: false,
|
||||||
|
fImages: newSyncedItem.fImages,
|
||||||
|
fTrickPlayModel: newSyncedItem.fTrickPlayModel,
|
||||||
|
subtitles: newSyncedItem.subtitles,
|
||||||
|
userData: UserData.determineLastUserData([item.userData, newSyncedItem.userData]),
|
||||||
|
);
|
||||||
|
|
||||||
|
newItems.add(updatedItem);
|
||||||
|
|
||||||
|
if (itemToSync.id == parentItem.id) {
|
||||||
|
parentItem = updatedItem;
|
||||||
}
|
}
|
||||||
if (newItem == null) return null;
|
|
||||||
return SyncedItem.fromIsar(newItem, syncPath ?? "");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemBaseModel? getItem(SyncedItem? syncedItem) {
|
await db.insertMultipleEntries(newItems);
|
||||||
if (syncedItem == null) return null;
|
|
||||||
return syncedItem.createItemModel(ref);
|
return parentItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> addSyncItem(BuildContext? context, ItemBaseModel item) async {
|
Future<void> addSyncItem(BuildContext? context, ItemBaseModel item) async {
|
||||||
|
|
@ -229,7 +228,7 @@ class SyncNotifier extends StateNotifier<SyncSettingsModel> {
|
||||||
if (saveDirectory == null) {
|
if (saveDirectory == null) {
|
||||||
String? selectedDirectory =
|
String? selectedDirectory =
|
||||||
await FilePicker.platform.getDirectoryPath(dialogTitle: context.localized.syncSelectDownloadsFolder);
|
await FilePicker.platform.getDirectoryPath(dialogTitle: context.localized.syncSelectDownloadsFolder);
|
||||||
if (selectedDirectory?.isEmpty == true) {
|
if (selectedDirectory?.isEmpty == true && context.mounted) {
|
||||||
fladderSnackbar(context, title: context.localized.syncNoFolderSetup);
|
fladderSnackbar(context, title: context.localized.syncNoFolderSetup);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -244,18 +243,24 @@ class SyncNotifier extends StateNotifier<SyncSettingsModel> {
|
||||||
MovieModel movie => await syncMovie(movie),
|
MovieModel movie => await syncMovie(movie),
|
||||||
_ => null
|
_ => null
|
||||||
};
|
};
|
||||||
|
if (context.mounted) {
|
||||||
fladderSnackbar(context,
|
fladderSnackbar(context,
|
||||||
title: newSync != null
|
title: newSync != null
|
||||||
? context.localized.startedSyncingItem(item.detailedName(context) ?? "Unknown")
|
? context.localized.startedSyncingItem(item.detailedName(context) ?? "Unknown")
|
||||||
: context.localized.unableToSyncItem(item.detailedName(context) ?? "Unknown"));
|
: context.localized.unableToSyncItem(item.detailedName(context) ?? "Unknown"));
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void viewDatabase(BuildContext context) =>
|
||||||
|
Navigator.of(context, rootNavigator: true).push(MaterialPageRoute(builder: (context) => DriftDbViewer(db)));
|
||||||
|
|
||||||
Future<bool> removeSync(BuildContext context, SyncedItem? item) async {
|
Future<bool> removeSync(BuildContext context, SyncedItem? item) async {
|
||||||
try {
|
try {
|
||||||
if (item == null) return false;
|
if (item == null) return false;
|
||||||
|
|
||||||
final nestedChildren = getNestedChildren(item);
|
final nestedChildren = await getNestedChildren(item);
|
||||||
|
|
||||||
state = state.copyWith(
|
state = state.copyWith(
|
||||||
items: state.items
|
items: state.items
|
||||||
|
|
@ -268,13 +273,7 @@ class SyncNotifier extends StateNotifier<SyncSettingsModel> {
|
||||||
await ref.read(backgroundDownloaderProvider).cancelTaskWithId(item.taskId!);
|
await ref.read(backgroundDownloaderProvider).cancelTaskWithId(item.taskId!);
|
||||||
}
|
}
|
||||||
|
|
||||||
final deleteFromDatabase = isar?.write((isar) => syncedItems?.deleteAll([...nestedChildren, item]
|
await db.deleteAllItems([...nestedChildren, item]);
|
||||||
.map(
|
|
||||||
(e) => e.id,
|
|
||||||
)
|
|
||||||
.toList()));
|
|
||||||
|
|
||||||
if (deleteFromDatabase == 0) return false;
|
|
||||||
|
|
||||||
for (var i = 0; i < nestedChildren.length; i++) {
|
for (var i = 0; i < nestedChildren.length; i++) {
|
||||||
final element = nestedChildren[i];
|
final element = nestedChildren[i];
|
||||||
|
|
@ -396,12 +395,7 @@ class SyncNotifier extends StateNotifier<SyncSettingsModel> {
|
||||||
return data?.copyWith(path: fileName);
|
return data?.copyWith(path: fileName);
|
||||||
}
|
}
|
||||||
|
|
||||||
void updateItemSync(SyncedItem syncedItem) =>
|
Future<void> updateItem(SyncedItem syncedItem) async => db.insertItem(syncedItem);
|
||||||
isar?.write((isar) => syncedItems?.put(ISyncedItem.fromSynced(syncedItem, syncPath ?? "")));
|
|
||||||
|
|
||||||
Future<void> updateItem(SyncedItem syncedItem) async {
|
|
||||||
isar?.write((isar) => syncedItems?.put(ISyncedItem.fromSynced(syncedItem, syncPath ?? "")));
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<SyncedItem> deleteFullSyncFiles(SyncedItem syncedItem, DownloadTask? task) async {
|
Future<SyncedItem> deleteFullSyncFiles(SyncedItem syncedItem, DownloadTask? task) async {
|
||||||
await syncedItem.deleteDatFiles(ref);
|
await syncedItem.deleteDatFiles(ref);
|
||||||
|
|
@ -512,7 +506,7 @@ class SyncNotifier extends StateNotifier<SyncSettingsModel> {
|
||||||
|
|
||||||
Future<void> clear() async {
|
Future<void> clear() async {
|
||||||
await mainDirectory.delete(recursive: true);
|
await mainDirectory.delete(recursive: true);
|
||||||
isar?.write((isar) => syncedItems?.clear());
|
await db.clearDatabase();
|
||||||
state = state.copyWith(items: []);
|
state = state.copyWith(items: []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -526,10 +520,24 @@ extension SyncNotifierHelpers on SyncNotifier {
|
||||||
Future<SyncedItem> createSyncItem(BaseItemDto response, {SyncedItem? parent}) async {
|
Future<SyncedItem> createSyncItem(BaseItemDto response, {SyncedItem? parent}) async {
|
||||||
final ItemBaseModel item = ItemBaseModel.fromBaseDto(response, ref);
|
final ItemBaseModel item = ItemBaseModel.fromBaseDto(response, ref);
|
||||||
|
|
||||||
final existingSyncedItem = getSyncedItem(item);
|
final existingSyncedItem = await getSyncedItem(item);
|
||||||
|
|
||||||
if (existingSyncedItem != null) return existingSyncedItem;
|
if (existingSyncedItem != null) return existingSyncedItem;
|
||||||
|
|
||||||
|
SyncedItem syncItem = await _syncItemData(parent, item, response);
|
||||||
|
|
||||||
|
if (parent == null) {
|
||||||
|
await db.insertItem(syncItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
return syncItem.copyWith(
|
||||||
|
fileSize: response.mediaSources?.firstOrNull?.size ?? 0,
|
||||||
|
syncing: false,
|
||||||
|
videoFileName: response.path?.split('/').lastOrNull ?? "",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<SyncedItem> _syncItemData(SyncedItem? parent, ItemBaseModel item, BaseItemDto response) async {
|
||||||
final Directory? parentDirectory = parent?.directory;
|
final Directory? parentDirectory = parent?.directory;
|
||||||
|
|
||||||
final directory = Directory(path.joinAll([(parentDirectory ?? saveDirectory)?.path ?? "", item.id]));
|
final directory = Directory(path.joinAll([(parentDirectory ?? saveDirectory)?.path ?? "", item.id]));
|
||||||
|
|
@ -550,16 +558,7 @@ extension SyncNotifierHelpers on SyncNotifier {
|
||||||
path: directory.path,
|
path: directory.path,
|
||||||
userData: item.userData,
|
userData: item.userData,
|
||||||
);
|
);
|
||||||
|
return syncItem;
|
||||||
if (parent == null) {
|
|
||||||
isar?.write((isar) => syncedItems?.put(ISyncedItem.fromSynced(syncItem, syncPath)));
|
|
||||||
}
|
|
||||||
|
|
||||||
return syncItem.copyWith(
|
|
||||||
fileSize: response.mediaSources?.firstOrNull?.size ?? 0,
|
|
||||||
syncing: false,
|
|
||||||
videoFileName: response.path?.split('/').lastOrNull ?? "",
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<SyncedItem?> syncMovie(ItemBaseModel item, {bool skipDownload = false}) async {
|
Future<SyncedItem?> syncMovie(ItemBaseModel item, {bool skipDownload = false}) async {
|
||||||
|
|
@ -574,9 +573,9 @@ extension SyncNotifierHelpers on SyncNotifier {
|
||||||
|
|
||||||
if (!syncItem.directory.existsSync()) return null;
|
if (!syncItem.directory.existsSync()) return null;
|
||||||
|
|
||||||
await syncFile(syncItem, skipDownload);
|
await db.insertItem(syncItem);
|
||||||
|
|
||||||
isar?.write((isar) => syncedItems?.put(ISyncedItem.fromSynced(syncItem, syncPath)));
|
await syncFile(syncItem, skipDownload);
|
||||||
|
|
||||||
return syncItem;
|
return syncItem;
|
||||||
}
|
}
|
||||||
|
|
@ -595,6 +594,7 @@ extension SyncNotifierHelpers on SyncNotifier {
|
||||||
if (!seriesItem.directory.existsSync()) return null;
|
if (!seriesItem.directory.existsSync()) return null;
|
||||||
|
|
||||||
final seasonsResponse = await api.showsSeriesIdSeasonsGet(
|
final seasonsResponse = await api.showsSeriesIdSeasonsGet(
|
||||||
|
seriesId: item.id,
|
||||||
isMissing: false,
|
isMissing: false,
|
||||||
enableUserData: true,
|
enableUserData: true,
|
||||||
fields: [
|
fields: [
|
||||||
|
|
@ -615,7 +615,6 @@ extension SyncNotifierHelpers on SyncNotifier {
|
||||||
ItemFields.chapters,
|
ItemFields.chapters,
|
||||||
ItemFields.trickplay,
|
ItemFields.trickplay,
|
||||||
],
|
],
|
||||||
seriesId: item.id,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final seasons = seasonsResponse.body?.items ?? [];
|
final seasons = seasonsResponse.body?.items ?? [];
|
||||||
|
|
@ -660,23 +659,17 @@ extension SyncNotifierHelpers on SyncNotifier {
|
||||||
|
|
||||||
for (final (ep, newEpisode) in episodeResults) {
|
for (final (ep, newEpisode) in episodeResults) {
|
||||||
newItems.add(newEpisode);
|
newItems.add(newEpisode);
|
||||||
if (episode?.id == ep.id || newSeason.id == season?.id) {
|
if (episode?.id == ep.id || newSeason.id == season?.id && !await newEpisode.videoFile.exists()) {
|
||||||
itemsToDownload.add(newEpisode);
|
itemsToDownload.add(newEpisode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
isar?.write(
|
await db.insertMultipleEntries(newItems);
|
||||||
(isar) => syncedItems?.putAll(newItems
|
|
||||||
.map(
|
|
||||||
(e) => ISyncedItem.fromSynced(e, syncPath ?? ""),
|
|
||||||
)
|
|
||||||
.toList()),
|
|
||||||
);
|
|
||||||
|
|
||||||
for (var i = 0; i < itemsToDownload.length; i++) {
|
for (var i = 0; i < itemsToDownload.length; i++) {
|
||||||
final item = itemsToDownload[i];
|
final item = itemsToDownload[i];
|
||||||
await syncFile(item, false);
|
syncFile(item, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return seriesItem;
|
return seriesItem;
|
||||||
|
|
|
||||||
|
|
@ -464,7 +464,7 @@ class SplashRouteArgs {
|
||||||
class SyncedRoute extends _i17.PageRouteInfo<SyncedRouteArgs> {
|
class SyncedRoute extends _i17.PageRouteInfo<SyncedRouteArgs> {
|
||||||
SyncedRoute({
|
SyncedRoute({
|
||||||
_i22.ScrollController? navigationScrollController,
|
_i22.ScrollController? navigationScrollController,
|
||||||
_i22.Key? key,
|
_i19.Key? key,
|
||||||
List<_i17.PageRouteInfo>? children,
|
List<_i17.PageRouteInfo>? children,
|
||||||
}) : super(
|
}) : super(
|
||||||
SyncedRoute.name,
|
SyncedRoute.name,
|
||||||
|
|
@ -498,7 +498,7 @@ class SyncedRouteArgs {
|
||||||
|
|
||||||
final _i22.ScrollController? navigationScrollController;
|
final _i22.ScrollController? navigationScrollController;
|
||||||
|
|
||||||
final _i22.Key? key;
|
final _i19.Key? key;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() {
|
String toString() {
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
import 'package:iconsax_plus/iconsax_plus.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:iconsax_plus/iconsax_plus.dart';
|
||||||
|
|
||||||
import 'package:fladder/models/book_model.dart';
|
import 'package:fladder/models/book_model.dart';
|
||||||
import 'package:fladder/models/items/images_models.dart';
|
import 'package:fladder/models/items/images_models.dart';
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,10 @@ import 'package:iconsax_plus/iconsax_plus.dart';
|
||||||
|
|
||||||
import 'package:fladder/models/item_base_model.dart';
|
import 'package:fladder/models/item_base_model.dart';
|
||||||
import 'package:fladder/models/items/images_models.dart';
|
import 'package:fladder/models/items/images_models.dart';
|
||||||
|
import 'package:fladder/providers/sync/sync_provider_helpers.dart';
|
||||||
import 'package:fladder/routes/auto_router.gr.dart';
|
import 'package:fladder/routes/auto_router.gr.dart';
|
||||||
|
import 'package:fladder/screens/syncing/sync_button.dart';
|
||||||
|
import 'package:fladder/screens/syncing/sync_item_details.dart';
|
||||||
import 'package:fladder/theme.dart';
|
import 'package:fladder/theme.dart';
|
||||||
import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
|
import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
|
||||||
import 'package:fladder/util/fladder_image.dart';
|
import 'package:fladder/util/fladder_image.dart';
|
||||||
|
|
@ -196,6 +199,19 @@ class _DetailScaffoldState extends ConsumerState<DetailScaffold> {
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
children: [
|
children: [
|
||||||
if (widget.item != null) ...[
|
if (widget.item != null) ...[
|
||||||
|
ref.watch(syncedItemProvider(widget.item)).when(
|
||||||
|
error: (error, stackTrace) => const SizedBox.shrink(),
|
||||||
|
data: (syncedItem) {
|
||||||
|
if (syncedItem == null) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
|
return IconButton(
|
||||||
|
onPressed: () => showSyncItemDetails(context, syncedItem, ref),
|
||||||
|
icon: SyncButton(item: widget.item!, syncedItem: syncedItem),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
loading: () => const SizedBox.shrink(),
|
||||||
|
),
|
||||||
Builder(
|
Builder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
final newActions = widget.actions?.call(context);
|
final newActions = widget.actions?.call(context);
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ void fladderSnackbar(
|
||||||
bool showCloseButton = false,
|
bool showCloseButton = false,
|
||||||
Duration duration = const Duration(seconds: 3),
|
Duration duration = const Duration(seconds: 3),
|
||||||
}) {
|
}) {
|
||||||
|
if (!context.mounted) return;
|
||||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||||
content: Text(
|
content: Text(
|
||||||
title,
|
title,
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
|
import 'package:flutter_widget_from_html/flutter_widget_from_html.dart';
|
||||||
|
|
||||||
import 'package:fladder/models/items/episode_model.dart';
|
import 'package:fladder/models/items/episode_model.dart';
|
||||||
import 'package:fladder/providers/sync_provider.dart';
|
|
||||||
import 'package:fladder/screens/details_screens/components/media_stream_information.dart';
|
import 'package:fladder/screens/details_screens/components/media_stream_information.dart';
|
||||||
import 'package:fladder/screens/shared/media/episode_posters.dart';
|
import 'package:fladder/screens/shared/media/episode_posters.dart';
|
||||||
import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
|
import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
|
||||||
|
|
@ -31,7 +30,7 @@ class NextUpEpisode extends ConsumerWidget {
|
||||||
Opacity(
|
Opacity(
|
||||||
opacity: 0.75,
|
opacity: 0.75,
|
||||||
child: SelectableText(
|
child: SelectableText(
|
||||||
"${context.localized.season(1)} ${nextEpisode.season} - ${context.localized.episode(1)} ${nextEpisode.episode}",
|
nextEpisode.seasonEpisodeLabelFull(context),
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -42,13 +41,11 @@ class NextUpEpisode extends ConsumerWidget {
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
LayoutBuilder(
|
LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
final syncedItem = ref.read(syncProvider.notifier).getSyncedItem(nextEpisode);
|
|
||||||
if (constraints.maxWidth < 550) {
|
if (constraints.maxWidth < 550) {
|
||||||
return Column(
|
return Column(
|
||||||
children: [
|
children: [
|
||||||
EpisodePoster(
|
EpisodePoster(
|
||||||
episode: nextEpisode,
|
episode: nextEpisode,
|
||||||
syncedItem: syncedItem,
|
|
||||||
showLabel: false,
|
showLabel: false,
|
||||||
onTap: () => nextEpisode.navigateTo(context),
|
onTap: () => nextEpisode.navigateTo(context),
|
||||||
actions: const [],
|
actions: const [],
|
||||||
|
|
@ -71,7 +68,6 @@ class NextUpEpisode extends ConsumerWidget {
|
||||||
maxWidth: MediaQuery.of(context).size.width / 2),
|
maxWidth: MediaQuery.of(context).size.width / 2),
|
||||||
child: EpisodePoster(
|
child: EpisodePoster(
|
||||||
episode: nextEpisode,
|
episode: nextEpisode,
|
||||||
syncedItem: syncedItem,
|
|
||||||
showLabel: false,
|
showLabel: false,
|
||||||
onTap: () => nextEpisode.navigateTo(context),
|
onTap: () => nextEpisode.navigateTo(context),
|
||||||
actions: const [],
|
actions: const [],
|
||||||
|
|
|
||||||
|
|
@ -1,16 +1,16 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:iconsax_plus/iconsax_plus.dart';
|
import 'package:iconsax_plus/iconsax_plus.dart';
|
||||||
|
|
||||||
|
import 'package:fladder/models/items/episode_model.dart';
|
||||||
import 'package:fladder/providers/settings/client_settings_provider.dart';
|
import 'package:fladder/providers/settings/client_settings_provider.dart';
|
||||||
import 'package:fladder/providers/sync_provider.dart';
|
|
||||||
import 'package:fladder/screens/shared/media/episode_posters.dart';
|
import 'package:fladder/screens/shared/media/episode_posters.dart';
|
||||||
import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
|
import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
|
||||||
|
import 'package:fladder/util/humanize_duration.dart';
|
||||||
import 'package:fladder/util/item_base_model/item_base_model_extensions.dart';
|
import 'package:fladder/util/item_base_model/item_base_model_extensions.dart';
|
||||||
import 'package:fladder/util/list_padding.dart';
|
import 'package:fladder/util/list_padding.dart';
|
||||||
import 'package:fladder/util/localization_helper.dart';
|
import 'package:fladder/util/localization_helper.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
|
|
||||||
import 'package:fladder/models/items/episode_model.dart';
|
|
||||||
import 'package:fladder/util/humanize_duration.dart';
|
|
||||||
|
|
||||||
enum EpisodeDetailsViewType {
|
enum EpisodeDetailsViewType {
|
||||||
list(icon: IconsaxPlusBold.grid_6),
|
list(icon: IconsaxPlusBold.grid_6),
|
||||||
|
|
@ -48,14 +48,12 @@ class EpisodeDetailsList extends ConsumerWidget {
|
||||||
itemCount: episodes.length,
|
itemCount: episodes.length,
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final episode = episodes[index];
|
final episode = episodes[index];
|
||||||
final syncedItem = ref.watch(syncProvider.notifier).getSyncedItem(episode);
|
|
||||||
List<Widget> children = [
|
List<Widget> children = [
|
||||||
Flexible(
|
Flexible(
|
||||||
flex: 1,
|
flex: 1,
|
||||||
child: EpisodePoster(
|
child: EpisodePoster(
|
||||||
episode: episode,
|
episode: episode,
|
||||||
showLabel: false,
|
showLabel: false,
|
||||||
syncedItem: syncedItem,
|
|
||||||
actions: episode.generateActions(context, ref),
|
actions: episode.generateActions(context, ref),
|
||||||
onTap: () => episode.navigateTo(context),
|
onTap: () => episode.navigateTo(context),
|
||||||
isCurrentEpisode: false,
|
isCurrentEpisode: false,
|
||||||
|
|
|
||||||
|
|
@ -3,10 +3,8 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
import 'package:fladder/models/items/episode_model.dart';
|
import 'package:fladder/models/items/episode_model.dart';
|
||||||
import 'package:fladder/models/syncing/sync_item.dart';
|
|
||||||
import 'package:fladder/providers/settings/client_settings_provider.dart';
|
import 'package:fladder/providers/settings/client_settings_provider.dart';
|
||||||
import 'package:fladder/providers/sync/sync_provider_helpers.dart';
|
import 'package:fladder/providers/sync/sync_provider_helpers.dart';
|
||||||
import 'package:fladder/providers/sync_provider.dart';
|
|
||||||
import 'package:fladder/screens/shared/flat_button.dart';
|
import 'package:fladder/screens/shared/flat_button.dart';
|
||||||
import 'package:fladder/screens/syncing/sync_button.dart';
|
import 'package:fladder/screens/syncing/sync_button.dart';
|
||||||
import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
|
import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
|
||||||
|
|
@ -88,11 +86,9 @@ class _EpisodePosterState extends ConsumerState<EpisodePosters> {
|
||||||
itemBuilder: (context, index) {
|
itemBuilder: (context, index) {
|
||||||
final episode = episodes[index];
|
final episode = episodes[index];
|
||||||
final isCurrentEpisode = index == indexOfCurrent;
|
final isCurrentEpisode = index == indexOfCurrent;
|
||||||
final syncedItem = ref.watch(syncProvider.notifier).getSyncedItem(episode);
|
|
||||||
return EpisodePoster(
|
return EpisodePoster(
|
||||||
episode: episode,
|
episode: episode,
|
||||||
blur: allPlayed ? false : indexOfCurrent < index,
|
blur: allPlayed ? false : indexOfCurrent < index,
|
||||||
syncedItem: syncedItem,
|
|
||||||
onTap: widget.onEpisodeTap != null
|
onTap: widget.onEpisodeTap != null
|
||||||
? () {
|
? () {
|
||||||
widget.onEpisodeTap?.call(
|
widget.onEpisodeTap?.call(
|
||||||
|
|
@ -130,7 +126,6 @@ class _EpisodePosterState extends ConsumerState<EpisodePosters> {
|
||||||
|
|
||||||
class EpisodePoster extends ConsumerWidget {
|
class EpisodePoster extends ConsumerWidget {
|
||||||
final EpisodeModel episode;
|
final EpisodeModel episode;
|
||||||
final SyncedItem? syncedItem;
|
|
||||||
final bool showLabel;
|
final bool showLabel;
|
||||||
final Function()? onTap;
|
final Function()? onTap;
|
||||||
final Function()? onLongPress;
|
final Function()? onLongPress;
|
||||||
|
|
@ -141,7 +136,6 @@ class EpisodePoster extends ConsumerWidget {
|
||||||
const EpisodePoster({
|
const EpisodePoster({
|
||||||
super.key,
|
super.key,
|
||||||
required this.episode,
|
required this.episode,
|
||||||
this.syncedItem,
|
|
||||||
this.showLabel = true,
|
this.showLabel = true,
|
||||||
this.onTap,
|
this.onTap,
|
||||||
this.onLongPress,
|
this.onLongPress,
|
||||||
|
|
@ -156,7 +150,6 @@ class EpisodePoster extends ConsumerWidget {
|
||||||
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
color: Theme.of(context).colorScheme.surfaceContainerHighest,
|
||||||
child: const Icon(Icons.local_movies_outlined),
|
child: const Icon(Icons.local_movies_outlined),
|
||||||
);
|
);
|
||||||
final SyncedItem? iSyncedItem = syncedItem;
|
|
||||||
bool episodeAvailable = episode.status == EpisodeStatus.available;
|
bool episodeAvailable = episode.status == EpisodeStatus.available;
|
||||||
return AspectRatio(
|
return AspectRatio(
|
||||||
aspectRatio: 1.76,
|
aspectRatio: 1.76,
|
||||||
|
|
@ -203,15 +196,18 @@ class EpisodePoster extends ConsumerWidget {
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
children: [
|
children: [
|
||||||
if (iSyncedItem != null)
|
ref.watch(syncedItemProvider(episode)).when(
|
||||||
Consumer(builder: (context, ref, child) {
|
error: (error, stackTrace) => const SizedBox.shrink(),
|
||||||
final SyncStatus syncStatus =
|
data: (syncedItem) {
|
||||||
ref.watch(syncStatusesProvider(iSyncedItem, null)).value ?? SyncStatus.partially;
|
if (syncedItem == null) {
|
||||||
|
return const SizedBox.shrink();
|
||||||
|
}
|
||||||
return StatusCard(
|
return StatusCard(
|
||||||
color: syncStatus.color,
|
|
||||||
child: SyncButton(item: episode, syncedItem: syncedItem),
|
child: SyncButton(item: episode, syncedItem: syncedItem),
|
||||||
);
|
);
|
||||||
}),
|
},
|
||||||
|
loading: () => const SizedBox.shrink(),
|
||||||
|
),
|
||||||
if (episode.userData.isFavourite)
|
if (episode.userData.isFavourite)
|
||||||
const StatusCard(
|
const StatusCard(
|
||||||
color: Colors.red,
|
color: Colors.red,
|
||||||
|
|
@ -259,7 +255,7 @@ class EpisodePoster extends ConsumerWidget {
|
||||||
child: Align(
|
child: Align(
|
||||||
alignment: Alignment.bottomRight,
|
alignment: Alignment.bottomRight,
|
||||||
child: PopupMenuButton(
|
child: PopupMenuButton(
|
||||||
tooltip: "Options",
|
tooltip: context.localized.options,
|
||||||
icon: const Icon(
|
icon: const Icon(
|
||||||
Icons.more_vert,
|
Icons.more_vert,
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ class PosterWidget extends ConsumerWidget {
|
||||||
final int maxLines;
|
final int maxLines;
|
||||||
final double? aspectRatio;
|
final double? aspectRatio;
|
||||||
final bool inlineTitle;
|
final bool inlineTitle;
|
||||||
|
final bool underTitle;
|
||||||
final Set<ItemActions> excludeActions;
|
final Set<ItemActions> excludeActions;
|
||||||
final List<ItemAction> otherActions;
|
final List<ItemAction> otherActions;
|
||||||
final Function(String id, UserData? newData)? onUserDataChanged;
|
final Function(String id, UserData? newData)? onUserDataChanged;
|
||||||
|
|
@ -33,6 +34,7 @@ class PosterWidget extends ConsumerWidget {
|
||||||
this.heroTag,
|
this.heroTag,
|
||||||
this.aspectRatio,
|
this.aspectRatio,
|
||||||
this.inlineTitle = false,
|
this.inlineTitle = false,
|
||||||
|
this.underTitle = true,
|
||||||
this.excludeActions = const {},
|
this.excludeActions = const {},
|
||||||
this.otherActions = const [],
|
this.otherActions = const [],
|
||||||
this.onUserDataChanged,
|
this.onUserDataChanged,
|
||||||
|
|
@ -64,7 +66,7 @@ class PosterWidget extends ConsumerWidget {
|
||||||
onPressed: onPressed,
|
onPressed: onPressed,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (!inlineTitle)
|
if (!inlineTitle && underTitle)
|
||||||
Column(
|
Column(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
import 'package:fladder/models/items/season_model.dart';
|
import 'package:fladder/models/items/season_model.dart';
|
||||||
import 'package:fladder/providers/sync_provider.dart';
|
import 'package:fladder/providers/sync/sync_provider_helpers.dart';
|
||||||
import 'package:fladder/screens/shared/flat_button.dart';
|
import 'package:fladder/screens/shared/flat_button.dart';
|
||||||
import 'package:fladder/screens/syncing/sync_button.dart';
|
import 'package:fladder/screens/syncing/sync_button.dart';
|
||||||
import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
|
import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
|
||||||
|
|
@ -58,7 +58,6 @@ class SeasonPoster extends ConsumerWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final syncedItem = ref.watch(syncProvider.notifier).getSyncedItem(season);
|
|
||||||
Padding placeHolder(String title) {
|
Padding placeHolder(String title) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(4),
|
padding: const EdgeInsets.all(4),
|
||||||
|
|
@ -100,19 +99,25 @@ class SeasonPoster extends ConsumerWidget {
|
||||||
alignment: Alignment.topLeft,
|
alignment: Alignment.topLeft,
|
||||||
child: placeHolder(season.name),
|
child: placeHolder(season.name),
|
||||||
),
|
),
|
||||||
if (season.userData.unPlayedItemCount != 0)
|
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.topRight,
|
alignment: Alignment.topRight,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.end,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
children: [
|
children: [
|
||||||
if (syncedItem != null)
|
ref.watch(syncedItemProvider(season)).when(
|
||||||
StatusCard(
|
error: (error, stackTrace) => const SizedBox.shrink(),
|
||||||
child: SyncButton(
|
data: (syncedItem) {
|
||||||
item: season,
|
if (syncedItem == null) {
|
||||||
syncedItem: syncedItem,
|
return const SizedBox.shrink();
|
||||||
),
|
}
|
||||||
|
return StatusCard(
|
||||||
|
child: SyncButton(item: season, syncedItem: syncedItem),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
loading: () => const SizedBox.shrink(),
|
||||||
),
|
),
|
||||||
|
if (season.userData.unPlayedItemCount != 0)
|
||||||
StatusCard(
|
StatusCard(
|
||||||
color: Theme.of(context).colorScheme.primary,
|
color: Theme.of(context).colorScheme.primary,
|
||||||
useFittedBox: true,
|
useFittedBox: true,
|
||||||
|
|
@ -122,9 +127,6 @@ class SeasonPoster extends ConsumerWidget {
|
||||||
style: const TextStyle(fontWeight: FontWeight.w700, fontSize: 14),
|
style: const TextStyle(fontWeight: FontWeight.w700, fontSize: 14),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
Align(
|
Align(
|
||||||
|
|
@ -136,6 +138,9 @@ class SeasonPoster extends ConsumerWidget {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
LayoutBuilder(
|
LayoutBuilder(
|
||||||
builder: (context, constraints) {
|
builder: (context, constraints) {
|
||||||
return FlatButton(
|
return FlatButton(
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:background_downloader/background_downloader.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:iconsax_plus/iconsax_plus.dart';
|
import 'package:iconsax_plus/iconsax_plus.dart';
|
||||||
|
|
||||||
|
|
@ -7,48 +8,44 @@ import 'package:fladder/models/item_base_model.dart';
|
||||||
import 'package:fladder/models/syncing/sync_item.dart';
|
import 'package:fladder/models/syncing/sync_item.dart';
|
||||||
import 'package:fladder/providers/sync/sync_provider_helpers.dart';
|
import 'package:fladder/providers/sync/sync_provider_helpers.dart';
|
||||||
|
|
||||||
class SyncButton extends ConsumerStatefulWidget {
|
class SyncButton extends ConsumerWidget {
|
||||||
final ItemBaseModel item;
|
final ItemBaseModel item;
|
||||||
final SyncedItem? syncedItem;
|
final SyncedItem syncedItem;
|
||||||
const SyncButton({required this.item, required this.syncedItem, super.key});
|
const SyncButton({required this.item, required this.syncedItem, super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ConsumerState<ConsumerStatefulWidget> createState() => _SyncButtonState();
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
}
|
final nested = ref.watch(syncedNestedChildrenProvider(syncedItem));
|
||||||
|
return nested.when(
|
||||||
|
loading: () => const SizedBox.shrink(),
|
||||||
|
error: (err, stack) => const SizedBox.shrink(),
|
||||||
|
data: (children) {
|
||||||
|
final download = ref.watch(syncDownloadStatusProvider(syncedItem, children));
|
||||||
|
final status = download?.status ?? TaskStatus.notFound;
|
||||||
|
final progress = download?.progress ?? 0.0;
|
||||||
|
|
||||||
class _SyncButtonState extends ConsumerState<SyncButton> {
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final syncedItem = widget.syncedItem;
|
|
||||||
final status = syncedItem != null ? ref.watch(syncStatusesProvider(syncedItem, null)).value : null;
|
|
||||||
final progress = syncedItem != null ? ref.watch(syncDownloadStatusProvider(syncedItem, [])) : null;
|
|
||||||
return Stack(
|
return Stack(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
children: [
|
children: [
|
||||||
Icon(
|
Icon(
|
||||||
syncedItem != null
|
status == TaskStatus.notFound
|
||||||
? status == SyncStatus.partially
|
? (progress > 0 ? IconsaxPlusLinear.arrow_down_1 : IconsaxPlusLinear.more_circle)
|
||||||
? (progress?.progress ?? 0) > 0
|
: status.icon,
|
||||||
? IconsaxPlusLinear.arrow_down
|
color: status.color(context),
|
||||||
: IconsaxPlusLinear.more_circle
|
size: status == TaskStatus.running && progress > 0 ? 16 : null,
|
||||||
: IconsaxPlusLinear.tick_circle
|
|
||||||
: IconsaxPlusLinear.arrow_down_2,
|
|
||||||
color: status?.color,
|
|
||||||
size: (progress?.progress ?? 0) > 0 ? 16 : null,
|
|
||||||
),
|
),
|
||||||
if ((progress?.progress ?? 0) > 0)
|
SizedBox.fromSize(
|
||||||
IgnorePointer(
|
|
||||||
child: SizedBox.fromSize(
|
|
||||||
size: const Size.fromRadius(10),
|
size: const Size.fromRadius(10),
|
||||||
child: CircularProgressIndicator(
|
child: CircularProgressIndicator(
|
||||||
strokeCap: StrokeCap.round,
|
strokeCap: StrokeCap.round,
|
||||||
strokeWidth: 2,
|
strokeWidth: 1.5,
|
||||||
color: status?.color,
|
color: status.color(context),
|
||||||
value: progress?.progress,
|
value: status == TaskStatus.running ? progress.clamp(0.0, 1.0) : 0,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:fladder/models/items/episode_model.dart';
|
import 'package:fladder/models/items/episode_model.dart';
|
||||||
import 'package:fladder/models/items/season_model.dart';
|
import 'package:fladder/models/items/season_model.dart';
|
||||||
import 'package:fladder/models/syncing/sync_item.dart';
|
import 'package:fladder/models/syncing/sync_item.dart';
|
||||||
import 'package:fladder/providers/sync_provider.dart';
|
|
||||||
import 'package:fladder/screens/syncing/widgets/synced_season_poster.dart';
|
import 'package:fladder/screens/syncing/widgets/synced_season_poster.dart';
|
||||||
|
|
||||||
import 'widgets/synced_episode_item.dart';
|
import 'widgets/synced_episode_item.dart';
|
||||||
|
|
@ -26,8 +25,7 @@ class _ChildSyncWidgetState extends ConsumerState<ChildSyncWidget> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final baseItem = ref.read(syncProvider.notifier).getItem(syncedItem);
|
final baseItem = syncedItem.itemModel;
|
||||||
final hasFile = syncedItem.videoFile.existsSync();
|
|
||||||
if (baseItem == null) {
|
if (baseItem == null) {
|
||||||
return Container();
|
return Container();
|
||||||
}
|
}
|
||||||
|
|
@ -47,7 +45,6 @@ class _ChildSyncWidgetState extends ConsumerState<ChildSyncWidget> {
|
||||||
EpisodeModel episode => SyncedEpisodeItem(
|
EpisodeModel episode => SyncedEpisodeItem(
|
||||||
episode: episode,
|
episode: episode,
|
||||||
syncedItem: syncedItem,
|
syncedItem: syncedItem,
|
||||||
hasFile: hasFile,
|
|
||||||
),
|
),
|
||||||
_ => Container(),
|
_ => Container(),
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:background_downloader/background_downloader.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:iconsax_plus/iconsax_plus.dart';
|
import 'package:iconsax_plus/iconsax_plus.dart';
|
||||||
|
|
||||||
import 'package:fladder/models/items/episode_model.dart';
|
|
||||||
import 'package:fladder/models/syncing/sync_item.dart';
|
import 'package:fladder/models/syncing/sync_item.dart';
|
||||||
import 'package:fladder/providers/settings/client_settings_provider.dart';
|
import 'package:fladder/providers/settings/client_settings_provider.dart';
|
||||||
import 'package:fladder/providers/sync/sync_provider_helpers.dart';
|
import 'package:fladder/providers/sync/sync_provider_helpers.dart';
|
||||||
|
|
@ -13,26 +13,29 @@ import 'package:fladder/screens/shared/default_alert_dialog.dart';
|
||||||
import 'package:fladder/screens/shared/media/poster_widget.dart';
|
import 'package:fladder/screens/shared/media/poster_widget.dart';
|
||||||
import 'package:fladder/screens/syncing/sync_child_item.dart';
|
import 'package:fladder/screens/syncing/sync_child_item.dart';
|
||||||
import 'package:fladder/screens/syncing/sync_widgets.dart';
|
import 'package:fladder/screens/syncing/sync_widgets.dart';
|
||||||
|
import 'package:fladder/screens/syncing/widgets/sync_options_button.dart';
|
||||||
import 'package:fladder/screens/syncing/widgets/sync_progress_builder.dart';
|
import 'package:fladder/screens/syncing/widgets/sync_progress_builder.dart';
|
||||||
import 'package:fladder/screens/syncing/widgets/sync_status_overlay.dart';
|
import 'package:fladder/screens/syncing/widgets/sync_status_overlay.dart';
|
||||||
import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
|
import 'package:fladder/util/adaptive_layout/adaptive_layout.dart';
|
||||||
import 'package:fladder/util/list_padding.dart';
|
|
||||||
import 'package:fladder/util/localization_helper.dart';
|
import 'package:fladder/util/localization_helper.dart';
|
||||||
|
import 'package:fladder/util/refresh_state.dart';
|
||||||
import 'package:fladder/util/size_formatting.dart';
|
import 'package:fladder/util/size_formatting.dart';
|
||||||
import 'package:fladder/widgets/shared/alert_content.dart';
|
import 'package:fladder/widgets/shared/alert_content.dart';
|
||||||
import 'package:fladder/widgets/shared/icon_button_await.dart';
|
import 'package:fladder/widgets/shared/icon_button_await.dart';
|
||||||
|
import 'package:fladder/widgets/shared/pull_to_refresh.dart';
|
||||||
|
|
||||||
Future<void> showSyncItemDetails(
|
Future<void> showSyncItemDetails(
|
||||||
BuildContext context,
|
BuildContext context,
|
||||||
SyncedItem syncItem,
|
SyncedItem syncItem,
|
||||||
WidgetRef ref,
|
WidgetRef ref,
|
||||||
) {
|
) async {
|
||||||
return showDialogAdaptive(
|
await showDialogAdaptive(
|
||||||
context: context,
|
context: context,
|
||||||
builder: (context) => SyncItemDetails(
|
builder: (context) => SyncItemDetails(
|
||||||
syncItem: syncItem,
|
syncItem: syncItem,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
context.refreshData();
|
||||||
}
|
}
|
||||||
|
|
||||||
class SyncItemDetails extends ConsumerStatefulWidget {
|
class SyncItemDetails extends ConsumerStatefulWidget {
|
||||||
|
|
@ -48,14 +51,23 @@ class _SyncItemDetailsState extends ConsumerState<SyncItemDetails> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final baseItem = ref.read(syncProvider.notifier).getItem(syncedItem);
|
final baseItem = syncedItem.itemModel;
|
||||||
final hasFile = syncedItem.videoFile.existsSync();
|
final hasFile = syncedItem.videoFile.existsSync();
|
||||||
final syncChildren = ref.read(syncProvider.notifier).getChildren(syncedItem);
|
final downloadTask = ref.watch(downloadTasksProvider(syncedItem.id));
|
||||||
final downloadTask = ref.read(downloadTasksProvider(syncedItem.id));
|
final syncedChildren = ref.watch(syncedChildrenProvider(syncedItem));
|
||||||
|
final nestedChildren = ref.watch(syncedNestedChildrenProvider(syncedItem));
|
||||||
return SyncStatusOverlay(
|
return PullToRefresh(
|
||||||
|
refreshOnStart: false,
|
||||||
|
onRefresh: () async {
|
||||||
|
final newItem = await ref.read(syncProvider.notifier).refreshSyncItem(syncedItem);
|
||||||
|
setState(() {
|
||||||
|
syncedItem = newItem;
|
||||||
|
});
|
||||||
|
},
|
||||||
|
child: SyncStatusOverlay(
|
||||||
syncedItem: syncedItem,
|
syncedItem: syncedItem,
|
||||||
child: ActionContent(
|
child: switch (syncedChildren) {
|
||||||
|
AsyncValue<List<SyncedItem>>(value: final children) => ActionContent(
|
||||||
title: Row(
|
title: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -75,14 +87,14 @@ class _SyncItemDetailsState extends ConsumerState<SyncItemDetails> {
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
child: Column(
|
child: ListView(
|
||||||
mainAxisSize: MainAxisSize.min,
|
shrinkWrap: true,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
|
||||||
children: [
|
children: [
|
||||||
if (baseItem != null) ...{
|
if (baseItem != null) ...{
|
||||||
Row(
|
Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
spacing: 16,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
height: (AdaptiveLayout.poster(context).size *
|
height: (AdaptiveLayout.poster(context).size *
|
||||||
|
|
@ -90,46 +102,62 @@ class _SyncItemDetailsState extends ConsumerState<SyncItemDetails> {
|
||||||
0.6,
|
0.6,
|
||||||
child: IgnorePointer(
|
child: IgnorePointer(
|
||||||
child: PosterWidget(
|
child: PosterWidget(
|
||||||
aspectRatio: 0.7,
|
aspectRatio: 0.70,
|
||||||
poster: baseItem,
|
poster: baseItem,
|
||||||
inlineTitle: true,
|
underTitle: false,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SyncProgressBuilder(
|
child: switch (nestedChildren) {
|
||||||
|
AsyncValue<List<SyncedItem>>(:final value) => Builder(
|
||||||
|
builder: (context) {
|
||||||
|
final nestedChildren = value ?? [];
|
||||||
|
return SyncProgressBuilder(
|
||||||
item: syncedItem,
|
item: syncedItem,
|
||||||
children: syncChildren,
|
children: nestedChildren,
|
||||||
builder: (context, combinedStream) {
|
builder: (context, combinedStream) {
|
||||||
return Row(
|
return Column(
|
||||||
children: [
|
|
||||||
Expanded(
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
spacing: 4,
|
||||||
children: [
|
children: [
|
||||||
Text(
|
Flexible(
|
||||||
|
child: Text(
|
||||||
baseItem.detailedName(context) ?? "",
|
baseItem.detailedName(context) ?? "",
|
||||||
|
maxLines: 3,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
),
|
),
|
||||||
SyncSubtitle(syncItem: syncedItem),
|
|
||||||
SyncLabel(
|
|
||||||
label: context.localized.totalSize(
|
|
||||||
ref.watch(syncSizeProvider(syncedItem, syncChildren)).byteFormat ?? '--'),
|
|
||||||
status: ref.watch(syncStatusesProvider(syncedItem, syncChildren)).value ??
|
|
||||||
SyncStatus.partially,
|
|
||||||
),
|
),
|
||||||
if (combinedStream?.task != null && combinedStream != null) ...{
|
Flexible(
|
||||||
|
child: SyncSubtitle(
|
||||||
|
syncItem: syncedItem,
|
||||||
|
children: nestedChildren,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Flexible(
|
||||||
|
child: Consumer(
|
||||||
|
builder: (context, ref, child) => SyncLabel(
|
||||||
|
label: context.localized.totalSize(ref
|
||||||
|
.watch(syncSizeProvider(syncedItem, nestedChildren))
|
||||||
|
.byteFormat ??
|
||||||
|
'--'),
|
||||||
|
status: combinedStream?.status ?? TaskStatus.notFound,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (combinedStream != null && combinedStream.hasDownload == true)
|
||||||
SyncProgressBar(item: syncedItem, task: combinedStream)
|
SyncProgressBar(item: syncedItem, task: combinedStream)
|
||||||
},
|
|
||||||
].addInBetween(const SizedBox(height: 8)),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
|
},
|
||||||
),
|
),
|
||||||
if (!hasFile && !downloadTask.hasDownload && syncedItem.hasVideoFile)
|
if (syncedItem.hasVideoFile && !hasFile && !downloadTask.hasDownload)
|
||||||
IconButtonAwait(
|
IconButtonAwait(
|
||||||
onPressed: () async => await ref.read(syncProvider.notifier).syncFile(syncedItem, false),
|
onPressed: () async => await ref.read(syncProvider.notifier).syncFile(syncedItem, false),
|
||||||
icon: const Icon(IconsaxPlusLinear.cloud_change),
|
icon: const Icon(IconsaxPlusLinear.cloud_change),
|
||||||
|
|
@ -153,25 +181,27 @@ class _SyncItemDetailsState extends ConsumerState<SyncItemDetails> {
|
||||||
},
|
},
|
||||||
icon: const Icon(IconsaxPlusLinear.trash),
|
icon: const Icon(IconsaxPlusLinear.trash),
|
||||||
),
|
),
|
||||||
].addInBetween(const SizedBox(width: 16)),
|
nestedChildren.when(
|
||||||
|
data: (data) => SyncOptionsButton(
|
||||||
|
syncedItem: syncedItem,
|
||||||
|
children: data,
|
||||||
|
),
|
||||||
|
error: (error, stackTrace) => const SizedBox.shrink(),
|
||||||
|
loading: () => const SizedBox.shrink(),
|
||||||
|
)
|
||||||
|
],
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
|
if (children?.isNotEmpty == true) ...[
|
||||||
const Divider(),
|
const Divider(),
|
||||||
if (syncChildren.isNotEmpty == true)
|
...children!.map(
|
||||||
Flexible(
|
|
||||||
child: ListView(
|
|
||||||
shrinkWrap: true,
|
|
||||||
children: [
|
|
||||||
...syncChildren.map(
|
|
||||||
(e) => ChildSyncWidget(syncedChild: e),
|
(e) => ChildSyncWidget(syncedChild: e),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
actions: [
|
actions: [
|
||||||
if (baseItem is! EpisodeModel)
|
if (syncedItem.parentId == null)
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
style: ElevatedButton.styleFrom(
|
style: ElevatedButton.styleFrom(
|
||||||
backgroundColor: Theme.of(context).colorScheme.errorContainer,
|
backgroundColor: Theme.of(context).colorScheme.errorContainer,
|
||||||
|
|
@ -183,9 +213,9 @@ class _SyncItemDetailsState extends ConsumerState<SyncItemDetails> {
|
||||||
context,
|
context,
|
||||||
context.localized.syncDeleteItemTitle,
|
context.localized.syncDeleteItemTitle,
|
||||||
context.localized.syncDeleteItemDesc(baseItem?.detailedName(context) ?? ""),
|
context.localized.syncDeleteItemDesc(baseItem?.detailedName(context) ?? ""),
|
||||||
(context) async {
|
(localContext) async {
|
||||||
await ref.read(syncProvider.notifier).removeSync(context, syncedItem);
|
await ref.read(syncProvider.notifier).removeSync(context, syncedItem);
|
||||||
Navigator.pop(context);
|
Navigator.pop(localContext);
|
||||||
Navigator.pop(context);
|
Navigator.pop(context);
|
||||||
},
|
},
|
||||||
context.localized.delete,
|
context.localized.delete,
|
||||||
|
|
@ -195,10 +225,10 @@ class _SyncItemDetailsState extends ConsumerState<SyncItemDetails> {
|
||||||
},
|
},
|
||||||
child: Text(context.localized.delete),
|
child: Text(context.localized.delete),
|
||||||
)
|
)
|
||||||
else if (syncedItem.parentId != null)
|
else if (baseItem?.parentBaseModel != null)
|
||||||
ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: () {
|
onPressed: () async {
|
||||||
final parentItem = ref.read(syncProvider.notifier).getParentItem(syncedItem.parentId!);
|
final parentItem = await ref.read(syncProvider.notifier).getSyncedItem(baseItem!.parentBaseModel);
|
||||||
setState(() {
|
setState(() {
|
||||||
if (parentItem != null) {
|
if (parentItem != null) {
|
||||||
syncedItem = parentItem;
|
syncedItem = parentItem;
|
||||||
|
|
@ -208,6 +238,9 @@ class _SyncItemDetailsState extends ConsumerState<SyncItemDetails> {
|
||||||
child: Text(context.localized.syncOpenParent),
|
child: Text(context.localized.syncOpenParent),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
));
|
),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:background_downloader/background_downloader.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:iconsax_plus/iconsax_plus.dart';
|
import 'package:iconsax_plus/iconsax_plus.dart';
|
||||||
|
|
||||||
|
|
@ -12,7 +13,6 @@ import 'package:fladder/screens/syncing/sync_widgets.dart';
|
||||||
import 'package:fladder/screens/syncing/widgets/sync_progress_builder.dart';
|
import 'package:fladder/screens/syncing/widgets/sync_progress_builder.dart';
|
||||||
import 'package:fladder/screens/syncing/widgets/sync_status_overlay.dart';
|
import 'package:fladder/screens/syncing/widgets/sync_status_overlay.dart';
|
||||||
import 'package:fladder/util/fladder_image.dart';
|
import 'package:fladder/util/fladder_image.dart';
|
||||||
import 'package:fladder/util/list_padding.dart';
|
|
||||||
import 'package:fladder/util/localization_helper.dart';
|
import 'package:fladder/util/localization_helper.dart';
|
||||||
import 'package:fladder/util/size_formatting.dart';
|
import 'package:fladder/util/size_formatting.dart';
|
||||||
|
|
||||||
|
|
@ -28,8 +28,7 @@ class SyncListItemState extends ConsumerState<SyncListItem> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final syncedItem = widget.syncedItem;
|
final syncedItem = widget.syncedItem;
|
||||||
final baseItem = ref.read(syncProvider.notifier).getItem(syncedItem);
|
final baseItem = syncedItem.itemModel;
|
||||||
final children = ref.watch(syncChildrenProvider(syncedItem));
|
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
child: SyncStatusOverlay(
|
child: SyncStatusOverlay(
|
||||||
|
|
@ -67,7 +66,8 @@ class SyncListItemState extends ConsumerState<SyncListItem> {
|
||||||
context.localized.cancel);
|
context.localized.cancel);
|
||||||
return false;
|
return false;
|
||||||
},
|
},
|
||||||
child: LayoutBuilder(builder: (context, constraints) {
|
child: LayoutBuilder(
|
||||||
|
builder: (context, constraints) {
|
||||||
return IntrinsicHeight(
|
return IntrinsicHeight(
|
||||||
child: InkWell(
|
child: InkWell(
|
||||||
onTap: () => baseItem?.navigateTo(context),
|
onTap: () => baseItem?.navigateTo(context),
|
||||||
|
|
@ -75,6 +75,7 @@ class SyncListItemState extends ConsumerState<SyncListItem> {
|
||||||
padding: const EdgeInsets.all(8.0),
|
padding: const EdgeInsets.all(8.0),
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
spacing: 16,
|
||||||
children: [
|
children: [
|
||||||
ConstrainedBox(
|
ConstrainedBox(
|
||||||
constraints: BoxConstraints(maxHeight: 125, maxWidth: constraints.maxWidth * 0.2),
|
constraints: BoxConstraints(maxHeight: 125, maxWidth: constraints.maxWidth * 0.2),
|
||||||
|
|
@ -88,13 +89,18 @@ class SyncListItemState extends ConsumerState<SyncListItem> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SyncProgressBuilder(
|
child: FutureBuilder(
|
||||||
|
future: ref.read(syncProvider.notifier).getNestedChildren(syncedItem),
|
||||||
|
builder: (context, asyncSnapshot) {
|
||||||
|
final nestedChildren = asyncSnapshot.data ?? [];
|
||||||
|
return SyncProgressBuilder(
|
||||||
item: syncedItem,
|
item: syncedItem,
|
||||||
children: children,
|
children: nestedChildren,
|
||||||
builder: (context, combinedStream) {
|
builder: (context, combinedStream) {
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
spacing: 4,
|
||||||
children: [
|
children: [
|
||||||
Flexible(
|
Flexible(
|
||||||
child: Text(
|
child: Text(
|
||||||
|
|
@ -107,20 +113,24 @@ class SyncListItemState extends ConsumerState<SyncListItem> {
|
||||||
Flexible(
|
Flexible(
|
||||||
child: SyncSubtitle(
|
child: SyncSubtitle(
|
||||||
syncItem: syncedItem,
|
syncItem: syncedItem,
|
||||||
children: children,
|
children: nestedChildren,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Flexible(
|
Flexible(
|
||||||
child: SyncLabel(
|
child: Consumer(
|
||||||
|
builder: (context, ref, child) => SyncLabel(
|
||||||
label: context.localized.totalSize(
|
label: context.localized.totalSize(
|
||||||
ref.watch(syncSizeProvider(syncedItem, children)).byteFormat ?? '--'),
|
ref.watch(syncSizeProvider(syncedItem, nestedChildren)).byteFormat ??
|
||||||
status: ref.watch(syncStatusesProvider(syncedItem, children)).value ??
|
'--'),
|
||||||
SyncStatus.partially,
|
status: combinedStream?.status ?? TaskStatus.notFound,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (combinedStream != null && combinedStream.hasDownload == true)
|
if (combinedStream != null && combinedStream.hasDownload == true)
|
||||||
SyncProgressBar(item: syncedItem, task: combinedStream)
|
SyncProgressBar(item: syncedItem, task: combinedStream)
|
||||||
].addInBetween(const SizedBox(height: 4)),
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
@ -142,12 +152,13 @@ class SyncListItemState extends ConsumerState<SyncListItem> {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
].addInBetween(const SizedBox(width: 16)),
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}),
|
},
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,6 @@ import 'package:fladder/models/syncing/sync_item.dart';
|
||||||
import 'package:fladder/providers/sync/background_download_provider.dart';
|
import 'package:fladder/providers/sync/background_download_provider.dart';
|
||||||
import 'package:fladder/providers/sync/sync_provider_helpers.dart';
|
import 'package:fladder/providers/sync/sync_provider_helpers.dart';
|
||||||
import 'package:fladder/providers/sync_provider.dart';
|
import 'package:fladder/providers/sync_provider.dart';
|
||||||
import 'package:fladder/util/list_padding.dart';
|
|
||||||
import 'package:fladder/util/localization_helper.dart';
|
import 'package:fladder/util/localization_helper.dart';
|
||||||
|
|
||||||
const _cancellableStatuses = {
|
const _cancellableStatuses = {
|
||||||
|
|
@ -24,23 +23,23 @@ const _cancellableStatuses = {
|
||||||
|
|
||||||
class SyncLabel extends ConsumerWidget {
|
class SyncLabel extends ConsumerWidget {
|
||||||
final String? label;
|
final String? label;
|
||||||
final SyncStatus status;
|
final TaskStatus status;
|
||||||
const SyncLabel({this.label, required this.status, super.key});
|
const SyncLabel({this.label, required this.status, super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
return Container(
|
return Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: status.color.withValues(alpha: 0.15),
|
color: status.color(context).withValues(alpha: 0.15),
|
||||||
borderRadius: BorderRadius.circular(10),
|
borderRadius: BorderRadius.circular(10),
|
||||||
),
|
),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||||
child: Text(
|
child: Text(
|
||||||
label ?? status.label(context),
|
label ?? status.name(context),
|
||||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||||
fontWeight: FontWeight.bold,
|
fontWeight: FontWeight.bold,
|
||||||
color: status.color,
|
color: status.color(context),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
@ -68,6 +67,7 @@ class SyncProgressBar extends ConsumerWidget {
|
||||||
Row(
|
Row(
|
||||||
crossAxisAlignment: CrossAxisAlignment.center,
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
spacing: 8,
|
||||||
children: [
|
children: [
|
||||||
Flexible(
|
Flexible(
|
||||||
child: LinearProgressIndicator(
|
child: LinearProgressIndicator(
|
||||||
|
|
@ -79,7 +79,7 @@ class SyncProgressBar extends ConsumerWidget {
|
||||||
),
|
),
|
||||||
Opacity(opacity: 0.75, child: Text("${(downloadProgress * 100).toStringAsFixed(0)}%")),
|
Opacity(opacity: 0.75, child: Text("${(downloadProgress * 100).toStringAsFixed(0)}%")),
|
||||||
if (downloadTask != null) ...{
|
if (downloadTask != null) ...{
|
||||||
if (downloadStatus != TaskStatus.paused)
|
if (downloadStatus != TaskStatus.paused && downloadStatus != TaskStatus.enqueued)
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => ref.read(backgroundDownloaderProvider).pause(downloadTask),
|
onPressed: () => ref.read(backgroundDownloaderProvider).pause(downloadTask),
|
||||||
icon: const Icon(IconsaxPlusBold.pause),
|
icon: const Icon(IconsaxPlusBold.pause),
|
||||||
|
|
@ -101,7 +101,7 @@ class SyncProgressBar extends ConsumerWidget {
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
].addInBetween(const SizedBox(width: 8)),
|
],
|
||||||
),
|
),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
],
|
],
|
||||||
|
|
@ -120,33 +120,46 @@ class SyncSubtitle extends ConsumerWidget {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final baseItem = ref.read(syncProvider.notifier).getItem(syncItem);
|
final baseItem = syncItem.itemModel;
|
||||||
final syncStatus = ref.watch(syncStatusesProvider(syncItem, children)).value ?? SyncStatus.partially;
|
final syncStatus = ref
|
||||||
|
.watch(syncDownloadStatusProvider(syncItem, children).select((value) => value?.status ?? TaskStatus.notFound));
|
||||||
return Container(
|
return Container(
|
||||||
decoration:
|
decoration: BoxDecoration(
|
||||||
BoxDecoration(color: syncStatus.color.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(10)),
|
color: syncStatus.color(context).withValues(alpha: 0.15), borderRadius: BorderRadius.circular(10)),
|
||||||
child: Material(
|
child: Material(
|
||||||
color: const Color.fromARGB(0, 208, 130, 130),
|
color: const Color.fromARGB(0, 208, 130, 130),
|
||||||
textStyle:
|
textStyle: Theme.of(context)
|
||||||
Theme.of(context).textTheme.bodyMedium?.copyWith(fontWeight: FontWeight.bold, color: syncStatus.color),
|
.textTheme
|
||||||
|
.bodyMedium
|
||||||
|
?.copyWith(fontWeight: FontWeight.bold, color: syncStatus.color(context)),
|
||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
|
||||||
child: switch (baseItem) {
|
child: switch (baseItem) {
|
||||||
SeriesModel _ => Builder(
|
SeasonModel _ => Builder(
|
||||||
builder: (context) {
|
builder: (context) {
|
||||||
final itemBaseModels = children.map((e) => ref.read(syncProvider.notifier).getItem(e));
|
final itemBaseModels = children.map((e) => e.itemModel);
|
||||||
final seriesItemsSyncLeft = children.where((element) => element.taskId != null).length;
|
|
||||||
final seasons = itemBaseModels.whereType<SeasonModel>().length;
|
|
||||||
final episodes = itemBaseModels.whereType<EpisodeModel>().length;
|
final episodes = itemBaseModels.whereType<EpisodeModel>().length;
|
||||||
return Text(
|
return Text(
|
||||||
[
|
[
|
||||||
"${context.localized.season(seasons)}: $seasons",
|
"${context.localized.episode(2)}: $episodes | ${context.localized.syncStatusSynced}: ${children.where((element) => element.videoFile.existsSync()).length}"
|
||||||
"${context.localized.episode(seasons)}: $episodes | ${context.localized.sync}: ${children.where((element) => element.videoFile.existsSync()).length}${seriesItemsSyncLeft > 0 ? " | Syncing: $seriesItemsSyncLeft" : ""}"
|
|
||||||
].join('\n'),
|
].join('\n'),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
_ => Text(syncStatus.label(context)),
|
SeriesModel _ => Builder(
|
||||||
|
builder: (context) {
|
||||||
|
final itemBaseModels = children.map((e) => e.itemModel);
|
||||||
|
final seasons = itemBaseModels.whereType<SeasonModel>().length;
|
||||||
|
final episodes = itemBaseModels.whereType<EpisodeModel>().length;
|
||||||
|
return Text(
|
||||||
|
[
|
||||||
|
"${context.localized.season(2)}: $seasons",
|
||||||
|
"${context.localized.episode(2)}: $episodes | ${context.localized.syncStatusSynced}: ${children.where((element) => element.videoFile.existsSync()).length}"
|
||||||
|
].join('\n'),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
_ => Text(syncStatus.name(context)),
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
|
|
@ -52,6 +53,30 @@ class _SyncedScreenState extends ConsumerState<SyncedScreen> {
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
const DefaultSliverTopBadding(),
|
const DefaultSliverTopBadding(),
|
||||||
|
if (kDebugMode)
|
||||||
|
SliverToBoxAdapter(
|
||||||
|
child: Padding(
|
||||||
|
padding: padding,
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
|
spacing: 12,
|
||||||
|
children: [
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => ref.read(syncProvider.notifier).viewDatabase(context),
|
||||||
|
child: const Text("View Database"),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => ref.read(syncProvider.notifier).db.clearDatabase(),
|
||||||
|
child: const Text("Clear drift database"),
|
||||||
|
),
|
||||||
|
ElevatedButton(
|
||||||
|
onPressed: () => ref.read(syncProvider.notifier).migrateFromIsar(),
|
||||||
|
child: const Text("Migrate Isar to Drift"),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
if (items.isNotEmpty) ...[
|
if (items.isNotEmpty) ...[
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: Padding(
|
child: Padding(
|
||||||
|
|
|
||||||
139
lib/screens/syncing/widgets/sync_options_button.dart
Normal file
139
lib/screens/syncing/widgets/sync_options_button.dart
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
import 'dart:async';
|
||||||
|
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:background_downloader/background_downloader.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:iconsax_plus/iconsax_plus.dart';
|
||||||
|
|
||||||
|
import 'package:fladder/models/syncing/sync_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';
|
||||||
|
import 'package:fladder/util/refresh_state.dart';
|
||||||
|
import 'package:fladder/widgets/shared/filled_button_await.dart';
|
||||||
|
|
||||||
|
class SyncOptionsButton extends ConsumerWidget {
|
||||||
|
final SyncedItem syncedItem;
|
||||||
|
final List<SyncedItem> children;
|
||||||
|
const SyncOptionsButton({
|
||||||
|
required this.syncedItem,
|
||||||
|
required this.children,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
|
return PopupMenuButton(
|
||||||
|
itemBuilder: (context) {
|
||||||
|
final unSyncedChildren = children.where((element) {
|
||||||
|
final hasDownload = ref.read(syncDownloadStatusProvider(element, []));
|
||||||
|
return element.hasVideoFile && !element.videoFile.existsSync() && hasDownload?.status == TaskStatus.notFound;
|
||||||
|
}).toList();
|
||||||
|
|
||||||
|
final syncedChildren =
|
||||||
|
children.where((element) => element.hasVideoFile && element.videoFile.existsSync()).toList();
|
||||||
|
return [
|
||||||
|
PopupMenuItem(
|
||||||
|
child: Row(
|
||||||
|
spacing: 12,
|
||||||
|
children: [
|
||||||
|
const Icon(IconsaxPlusLinear.refresh_2),
|
||||||
|
Text(context.localized.refreshMetadata),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: () => context.refreshData(),
|
||||||
|
),
|
||||||
|
if (children.isNotEmpty) ...[
|
||||||
|
PopupMenuItem(
|
||||||
|
enabled: unSyncedChildren.isNotEmpty,
|
||||||
|
child: Row(
|
||||||
|
spacing: 12,
|
||||||
|
children: [
|
||||||
|
const Icon(IconsaxPlusLinear.cloud_add),
|
||||||
|
Text(context.localized.sync),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: () async => _syncRemainingItems(context, syncedItem, unSyncedChildren, ref),
|
||||||
|
),
|
||||||
|
PopupMenuItem(
|
||||||
|
enabled: syncedChildren.isNotEmpty,
|
||||||
|
child: Row(
|
||||||
|
spacing: 12,
|
||||||
|
children: [
|
||||||
|
const Icon(IconsaxPlusLinear.trash),
|
||||||
|
Text(context.localized.delete),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
onTap: () async => _deleteSyncedItems(context, syncedItem, syncedChildren, ref),
|
||||||
|
)
|
||||||
|
]
|
||||||
|
];
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<dynamic> _deleteSyncedItems(
|
||||||
|
BuildContext context, SyncedItem syncedItem, List<SyncedItem> syncedChildren, WidgetRef ref) {
|
||||||
|
return showDialog(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: Text(context.localized.syncDeleteAllItemsTitle(syncedItem.itemModel?.name ?? "")),
|
||||||
|
content: Text(
|
||||||
|
context.localized.syncDeleteAllItemsDesc(syncedItem.itemModel?.name ?? "", syncedChildren.length),
|
||||||
|
),
|
||||||
|
scrollable: true,
|
||||||
|
actions: [
|
||||||
|
ElevatedButton(onPressed: () => Navigator.of(context).pop(), child: Text(context.localized.cancel)),
|
||||||
|
FilledButtonAwait(
|
||||||
|
style: FilledButton.styleFrom(
|
||||||
|
backgroundColor: Theme.of(context).colorScheme.errorContainer,
|
||||||
|
foregroundColor: Theme.of(context).colorScheme.onErrorContainer,
|
||||||
|
iconColor: Theme.of(context).colorScheme.onErrorContainer,
|
||||||
|
),
|
||||||
|
onPressed: () async {
|
||||||
|
final deleteList = syncedChildren.map((e) => ref.read(syncProvider.notifier).deleteFullSyncFiles(e, null));
|
||||||
|
await Future.wait(deleteList);
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
context.localized.delete,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<dynamic> _syncRemainingItems(
|
||||||
|
BuildContext context, SyncedItem syncedItem, List<SyncedItem> unSyncedChildren, WidgetRef ref) {
|
||||||
|
return showDialog(
|
||||||
|
context: context,
|
||||||
|
barrierDismissible: false,
|
||||||
|
builder: (context) => AlertDialog(
|
||||||
|
title: Text(context.localized.syncAllItemsTitle(syncedItem.itemModel?.name ?? "")),
|
||||||
|
content: Text(
|
||||||
|
context.localized.syncAllItemsDesc(
|
||||||
|
syncedItem.itemModel?.name ?? "",
|
||||||
|
unSyncedChildren.length,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
scrollable: true,
|
||||||
|
actions: [
|
||||||
|
ElevatedButton(onPressed: () => Navigator.of(context).pop(), child: Text(context.localized.cancel)),
|
||||||
|
FilledButtonAwait(
|
||||||
|
onPressed: () async {
|
||||||
|
final syncList = unSyncedChildren.map((e) => ref.read(syncProvider.notifier).syncFile(e, false));
|
||||||
|
await Future.wait(syncList);
|
||||||
|
Navigator.of(context).pop();
|
||||||
|
},
|
||||||
|
child: Text(
|
||||||
|
context.localized.sync,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
@ -1,6 +1,7 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
|
import 'package:background_downloader/background_downloader.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:iconsax_plus/iconsax_plus.dart';
|
import 'package:iconsax_plus/iconsax_plus.dart';
|
||||||
|
|
||||||
|
|
@ -22,12 +23,10 @@ class SyncedEpisodeItem extends ConsumerStatefulWidget {
|
||||||
super.key,
|
super.key,
|
||||||
required this.episode,
|
required this.episode,
|
||||||
required this.syncedItem,
|
required this.syncedItem,
|
||||||
required this.hasFile,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
final EpisodeModel episode;
|
final EpisodeModel episode;
|
||||||
final SyncedItem syncedItem;
|
final SyncedItem syncedItem;
|
||||||
final bool hasFile;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ConsumerState<SyncedEpisodeItem> createState() => _SyncedEpisodeItemState();
|
ConsumerState<SyncedEpisodeItem> createState() => _SyncedEpisodeItemState();
|
||||||
|
|
@ -40,7 +39,8 @@ class _SyncedEpisodeItemState extends ConsumerState<SyncedEpisodeItem> {
|
||||||
final downloadTask = ref.watch(downloadTasksProvider(syncedItem.id));
|
final downloadTask = ref.watch(downloadTasksProvider(syncedItem.id));
|
||||||
final hasFile = widget.syncedItem.videoFile.existsSync();
|
final hasFile = widget.syncedItem.videoFile.existsSync();
|
||||||
|
|
||||||
return Row(
|
return IntrinsicHeight(
|
||||||
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
ConstrainedBox(
|
ConstrainedBox(
|
||||||
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.3),
|
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.3),
|
||||||
|
|
@ -53,7 +53,6 @@ class _SyncedEpisodeItemState extends ConsumerState<SyncedEpisodeItem> {
|
||||||
width: 175,
|
width: 175,
|
||||||
child: EpisodePoster(
|
child: EpisodePoster(
|
||||||
episode: widget.episode,
|
episode: widget.episode,
|
||||||
syncedItem: syncedItem,
|
|
||||||
actions: [],
|
actions: [],
|
||||||
showLabel: false,
|
showLabel: false,
|
||||||
isCurrentEpisode: false,
|
isCurrentEpisode: false,
|
||||||
|
|
@ -86,15 +85,17 @@ class _SyncedEpisodeItemState extends ConsumerState<SyncedEpisodeItem> {
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (!widget.hasFile && downloadTask.hasDownload)
|
if (!hasFile && downloadTask.hasDownload)
|
||||||
Flexible(
|
Flexible(
|
||||||
child: SyncProgressBar(item: syncedItem, task: downloadTask),
|
child: SyncProgressBar(item: syncedItem, task: downloadTask),
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
Flexible(
|
Flexible(
|
||||||
child: SyncLabel(
|
child: SyncLabel(
|
||||||
label: context.localized.totalSize(ref.watch(syncSizeProvider(syncedItem, [])).byteFormat ?? '--'),
|
label:
|
||||||
status: ref.watch(syncStatusesProvider(syncedItem, [])).value ?? SyncStatus.partially,
|
context.localized.totalSize(ref.watch(syncSizeProvider(syncedItem, [])).byteFormat ?? '--'),
|
||||||
|
status: ref.watch(syncDownloadStatusProvider(syncedItem, [])
|
||||||
|
.select((value) => value?.status ?? TaskStatus.notFound)),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
|
@ -125,6 +126,7 @@ class _SyncedEpisodeItemState extends ConsumerState<SyncedEpisodeItem> {
|
||||||
icon: const Icon(IconsaxPlusLinear.trash),
|
icon: const Icon(IconsaxPlusLinear.trash),
|
||||||
)
|
)
|
||||||
].addInBetween(const SizedBox(width: 16)),
|
].addInBetween(const SizedBox(width: 16)),
|
||||||
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,21 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:auto_route/auto_route.dart';
|
import 'package:auto_route/auto_route.dart';
|
||||||
|
import 'package:background_downloader/background_downloader.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:iconsax_plus/iconsax_plus.dart';
|
|
||||||
|
|
||||||
import 'package:fladder/models/items/episode_model.dart';
|
import 'package:fladder/models/items/episode_model.dart';
|
||||||
import 'package:fladder/models/items/season_model.dart';
|
import 'package:fladder/models/items/season_model.dart';
|
||||||
import 'package:fladder/models/syncing/sync_item.dart';
|
import 'package:fladder/models/syncing/sync_item.dart';
|
||||||
import 'package:fladder/providers/sync_provider.dart';
|
import 'package:fladder/providers/sync/sync_provider_helpers.dart';
|
||||||
import 'package:fladder/screens/shared/flat_button.dart';
|
import 'package:fladder/screens/shared/flat_button.dart';
|
||||||
|
import 'package:fladder/screens/syncing/sync_widgets.dart';
|
||||||
|
import 'package:fladder/screens/syncing/widgets/sync_options_button.dart';
|
||||||
|
import 'package:fladder/screens/syncing/widgets/sync_progress_builder.dart';
|
||||||
import 'package:fladder/screens/syncing/widgets/synced_episode_item.dart';
|
import 'package:fladder/screens/syncing/widgets/synced_episode_item.dart';
|
||||||
import 'package:fladder/util/fladder_image.dart';
|
import 'package:fladder/util/fladder_image.dart';
|
||||||
import 'package:fladder/widgets/shared/icon_button_await.dart';
|
import 'package:fladder/util/localization_helper.dart';
|
||||||
|
import 'package:fladder/util/size_formatting.dart';
|
||||||
|
|
||||||
class SyncedSeasonPoster extends ConsumerStatefulWidget {
|
class SyncedSeasonPoster extends ConsumerStatefulWidget {
|
||||||
const SyncedSeasonPoster({
|
const SyncedSeasonPoster({
|
||||||
|
|
@ -32,12 +36,16 @@ class _SyncedSeasonPosterState extends ConsumerState<SyncedSeasonPoster> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final season = widget.season;
|
final season = widget.season;
|
||||||
final children = ref.read(syncProvider.notifier).getChildren(widget.syncedItem);
|
final nestedChildren = ref.watch(syncedNestedChildrenProvider(widget.syncedItem));
|
||||||
final unSyncedChildren = children.where((child) => child.status == SyncStatus.partially).toList();
|
return nestedChildren.when(
|
||||||
|
data: (children) => Builder(
|
||||||
|
builder: (context) {
|
||||||
|
final syncedItem = widget.syncedItem;
|
||||||
return ExpansionTile(
|
return ExpansionTile(
|
||||||
tilePadding: EdgeInsets.zero,
|
tilePadding: EdgeInsets.zero,
|
||||||
|
shape: const Border(),
|
||||||
title: Row(
|
title: Row(
|
||||||
spacing: 6,
|
spacing: 12,
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 75,
|
width: 75,
|
||||||
|
|
@ -58,46 +66,66 @@ class _SyncedSeasonPosterState extends ConsumerState<SyncedSeasonPoster> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Column(
|
Flexible(
|
||||||
children: [
|
child: SyncProgressBuilder(
|
||||||
Text(
|
item: syncedItem,
|
||||||
season.name,
|
children: children,
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
builder: (context, combinedStream) {
|
||||||
)
|
return Column(
|
||||||
],
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
trailing: Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
spacing: 4,
|
||||||
children: [
|
children: [
|
||||||
if (unSyncedChildren.isNotEmpty)
|
Flexible(
|
||||||
IconButtonAwait(
|
child: Text(
|
||||||
onPressed: () async {
|
season.name,
|
||||||
for (var i = 0; i < unSyncedChildren.length; i++) {
|
maxLines: 3,
|
||||||
final childSyncedItem = unSyncedChildren[i];
|
overflow: TextOverflow.ellipsis,
|
||||||
await ref.read(syncProvider.notifier).syncFile(childSyncedItem, false);
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
}
|
),
|
||||||
|
),
|
||||||
|
Flexible(
|
||||||
|
child: SyncSubtitle(
|
||||||
|
syncItem: syncedItem,
|
||||||
|
children: children,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Flexible(
|
||||||
|
child: Consumer(
|
||||||
|
builder: (context, ref, child) => SyncLabel(
|
||||||
|
label: context.localized
|
||||||
|
.totalSize(ref.watch(syncSizeProvider(syncedItem, children))?.byteFormat ?? '--'),
|
||||||
|
status: combinedStream?.status ?? TaskStatus.notFound,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (combinedStream != null && combinedStream.hasDownload == true)
|
||||||
|
SyncProgressBar(item: syncedItem, task: combinedStream)
|
||||||
|
],
|
||||||
|
);
|
||||||
},
|
},
|
||||||
icon: const Icon(IconsaxPlusLinear.cloud_change),
|
),
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
trailing: SyncOptionsButton(syncedItem: syncedItem, children: children),
|
||||||
children: children.map(
|
children: children.map(
|
||||||
(item) {
|
(item) {
|
||||||
final baseItem = ref.read(syncProvider.notifier).getItem(item);
|
final baseItem = item.itemModel;
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 8),
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
child: IntrinsicHeight(
|
|
||||||
child: SyncedEpisodeItem(
|
child: SyncedEpisodeItem(
|
||||||
episode: baseItem as EpisodeModel,
|
episode: baseItem as EpisodeModel,
|
||||||
syncedItem: item,
|
syncedItem: item,
|
||||||
hasFile: item.videoFile.existsSync(),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
).toList(),
|
).toList(),
|
||||||
);
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
error: (error, stackTrace) => const SizedBox.shrink(),
|
||||||
|
loading: () => const SizedBox.shrink(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,7 +9,6 @@ import 'package:fladder/models/item_base_model.dart';
|
||||||
import 'package:fladder/models/items/episode_model.dart';
|
import 'package:fladder/models/items/episode_model.dart';
|
||||||
import 'package:fladder/models/items/item_shared_models.dart';
|
import 'package:fladder/models/items/item_shared_models.dart';
|
||||||
import 'package:fladder/models/items/photos_model.dart';
|
import 'package:fladder/models/items/photos_model.dart';
|
||||||
import 'package:fladder/models/syncing/sync_item.dart';
|
|
||||||
import 'package:fladder/providers/sync_provider.dart';
|
import 'package:fladder/providers/sync_provider.dart';
|
||||||
import 'package:fladder/providers/user_provider.dart';
|
import 'package:fladder/providers/user_provider.dart';
|
||||||
import 'package:fladder/screens/collections/add_to_collection.dart';
|
import 'package:fladder/screens/collections/add_to_collection.dart';
|
||||||
|
|
@ -110,8 +109,8 @@ extension ItemBaseModelExtensions on ItemBaseModel {
|
||||||
)) &&
|
)) &&
|
||||||
syncAble &&
|
syncAble &&
|
||||||
(canDownload ?? false);
|
(canDownload ?? false);
|
||||||
final syncedItem = ref.read(syncProvider.notifier).getSyncedItem(this);
|
|
||||||
final downloadUrl = ref.read(userProvider.notifier).createDownloadUrl(this);
|
final downloadUrl = ref.read(userProvider.notifier).createDownloadUrl(this);
|
||||||
|
final syncedItemFuture = ref.read(syncProvider.notifier).getSyncedItem(this);
|
||||||
return [
|
return [
|
||||||
if (!exclude.contains(ItemActions.play))
|
if (!exclude.contains(ItemActions.play))
|
||||||
if (playAble)
|
if (playAble)
|
||||||
|
|
@ -235,21 +234,38 @@ extension ItemBaseModelExtensions on ItemBaseModel {
|
||||||
),
|
),
|
||||||
if (!exclude.contains(ItemActions.download) && downloadEnabled) ...[
|
if (!exclude.contains(ItemActions.download) && downloadEnabled) ...[
|
||||||
if (!kIsWeb)
|
if (!kIsWeb)
|
||||||
if (syncedItem == null)
|
|
||||||
ItemActionButton(
|
ItemActionButton(
|
||||||
icon: const Icon(IconsaxPlusLinear.arrow_down_2),
|
icon: FutureBuilder(
|
||||||
label: Text(context.localized.sync),
|
future: syncedItemFuture,
|
||||||
action: () => ref.read(syncProvider.notifier).addSyncItem(context, this),
|
builder: (context, snapshot) {
|
||||||
)
|
final syncedItem = snapshot.data;
|
||||||
else
|
if (syncedItem != null) {
|
||||||
ItemActionButton(
|
return IgnorePointer(child: SyncButton(item: this, syncedItem: syncedItem));
|
||||||
icon: IgnorePointer(child: SyncButton(item: this, syncedItem: syncedItem)),
|
}
|
||||||
action: () => syncedItem.status == SyncStatus.complete
|
return const Icon(IconsaxPlusLinear.arrow_down_2);
|
||||||
? ref.read(syncProvider.notifier).deleteFullSyncFiles(syncedItem, null)
|
},
|
||||||
: ref.read(syncProvider.notifier).syncFile(syncedItem, false),
|
|
||||||
label: Text(
|
|
||||||
syncedItem.status == SyncStatus.complete ? context.localized.delete : context.localized.sync,
|
|
||||||
),
|
),
|
||||||
|
label: FutureBuilder(
|
||||||
|
future: syncedItemFuture,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
final syncedItem = snapshot.data;
|
||||||
|
if (syncedItem != null) {
|
||||||
|
return Text(
|
||||||
|
context.localized.syncDetails,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return Text(context.localized.sync);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
action: () async {
|
||||||
|
final syncedItem = await syncedItemFuture;
|
||||||
|
if (syncedItem != null) {
|
||||||
|
await showSyncItemDetails(context, syncedItem, ref);
|
||||||
|
} else {
|
||||||
|
await ref.read(syncProvider.notifier).addSyncItem(context, this);
|
||||||
|
}
|
||||||
|
context.refreshData();
|
||||||
|
},
|
||||||
)
|
)
|
||||||
else if (downloadUrl != null) ...[
|
else if (downloadUrl != null) ...[
|
||||||
ItemActionButton(
|
ItemActionButton(
|
||||||
|
|
|
||||||
80
lib/util/migration/isar_drift_migration.dart
Normal file
80
lib/util/migration/isar_drift_migration.dart
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
import 'dart:convert';
|
||||||
|
import 'dart:developer';
|
||||||
|
import 'dart:io';
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
import 'package:drift/drift.dart';
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
import 'package:isar/isar.dart';
|
||||||
|
import 'package:path/path.dart' as path;
|
||||||
|
import 'package:path_provider/path_provider.dart';
|
||||||
|
|
||||||
|
import 'package:fladder/models/syncing/database_item.dart';
|
||||||
|
import 'package:fladder/models/syncing/i_synced_item.dart';
|
||||||
|
import 'package:fladder/models/syncing/sync_item.dart';
|
||||||
|
|
||||||
|
Future<void> isarMigration(Ref ref, AppDatabase db, String savePath) async {
|
||||||
|
if (kIsWeb) return;
|
||||||
|
|
||||||
|
//Return if the database is already migrated
|
||||||
|
final isNotEmtpy = await db.select(db.databaseItems).get().then((value) => value.isNotEmpty);
|
||||||
|
if (isNotEmtpy) {
|
||||||
|
log('Isar database is not empty, skipping migration');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
//Open isar database
|
||||||
|
final applicationDirectory = await getApplicationDocumentsDirectory();
|
||||||
|
final isarPath = Directory(path.joinAll([applicationDirectory.path, 'Fladder', 'Database']));
|
||||||
|
await isarPath.create(recursive: true);
|
||||||
|
final isar = Isar.open(
|
||||||
|
schemas: [ISyncedItemSchema],
|
||||||
|
directory: isarPath.path,
|
||||||
|
);
|
||||||
|
|
||||||
|
//Fetch all synced items from the old database
|
||||||
|
List<SyncedItem> items = isar.iSyncedItems
|
||||||
|
.where()
|
||||||
|
.findAll()
|
||||||
|
.map((e) => SyncedItem.fromIsar(e, path.joinAll([savePath, e.userId ?? "Unkown User"])))
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
//Clear any missing paths
|
||||||
|
items = items.where((e) => e.path != null ? Directory(e.path!).existsSync() : false).toList();
|
||||||
|
|
||||||
|
//Convert to drift database items
|
||||||
|
final driftItems = items.map(
|
||||||
|
(item) => DatabaseItemsCompanion(
|
||||||
|
id: Value(item.id),
|
||||||
|
parentId: Value(item.parentId),
|
||||||
|
syncing: Value(item.syncing),
|
||||||
|
userId: Value(item.userId),
|
||||||
|
path: Value(item.path),
|
||||||
|
fileSize: Value(item.fileSize),
|
||||||
|
sortName: Value(item.sortName),
|
||||||
|
videoFileName: Value(item.videoFileName),
|
||||||
|
trickPlayModel: Value(item.fTrickPlayModel != null ? jsonEncode(item.fTrickPlayModel?.toJson()) : null),
|
||||||
|
mediaSegments: Value(item.mediaSegments != null ? jsonEncode(item.mediaSegments?.toJson()) : null),
|
||||||
|
images: Value(item.fImages != null ? jsonEncode(item.fImages?.toJson()) : null),
|
||||||
|
chapters: Value(jsonEncode(item.fChapters.map((e) => e.toJson()).toList())),
|
||||||
|
subtitles: Value(jsonEncode(item.subtitles.map((e) => e.toJson()).toList())),
|
||||||
|
userData: Value(item.userData != null ? jsonEncode(item.userData?.toJson()) : null),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
await db.batch((batch) {
|
||||||
|
batch.insertAll(
|
||||||
|
db.databaseItems,
|
||||||
|
driftItems,
|
||||||
|
mode: InsertMode.insertOrReplace,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
//Delete database file
|
||||||
|
final baseFolder = Directory(path.join(applicationDirectory.path, 'Fladder'));
|
||||||
|
if (await baseFolder.exists()) {
|
||||||
|
log('Deleting old Fladder base folder: ${baseFolder.path}');
|
||||||
|
// await baseFolder.delete(recursive: true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -30,7 +30,7 @@ class ActionContent extends StatelessWidget {
|
||||||
height: 4,
|
height: 4,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
Expanded(child: child),
|
Flexible(child: child),
|
||||||
if (actions.isNotEmpty) ...[
|
if (actions.isNotEmpty) ...[
|
||||||
if (showDividers)
|
if (showDividers)
|
||||||
const Divider(
|
const Divider(
|
||||||
|
|
|
||||||
|
|
@ -34,11 +34,12 @@ class IconButtonAwaitState extends State<IconButtonAwait> {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e.toString());
|
log(e.toString());
|
||||||
} finally {
|
} finally {
|
||||||
|
if (mounted) {
|
||||||
setState(() {
|
setState(() {
|
||||||
if (!mounted) return;
|
|
||||||
loading = false;
|
loading = false;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
icon: AnimatedFadeSize(
|
icon: AnimatedFadeSize(
|
||||||
duration: duration,
|
duration: duration,
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@ class StatusCard extends ConsumerWidget {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.all(5),
|
padding: const EdgeInsets.all(2),
|
||||||
child: SizedBox.square(
|
child: SizedBox.square(
|
||||||
dimension: 33,
|
dimension: 33,
|
||||||
child: Card(
|
child: Card(
|
||||||
|
|
|
||||||
|
|
@ -13,6 +13,7 @@
|
||||||
#include <media_kit_libs_linux/media_kit_libs_linux_plugin.h>
|
#include <media_kit_libs_linux/media_kit_libs_linux_plugin.h>
|
||||||
#include <media_kit_video/media_kit_video_plugin.h>
|
#include <media_kit_video/media_kit_video_plugin.h>
|
||||||
#include <screen_retriever_linux/screen_retriever_linux_plugin.h>
|
#include <screen_retriever_linux/screen_retriever_linux_plugin.h>
|
||||||
|
#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
|
||||||
#include <url_launcher_linux/url_launcher_plugin.h>
|
#include <url_launcher_linux/url_launcher_plugin.h>
|
||||||
#include <volume_controller/volume_controller_plugin.h>
|
#include <volume_controller/volume_controller_plugin.h>
|
||||||
#include <window_manager/window_manager_plugin.h>
|
#include <window_manager/window_manager_plugin.h>
|
||||||
|
|
@ -39,6 +40,9 @@ void fl_register_plugins(FlPluginRegistry* registry) {
|
||||||
g_autoptr(FlPluginRegistrar) screen_retriever_linux_registrar =
|
g_autoptr(FlPluginRegistrar) screen_retriever_linux_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverLinuxPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "ScreenRetrieverLinuxPlugin");
|
||||||
screen_retriever_linux_plugin_register_with_registrar(screen_retriever_linux_registrar);
|
screen_retriever_linux_plugin_register_with_registrar(screen_retriever_linux_registrar);
|
||||||
|
g_autoptr(FlPluginRegistrar) sqlite3_flutter_libs_registrar =
|
||||||
|
fl_plugin_registry_get_registrar_for_plugin(registry, "Sqlite3FlutterLibsPlugin");
|
||||||
|
sqlite3_flutter_libs_plugin_register_with_registrar(sqlite3_flutter_libs_registrar);
|
||||||
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
g_autoptr(FlPluginRegistrar) url_launcher_linux_registrar =
|
||||||
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
fl_plugin_registry_get_registrar_for_plugin(registry, "UrlLauncherPlugin");
|
||||||
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
url_launcher_plugin_register_with_registrar(url_launcher_linux_registrar);
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
media_kit_libs_linux
|
media_kit_libs_linux
|
||||||
media_kit_video
|
media_kit_video
|
||||||
screen_retriever_linux
|
screen_retriever_linux
|
||||||
|
sqlite3_flutter_libs
|
||||||
url_launcher_linux
|
url_launcher_linux
|
||||||
volume_controller
|
volume_controller
|
||||||
window_manager
|
window_manager
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@ import screen_retriever_macos
|
||||||
import share_plus
|
import share_plus
|
||||||
import shared_preferences_foundation
|
import shared_preferences_foundation
|
||||||
import sqflite_darwin
|
import sqflite_darwin
|
||||||
|
import sqlite3_flutter_libs
|
||||||
import url_launcher_macos
|
import url_launcher_macos
|
||||||
import video_player_avfoundation
|
import video_player_avfoundation
|
||||||
import volume_controller
|
import volume_controller
|
||||||
|
|
@ -51,6 +52,7 @@ func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) {
|
||||||
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
SharePlusMacosPlugin.register(with: registry.registrar(forPlugin: "SharePlusMacosPlugin"))
|
||||||
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin"))
|
||||||
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
SqflitePlugin.register(with: registry.registrar(forPlugin: "SqflitePlugin"))
|
||||||
|
Sqlite3FlutterLibsPlugin.register(with: registry.registrar(forPlugin: "Sqlite3FlutterLibsPlugin"))
|
||||||
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
UrlLauncherPlugin.register(with: registry.registrar(forPlugin: "UrlLauncherPlugin"))
|
||||||
FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin"))
|
FVPVideoPlayerPlugin.register(with: registry.registrar(forPlugin: "FVPVideoPlayerPlugin"))
|
||||||
VolumeControllerPlugin.register(with: registry.registrar(forPlugin: "VolumeControllerPlugin"))
|
VolumeControllerPlugin.register(with: registry.registrar(forPlugin: "VolumeControllerPlugin"))
|
||||||
|
|
|
||||||
144
pubspec.lock
144
pubspec.lock
|
|
@ -146,10 +146,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build
|
name: build
|
||||||
sha256: cef23f1eda9b57566c81e2133d196f8e3df48f244b317368d65c5943d91148f0
|
sha256: "51dc711996cbf609b90cbe5b335bbce83143875a9d58e4b5c6d3c4f684d3dda7"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.2"
|
version: "2.5.4"
|
||||||
build_cli_annotations:
|
build_cli_annotations:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -178,26 +178,26 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build_resolvers
|
name: build_resolvers
|
||||||
sha256: b9e4fda21d846e192628e7a4f6deda6888c36b5b69ba02ff291a01fd529140f0
|
sha256: ee4257b3f20c0c90e72ed2b57ad637f694ccba48839a821e87db762548c22a62
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.4"
|
version: "2.5.4"
|
||||||
build_runner:
|
build_runner:
|
||||||
dependency: "direct dev"
|
dependency: "direct dev"
|
||||||
description:
|
description:
|
||||||
name: build_runner
|
name: build_runner
|
||||||
sha256: "058fe9dce1de7d69c4b84fada934df3e0153dd000758c4d65964d0166779aa99"
|
sha256: "382a4d649addbfb7ba71a3631df0ec6a45d5ab9b098638144faf27f02778eb53"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.15"
|
version: "2.5.4"
|
||||||
build_runner_core:
|
build_runner_core:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: build_runner_core
|
name: build_runner_core
|
||||||
sha256: "22e3aa1c80e0ada3722fe5b63fd43d9c8990759d0a2cf489c8c5d7b2bdebc021"
|
sha256: "85fbbb1036d576d966332a3f5ce83f2ce66a40bea1a94ad2d5fc29a19a0d3792"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "8.0.0"
|
version: "9.1.2"
|
||||||
built_collection:
|
built_collection:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -430,6 +430,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.8"
|
version: "2.3.8"
|
||||||
|
db_viewer:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: db_viewer
|
||||||
|
sha256: "5f7e3cfcde9663321797d8f6f0c876f7c13f0825a2e77ec1ef065656797144d9"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "1.1.0"
|
||||||
dbus:
|
dbus:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -438,6 +446,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "0.7.11"
|
version: "0.7.11"
|
||||||
|
decimal:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: decimal
|
||||||
|
sha256: fc706a5618b81e5b367b01dd62621def37abc096f2b46a9bd9068b64c1fa36d0
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "3.2.4"
|
||||||
desktop_drop:
|
desktop_drop:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -454,6 +470,46 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "3.0.0"
|
version: "3.0.0"
|
||||||
|
drift:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: drift
|
||||||
|
sha256: b584ddeb2b74436735dd2cf746d2d021e19a9a6770f409212fd5cbc2814ada85
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.26.1"
|
||||||
|
drift_db_viewer:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: drift_db_viewer
|
||||||
|
sha256: "5ea77858c52b55460a1e8f34ab5f88324621d486717d876fd745765fbc227f3f"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.1.0"
|
||||||
|
drift_dev:
|
||||||
|
dependency: "direct dev"
|
||||||
|
description:
|
||||||
|
name: drift_dev
|
||||||
|
sha256: "0d3f8b33b76cf1c6a82ee34d9511c40957549c4674b8f1688609e6d6c7306588"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.26.0"
|
||||||
|
drift_flutter:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: drift_flutter
|
||||||
|
sha256: ccfb42bc942e59f81500b16228df59cf8eb40d2fbd96637ff677df923350af7b
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.2.5"
|
||||||
|
drift_sync:
|
||||||
|
dependency: "direct main"
|
||||||
|
description:
|
||||||
|
name: drift_sync
|
||||||
|
sha256: "522f8e651ceb9dcfb44b576593ac2d2eae79f0663d7418fb7804b3cc4d9d27e3"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.13.0"
|
||||||
dynamic_color:
|
dynamic_color:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
@ -842,6 +898,22 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.1.3"
|
version: "2.1.3"
|
||||||
|
google_identity_services_web:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: google_identity_services_web
|
||||||
|
sha256: "5d187c46dc59e02646e10fe82665fc3884a9b71bc1c90c2b8b749316d33ee454"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.3.3+1"
|
||||||
|
googleapis_auth:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: googleapis_auth
|
||||||
|
sha256: b81fe352cc4a330b3710d2b7ad258d9bcef6f909bb759b306bf42973a7d046db
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.0.0"
|
||||||
graphs:
|
graphs:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -850,6 +922,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.3.2"
|
version: "2.3.2"
|
||||||
|
grpc:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: grpc
|
||||||
|
sha256: "2dde469ddd8bbd7a33a0765da417abe1ad2142813efce3a86c512041294e2b26"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.1.0"
|
||||||
highlight:
|
highlight:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -874,6 +954,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.4.0"
|
version: "1.4.0"
|
||||||
|
http2:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: http2
|
||||||
|
sha256: "382d3aefc5bd6dc68c6b892d7664f29b5beb3251611ae946a98d35158a82bbfa"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.3.1"
|
||||||
http_client_helper:
|
http_client_helper:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1402,6 +1490,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "6.0.2"
|
version: "6.0.2"
|
||||||
|
protobuf:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: protobuf
|
||||||
|
sha256: "6153efcc92a06910918f3db8231fd2cf828ac81e50ebd87adc8f8a8cb3caff0e"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "4.1.1"
|
||||||
provider:
|
provider:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1434,6 +1530,14 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.3.7+1"
|
version: "1.3.7+1"
|
||||||
|
rational:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: rational
|
||||||
|
sha256: cb808fb6f1a839e6fc5f7d8cb3b0a10e1db48b3be102de73938c627f0b636336
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.2.3"
|
||||||
recase:
|
recase:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -1791,6 +1895,30 @@ packages:
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "2.4.0"
|
version: "2.4.0"
|
||||||
|
sqlite3:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sqlite3
|
||||||
|
sha256: "608b56d594e4c8498c972c8f1507209f9fd74939971b948ddbbfbfd1c9cb3c15"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "2.7.7"
|
||||||
|
sqlite3_flutter_libs:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sqlite3_flutter_libs
|
||||||
|
sha256: "8b4bd239bedd20ee628aed587b4c5b387328e85945c9ecbae19a93bdcd171524"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.5.36"
|
||||||
|
sqlparser:
|
||||||
|
dependency: transitive
|
||||||
|
description:
|
||||||
|
name: sqlparser
|
||||||
|
sha256: "7c859c803cf7e9a84d6db918bac824545045692bbe94a6386bd3a45132235d09"
|
||||||
|
url: "https://pub.dev"
|
||||||
|
source: hosted
|
||||||
|
version: "0.41.1"
|
||||||
square_progress_indicator:
|
square_progress_indicator:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
|
|
@ -130,6 +130,10 @@ dependencies:
|
||||||
share_plus: ^11.0.0
|
share_plus: ^11.0.0
|
||||||
archive: ^4.0.7
|
archive: ^4.0.7
|
||||||
dart_mappable: ^4.5.0
|
dart_mappable: ^4.5.0
|
||||||
|
drift: ^2.26.1
|
||||||
|
drift_flutter: ^0.2.5
|
||||||
|
drift_sync: ^0.13.0
|
||||||
|
drift_db_viewer: ^2.1.0
|
||||||
|
|
||||||
dev_dependencies:
|
dev_dependencies:
|
||||||
flutter_test:
|
flutter_test:
|
||||||
|
|
@ -140,8 +144,9 @@ dev_dependencies:
|
||||||
# activated in the `analysis_options.yaml` file located at the root of your
|
# activated in the `analysis_options.yaml` file located at the root of your
|
||||||
# package. See that file for information about deactivating specific lint
|
# package. See that file for information about deactivating specific lint
|
||||||
# rules and activating additional ones.
|
# rules and activating additional ones.
|
||||||
|
drift_dev: ^2.26.0
|
||||||
flutter_lints: ^6.0.0
|
flutter_lints: ^6.0.0
|
||||||
build_runner: ^2.4.15
|
build_runner: ^2.5.4
|
||||||
chopper_generator: ^8.1.0
|
chopper_generator: ^8.1.0
|
||||||
json_serializable: ^6.9.0
|
json_serializable: ^6.9.0
|
||||||
custom_lint: ^0.7.0
|
custom_lint: ^0.7.0
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
#include <screen_brightness_windows/screen_brightness_windows_plugin.h>
|
#include <screen_brightness_windows/screen_brightness_windows_plugin.h>
|
||||||
#include <screen_retriever_windows/screen_retriever_windows_plugin_c_api.h>
|
#include <screen_retriever_windows/screen_retriever_windows_plugin_c_api.h>
|
||||||
#include <share_plus/share_plus_windows_plugin_c_api.h>
|
#include <share_plus/share_plus_windows_plugin_c_api.h>
|
||||||
|
#include <sqlite3_flutter_libs/sqlite3_flutter_libs_plugin.h>
|
||||||
#include <url_launcher_windows/url_launcher_windows.h>
|
#include <url_launcher_windows/url_launcher_windows.h>
|
||||||
#include <volume_controller/volume_controller_plugin_c_api.h>
|
#include <volume_controller/volume_controller_plugin_c_api.h>
|
||||||
#include <window_manager/window_manager_plugin.h>
|
#include <window_manager/window_manager_plugin.h>
|
||||||
|
|
@ -44,6 +45,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) {
|
||||||
registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi"));
|
registry->GetRegistrarForPlugin("ScreenRetrieverWindowsPluginCApi"));
|
||||||
SharePlusWindowsPluginCApiRegisterWithRegistrar(
|
SharePlusWindowsPluginCApiRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
|
registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi"));
|
||||||
|
Sqlite3FlutterLibsPluginRegisterWithRegistrar(
|
||||||
|
registry->GetRegistrarForPlugin("Sqlite3FlutterLibsPlugin"));
|
||||||
UrlLauncherWindowsRegisterWithRegistrar(
|
UrlLauncherWindowsRegisterWithRegistrar(
|
||||||
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
registry->GetRegistrarForPlugin("UrlLauncherWindows"));
|
||||||
VolumeControllerPluginCApiRegisterWithRegistrar(
|
VolumeControllerPluginCApiRegisterWithRegistrar(
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ list(APPEND FLUTTER_PLUGIN_LIST
|
||||||
screen_brightness_windows
|
screen_brightness_windows
|
||||||
screen_retriever_windows
|
screen_retriever_windows
|
||||||
share_plus
|
share_plus
|
||||||
|
sqlite3_flutter_libs
|
||||||
url_launcher_windows
|
url_launcher_windows
|
||||||
volume_controller
|
volume_controller
|
||||||
window_manager
|
window_manager
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue