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