feature: Improved sync capability

This commit is contained in:
PartyDonut 2025-06-09 21:23:02 +02:00
parent f3e920ac79
commit c5c7f71b84
31 changed files with 500 additions and 344 deletions

View file

@ -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/sync_item.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 {
final SyncedItem item;
final List<SyncedItem> children;
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
Widget build(BuildContext context, WidgetRef ref) {
final syncStatus = ref.watch(syncDownloadStatusProvider(item));
final syncStatus = ref.watch(syncDownloadStatusProvider(item, children));
return builder(context, syncStatus);
}
}

View file

@ -1,13 +1,15 @@
import 'package:flutter/material.dart';
import 'package:iconsax_plus/iconsax_plus.dart';
import 'package:auto_route/auto_route.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:iconsax_plus/iconsax_plus.dart';
import 'package:fladder/models/items/episode_model.dart';
import 'package:fladder/models/syncing/sync_item.dart';
import 'package:fladder/providers/sync/sync_provider_helpers.dart';
import 'package:fladder/providers/sync_provider.dart';
import 'package:fladder/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/syncing/sync_widgets.dart';
import 'package:fladder/util/list_padding.dart';
@ -40,11 +42,15 @@ class _SyncedEpisodeItemState extends ConsumerState<SyncedEpisodeItem> {
return Row(
children: [
IgnorePointer(
child: ConstrainedBox(
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.3),
ConstrainedBox(
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width * 0.3),
child: FlatButton(
onTap: () {
widget.episode.navigateTo(context);
return context.maybePop();
},
child: SizedBox(
width: 250,
width: 175,
child: EpisodePoster(
episode: widget.episode,
syncedItem: syncedItem,
@ -87,8 +93,8 @@ class _SyncedEpisodeItemState extends ConsumerState<SyncedEpisodeItem> {
else
Flexible(
child: SyncLabel(
label: context.localized.totalSize(ref.watch(syncSizeProvider(syncedItem)).byteFormat ?? '--'),
status: ref.watch(syncStatusesProvider(syncedItem)).value ?? SyncStatus.partially,
label: context.localized.totalSize(ref.watch(syncSizeProvider(syncedItem, [])).byteFormat ?? '--'),
status: ref.watch(syncStatusesProvider(syncedItem, [])).value ?? SyncStatus.partially,
),
)
],
@ -96,7 +102,7 @@ class _SyncedEpisodeItemState extends ConsumerState<SyncedEpisodeItem> {
),
if (!hasFile && !downloadTask.hasDownload)
IconButtonAwait(
onPressed: () async => await ref.read(syncProvider.notifier).syncVideoFile(syncedItem, false),
onPressed: () async => await ref.read(syncProvider.notifier).syncFile(syncedItem, false),
icon: const Icon(IconsaxPlusLinear.cloud_change),
)
else if (hasFile)

View file

@ -1,13 +1,17 @@
import 'package:flutter/material.dart';
import 'package:auto_route/auto_route.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:iconsax_plus/iconsax_plus.dart';
import 'package:fladder/models/items/episode_model.dart';
import 'package:fladder/models/items/season_model.dart';
import 'package:fladder/models/syncing/sync_item.dart';
import 'package:fladder/providers/sync_provider.dart';
import 'package:fladder/screens/shared/animated_fade_size.dart';
import 'package:fladder/screens/shared/flat_button.dart';
import 'package:fladder/screens/syncing/widgets/synced_episode_item.dart';
import 'package:fladder/util/fladder_image.dart';
import 'package:fladder/util/list_padding.dart';
import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:fladder/widgets/shared/icon_button_await.dart';
class SyncedSeasonPoster extends ConsumerStatefulWidget {
const SyncedSeasonPoster({
@ -29,14 +33,21 @@ class _SyncedSeasonPosterState extends ConsumerState<SyncedSeasonPoster> {
Widget build(BuildContext context) {
final season = widget.season;
final children = ref.read(syncProvider.notifier).getChildren(widget.syncedItem);
return Column(
children: [
Row(
children: [
SizedBox(
width: 125,
child: AspectRatio(
aspectRatio: 0.65,
final unSyncedChildren = children.where((child) => child.status == SyncStatus.partially).toList();
return ExpansionTile(
tilePadding: EdgeInsets.zero,
title: Row(
spacing: 6,
children: [
SizedBox(
width: 75,
child: AspectRatio(
aspectRatio: 0.65,
child: FlatButton(
onTap: () {
season.navigateTo(context);
return context.maybePop();
},
child: Card(
child: FladderImage(
image: season.getPosters?.primary ??
@ -46,50 +57,47 @@ class _SyncedSeasonPosterState extends ConsumerState<SyncedSeasonPoster> {
),
),
),
Column(
children: [
Text(
season.name,
style: Theme.of(context).textTheme.titleMedium,
)
],
),
const Spacer(),
IconButton(
onPressed: () {
setState(() {
expanded = !expanded;
});
),
Column(
children: [
Text(
season.name,
style: Theme.of(context).textTheme.titleMedium,
)
],
),
],
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (unSyncedChildren.isNotEmpty)
IconButtonAwait(
onPressed: () async {
for (var i = 0; i < unSyncedChildren.length; i++) {
final childSyncedItem = unSyncedChildren[i];
await ref.read(syncProvider.notifier).syncFile(childSyncedItem, false);
}
},
icon: Icon(!expanded ? Icons.keyboard_arrow_down_rounded : Icons.keyboard_arrow_up_rounded),
)
].addPadding(const EdgeInsets.symmetric(horizontal: 6)),
),
AnimatedFadeSize(
duration: const Duration(milliseconds: 250),
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)),
icon: const Icon(IconsaxPlusLinear.cloud_change),
),
],
),
children: children.map(
(item) {
final baseItem = ref.read(syncProvider.notifier).getItem(item);
return Padding(
padding: const EdgeInsets.symmetric(vertical: 8),
child: IntrinsicHeight(
child: SyncedEpisodeItem(
episode: baseItem as EpisodeModel,
syncedItem: item,
hasFile: item.videoFile.existsSync(),
),
),
);
},
).toList(),
);
}
}