mirror of
https://github.com/gabehf/Fladder.git
synced 2026-03-07 21:48:14 -08:00
feature: Improved sync capability
This commit is contained in:
parent
f3e920ac79
commit
c5c7f71b84
31 changed files with 500 additions and 344 deletions
|
|
@ -1238,5 +1238,9 @@
|
||||||
},
|
},
|
||||||
"newUpdateFoundOnGithub": "Found a new update on Github",
|
"newUpdateFoundOnGithub": "Found a new update on Github",
|
||||||
"enableBackgroundPostersTitle": "Enable background posters",
|
"enableBackgroundPostersTitle": "Enable background posters",
|
||||||
"enableBackgroundPostersDesc": "Show random posters in applicable screens"
|
"enableBackgroundPostersDesc": "Show random posters in applicable screens",
|
||||||
|
"notificationDownloadingDownloading": "Downloading",
|
||||||
|
"notificationDownloadingPaused": "Download paused",
|
||||||
|
"notificationDownloadingFinished": "Download finished",
|
||||||
|
"notificationDownloadingError": "Download error"
|
||||||
}
|
}
|
||||||
|
|
@ -297,6 +297,7 @@ class _MainState extends ConsumerState<Main> with WindowListener, WidgetsBinding
|
||||||
},
|
},
|
||||||
builder: (context, child) => LocalizationContextWrapper(
|
builder: (context, child) => LocalizationContextWrapper(
|
||||||
child: ScaffoldMessenger(child: child ?? Container()),
|
child: ScaffoldMessenger(child: child ?? Container()),
|
||||||
|
currentLocale: language,
|
||||||
),
|
),
|
||||||
debugShowCheckedModeBanner: false,
|
debugShowCheckedModeBanner: false,
|
||||||
darkTheme: darkTheme.copyWith(
|
darkTheme: darkTheme.copyWith(
|
||||||
|
|
|
||||||
|
|
@ -26,7 +26,6 @@ class CredentialsModel {
|
||||||
Map<String, String> header(Ref ref) {
|
Map<String, String> header(Ref ref) {
|
||||||
final application = ref.read(applicationInfoProvider);
|
final application = ref.read(applicationInfoProvider);
|
||||||
final headers = {
|
final headers = {
|
||||||
'content-type': 'application/json',
|
|
||||||
'authorization':
|
'authorization':
|
||||||
'MediaBrowser Token="$token", Client="${application.name}", Device="${application.os}", DeviceId="$deviceId", Version="${application.version}"'
|
'MediaBrowser Token="$token", Client="${application.name}", Device="${application.os}", DeviceId="$deviceId", Version="${application.version}"'
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -74,6 +74,9 @@ class SeasonModel extends ItemBaseModel with SeasonModelMappable {
|
||||||
episodes.firstWhereOrNull((element) => element.userData.played == false);
|
episodes.firstWhereOrNull((element) => element.userData.played == false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool get syncAble => true;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ImagesData? get getPosters => images ?? parentImages;
|
ImagesData? get getPosters => images ?? parentImages;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -30,6 +30,7 @@ class ISyncedItem {
|
||||||
String id;
|
String id;
|
||||||
bool syncing;
|
bool syncing;
|
||||||
String? sortName;
|
String? sortName;
|
||||||
|
@Index()
|
||||||
String? parentId;
|
String? parentId;
|
||||||
String? path;
|
String? path;
|
||||||
int? fileSize;
|
int? fileSize;
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,16 @@ const ISyncedItemSchema = IsarGeneratedSchema(
|
||||||
type: IsarType.string,
|
type: IsarType.string,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
indexes: [],
|
indexes: [
|
||||||
|
IsarIndexSchema(
|
||||||
|
name: 'parentId',
|
||||||
|
properties: [
|
||||||
|
"parentId",
|
||||||
|
],
|
||||||
|
unique: false,
|
||||||
|
hash: false,
|
||||||
|
),
|
||||||
|
],
|
||||||
),
|
),
|
||||||
converter: IsarObjectConverter<String, ISyncedItem>(
|
converter: IsarObjectConverter<String, ISyncedItem>(
|
||||||
serialize: serializeISyncedItem,
|
serialize: serializeISyncedItem,
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@ class SyncedItem with _$SyncedItem {
|
||||||
}) = _SyncItem;
|
}) = _SyncItem;
|
||||||
|
|
||||||
static String trickPlayPath = "TrickPlay";
|
static String trickPlayPath = "TrickPlay";
|
||||||
|
static String chaptersPath = "Chapters";
|
||||||
|
|
||||||
List<Chapter> get chapters => fChapters.map((e) => e.copyWith(imageUrl: joinAll({"$path", e.imageUrl}))).toList();
|
List<Chapter> get chapters => fChapters.map((e) => e.copyWith(imageUrl: joinAll({"$path", e.imageUrl}))).toList();
|
||||||
|
|
||||||
|
|
@ -94,6 +95,7 @@ class SyncedItem with _$SyncedItem {
|
||||||
try {
|
try {
|
||||||
await videoFile.delete();
|
await videoFile.delete();
|
||||||
await Directory(joinAll([directory.path, trickPlayPath])).delete(recursive: true);
|
await Directory(joinAll([directory.path, trickPlayPath])).delete(recursive: true);
|
||||||
|
await Directory(joinAll([directory.path, chaptersPath])).delete(recursive: true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -32,20 +32,29 @@ class SeriesDetailViewNotifier extends StateNotifier<SeriesModel?> {
|
||||||
final response = await api.usersUserIdItemsItemIdGet(itemId: seriesModel.id);
|
final response = await api.usersUserIdItemsItemIdGet(itemId: seriesModel.id);
|
||||||
if (response.body == null) return null;
|
if (response.body == null) return null;
|
||||||
newState = response.bodyOrThrow as SeriesModel;
|
newState = response.bodyOrThrow as SeriesModel;
|
||||||
final seasons = await api.showsSeriesIdSeasonsGet(seriesId: seriesModel.id);
|
|
||||||
newState = newState.copyWith(seasons: SeasonModel.seasonsFromDto(seasons.body?.items, ref));
|
|
||||||
|
|
||||||
final episodes = await api.showsSeriesIdEpisodesGet(seriesId: seriesModel.id, fields: [
|
final episodes = await api.showsSeriesIdEpisodesGet(seriesId: seriesModel.id, fields: [
|
||||||
ItemFields.mediastreams,
|
ItemFields.mediastreams,
|
||||||
ItemFields.mediasources,
|
ItemFields.mediasources,
|
||||||
ItemFields.overview,
|
ItemFields.overview,
|
||||||
|
ItemFields.candownload,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
final newEpisodes = EpisodeModel.episodesFromDto(
|
||||||
|
episodes.body?.items,
|
||||||
|
ref,
|
||||||
|
);
|
||||||
|
final episodesCanDownload = newEpisodes.any((episode) => episode.canDownload == true);
|
||||||
|
final seasons = await api.showsSeriesIdSeasonsGet(seriesId: seriesModel.id);
|
||||||
newState = newState.copyWith(
|
newState = newState.copyWith(
|
||||||
availableEpisodes: EpisodeModel.episodesFromDto(
|
seasons: SeasonModel.seasonsFromDto(seasons.body?.items, ref)
|
||||||
episodes.body?.items,
|
.map((element) => element.copyWith(canDownload: true))
|
||||||
ref,
|
.toList(),
|
||||||
),
|
);
|
||||||
|
|
||||||
|
newState = newState.copyWith(
|
||||||
|
canDownload: episodesCanDownload,
|
||||||
|
availableEpisodes: newEpisodes,
|
||||||
);
|
);
|
||||||
|
|
||||||
final related = await ref.read(relatedUtilityProvider).relatedContent(seriesModel.id);
|
final related = await ref.read(relatedUtilityProvider).relatedContent(seriesModel.id);
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
|
||||||
import 'package:background_downloader/background_downloader.dart';
|
import 'package:background_downloader/background_downloader.dart';
|
||||||
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
import 'package:riverpod_annotation/riverpod_annotation.dart';
|
||||||
|
|
||||||
import 'package:fladder/providers/settings/client_settings_provider.dart';
|
import 'package:fladder/providers/settings/client_settings_provider.dart';
|
||||||
|
import 'package:fladder/util/localization_helper.dart';
|
||||||
|
|
||||||
part 'background_download_provider.g.dart';
|
part 'background_download_provider.g.dart';
|
||||||
|
|
||||||
|
|
@ -14,13 +17,7 @@ class BackgroundDownloader extends _$BackgroundDownloader {
|
||||||
..configure(
|
..configure(
|
||||||
globalConfig: globalConfig(maxDownloads),
|
globalConfig: globalConfig(maxDownloads),
|
||||||
)
|
)
|
||||||
..trackTasks()
|
..trackTasks();
|
||||||
..configureNotification(
|
|
||||||
running: const TaskNotification('Downloading', 'file: {filename}'),
|
|
||||||
complete: const TaskNotification('Download finished', 'file: {filename}'),
|
|
||||||
paused: const TaskNotification('Download paused', 'file: {filename}'),
|
|
||||||
progressBar: true,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void setMaxConcurrent(int value) {
|
void setMaxConcurrent(int value) {
|
||||||
|
|
@ -29,6 +26,16 @@ class BackgroundDownloader extends _$BackgroundDownloader {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void updateTranslations(BuildContext context) async {
|
||||||
|
state.configureNotification(
|
||||||
|
running: TaskNotification(context.localized.notificationDownloadingDownloading, '{filename}\n{networkSpeed}'),
|
||||||
|
complete: TaskNotification(context.localized.notificationDownloadingFinished, '{filename}'),
|
||||||
|
paused: TaskNotification(context.localized.notificationDownloadingPaused, '{filename}'),
|
||||||
|
error: TaskNotification(context.localized.notificationDownloadingError, '{filename}'),
|
||||||
|
progressBar: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
(String, dynamic) globalConfig(int value) => value == 0
|
(String, dynamic) globalConfig(int value) => value == 0
|
||||||
? ("", "")
|
? ("", "")
|
||||||
: (
|
: (
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ part of 'background_download_provider.dart';
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$backgroundDownloaderHash() =>
|
String _$backgroundDownloaderHash() =>
|
||||||
r'dc27f708fc2f1695d37afcb99f8814bc024037af';
|
r'9d866549ed7632e855ba30de2765368960889cff';
|
||||||
|
|
||||||
/// See also [BackgroundDownloader].
|
/// See also [BackgroundDownloader].
|
||||||
@ProviderFor(BackgroundDownloader)
|
@ProviderFor(BackgroundDownloader)
|
||||||
|
|
|
||||||
|
|
@ -11,30 +11,40 @@ part 'sync_provider_helpers.g.dart';
|
||||||
@riverpod
|
@riverpod
|
||||||
class SyncChildren extends _$SyncChildren {
|
class SyncChildren extends _$SyncChildren {
|
||||||
@override
|
@override
|
||||||
List<SyncedItem> build(SyncedItem arg) {
|
List<SyncedItem> build(SyncedItem root) {
|
||||||
final syncedItemIsar = ref.watch(syncProvider.notifier).isar;
|
final isar = ref.watch(syncProvider.notifier).isar;
|
||||||
final allChildren = <SyncedItem>[];
|
final syncPath = ref.read(syncProvider.notifier).syncPath ?? "";
|
||||||
List<SyncedItem> toProcess = [arg];
|
|
||||||
|
if (isar == null) return [];
|
||||||
|
|
||||||
|
final all = <SyncedItem>[];
|
||||||
|
List<SyncedItem> toProcess = [root];
|
||||||
|
|
||||||
while (toProcess.isNotEmpty) {
|
while (toProcess.isNotEmpty) {
|
||||||
final currentLevel = toProcess.map(
|
final parentIds = toProcess.map((e) => e.id).toList();
|
||||||
(parent) {
|
|
||||||
final children = syncedItemIsar?.iSyncedItems.where().parentIdEqualTo(parent.id).sortBySortName().findAll();
|
final children = <ISyncedItem>[];
|
||||||
return children?.map((e) => SyncedItem.fromIsar(e, ref.read(syncProvider.notifier).syncPath ?? "")) ??
|
for (final id in parentIds) {
|
||||||
<SyncedItem>[];
|
final results = isar.iSyncedItems.where().parentIdEqualTo(id).sortBySortName().findAll();
|
||||||
},
|
children.addAll(results);
|
||||||
);
|
}
|
||||||
allChildren.addAll(currentLevel.expand((list) => list));
|
|
||||||
toProcess = currentLevel.expand((list) => list).toList();
|
if (children.isEmpty) break;
|
||||||
|
|
||||||
|
final wrapped = children.map((e) => SyncedItem.fromIsar(e, syncPath)).toList();
|
||||||
|
all.addAll(wrapped);
|
||||||
|
toProcess = wrapped;
|
||||||
}
|
}
|
||||||
return allChildren;
|
|
||||||
|
return all;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@riverpod
|
@riverpod
|
||||||
class SyncDownloadStatus extends _$SyncDownloadStatus {
|
class SyncDownloadStatus extends _$SyncDownloadStatus {
|
||||||
@override
|
@override
|
||||||
DownloadStream? build(SyncedItem arg) {
|
DownloadStream? build(SyncedItem arg, List<SyncedItem> children) {
|
||||||
final nestedChildren = ref.watch(syncChildrenProvider(arg));
|
final nestedChildren = children;
|
||||||
|
|
||||||
ref.watch(downloadTasksProvider(arg.id));
|
ref.watch(downloadTasksProvider(arg.id));
|
||||||
for (var element in nestedChildren) {
|
for (var element in nestedChildren) {
|
||||||
|
|
@ -64,20 +74,23 @@ class SyncDownloadStatus extends _$SyncDownloadStatus {
|
||||||
@riverpod
|
@riverpod
|
||||||
class SyncStatuses extends _$SyncStatuses {
|
class SyncStatuses extends _$SyncStatuses {
|
||||||
@override
|
@override
|
||||||
FutureOr<SyncStatus> build(SyncedItem arg) async {
|
FutureOr<SyncStatus> build(SyncedItem arg, List<SyncedItem>? children) async {
|
||||||
final nestedChildren = ref.watch(syncChildrenProvider(arg));
|
final nestedChildren = children;
|
||||||
|
|
||||||
ref.watch(downloadTasksProvider(arg.id));
|
ref.watch(downloadTasksProvider(arg.id));
|
||||||
for (var element in nestedChildren) {
|
if (nestedChildren != null) {
|
||||||
ref.watch(downloadTasksProvider(element.id));
|
for (var element in nestedChildren) {
|
||||||
}
|
ref.watch(downloadTasksProvider(element.id));
|
||||||
|
}
|
||||||
|
|
||||||
for (var i = 0; i < nestedChildren.length; i++) {
|
for (var i = 0; i < nestedChildren.length; i++) {
|
||||||
final item = nestedChildren[i];
|
final item = nestedChildren[i];
|
||||||
if (item.hasVideoFile && !await item.videoFile.exists()) {
|
if (item.hasVideoFile && !await item.videoFile.exists()) {
|
||||||
return SyncStatus.partially;
|
return SyncStatus.partially;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (arg.hasVideoFile && !await arg.videoFile.exists()) {
|
if (arg.hasVideoFile && !await arg.videoFile.exists()) {
|
||||||
return SyncStatus.partially;
|
return SyncStatus.partially;
|
||||||
}
|
}
|
||||||
|
|
@ -88,17 +101,21 @@ class SyncStatuses extends _$SyncStatuses {
|
||||||
@riverpod
|
@riverpod
|
||||||
class SyncSize extends _$SyncSize {
|
class SyncSize extends _$SyncSize {
|
||||||
@override
|
@override
|
||||||
int? build(SyncedItem arg) {
|
int? build(SyncedItem arg, List<SyncedItem>? children) {
|
||||||
final nestedChildren = ref.watch(syncChildrenProvider(arg));
|
final nestedChildren = children;
|
||||||
|
|
||||||
ref.watch(downloadTasksProvider(arg.id));
|
ref.watch(downloadTasksProvider(arg.id));
|
||||||
for (var element in nestedChildren) {
|
|
||||||
ref.watch(downloadTasksProvider(element.id));
|
|
||||||
}
|
|
||||||
int size = arg.fileSize ?? 0;
|
int size = arg.fileSize ?? 0;
|
||||||
for (var element in nestedChildren) {
|
|
||||||
size += element.fileSize ?? 0;
|
if (nestedChildren != null) {
|
||||||
|
for (var element in nestedChildren) {
|
||||||
|
ref.watch(downloadTasksProvider(element.id));
|
||||||
|
}
|
||||||
|
for (var element in nestedChildren) {
|
||||||
|
size += element.fileSize ?? 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,7 @@ part of 'sync_provider_helpers.dart';
|
||||||
// RiverpodGenerator
|
// RiverpodGenerator
|
||||||
// **************************************************************************
|
// **************************************************************************
|
||||||
|
|
||||||
String _$syncChildrenHash() => r'f6fdb1aa36d6655976baa5fbe0d8a6b812d7e95b';
|
String _$syncChildrenHash() => r'c5a90d630d49f59ad4fbaacb5154f1205799f5ab';
|
||||||
|
|
||||||
/// Copied from Dart SDK
|
/// Copied from Dart SDK
|
||||||
class _SystemHash {
|
class _SystemHash {
|
||||||
|
|
@ -31,10 +31,10 @@ class _SystemHash {
|
||||||
|
|
||||||
abstract class _$SyncChildren
|
abstract class _$SyncChildren
|
||||||
extends BuildlessAutoDisposeNotifier<List<SyncedItem>> {
|
extends BuildlessAutoDisposeNotifier<List<SyncedItem>> {
|
||||||
late final SyncedItem arg;
|
late final SyncedItem root;
|
||||||
|
|
||||||
List<SyncedItem> build(
|
List<SyncedItem> build(
|
||||||
SyncedItem arg,
|
SyncedItem root,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -49,10 +49,10 @@ class SyncChildrenFamily extends Family<List<SyncedItem>> {
|
||||||
|
|
||||||
/// See also [SyncChildren].
|
/// See also [SyncChildren].
|
||||||
SyncChildrenProvider call(
|
SyncChildrenProvider call(
|
||||||
SyncedItem arg,
|
SyncedItem root,
|
||||||
) {
|
) {
|
||||||
return SyncChildrenProvider(
|
return SyncChildrenProvider(
|
||||||
arg,
|
root,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -61,7 +61,7 @@ class SyncChildrenFamily extends Family<List<SyncedItem>> {
|
||||||
covariant SyncChildrenProvider provider,
|
covariant SyncChildrenProvider provider,
|
||||||
) {
|
) {
|
||||||
return call(
|
return call(
|
||||||
provider.arg,
|
provider.root,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -85,9 +85,9 @@ class SyncChildrenProvider
|
||||||
extends AutoDisposeNotifierProviderImpl<SyncChildren, List<SyncedItem>> {
|
extends AutoDisposeNotifierProviderImpl<SyncChildren, List<SyncedItem>> {
|
||||||
/// See also [SyncChildren].
|
/// See also [SyncChildren].
|
||||||
SyncChildrenProvider(
|
SyncChildrenProvider(
|
||||||
SyncedItem arg,
|
SyncedItem root,
|
||||||
) : this._internal(
|
) : this._internal(
|
||||||
() => SyncChildren()..arg = arg,
|
() => SyncChildren()..root = root,
|
||||||
from: syncChildrenProvider,
|
from: syncChildrenProvider,
|
||||||
name: r'syncChildrenProvider',
|
name: r'syncChildrenProvider',
|
||||||
debugGetCreateSourceHash:
|
debugGetCreateSourceHash:
|
||||||
|
|
@ -97,7 +97,7 @@ class SyncChildrenProvider
|
||||||
dependencies: SyncChildrenFamily._dependencies,
|
dependencies: SyncChildrenFamily._dependencies,
|
||||||
allTransitiveDependencies:
|
allTransitiveDependencies:
|
||||||
SyncChildrenFamily._allTransitiveDependencies,
|
SyncChildrenFamily._allTransitiveDependencies,
|
||||||
arg: arg,
|
root: root,
|
||||||
);
|
);
|
||||||
|
|
||||||
SyncChildrenProvider._internal(
|
SyncChildrenProvider._internal(
|
||||||
|
|
@ -107,17 +107,17 @@ class SyncChildrenProvider
|
||||||
required super.allTransitiveDependencies,
|
required super.allTransitiveDependencies,
|
||||||
required super.debugGetCreateSourceHash,
|
required super.debugGetCreateSourceHash,
|
||||||
required super.from,
|
required super.from,
|
||||||
required this.arg,
|
required this.root,
|
||||||
}) : super.internal();
|
}) : super.internal();
|
||||||
|
|
||||||
final SyncedItem arg;
|
final SyncedItem root;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<SyncedItem> runNotifierBuild(
|
List<SyncedItem> runNotifierBuild(
|
||||||
covariant SyncChildren notifier,
|
covariant SyncChildren notifier,
|
||||||
) {
|
) {
|
||||||
return notifier.build(
|
return notifier.build(
|
||||||
arg,
|
root,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -126,13 +126,13 @@ class SyncChildrenProvider
|
||||||
return ProviderOverride(
|
return ProviderOverride(
|
||||||
origin: this,
|
origin: this,
|
||||||
override: SyncChildrenProvider._internal(
|
override: SyncChildrenProvider._internal(
|
||||||
() => create()..arg = arg,
|
() => create()..root = root,
|
||||||
from: from,
|
from: from,
|
||||||
name: null,
|
name: null,
|
||||||
dependencies: null,
|
dependencies: null,
|
||||||
allTransitiveDependencies: null,
|
allTransitiveDependencies: null,
|
||||||
debugGetCreateSourceHash: null,
|
debugGetCreateSourceHash: null,
|
||||||
arg: arg,
|
root: root,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -145,13 +145,13 @@ class SyncChildrenProvider
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return other is SyncChildrenProvider && other.arg == arg;
|
return other is SyncChildrenProvider && other.root == root;
|
||||||
}
|
}
|
||||||
|
|
||||||
@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, arg.hashCode);
|
hash = _SystemHash.combine(hash, root.hashCode);
|
||||||
|
|
||||||
return _SystemHash.finish(hash);
|
return _SystemHash.finish(hash);
|
||||||
}
|
}
|
||||||
|
|
@ -160,8 +160,8 @@ 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 SyncChildrenRef on AutoDisposeNotifierProviderRef<List<SyncedItem>> {
|
||||||
/// The parameter `arg` of this provider.
|
/// The parameter `root` of this provider.
|
||||||
SyncedItem get arg;
|
SyncedItem get root;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SyncChildrenProviderElement
|
class _SyncChildrenProviderElement
|
||||||
|
|
@ -170,18 +170,20 @@ class _SyncChildrenProviderElement
|
||||||
_SyncChildrenProviderElement(super.provider);
|
_SyncChildrenProviderElement(super.provider);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
SyncedItem get arg => (origin as SyncChildrenProvider).arg;
|
SyncedItem get root => (origin as SyncChildrenProvider).root;
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$syncDownloadStatusHash() =>
|
String _$syncDownloadStatusHash() =>
|
||||||
r'5a0f8537a977c52e6083bd84265631ea5d160637';
|
r'1036352200e1138b4ef70e524c0baf13bb9cd452';
|
||||||
|
|
||||||
abstract class _$SyncDownloadStatus
|
abstract class _$SyncDownloadStatus
|
||||||
extends BuildlessAutoDisposeNotifier<DownloadStream?> {
|
extends BuildlessAutoDisposeNotifier<DownloadStream?> {
|
||||||
late final SyncedItem arg;
|
late final SyncedItem arg;
|
||||||
|
late final List<SyncedItem> children;
|
||||||
|
|
||||||
DownloadStream? build(
|
DownloadStream? build(
|
||||||
SyncedItem arg,
|
SyncedItem arg,
|
||||||
|
List<SyncedItem> children,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -197,9 +199,11 @@ class SyncDownloadStatusFamily extends Family<DownloadStream?> {
|
||||||
/// See also [SyncDownloadStatus].
|
/// See also [SyncDownloadStatus].
|
||||||
SyncDownloadStatusProvider call(
|
SyncDownloadStatusProvider call(
|
||||||
SyncedItem arg,
|
SyncedItem arg,
|
||||||
|
List<SyncedItem> children,
|
||||||
) {
|
) {
|
||||||
return SyncDownloadStatusProvider(
|
return SyncDownloadStatusProvider(
|
||||||
arg,
|
arg,
|
||||||
|
children,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -209,6 +213,7 @@ class SyncDownloadStatusFamily extends Family<DownloadStream?> {
|
||||||
) {
|
) {
|
||||||
return call(
|
return call(
|
||||||
provider.arg,
|
provider.arg,
|
||||||
|
provider.children,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -233,8 +238,11 @@ class SyncDownloadStatusProvider extends AutoDisposeNotifierProviderImpl<
|
||||||
/// See also [SyncDownloadStatus].
|
/// See also [SyncDownloadStatus].
|
||||||
SyncDownloadStatusProvider(
|
SyncDownloadStatusProvider(
|
||||||
SyncedItem arg,
|
SyncedItem arg,
|
||||||
|
List<SyncedItem> children,
|
||||||
) : this._internal(
|
) : this._internal(
|
||||||
() => SyncDownloadStatus()..arg = arg,
|
() => SyncDownloadStatus()
|
||||||
|
..arg = arg
|
||||||
|
..children = children,
|
||||||
from: syncDownloadStatusProvider,
|
from: syncDownloadStatusProvider,
|
||||||
name: r'syncDownloadStatusProvider',
|
name: r'syncDownloadStatusProvider',
|
||||||
debugGetCreateSourceHash:
|
debugGetCreateSourceHash:
|
||||||
|
|
@ -245,6 +253,7 @@ class SyncDownloadStatusProvider extends AutoDisposeNotifierProviderImpl<
|
||||||
allTransitiveDependencies:
|
allTransitiveDependencies:
|
||||||
SyncDownloadStatusFamily._allTransitiveDependencies,
|
SyncDownloadStatusFamily._allTransitiveDependencies,
|
||||||
arg: arg,
|
arg: arg,
|
||||||
|
children: children,
|
||||||
);
|
);
|
||||||
|
|
||||||
SyncDownloadStatusProvider._internal(
|
SyncDownloadStatusProvider._internal(
|
||||||
|
|
@ -255,9 +264,11 @@ class SyncDownloadStatusProvider extends AutoDisposeNotifierProviderImpl<
|
||||||
required super.debugGetCreateSourceHash,
|
required super.debugGetCreateSourceHash,
|
||||||
required super.from,
|
required super.from,
|
||||||
required this.arg,
|
required this.arg,
|
||||||
|
required this.children,
|
||||||
}) : super.internal();
|
}) : super.internal();
|
||||||
|
|
||||||
final SyncedItem arg;
|
final SyncedItem arg;
|
||||||
|
final List<SyncedItem> children;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
DownloadStream? runNotifierBuild(
|
DownloadStream? runNotifierBuild(
|
||||||
|
|
@ -265,6 +276,7 @@ class SyncDownloadStatusProvider extends AutoDisposeNotifierProviderImpl<
|
||||||
) {
|
) {
|
||||||
return notifier.build(
|
return notifier.build(
|
||||||
arg,
|
arg,
|
||||||
|
children,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -273,13 +285,16 @@ class SyncDownloadStatusProvider extends AutoDisposeNotifierProviderImpl<
|
||||||
return ProviderOverride(
|
return ProviderOverride(
|
||||||
origin: this,
|
origin: this,
|
||||||
override: SyncDownloadStatusProvider._internal(
|
override: SyncDownloadStatusProvider._internal(
|
||||||
() => create()..arg = arg,
|
() => create()
|
||||||
|
..arg = arg
|
||||||
|
..children = children,
|
||||||
from: from,
|
from: from,
|
||||||
name: null,
|
name: null,
|
||||||
dependencies: null,
|
dependencies: null,
|
||||||
allTransitiveDependencies: null,
|
allTransitiveDependencies: null,
|
||||||
debugGetCreateSourceHash: null,
|
debugGetCreateSourceHash: null,
|
||||||
arg: arg,
|
arg: arg,
|
||||||
|
children: children,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -292,13 +307,16 @@ class SyncDownloadStatusProvider extends AutoDisposeNotifierProviderImpl<
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return other is SyncDownloadStatusProvider && other.arg == arg;
|
return other is SyncDownloadStatusProvider &&
|
||||||
|
other.arg == arg &&
|
||||||
|
other.children == children;
|
||||||
}
|
}
|
||||||
|
|
||||||
@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, arg.hashCode);
|
hash = _SystemHash.combine(hash, arg.hashCode);
|
||||||
|
hash = _SystemHash.combine(hash, children.hashCode);
|
||||||
|
|
||||||
return _SystemHash.finish(hash);
|
return _SystemHash.finish(hash);
|
||||||
}
|
}
|
||||||
|
|
@ -309,6 +327,9 @@ class SyncDownloadStatusProvider extends AutoDisposeNotifierProviderImpl<
|
||||||
mixin SyncDownloadStatusRef on AutoDisposeNotifierProviderRef<DownloadStream?> {
|
mixin SyncDownloadStatusRef on AutoDisposeNotifierProviderRef<DownloadStream?> {
|
||||||
/// The parameter `arg` of this provider.
|
/// The parameter `arg` of this provider.
|
||||||
SyncedItem get arg;
|
SyncedItem get arg;
|
||||||
|
|
||||||
|
/// The parameter `children` of this provider.
|
||||||
|
List<SyncedItem> get children;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SyncDownloadStatusProviderElement
|
class _SyncDownloadStatusProviderElement
|
||||||
|
|
@ -318,16 +339,21 @@ class _SyncDownloadStatusProviderElement
|
||||||
|
|
||||||
@override
|
@override
|
||||||
SyncedItem get arg => (origin as SyncDownloadStatusProvider).arg;
|
SyncedItem get arg => (origin as SyncDownloadStatusProvider).arg;
|
||||||
|
@override
|
||||||
|
List<SyncedItem> get children =>
|
||||||
|
(origin as SyncDownloadStatusProvider).children;
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$syncStatusesHash() => r'f05ee53368d1de130714bba09132e08aba15bc44';
|
String _$syncStatusesHash() => r'64a3499fc7b7bbdbd6594b1eec76cf42a119a041';
|
||||||
|
|
||||||
abstract class _$SyncStatuses
|
abstract class _$SyncStatuses
|
||||||
extends BuildlessAutoDisposeAsyncNotifier<SyncStatus> {
|
extends BuildlessAutoDisposeAsyncNotifier<SyncStatus> {
|
||||||
late final SyncedItem arg;
|
late final SyncedItem arg;
|
||||||
|
late final List<SyncedItem>? children;
|
||||||
|
|
||||||
FutureOr<SyncStatus> build(
|
FutureOr<SyncStatus> build(
|
||||||
SyncedItem arg,
|
SyncedItem arg,
|
||||||
|
List<SyncedItem>? children,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -343,9 +369,11 @@ class SyncStatusesFamily extends Family<AsyncValue<SyncStatus>> {
|
||||||
/// See also [SyncStatuses].
|
/// See also [SyncStatuses].
|
||||||
SyncStatusesProvider call(
|
SyncStatusesProvider call(
|
||||||
SyncedItem arg,
|
SyncedItem arg,
|
||||||
|
List<SyncedItem>? children,
|
||||||
) {
|
) {
|
||||||
return SyncStatusesProvider(
|
return SyncStatusesProvider(
|
||||||
arg,
|
arg,
|
||||||
|
children,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -355,6 +383,7 @@ class SyncStatusesFamily extends Family<AsyncValue<SyncStatus>> {
|
||||||
) {
|
) {
|
||||||
return call(
|
return call(
|
||||||
provider.arg,
|
provider.arg,
|
||||||
|
provider.children,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -379,8 +408,11 @@ class SyncStatusesProvider
|
||||||
/// See also [SyncStatuses].
|
/// See also [SyncStatuses].
|
||||||
SyncStatusesProvider(
|
SyncStatusesProvider(
|
||||||
SyncedItem arg,
|
SyncedItem arg,
|
||||||
|
List<SyncedItem>? children,
|
||||||
) : this._internal(
|
) : this._internal(
|
||||||
() => SyncStatuses()..arg = arg,
|
() => SyncStatuses()
|
||||||
|
..arg = arg
|
||||||
|
..children = children,
|
||||||
from: syncStatusesProvider,
|
from: syncStatusesProvider,
|
||||||
name: r'syncStatusesProvider',
|
name: r'syncStatusesProvider',
|
||||||
debugGetCreateSourceHash:
|
debugGetCreateSourceHash:
|
||||||
|
|
@ -391,6 +423,7 @@ class SyncStatusesProvider
|
||||||
allTransitiveDependencies:
|
allTransitiveDependencies:
|
||||||
SyncStatusesFamily._allTransitiveDependencies,
|
SyncStatusesFamily._allTransitiveDependencies,
|
||||||
arg: arg,
|
arg: arg,
|
||||||
|
children: children,
|
||||||
);
|
);
|
||||||
|
|
||||||
SyncStatusesProvider._internal(
|
SyncStatusesProvider._internal(
|
||||||
|
|
@ -401,9 +434,11 @@ class SyncStatusesProvider
|
||||||
required super.debugGetCreateSourceHash,
|
required super.debugGetCreateSourceHash,
|
||||||
required super.from,
|
required super.from,
|
||||||
required this.arg,
|
required this.arg,
|
||||||
|
required this.children,
|
||||||
}) : super.internal();
|
}) : super.internal();
|
||||||
|
|
||||||
final SyncedItem arg;
|
final SyncedItem arg;
|
||||||
|
final List<SyncedItem>? children;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
FutureOr<SyncStatus> runNotifierBuild(
|
FutureOr<SyncStatus> runNotifierBuild(
|
||||||
|
|
@ -411,6 +446,7 @@ class SyncStatusesProvider
|
||||||
) {
|
) {
|
||||||
return notifier.build(
|
return notifier.build(
|
||||||
arg,
|
arg,
|
||||||
|
children,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -419,13 +455,16 @@ class SyncStatusesProvider
|
||||||
return ProviderOverride(
|
return ProviderOverride(
|
||||||
origin: this,
|
origin: this,
|
||||||
override: SyncStatusesProvider._internal(
|
override: SyncStatusesProvider._internal(
|
||||||
() => create()..arg = arg,
|
() => create()
|
||||||
|
..arg = arg
|
||||||
|
..children = children,
|
||||||
from: from,
|
from: from,
|
||||||
name: null,
|
name: null,
|
||||||
dependencies: null,
|
dependencies: null,
|
||||||
allTransitiveDependencies: null,
|
allTransitiveDependencies: null,
|
||||||
debugGetCreateSourceHash: null,
|
debugGetCreateSourceHash: null,
|
||||||
arg: arg,
|
arg: arg,
|
||||||
|
children: children,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -438,13 +477,16 @@ class SyncStatusesProvider
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return other is SyncStatusesProvider && other.arg == arg;
|
return other is SyncStatusesProvider &&
|
||||||
|
other.arg == arg &&
|
||||||
|
other.children == children;
|
||||||
}
|
}
|
||||||
|
|
||||||
@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, arg.hashCode);
|
hash = _SystemHash.combine(hash, arg.hashCode);
|
||||||
|
hash = _SystemHash.combine(hash, children.hashCode);
|
||||||
|
|
||||||
return _SystemHash.finish(hash);
|
return _SystemHash.finish(hash);
|
||||||
}
|
}
|
||||||
|
|
@ -455,6 +497,9 @@ class SyncStatusesProvider
|
||||||
mixin SyncStatusesRef on AutoDisposeAsyncNotifierProviderRef<SyncStatus> {
|
mixin SyncStatusesRef on AutoDisposeAsyncNotifierProviderRef<SyncStatus> {
|
||||||
/// The parameter `arg` of this provider.
|
/// The parameter `arg` of this provider.
|
||||||
SyncedItem get arg;
|
SyncedItem get arg;
|
||||||
|
|
||||||
|
/// The parameter `children` of this provider.
|
||||||
|
List<SyncedItem>? get children;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SyncStatusesProviderElement
|
class _SyncStatusesProviderElement
|
||||||
|
|
@ -464,15 +509,19 @@ class _SyncStatusesProviderElement
|
||||||
|
|
||||||
@override
|
@override
|
||||||
SyncedItem get arg => (origin as SyncStatusesProvider).arg;
|
SyncedItem get arg => (origin as SyncStatusesProvider).arg;
|
||||||
|
@override
|
||||||
|
List<SyncedItem>? get children => (origin as SyncStatusesProvider).children;
|
||||||
}
|
}
|
||||||
|
|
||||||
String _$syncSizeHash() => r'138702f2dd69ab28d142bab67ab4a497bb24f252';
|
String _$syncSizeHash() => r'81797ecc4a6f600691b6f1fe0c16bae0228ec920';
|
||||||
|
|
||||||
abstract class _$SyncSize extends BuildlessAutoDisposeNotifier<int?> {
|
abstract class _$SyncSize extends BuildlessAutoDisposeNotifier<int?> {
|
||||||
late final SyncedItem arg;
|
late final SyncedItem arg;
|
||||||
|
late final List<SyncedItem>? children;
|
||||||
|
|
||||||
int? build(
|
int? build(
|
||||||
SyncedItem arg,
|
SyncedItem arg,
|
||||||
|
List<SyncedItem>? children,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -488,9 +537,11 @@ class SyncSizeFamily extends Family<int?> {
|
||||||
/// See also [SyncSize].
|
/// See also [SyncSize].
|
||||||
SyncSizeProvider call(
|
SyncSizeProvider call(
|
||||||
SyncedItem arg,
|
SyncedItem arg,
|
||||||
|
List<SyncedItem>? children,
|
||||||
) {
|
) {
|
||||||
return SyncSizeProvider(
|
return SyncSizeProvider(
|
||||||
arg,
|
arg,
|
||||||
|
children,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -500,6 +551,7 @@ class SyncSizeFamily extends Family<int?> {
|
||||||
) {
|
) {
|
||||||
return call(
|
return call(
|
||||||
provider.arg,
|
provider.arg,
|
||||||
|
provider.children,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -523,8 +575,11 @@ class SyncSizeProvider extends AutoDisposeNotifierProviderImpl<SyncSize, int?> {
|
||||||
/// See also [SyncSize].
|
/// See also [SyncSize].
|
||||||
SyncSizeProvider(
|
SyncSizeProvider(
|
||||||
SyncedItem arg,
|
SyncedItem arg,
|
||||||
|
List<SyncedItem>? children,
|
||||||
) : this._internal(
|
) : this._internal(
|
||||||
() => SyncSize()..arg = arg,
|
() => SyncSize()
|
||||||
|
..arg = arg
|
||||||
|
..children = children,
|
||||||
from: syncSizeProvider,
|
from: syncSizeProvider,
|
||||||
name: r'syncSizeProvider',
|
name: r'syncSizeProvider',
|
||||||
debugGetCreateSourceHash:
|
debugGetCreateSourceHash:
|
||||||
|
|
@ -534,6 +589,7 @@ class SyncSizeProvider extends AutoDisposeNotifierProviderImpl<SyncSize, int?> {
|
||||||
dependencies: SyncSizeFamily._dependencies,
|
dependencies: SyncSizeFamily._dependencies,
|
||||||
allTransitiveDependencies: SyncSizeFamily._allTransitiveDependencies,
|
allTransitiveDependencies: SyncSizeFamily._allTransitiveDependencies,
|
||||||
arg: arg,
|
arg: arg,
|
||||||
|
children: children,
|
||||||
);
|
);
|
||||||
|
|
||||||
SyncSizeProvider._internal(
|
SyncSizeProvider._internal(
|
||||||
|
|
@ -544,9 +600,11 @@ class SyncSizeProvider extends AutoDisposeNotifierProviderImpl<SyncSize, int?> {
|
||||||
required super.debugGetCreateSourceHash,
|
required super.debugGetCreateSourceHash,
|
||||||
required super.from,
|
required super.from,
|
||||||
required this.arg,
|
required this.arg,
|
||||||
|
required this.children,
|
||||||
}) : super.internal();
|
}) : super.internal();
|
||||||
|
|
||||||
final SyncedItem arg;
|
final SyncedItem arg;
|
||||||
|
final List<SyncedItem>? children;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int? runNotifierBuild(
|
int? runNotifierBuild(
|
||||||
|
|
@ -554,6 +612,7 @@ class SyncSizeProvider extends AutoDisposeNotifierProviderImpl<SyncSize, int?> {
|
||||||
) {
|
) {
|
||||||
return notifier.build(
|
return notifier.build(
|
||||||
arg,
|
arg,
|
||||||
|
children,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -562,13 +621,16 @@ class SyncSizeProvider extends AutoDisposeNotifierProviderImpl<SyncSize, int?> {
|
||||||
return ProviderOverride(
|
return ProviderOverride(
|
||||||
origin: this,
|
origin: this,
|
||||||
override: SyncSizeProvider._internal(
|
override: SyncSizeProvider._internal(
|
||||||
() => create()..arg = arg,
|
() => create()
|
||||||
|
..arg = arg
|
||||||
|
..children = children,
|
||||||
from: from,
|
from: from,
|
||||||
name: null,
|
name: null,
|
||||||
dependencies: null,
|
dependencies: null,
|
||||||
allTransitiveDependencies: null,
|
allTransitiveDependencies: null,
|
||||||
debugGetCreateSourceHash: null,
|
debugGetCreateSourceHash: null,
|
||||||
arg: arg,
|
arg: arg,
|
||||||
|
children: children,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
@ -580,13 +642,16 @@ class SyncSizeProvider extends AutoDisposeNotifierProviderImpl<SyncSize, int?> {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) {
|
bool operator ==(Object other) {
|
||||||
return other is SyncSizeProvider && other.arg == arg;
|
return other is SyncSizeProvider &&
|
||||||
|
other.arg == arg &&
|
||||||
|
other.children == children;
|
||||||
}
|
}
|
||||||
|
|
||||||
@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, arg.hashCode);
|
hash = _SystemHash.combine(hash, arg.hashCode);
|
||||||
|
hash = _SystemHash.combine(hash, children.hashCode);
|
||||||
|
|
||||||
return _SystemHash.finish(hash);
|
return _SystemHash.finish(hash);
|
||||||
}
|
}
|
||||||
|
|
@ -597,6 +662,9 @@ class SyncSizeProvider extends AutoDisposeNotifierProviderImpl<SyncSize, int?> {
|
||||||
mixin SyncSizeRef on AutoDisposeNotifierProviderRef<int?> {
|
mixin SyncSizeRef on AutoDisposeNotifierProviderRef<int?> {
|
||||||
/// The parameter `arg` of this provider.
|
/// The parameter `arg` of this provider.
|
||||||
SyncedItem get arg;
|
SyncedItem get arg;
|
||||||
|
|
||||||
|
/// The parameter `children` of this provider.
|
||||||
|
List<SyncedItem>? get children;
|
||||||
}
|
}
|
||||||
|
|
||||||
class _SyncSizeProviderElement
|
class _SyncSizeProviderElement
|
||||||
|
|
@ -606,6 +674,8 @@ class _SyncSizeProviderElement
|
||||||
|
|
||||||
@override
|
@override
|
||||||
SyncedItem get arg => (origin as SyncSizeProvider).arg;
|
SyncedItem get arg => (origin as SyncSizeProvider).arg;
|
||||||
|
@override
|
||||||
|
List<SyncedItem>? get children => (origin as SyncSizeProvider).children;
|
||||||
}
|
}
|
||||||
// ignore_for_file: type=lint
|
// ignore_for_file: type=lint
|
||||||
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
// ignore_for_file: subtype_of_sealed_class, invalid_use_of_internal_member, invalid_use_of_visible_for_testing_member, deprecated_member_use_from_same_package
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ 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/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/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/download_stream.dart';
|
import 'package:fladder/models/syncing/download_stream.dart';
|
||||||
|
|
@ -238,6 +239,7 @@ class SyncNotifier extends StateNotifier<SyncSettingsModel> {
|
||||||
fladderSnackbar(context, title: context.localized.syncAddItemForSyncing(item.detailedName(context) ?? "Unknown"));
|
fladderSnackbar(context, title: context.localized.syncAddItemForSyncing(item.detailedName(context) ?? "Unknown"));
|
||||||
final newSync = switch (item) {
|
final newSync = switch (item) {
|
||||||
EpisodeModel episode => await syncSeries(item.parentBaseModel, episode: episode),
|
EpisodeModel episode => await syncSeries(item.parentBaseModel, episode: episode),
|
||||||
|
SeasonModel season => await syncSeries(item.parentBaseModel, season: season),
|
||||||
SeriesModel series => await syncSeries(series),
|
SeriesModel series => await syncSeries(series),
|
||||||
MovieModel movie => await syncMovie(movie),
|
MovieModel movie => await syncMovie(movie),
|
||||||
_ => null
|
_ => null
|
||||||
|
|
@ -367,7 +369,7 @@ class SyncNotifier extends StateNotifier<SyncSettingsModel> {
|
||||||
if (data == null) return data;
|
if (data == null) return data;
|
||||||
if (!itemPath.existsSync()) return data;
|
if (!itemPath.existsSync()) return data;
|
||||||
if (data.isEmpty) return data;
|
if (data.isEmpty) return data;
|
||||||
final saveDirectory = Directory(path.joinAll([itemPath.path, "Chapters"]));
|
final saveDirectory = Directory(path.joinAll([itemPath.path, SyncedItem.chaptersPath]));
|
||||||
|
|
||||||
await saveDirectory.create(recursive: true);
|
await saveDirectory.create(recursive: true);
|
||||||
|
|
||||||
|
|
@ -378,7 +380,7 @@ class SyncNotifier extends StateNotifier<SyncSettingsModel> {
|
||||||
if (response.bodyBytes.isEmpty) return null;
|
if (response.bodyBytes.isEmpty) return null;
|
||||||
file.writeAsBytesSync(response.bodyBytes);
|
file.writeAsBytesSync(response.bodyBytes);
|
||||||
return event.copyWith(
|
return event.copyWith(
|
||||||
imageUrl: path.joinAll(["Chapters", fileName]),
|
imageUrl: path.joinAll([SyncedItem.chaptersPath, fileName]),
|
||||||
);
|
);
|
||||||
}).toList();
|
}).toList();
|
||||||
return saveChapters.nonNulls.toList();
|
return saveChapters.nonNulls.toList();
|
||||||
|
|
@ -415,7 +417,7 @@ class SyncNotifier extends StateNotifier<SyncSettingsModel> {
|
||||||
return syncedItem;
|
return syncedItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<DownloadStream?> syncVideoFile(SyncedItem syncItem, bool skipDownload) async {
|
Future<DownloadStream?> syncFile(SyncedItem syncItem, bool skipDownload) async {
|
||||||
cleanupTemporaryFiles();
|
cleanupTemporaryFiles();
|
||||||
|
|
||||||
final playbackResponse = await api.itemsItemIdPlaybackInfoPost(
|
final playbackResponse = await api.itemsItemIdPlaybackInfoPost(
|
||||||
|
|
@ -439,6 +441,7 @@ class SyncNotifier extends StateNotifier<SyncSettingsModel> {
|
||||||
final mediaSegments = (await api.mediaSegmentsGet(id: syncItem.id))?.body;
|
final mediaSegments = (await api.mediaSegmentsGet(id: syncItem.id))?.body;
|
||||||
|
|
||||||
syncItem = syncItem.copyWith(
|
syncItem = syncItem.copyWith(
|
||||||
|
fChapters: await saveChapterImages(item?.overview.chapters, directory) ?? [],
|
||||||
subtitles: subtitles,
|
subtitles: subtitles,
|
||||||
fTrickPlayModel: trickPlayFile,
|
fTrickPlayModel: trickPlayFile,
|
||||||
mediaSegments: mediaSegments,
|
mediaSegments: mediaSegments,
|
||||||
|
|
@ -447,23 +450,24 @@ class SyncNotifier extends StateNotifier<SyncSettingsModel> {
|
||||||
await updateItem(syncItem);
|
await updateItem(syncItem);
|
||||||
|
|
||||||
final currentTask = ref.read(downloadTasksProvider(syncItem.id));
|
final currentTask = ref.read(downloadTasksProvider(syncItem.id));
|
||||||
|
final user = ref.read(userProvider);
|
||||||
|
|
||||||
final downloadString = path.joinAll([
|
if (user == null) return null;
|
||||||
"${ref.read(userProvider)?.server}",
|
|
||||||
"Items",
|
final downloadUrl = path.joinAll([user.server, "Items", syncItem.id, "Download"]);
|
||||||
"${syncItem.id}/Download?api_key=${ref.read(userProvider)?.credentials.token}"
|
|
||||||
]);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if (!skipDownload && currentTask.task == null) {
|
if (!skipDownload && currentTask.task == null) {
|
||||||
final downloadTask = DownloadTask(
|
final downloadTask = DownloadTask(
|
||||||
url: Uri.parse(downloadString).toString(),
|
url: Uri.parse(downloadUrl).toString(),
|
||||||
directory: syncItem.directory.path,
|
directory: syncItem.directory.path,
|
||||||
filename: syncItem.videoFileName,
|
filename: syncItem.videoFileName,
|
||||||
updates: Updates.statusAndProgress,
|
updates: Updates.statusAndProgress,
|
||||||
baseDirectory: BaseDirectory.root,
|
baseDirectory: BaseDirectory.root,
|
||||||
|
urlQueryParameters: {"api_key": user.credentials.token},
|
||||||
|
headers: user.credentials.header(ref),
|
||||||
requiresWiFi: ref.read(clientSettingsProvider.select((value) => value.requireWifi)),
|
requiresWiFi: ref.read(clientSettingsProvider.select((value) => value.requireWifi)),
|
||||||
retries: 5,
|
retries: 3,
|
||||||
allowPause: true,
|
allowPause: true,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
@ -490,7 +494,7 @@ class SyncNotifier extends StateNotifier<SyncSettingsModel> {
|
||||||
(state) => state.copyWith(status: status),
|
(state) => state.copyWith(status: status),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (status == TaskStatus.complete) {
|
if (status == TaskStatus.complete || status == TaskStatus.canceled) {
|
||||||
ref.read(downloadTasksProvider(syncItem.id).notifier).update((state) => DownloadStream.empty());
|
ref.read(downloadTasksProvider(syncItem.id).notifier).update((state) => DownloadStream.empty());
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -522,6 +526,10 @@ 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);
|
||||||
|
|
||||||
|
if (existingSyncedItem != null) return existingSyncedItem;
|
||||||
|
|
||||||
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]));
|
||||||
|
|
@ -543,31 +551,17 @@ extension SyncNotifierHelpers on SyncNotifier {
|
||||||
userData: item.userData,
|
userData: item.userData,
|
||||||
);
|
);
|
||||||
|
|
||||||
//Save item if parent so the user is aware.
|
|
||||||
if (parent == null) {
|
if (parent == null) {
|
||||||
isar?.write((isar) => syncedItems?.put(ISyncedItem.fromSynced(syncItem, syncPath)));
|
isar?.write((isar) => syncedItems?.put(ISyncedItem.fromSynced(syncItem, syncPath)));
|
||||||
}
|
}
|
||||||
|
|
||||||
final origChapters = Chapter.chaptersFromInfo(item.id, response.chapters ?? [], ref);
|
|
||||||
|
|
||||||
return syncItem.copyWith(
|
return syncItem.copyWith(
|
||||||
fChapters: await saveChapterImages(origChapters, directory) ?? [],
|
|
||||||
fileSize: response.mediaSources?.firstOrNull?.size ?? 0,
|
fileSize: response.mediaSources?.firstOrNull?.size ?? 0,
|
||||||
syncing: false,
|
syncing: false,
|
||||||
videoFileName: response.path?.split('/').lastOrNull ?? "",
|
videoFileName: response.path?.split('/').lastOrNull ?? "",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Need to move the file after downloading on Android
|
|
||||||
Future<void> moveFile(DownloadTask downloadTask, SyncedItem syncItem) async {
|
|
||||||
final currentLocation = File(await downloadTask.filePath());
|
|
||||||
final wantedLocation = syncItem.videoFile;
|
|
||||||
if (currentLocation.path != wantedLocation.path) {
|
|
||||||
await currentLocation.copy(wantedLocation.path);
|
|
||||||
await currentLocation.delete();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Future<SyncedItem?> syncMovie(ItemBaseModel item, {bool skipDownload = false}) async {
|
Future<SyncedItem?> syncMovie(ItemBaseModel item, {bool skipDownload = false}) async {
|
||||||
final response = await api.usersUserIdItemsItemIdGetBaseItem(
|
final response = await api.usersUserIdItemsItemIdGetBaseItem(
|
||||||
itemId: item.id,
|
itemId: item.id,
|
||||||
|
|
@ -580,21 +574,21 @@ extension SyncNotifierHelpers on SyncNotifier {
|
||||||
|
|
||||||
if (!syncItem.directory.existsSync()) return null;
|
if (!syncItem.directory.existsSync()) return null;
|
||||||
|
|
||||||
await syncVideoFile(syncItem, skipDownload);
|
await syncFile(syncItem, skipDownload);
|
||||||
|
|
||||||
isar?.write((isar) => syncedItems?.put(ISyncedItem.fromSynced(syncItem, syncPath)));
|
isar?.write((isar) => syncedItems?.put(ISyncedItem.fromSynced(syncItem, syncPath)));
|
||||||
|
|
||||||
return syncItem;
|
return syncItem;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<SyncedItem?> syncSeries(SeriesModel item, {EpisodeModel? episode}) async {
|
Future<SyncedItem?> syncSeries(SeriesModel item, {SeasonModel? season, EpisodeModel? episode}) async {
|
||||||
final response = await api.usersUserIdItemsItemIdGetBaseItem(
|
final response = await api.usersUserIdItemsItemIdGetBaseItem(
|
||||||
itemId: item.id,
|
itemId: item.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
List<SyncedItem> newItems = [];
|
List<SyncedItem> newItems = [];
|
||||||
|
|
||||||
SyncedItem? itemToDownload;
|
List<SyncedItem>? itemsToDownload = [];
|
||||||
|
|
||||||
SyncedItem seriesItem = await createSyncItem(response.bodyOrThrow);
|
SyncedItem seriesItem = await createSyncItem(response.bodyOrThrow);
|
||||||
newItems.add(seriesItem);
|
newItems.add(seriesItem);
|
||||||
|
|
@ -627,8 +621,8 @@ extension SyncNotifierHelpers on SyncNotifier {
|
||||||
final seasons = seasonsResponse.body?.items ?? [];
|
final seasons = seasonsResponse.body?.items ?? [];
|
||||||
|
|
||||||
for (var i = 0; i < seasons.length; i++) {
|
for (var i = 0; i < seasons.length; i++) {
|
||||||
final season = seasons[i];
|
final newSeason = seasons[i];
|
||||||
final syncedSeason = await createSyncItem(season, parent: seriesItem);
|
final syncedSeason = await createSyncItem(newSeason, parent: seriesItem);
|
||||||
newItems.add(syncedSeason);
|
newItems.add(syncedSeason);
|
||||||
final episodesResponse = await api.showsSeriesIdEpisodesGet(
|
final episodesResponse = await api.showsSeriesIdEpisodesGet(
|
||||||
isMissing: false,
|
isMissing: false,
|
||||||
|
|
@ -651,16 +645,23 @@ extension SyncNotifierHelpers on SyncNotifier {
|
||||||
ItemFields.chapters,
|
ItemFields.chapters,
|
||||||
ItemFields.trickplay,
|
ItemFields.trickplay,
|
||||||
],
|
],
|
||||||
seasonId: season.id,
|
seasonId: newSeason.id,
|
||||||
seriesId: seriesItem.id,
|
seriesId: seriesItem.id,
|
||||||
);
|
);
|
||||||
|
|
||||||
final episodes = episodesResponse.body?.items ?? [];
|
final episodes = episodesResponse.body?.items ?? [];
|
||||||
for (var i = 0; i < episodes.length; i++) {
|
|
||||||
final item = episodes[i];
|
final episodeResults = await Future.wait(
|
||||||
final newEpisode = await createSyncItem(item, parent: syncedSeason);
|
episodes.map((ep) async {
|
||||||
|
final newEpisode = await createSyncItem(ep, parent: syncedSeason);
|
||||||
|
return (ep, newEpisode);
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
|
for (final (ep, newEpisode) in episodeResults) {
|
||||||
newItems.add(newEpisode);
|
newItems.add(newEpisode);
|
||||||
if (episode?.id == item.id) {
|
if (episode?.id == ep.id || newSeason.id == season?.id) {
|
||||||
itemToDownload = newEpisode;
|
itemsToDownload.add(newEpisode);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -673,8 +674,9 @@ extension SyncNotifierHelpers on SyncNotifier {
|
||||||
.toList()),
|
.toList()),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (itemToDownload != null) {
|
for (var i = 0; i < itemsToDownload.length; i++) {
|
||||||
await syncVideoFile(itemToDownload, false);
|
final item = itemsToDownload[i];
|
||||||
|
await syncFile(item, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
return seriesItem;
|
return seriesItem;
|
||||||
|
|
|
||||||
|
|
@ -71,22 +71,26 @@ final AutoRoute _dashboardRoute = CustomRoute(
|
||||||
page: DashboardRoute.page,
|
page: DashboardRoute.page,
|
||||||
transitionsBuilder: TransitionsBuilders.fadeIn,
|
transitionsBuilder: TransitionsBuilders.fadeIn,
|
||||||
initial: true,
|
initial: true,
|
||||||
|
maintainState: false,
|
||||||
path: 'dashboard',
|
path: 'dashboard',
|
||||||
);
|
);
|
||||||
final AutoRoute _favouritesRoute = CustomRoute(
|
final AutoRoute _favouritesRoute = CustomRoute(
|
||||||
page: FavouritesRoute.page,
|
page: FavouritesRoute.page,
|
||||||
transitionsBuilder: TransitionsBuilders.fadeIn,
|
transitionsBuilder: TransitionsBuilders.fadeIn,
|
||||||
|
maintainState: false,
|
||||||
path: 'favourites',
|
path: 'favourites',
|
||||||
);
|
);
|
||||||
final AutoRoute _syncedRoute = CustomRoute(
|
final AutoRoute _syncedRoute = CustomRoute(
|
||||||
page: SyncedRoute.page,
|
page: SyncedRoute.page,
|
||||||
transitionsBuilder: TransitionsBuilders.fadeIn,
|
transitionsBuilder: TransitionsBuilders.fadeIn,
|
||||||
|
maintainState: false,
|
||||||
path: 'synced',
|
path: 'synced',
|
||||||
);
|
);
|
||||||
|
|
||||||
final AutoRoute _librariesRoute = CustomRoute(
|
final AutoRoute _librariesRoute = CustomRoute(
|
||||||
page: LibraryRoute.page,
|
page: LibraryRoute.page,
|
||||||
transitionsBuilder: TransitionsBuilders.fadeIn,
|
transitionsBuilder: TransitionsBuilders.fadeIn,
|
||||||
|
maintainState: false,
|
||||||
path: 'libraries',
|
path: 'libraries',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -206,7 +206,7 @@ class EpisodePoster extends ConsumerWidget {
|
||||||
if (iSyncedItem != null)
|
if (iSyncedItem != null)
|
||||||
Consumer(builder: (context, ref, child) {
|
Consumer(builder: (context, ref, child) {
|
||||||
final SyncStatus syncStatus =
|
final SyncStatus syncStatus =
|
||||||
ref.watch(syncStatusesProvider(iSyncedItem)).value ?? SyncStatus.partially;
|
ref.watch(syncStatusesProvider(iSyncedItem, null)).value ?? SyncStatus.partially;
|
||||||
return StatusCard(
|
return StatusCard(
|
||||||
color: syncStatus.color,
|
color: syncStatus.color,
|
||||||
child: SyncButton(item: episode, syncedItem: syncedItem),
|
child: SyncButton(item: episode, syncedItem: syncedItem),
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@ 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/screens/shared/flat_button.dart';
|
import 'package:fladder/screens/shared/flat_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';
|
||||||
import 'package:fladder/util/disable_keypad_focus.dart';
|
import 'package:fladder/util/disable_keypad_focus.dart';
|
||||||
import 'package:fladder/util/fladder_image.dart';
|
import 'package:fladder/util/fladder_image.dart';
|
||||||
|
|
@ -56,6 +58,7 @@ 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,15 +103,27 @@ class SeasonPoster extends ConsumerWidget {
|
||||||
if (season.userData.unPlayedItemCount != 0)
|
if (season.userData.unPlayedItemCount != 0)
|
||||||
Align(
|
Align(
|
||||||
alignment: Alignment.topRight,
|
alignment: Alignment.topRight,
|
||||||
child: StatusCard(
|
child: Row(
|
||||||
color: Theme.of(context).colorScheme.primary,
|
mainAxisAlignment: MainAxisAlignment.end,
|
||||||
useFittedBox: true,
|
children: [
|
||||||
child: Center(
|
if (syncedItem != null)
|
||||||
child: Text(
|
StatusCard(
|
||||||
season.userData.unPlayedItemCount.toString(),
|
child: SyncButton(
|
||||||
style: const TextStyle(fontWeight: FontWeight.w700, fontSize: 14),
|
item: season,
|
||||||
|
syncedItem: syncedItem,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
StatusCard(
|
||||||
|
color: Theme.of(context).colorScheme.primary,
|
||||||
|
useFittedBox: true,
|
||||||
|
child: Center(
|
||||||
|
child: Text(
|
||||||
|
season.userData.unPlayedItemCount.toString(),
|
||||||
|
style: const TextStyle(fontWeight: FontWeight.w700, fontSize: 14),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
else
|
else
|
||||||
|
|
|
||||||
|
|
@ -1,12 +1,11 @@
|
||||||
|
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/item_base_model.dart';
|
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';
|
||||||
import 'package:fladder/providers/sync_provider.dart';
|
|
||||||
import 'package:fladder/screens/shared/default_alert_dialog.dart';
|
|
||||||
import 'package:fladder/screens/syncing/sync_item_details.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
|
|
||||||
class SyncButton extends ConsumerStatefulWidget {
|
class SyncButton extends ConsumerStatefulWidget {
|
||||||
final ItemBaseModel item;
|
final ItemBaseModel item;
|
||||||
|
|
@ -21,37 +20,21 @@ class _SyncButtonState extends ConsumerState<SyncButton> {
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
final syncedItem = widget.syncedItem;
|
final syncedItem = widget.syncedItem;
|
||||||
final status = syncedItem != null ? ref.watch(syncStatusesProvider(syncedItem)).value : null;
|
final status = syncedItem != null ? ref.watch(syncStatusesProvider(syncedItem, null)).value : null;
|
||||||
final progress = syncedItem != null ? ref.watch(syncDownloadStatusProvider(syncedItem)) : null;
|
final progress = syncedItem != null ? ref.watch(syncDownloadStatusProvider(syncedItem, [])) : null;
|
||||||
return Stack(
|
return Stack(
|
||||||
alignment: Alignment.center,
|
alignment: Alignment.center,
|
||||||
children: [
|
children: [
|
||||||
InkWell(
|
Icon(
|
||||||
onTap: syncedItem != null
|
syncedItem != null
|
||||||
? () => showSyncItemDetails(context, syncedItem, ref)
|
? status == SyncStatus.partially
|
||||||
: () => showDefaultActionDialog(
|
? (progress?.progress ?? 0) > 0
|
||||||
context,
|
? IconsaxPlusLinear.arrow_down
|
||||||
'Sync ${widget.item.detailedName}?',
|
: IconsaxPlusLinear.more_circle
|
||||||
null,
|
: IconsaxPlusLinear.tick_circle
|
||||||
(context) async {
|
: IconsaxPlusLinear.arrow_down_2,
|
||||||
await ref.read(syncProvider.notifier).addSyncItem(context, widget.item);
|
color: status?.color,
|
||||||
Navigator.of(context).pop();
|
size: (progress?.progress ?? 0) > 0 ? 16 : null,
|
||||||
},
|
|
||||||
"Sync",
|
|
||||||
(context) => Navigator.of(context).pop(),
|
|
||||||
"Cancel",
|
|
||||||
),
|
|
||||||
child: Icon(
|
|
||||||
syncedItem != null
|
|
||||||
? status == SyncStatus.partially
|
|
||||||
? (progress?.progress ?? 0) > 0
|
|
||||||
? IconsaxPlusLinear.arrow_down
|
|
||||||
: IconsaxPlusLinear.more_circle
|
|
||||||
: IconsaxPlusLinear.tick_circle
|
|
||||||
: IconsaxPlusLinear.arrow_down_2,
|
|
||||||
color: status?.color,
|
|
||||||
size: (progress?.progress ?? 0) > 0 ? 16 : null,
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
if ((progress?.progress ?? 0) > 0)
|
if ((progress?.progress ?? 0) > 0)
|
||||||
IgnorePointer(
|
IgnorePointer(
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,12 @@
|
||||||
import 'package:fladder/models/items/season_model.dart';
|
|
||||||
import 'package:fladder/models/syncing/sync_item.dart';
|
|
||||||
import 'package:fladder/screens/syncing/widgets/synced_season_poster.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
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/items/season_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/screens/syncing/widgets/synced_season_poster.dart';
|
||||||
|
|
||||||
import 'widgets/synced_episode_item.dart';
|
import 'widgets/synced_episode_item.dart';
|
||||||
|
|
||||||
|
|
@ -33,31 +34,25 @@ class _ChildSyncWidgetState extends ConsumerState<ChildSyncWidget> {
|
||||||
return Padding(
|
return Padding(
|
||||||
padding: const EdgeInsets.symmetric(vertical: 6),
|
padding: const EdgeInsets.symmetric(vertical: 6),
|
||||||
child: Card(
|
child: Card(
|
||||||
child: InkWell(
|
child: Padding(
|
||||||
onTap: () {
|
padding: const EdgeInsets.all(8.0),
|
||||||
Navigator.of(context).pop();
|
child: Row(
|
||||||
baseItem.navigateTo(context);
|
children: [
|
||||||
},
|
Flexible(
|
||||||
child: Padding(
|
child: switch (baseItem) {
|
||||||
padding: const EdgeInsets.all(8.0),
|
SeasonModel season => SyncedSeasonPoster(
|
||||||
child: Row(
|
syncedItem: syncedItem,
|
||||||
children: [
|
season: season,
|
||||||
Flexible(
|
),
|
||||||
child: switch (baseItem) {
|
EpisodeModel episode => SyncedEpisodeItem(
|
||||||
SeasonModel season => SyncedSeasonPoster(
|
episode: episode,
|
||||||
syncedItem: syncedItem,
|
syncedItem: syncedItem,
|
||||||
season: season,
|
hasFile: hasFile,
|
||||||
),
|
),
|
||||||
EpisodeModel episode => SyncedEpisodeItem(
|
_ => Container(),
|
||||||
episode: episode,
|
},
|
||||||
syncedItem: syncedItem,
|
),
|
||||||
hasFile: hasFile,
|
],
|
||||||
),
|
|
||||||
_ => Container(),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,11 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:background_downloader/background_downloader.dart';
|
|
||||||
import 'package:iconsax_plus/iconsax_plus.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package: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/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/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/screens/shared/adaptive_dialog.dart';
|
import 'package:fladder/screens/shared/adaptive_dialog.dart';
|
||||||
|
|
@ -101,6 +99,7 @@ class _SyncItemDetailsState extends ConsumerState<SyncItemDetails> {
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SyncProgressBuilder(
|
child: SyncProgressBuilder(
|
||||||
item: syncedItem,
|
item: syncedItem,
|
||||||
|
children: syncChildren,
|
||||||
builder: (context, combinedStream) {
|
builder: (context, combinedStream) {
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
|
|
@ -114,52 +113,17 @@ class _SyncItemDetailsState extends ConsumerState<SyncItemDetails> {
|
||||||
),
|
),
|
||||||
SyncSubtitle(syncItem: syncedItem),
|
SyncSubtitle(syncItem: syncedItem),
|
||||||
SyncLabel(
|
SyncLabel(
|
||||||
label: context.localized
|
label: context.localized.totalSize(
|
||||||
.totalSize(ref.watch(syncSizeProvider(syncedItem)).byteFormat ?? '--'),
|
ref.watch(syncSizeProvider(syncedItem, syncChildren)).byteFormat ?? '--'),
|
||||||
status: ref.watch(syncStatusesProvider(syncedItem)).value ?? SyncStatus.partially,
|
status: ref.watch(syncStatusesProvider(syncedItem, syncChildren)).value ??
|
||||||
|
SyncStatus.partially,
|
||||||
),
|
),
|
||||||
|
if (combinedStream?.task != null && combinedStream != null) ...{
|
||||||
|
SyncProgressBar(item: syncedItem, task: combinedStream)
|
||||||
|
},
|
||||||
].addInBetween(const SizedBox(height: 8)),
|
].addInBetween(const SizedBox(height: 8)),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (combinedStream?.task != null) ...{
|
|
||||||
if (combinedStream?.status != TaskStatus.paused)
|
|
||||||
IconButton(
|
|
||||||
onPressed: () =>
|
|
||||||
ref.read(backgroundDownloaderProvider).pause(combinedStream!.task!),
|
|
||||||
icon: const Icon(IconsaxPlusBold.pause),
|
|
||||||
),
|
|
||||||
if (combinedStream?.status == TaskStatus.paused) ...[
|
|
||||||
IconButton(
|
|
||||||
onPressed: () =>
|
|
||||||
ref.read(backgroundDownloaderProvider).resume(combinedStream!.task!),
|
|
||||||
icon: const Icon(IconsaxPlusBold.play),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
onPressed: () => ref
|
|
||||||
.read(syncProvider.notifier)
|
|
||||||
.deleteFullSyncFiles(syncedItem, combinedStream?.task),
|
|
||||||
icon: const Icon(IconsaxPlusBold.stop),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
const SizedBox(width: 16)
|
|
||||||
},
|
|
||||||
if (combinedStream != null && combinedStream.hasDownload)
|
|
||||||
SizedBox.fromSize(
|
|
||||||
size: const Size.fromRadius(35),
|
|
||||||
child: Stack(
|
|
||||||
fit: StackFit.expand,
|
|
||||||
alignment: Alignment.center,
|
|
||||||
children: [
|
|
||||||
CircularProgressIndicator(
|
|
||||||
value: combinedStream.progress,
|
|
||||||
strokeWidth: 8,
|
|
||||||
backgroundColor: Theme.of(context).colorScheme.surface.withValues(alpha: 0.5),
|
|
||||||
strokeCap: StrokeCap.round,
|
|
||||||
color: combinedStream.status.color(context),
|
|
||||||
),
|
|
||||||
Center(child: Text("${((combinedStream.progress) * 100).toStringAsFixed(0)}%"))
|
|
||||||
],
|
|
||||||
)),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|
@ -167,7 +131,7 @@ class _SyncItemDetailsState extends ConsumerState<SyncItemDetails> {
|
||||||
),
|
),
|
||||||
if (!hasFile && !downloadTask.hasDownload && syncedItem.hasVideoFile)
|
if (!hasFile && !downloadTask.hasDownload && syncedItem.hasVideoFile)
|
||||||
IconButtonAwait(
|
IconButtonAwait(
|
||||||
onPressed: () async => await ref.read(syncProvider.notifier).syncVideoFile(syncedItem, false),
|
onPressed: () async => await ref.read(syncProvider.notifier).syncFile(syncedItem, false),
|
||||||
icon: const Icon(IconsaxPlusLinear.cloud_change),
|
icon: const Icon(IconsaxPlusLinear.cloud_change),
|
||||||
)
|
)
|
||||||
else if (hasFile)
|
else if (hasFile)
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ class SyncListItemState extends ConsumerState<SyncListItem> {
|
||||||
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 = ref.read(syncProvider.notifier).getItem(syncedItem);
|
||||||
|
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(
|
||||||
|
|
@ -89,6 +90,7 @@ class SyncListItemState extends ConsumerState<SyncListItem> {
|
||||||
Expanded(
|
Expanded(
|
||||||
child: SyncProgressBuilder(
|
child: SyncProgressBuilder(
|
||||||
item: syncedItem,
|
item: syncedItem,
|
||||||
|
children: children,
|
||||||
builder: (context, combinedStream) {
|
builder: (context, combinedStream) {
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
|
@ -103,13 +105,17 @@ class SyncListItemState extends ConsumerState<SyncListItem> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Flexible(
|
Flexible(
|
||||||
child: SyncSubtitle(syncItem: syncedItem),
|
child: SyncSubtitle(
|
||||||
|
syncItem: syncedItem,
|
||||||
|
children: children,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
Flexible(
|
Flexible(
|
||||||
child: SyncLabel(
|
child: SyncLabel(
|
||||||
label: context.localized
|
label: context.localized.totalSize(
|
||||||
.totalSize(ref.watch(syncSizeProvider(syncedItem)).byteFormat ?? '--'),
|
ref.watch(syncSizeProvider(syncedItem, children)).byteFormat ?? '--'),
|
||||||
status: ref.watch(syncStatusesProvider(syncedItem)).value ?? SyncStatus.partially,
|
status: ref.watch(syncStatusesProvider(syncedItem, children)).value ??
|
||||||
|
SyncStatus.partially,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
if (combinedStream != null && combinedStream.hasDownload == true)
|
if (combinedStream != null && combinedStream.hasDownload == true)
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,8 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:background_downloader/background_downloader.dart';
|
import 'package:background_downloader/background_downloader.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/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';
|
||||||
|
|
@ -15,6 +15,13 @@ import 'package:fladder/providers/sync_provider.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';
|
||||||
|
|
||||||
|
const _cancellableStatuses = {
|
||||||
|
TaskStatus.canceled,
|
||||||
|
TaskStatus.failed,
|
||||||
|
TaskStatus.enqueued,
|
||||||
|
TaskStatus.waitingToRetry,
|
||||||
|
};
|
||||||
|
|
||||||
class SyncLabel extends ConsumerWidget {
|
class SyncLabel extends ConsumerWidget {
|
||||||
final String? label;
|
final String? label;
|
||||||
final SyncStatus status;
|
final SyncStatus status;
|
||||||
|
|
@ -76,18 +83,24 @@ class SyncProgressBar extends ConsumerWidget {
|
||||||
IconButton(
|
IconButton(
|
||||||
onPressed: () => ref.read(backgroundDownloaderProvider).pause(downloadTask),
|
onPressed: () => ref.read(backgroundDownloaderProvider).pause(downloadTask),
|
||||||
icon: const Icon(IconsaxPlusBold.pause),
|
icon: const Icon(IconsaxPlusBold.pause),
|
||||||
|
),
|
||||||
|
if (downloadStatus == TaskStatus.paused) ...[
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => ref.read(backgroundDownloaderProvider).resume(downloadTask),
|
||||||
|
icon: const Icon(IconsaxPlusBold.play),
|
||||||
|
),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => ref.read(syncProvider.notifier).deleteFullSyncFiles(item, downloadTask),
|
||||||
|
icon: const Icon(IconsaxPlusBold.stop),
|
||||||
)
|
)
|
||||||
|
],
|
||||||
|
if (_cancellableStatuses.contains(downloadStatus)) ...[
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => ref.read(syncProvider.notifier).deleteFullSyncFiles(item, downloadTask),
|
||||||
|
icon: const Icon(IconsaxPlusBold.stop),
|
||||||
|
),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
if (downloadStatus == TaskStatus.paused && downloadTask != null) ...[
|
|
||||||
IconButton(
|
|
||||||
onPressed: () => ref.read(backgroundDownloaderProvider).resume(downloadTask),
|
|
||||||
icon: const Icon(IconsaxPlusBold.play),
|
|
||||||
),
|
|
||||||
IconButton(
|
|
||||||
onPressed: () => ref.read(syncProvider.notifier).deleteFullSyncFiles(item, downloadTask),
|
|
||||||
icon: const Icon(IconsaxPlusBold.stop),
|
|
||||||
)
|
|
||||||
],
|
|
||||||
].addInBetween(const SizedBox(width: 8)),
|
].addInBetween(const SizedBox(width: 8)),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 6),
|
||||||
|
|
@ -98,16 +111,17 @@ class SyncProgressBar extends ConsumerWidget {
|
||||||
|
|
||||||
class SyncSubtitle extends ConsumerWidget {
|
class SyncSubtitle extends ConsumerWidget {
|
||||||
final SyncedItem syncItem;
|
final SyncedItem syncItem;
|
||||||
|
final List<SyncedItem> children;
|
||||||
const SyncSubtitle({
|
const SyncSubtitle({
|
||||||
required this.syncItem,
|
required this.syncItem,
|
||||||
|
this.children = const [],
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final baseItem = ref.read(syncProvider.notifier).getItem(syncItem);
|
final baseItem = ref.read(syncProvider.notifier).getItem(syncItem);
|
||||||
final children = syncItem.nestedChildren(ref);
|
final syncStatus = ref.watch(syncStatusesProvider(syncItem, children)).value ?? SyncStatus.partially;
|
||||||
final syncStatus = ref.watch(syncStatusesProvider(syncItem)).value ?? SyncStatus.partially;
|
|
||||||
return Container(
|
return Container(
|
||||||
decoration:
|
decoration:
|
||||||
BoxDecoration(color: syncStatus.color.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(10)),
|
BoxDecoration(color: syncStatus.color.withValues(alpha: 0.15), borderRadius: BorderRadius.circular(10)),
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,20 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
import 'package:fladder/models/syncing/download_stream.dart';
|
import 'package:fladder/models/syncing/download_stream.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';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
|
|
||||||
class SyncProgressBuilder extends ConsumerWidget {
|
class SyncProgressBuilder extends ConsumerWidget {
|
||||||
final SyncedItem item;
|
final SyncedItem item;
|
||||||
|
final List<SyncedItem> children;
|
||||||
final Widget Function(BuildContext context, DownloadStream? combinedStream) builder;
|
final Widget Function(BuildContext context, DownloadStream? combinedStream) builder;
|
||||||
const SyncProgressBuilder({required this.item, required this.builder, super.key});
|
const SyncProgressBuilder({required this.item, this.children = const [], required this.builder, super.key});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final syncStatus = ref.watch(syncDownloadStatusProvider(item));
|
final syncStatus = ref.watch(syncDownloadStatusProvider(item, children));
|
||||||
return builder(context, syncStatus);
|
return builder(context, syncStatus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,15 @@
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
import 'package:iconsax_plus/iconsax_plus.dart';
|
import 'package:auto_route/auto_route.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/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';
|
||||||
import 'package:fladder/providers/sync_provider.dart';
|
import 'package:fladder/providers/sync_provider.dart';
|
||||||
import 'package:fladder/screens/shared/default_alert_dialog.dart';
|
import 'package:fladder/screens/shared/default_alert_dialog.dart';
|
||||||
|
import 'package:fladder/screens/shared/flat_button.dart';
|
||||||
import 'package:fladder/screens/shared/media/episode_posters.dart';
|
import 'package:fladder/screens/shared/media/episode_posters.dart';
|
||||||
import 'package:fladder/screens/syncing/sync_widgets.dart';
|
import 'package:fladder/screens/syncing/sync_widgets.dart';
|
||||||
import 'package:fladder/util/list_padding.dart';
|
import 'package:fladder/util/list_padding.dart';
|
||||||
|
|
@ -40,11 +42,15 @@ class _SyncedEpisodeItemState extends ConsumerState<SyncedEpisodeItem> {
|
||||||
|
|
||||||
return Row(
|
return Row(
|
||||||
children: [
|
children: [
|
||||||
IgnorePointer(
|
ConstrainedBox(
|
||||||
child: ConstrainedBox(
|
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.3),
|
||||||
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.3),
|
child: FlatButton(
|
||||||
|
onTap: () {
|
||||||
|
widget.episode.navigateTo(context);
|
||||||
|
return context.maybePop();
|
||||||
|
},
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: 250,
|
width: 175,
|
||||||
child: EpisodePoster(
|
child: EpisodePoster(
|
||||||
episode: widget.episode,
|
episode: widget.episode,
|
||||||
syncedItem: syncedItem,
|
syncedItem: syncedItem,
|
||||||
|
|
@ -87,8 +93,8 @@ class _SyncedEpisodeItemState extends ConsumerState<SyncedEpisodeItem> {
|
||||||
else
|
else
|
||||||
Flexible(
|
Flexible(
|
||||||
child: SyncLabel(
|
child: SyncLabel(
|
||||||
label: context.localized.totalSize(ref.watch(syncSizeProvider(syncedItem)).byteFormat ?? '--'),
|
label: context.localized.totalSize(ref.watch(syncSizeProvider(syncedItem, [])).byteFormat ?? '--'),
|
||||||
status: ref.watch(syncStatusesProvider(syncedItem)).value ?? SyncStatus.partially,
|
status: ref.watch(syncStatusesProvider(syncedItem, [])).value ?? SyncStatus.partially,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
|
|
@ -96,7 +102,7 @@ class _SyncedEpisodeItemState extends ConsumerState<SyncedEpisodeItem> {
|
||||||
),
|
),
|
||||||
if (!hasFile && !downloadTask.hasDownload)
|
if (!hasFile && !downloadTask.hasDownload)
|
||||||
IconButtonAwait(
|
IconButtonAwait(
|
||||||
onPressed: () async => await ref.read(syncProvider.notifier).syncVideoFile(syncedItem, false),
|
onPressed: () async => await ref.read(syncProvider.notifier).syncFile(syncedItem, false),
|
||||||
icon: const Icon(IconsaxPlusLinear.cloud_change),
|
icon: const Icon(IconsaxPlusLinear.cloud_change),
|
||||||
)
|
)
|
||||||
else if (hasFile)
|
else if (hasFile)
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,17 @@
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:auto_route/auto_route.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_provider.dart';
|
||||||
import 'package:fladder/screens/shared/animated_fade_size.dart';
|
import 'package:fladder/screens/shared/flat_button.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/util/list_padding.dart';
|
import 'package:fladder/widgets/shared/icon_button_await.dart';
|
||||||
import 'package:flutter/material.dart';
|
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
||||||
|
|
||||||
class SyncedSeasonPoster extends ConsumerStatefulWidget {
|
class SyncedSeasonPoster extends ConsumerStatefulWidget {
|
||||||
const SyncedSeasonPoster({
|
const SyncedSeasonPoster({
|
||||||
|
|
@ -29,14 +33,21 @@ class _SyncedSeasonPosterState extends ConsumerState<SyncedSeasonPoster> {
|
||||||
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 children = ref.read(syncProvider.notifier).getChildren(widget.syncedItem);
|
||||||
return Column(
|
final unSyncedChildren = children.where((child) => child.status == SyncStatus.partially).toList();
|
||||||
children: [
|
return ExpansionTile(
|
||||||
Row(
|
tilePadding: EdgeInsets.zero,
|
||||||
children: [
|
title: Row(
|
||||||
SizedBox(
|
spacing: 6,
|
||||||
width: 125,
|
children: [
|
||||||
child: AspectRatio(
|
SizedBox(
|
||||||
aspectRatio: 0.65,
|
width: 75,
|
||||||
|
child: AspectRatio(
|
||||||
|
aspectRatio: 0.65,
|
||||||
|
child: FlatButton(
|
||||||
|
onTap: () {
|
||||||
|
season.navigateTo(context);
|
||||||
|
return context.maybePop();
|
||||||
|
},
|
||||||
child: Card(
|
child: Card(
|
||||||
child: FladderImage(
|
child: FladderImage(
|
||||||
image: season.getPosters?.primary ??
|
image: season.getPosters?.primary ??
|
||||||
|
|
@ -46,50 +57,47 @@ class _SyncedSeasonPosterState extends ConsumerState<SyncedSeasonPoster> {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Column(
|
),
|
||||||
children: [
|
Column(
|
||||||
Text(
|
children: [
|
||||||
season.name,
|
Text(
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
season.name,
|
||||||
)
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
],
|
)
|
||||||
),
|
],
|
||||||
const Spacer(),
|
),
|
||||||
IconButton(
|
],
|
||||||
onPressed: () {
|
),
|
||||||
setState(() {
|
trailing: Row(
|
||||||
expanded = !expanded;
|
mainAxisSize: MainAxisSize.min,
|
||||||
});
|
children: [
|
||||||
|
if (unSyncedChildren.isNotEmpty)
|
||||||
|
IconButtonAwait(
|
||||||
|
onPressed: () async {
|
||||||
|
for (var i = 0; i < unSyncedChildren.length; i++) {
|
||||||
|
final childSyncedItem = unSyncedChildren[i];
|
||||||
|
await ref.read(syncProvider.notifier).syncFile(childSyncedItem, false);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
icon: Icon(!expanded ? Icons.keyboard_arrow_down_rounded : Icons.keyboard_arrow_up_rounded),
|
icon: const Icon(IconsaxPlusLinear.cloud_change),
|
||||||
)
|
),
|
||||||
].addPadding(const EdgeInsets.symmetric(horizontal: 6)),
|
],
|
||||||
),
|
),
|
||||||
AnimatedFadeSize(
|
children: children.map(
|
||||||
duration: const Duration(milliseconds: 250),
|
(item) {
|
||||||
child: expanded && children.isNotEmpty
|
final baseItem = ref.read(syncProvider.notifier).getItem(item);
|
||||||
? ListView(
|
return Padding(
|
||||||
shrinkWrap: true,
|
padding: const EdgeInsets.symmetric(vertical: 8),
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
child: IntrinsicHeight(
|
||||||
children: <Widget>[
|
child: SyncedEpisodeItem(
|
||||||
const Divider(),
|
episode: baseItem as EpisodeModel,
|
||||||
...children.map(
|
syncedItem: item,
|
||||||
(item) {
|
hasFile: item.videoFile.existsSync(),
|
||||||
final baseItem = ref.read(syncProvider.notifier).getItem(item);
|
),
|
||||||
return IntrinsicHeight(
|
),
|
||||||
child: SyncedEpisodeItem(
|
);
|
||||||
episode: baseItem as EpisodeModel,
|
},
|
||||||
syncedItem: item,
|
).toList(),
|
||||||
hasFile: item.videoFile.existsSync(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
)
|
|
||||||
].addPadding(const EdgeInsets.symmetric(vertical: 10)),
|
|
||||||
)
|
|
||||||
: Container(),
|
|
||||||
)
|
|
||||||
].addPadding(EdgeInsets.only(top: 10, bottom: expanded ? 10 : 0)),
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -126,9 +126,10 @@ class FladderTheme {
|
||||||
listTileTheme: ListTileThemeData(
|
listTileTheme: ListTileThemeData(
|
||||||
shape: defaultShape,
|
shape: defaultShape,
|
||||||
),
|
),
|
||||||
dividerTheme: const DividerThemeData(
|
dividerTheme: DividerThemeData(
|
||||||
indent: 6,
|
indent: 6,
|
||||||
endIndent: 6,
|
endIndent: 6,
|
||||||
|
color: scheme?.onSurface.withAlpha(125),
|
||||||
),
|
),
|
||||||
segmentedButtonTheme: SegmentedButtonThemeData(
|
segmentedButtonTheme: SegmentedButtonThemeData(
|
||||||
style: ButtonStyle(
|
style: ButtonStyle(
|
||||||
|
|
|
||||||
|
|
@ -9,6 +9,7 @@ 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';
|
||||||
|
|
@ -243,8 +244,12 @@ extension ItemBaseModelExtensions on ItemBaseModel {
|
||||||
else
|
else
|
||||||
ItemActionButton(
|
ItemActionButton(
|
||||||
icon: IgnorePointer(child: SyncButton(item: this, syncedItem: syncedItem)),
|
icon: IgnorePointer(child: SyncButton(item: this, syncedItem: syncedItem)),
|
||||||
action: () => showSyncItemDetails(context, syncedItem, ref),
|
action: () => syncedItem.status == SyncStatus.complete
|
||||||
label: Text(context.localized.syncDetails),
|
? 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,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
else if (downloadUrl != null) ...[
|
else if (downloadUrl != null) ...[
|
||||||
ItemActionButton(
|
ItemActionButton(
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
|
|
||||||
import 'package:fladder/l10n/generated/app_localizations.dart';
|
import 'package:fladder/l10n/generated/app_localizations.dart';
|
||||||
|
import 'package:fladder/providers/sync/background_download_provider.dart';
|
||||||
|
|
||||||
///Only use for base translations, under normal circumstances ALWAYS use the widgets provided context
|
///Only use for base translations, under normal circumstances ALWAYS use the widgets provided context
|
||||||
final localizationContextProvider = StateProvider<BuildContext?>((ref) => null);
|
final localizationContextProvider = StateProvider<BuildContext?>((ref) => null);
|
||||||
|
|
@ -13,7 +14,12 @@ extension BuildContextExtension on BuildContext {
|
||||||
|
|
||||||
class LocalizationContextWrapper extends ConsumerStatefulWidget {
|
class LocalizationContextWrapper extends ConsumerStatefulWidget {
|
||||||
final Widget child;
|
final Widget child;
|
||||||
const LocalizationContextWrapper({required this.child, super.key});
|
final Locale currentLocale;
|
||||||
|
const LocalizationContextWrapper({
|
||||||
|
required this.child,
|
||||||
|
required this.currentLocale,
|
||||||
|
super.key,
|
||||||
|
});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
ConsumerState<LocalizationContextWrapper> createState() => _LocalizationContextWrapperState();
|
ConsumerState<LocalizationContextWrapper> createState() => _LocalizationContextWrapperState();
|
||||||
|
|
@ -23,8 +29,21 @@ class _LocalizationContextWrapperState extends ConsumerState<LocalizationContext
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
WidgetsBinding.instance.addPostFrameCallback((event) {
|
updateLanguageContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void didUpdateWidget(covariant LocalizationContextWrapper oldWidget) {
|
||||||
|
super.didUpdateWidget(oldWidget);
|
||||||
|
if (oldWidget.currentLocale != widget.currentLocale) {
|
||||||
|
updateLanguageContext();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateLanguageContext() {
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((value) {
|
||||||
ref.read(localizationContextProvider.notifier).update((cb) => context);
|
ref.read(localizationContextProvider.notifier).update((cb) => context);
|
||||||
|
ref.read(backgroundDownloaderProvider.notifier).updateTranslations(context);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -59,8 +59,9 @@ class _BackgroundImageState extends ConsumerState<BackgroundImage> {
|
||||||
if (itemId == null) return;
|
if (itemId == null) return;
|
||||||
|
|
||||||
final apiResponse = await ref.read(jellyApiProvider).usersUserIdItemsItemIdGet(itemId: itemId);
|
final apiResponse = await ref.read(jellyApiProvider).usersUserIdItemsItemIdGet(itemId: itemId);
|
||||||
final image =
|
final image = apiResponse.body?.parentBaseModel.getPosters?.randomBackDrop ??
|
||||||
apiResponse.body?.parentBaseModel.getPosters?.randomBackDrop ?? apiResponse.body?.getPosters?.randomBackDrop;
|
apiResponse.body?.getPosters?.randomBackDrop ??
|
||||||
|
apiResponse.body?.getPosters?.primary;
|
||||||
|
|
||||||
if (mounted) setState(() => backgroundImage = image);
|
if (mounted) setState(() => backgroundImage = image);
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,10 @@
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:developer';
|
import 'dart:developer';
|
||||||
|
|
||||||
import 'package:fladder/screens/shared/animated_fade_size.dart';
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
import 'package:fladder/screens/shared/animated_fade_size.dart';
|
||||||
|
|
||||||
class IconButtonAwait extends StatefulWidget {
|
class IconButtonAwait extends StatefulWidget {
|
||||||
final FutureOr<dynamic> Function() onPressed;
|
final FutureOr<dynamic> Function() onPressed;
|
||||||
final Color? color;
|
final Color? color;
|
||||||
|
|
@ -33,7 +34,10 @@ class IconButtonAwaitState extends State<IconButtonAwait> {
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log(e.toString());
|
log(e.toString());
|
||||||
} finally {
|
} finally {
|
||||||
setState(() => loading = false);
|
setState(() {
|
||||||
|
if (!mounted) return;
|
||||||
|
loading = false;
|
||||||
|
});
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
icon: AnimatedFadeSize(
|
icon: AnimatedFadeSize(
|
||||||
|
|
|
||||||
28
pubspec.lock
28
pubspec.lock
|
|
@ -514,10 +514,10 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: file_picker
|
name: file_picker
|
||||||
sha256: "77f8e81d22d2a07d0dee2c62e1dda71dc1da73bf43bb2d45af09727406167964"
|
sha256: ef9908739bdd9c476353d6adff72e88fd00c625f5b959ae23f7567bd5137db0a
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "10.1.9"
|
version: "10.2.0"
|
||||||
fixnum:
|
fixnum:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -942,18 +942,18 @@ packages:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: isar
|
name: isar
|
||||||
sha256: ebf74d87c400bd9f7da14acb31932b50c2407edbbd40930da3a6c2a8143f85a8
|
sha256: e987032e5d007a03ba4415cbf4d47add17b57ac664db8705db90fbfeb6a16737
|
||||||
url: "https://pub.dev"
|
url: "https://pub.isar-community.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.0-dev.14"
|
version: "4.0.3"
|
||||||
isar_flutter_libs:
|
isar_flutter_libs:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
name: isar_flutter_libs
|
name: isar_flutter_libs
|
||||||
sha256: "04a3f4035e213ddb6e78d0132a7c80296a085c2088c2a761b4a42ee5add36983"
|
sha256: a6b86d8618fe2d7d0e2ac6aa7a7f21c0c8ae912ccbef94a45d9f6e1e519ef610
|
||||||
url: "https://pub.dev"
|
url: "https://pub.isar-community.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "4.0.0-dev.14"
|
version: "4.0.3"
|
||||||
js:
|
js:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -2027,10 +2027,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: vector_graphics
|
name: vector_graphics
|
||||||
sha256: "44cc7104ff32563122a929e4620cf3efd584194eec6d1d913eb5ba593dbcf6de"
|
sha256: a4f059dc26fc8295b5921376600a194c4ec7d55e72f2fe4c7d2831e103d461e6
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.18"
|
version: "1.1.19"
|
||||||
vector_graphics_codec:
|
vector_graphics_codec:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -2139,10 +2139,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: watcher
|
name: watcher
|
||||||
sha256: "69da27e49efa56a15f8afe8f4438c4ec02eff0a117df1b22ea4aad194fe1c104"
|
sha256: "0b7fd4a0bbc4b92641dbf20adfd7e3fd1398fe17102d94b674234563e110088a"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "1.1.1"
|
version: "1.1.2"
|
||||||
weak_map:
|
weak_map:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
|
|
@ -2211,10 +2211,10 @@ packages:
|
||||||
dependency: transitive
|
dependency: transitive
|
||||||
description:
|
description:
|
||||||
name: win32
|
name: win32
|
||||||
sha256: "329edf97fdd893e0f1e3b9e88d6a0e627128cc17cc316a8d67fda8f1451178ba"
|
sha256: "66814138c3562338d05613a6e368ed8cfb237ad6d64a9e9334be3f309acfca03"
|
||||||
url: "https://pub.dev"
|
url: "https://pub.dev"
|
||||||
source: hosted
|
source: hosted
|
||||||
version: "5.13.0"
|
version: "5.14.0"
|
||||||
window_manager:
|
window_manager:
|
||||||
dependency: "direct main"
|
dependency: "direct main"
|
||||||
description:
|
description:
|
||||||
|
|
|
||||||
10
pubspec.yaml
10
pubspec.yaml
|
|
@ -98,7 +98,7 @@ dependencies:
|
||||||
|
|
||||||
# Utility
|
# Utility
|
||||||
path: ^1.9.1
|
path: ^1.9.1
|
||||||
file_picker: ^10.1.9
|
file_picker: ^10.2.0
|
||||||
transparent_image: ^2.0.1
|
transparent_image: ^2.0.1
|
||||||
universal_html: ^2.2.4
|
universal_html: ^2.2.4
|
||||||
collection: ^1.19.1
|
collection: ^1.19.1
|
||||||
|
|
@ -114,8 +114,12 @@ dependencies:
|
||||||
screen_retriever: ^0.2.0
|
screen_retriever: ^0.2.0
|
||||||
|
|
||||||
# Data
|
# Data
|
||||||
isar: ^4.0.0-dev.14
|
isar:
|
||||||
isar_flutter_libs: ^4.0.0-dev.14 # contains Isar Core
|
version: ^4.0.3
|
||||||
|
hosted: https://pub.isar-community.dev/
|
||||||
|
isar_flutter_libs: # contains Isar Core
|
||||||
|
version: ^4.0.3
|
||||||
|
hosted: https://pub.isar-community.dev/
|
||||||
|
|
||||||
# Other
|
# Other
|
||||||
async: ^2.13.0
|
async: ^2.13.0
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue