first commit
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
import 'package:file_sizes/file_sizes.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
|
||||
import '../../models/finamp_models.dart';
|
||||
import '../../services/downloads_helper.dart';
|
||||
|
||||
class AlbumFileSize extends StatelessWidget {
|
||||
const AlbumFileSize({Key? key, required this.downloadedParent})
|
||||
: super(key: key);
|
||||
|
||||
final DownloadedParent downloadedParent;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
DownloadsHelper downloadsHelper = GetIt.instance<DownloadsHelper>();
|
||||
int totalSize = 0;
|
||||
|
||||
for (final item in downloadedParent.downloadedChildren.values) {
|
||||
DownloadedSong? downloadedSong =
|
||||
downloadsHelper.getDownloadedSong(item.id);
|
||||
|
||||
if (downloadedSong?.mediaSourceInfo.size != null) {
|
||||
totalSize += downloadedSong!.mediaSourceInfo.size!;
|
||||
}
|
||||
}
|
||||
|
||||
return Text(FileSize.getSize(totalSize));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
import 'dart:isolate';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:finamp/services/downloads_helper.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_downloader/flutter_downloader.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
|
||||
import '../../models/finamp_models.dart';
|
||||
import '../error_snackbar.dart';
|
||||
import '../album_image.dart';
|
||||
|
||||
class CurrentDownloadsList extends StatefulWidget {
|
||||
const CurrentDownloadsList({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<CurrentDownloadsList> createState() => _CurrentDownloadsListState();
|
||||
}
|
||||
|
||||
class _CurrentDownloadsListState extends State<CurrentDownloadsList> {
|
||||
final ReceivePort _port = ReceivePort();
|
||||
final DownloadsHelper _downloadsHelper = GetIt.instance<DownloadsHelper>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
|
||||
IsolateNameServer.registerPortWithName(
|
||||
_port.sendPort, 'downloader_send_port');
|
||||
_port.listen((dynamic data) {
|
||||
setState(() {});
|
||||
});
|
||||
|
||||
FlutterDownloader.registerCallback(downloadCallback);
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
IsolateNameServer.removePortNameMapping('downloader_send_port');
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
// https://github.com/fluttercommunity/flutter_downloader/issues/629
|
||||
@pragma('vm:entry-point')
|
||||
static void downloadCallback(String id, int status, int progress) {
|
||||
final SendPort? send =
|
||||
IsolateNameServer.lookupPortByName('downloader_send_port');
|
||||
send?.send([id, status, progress]);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder<List<DownloadTask>?>(
|
||||
future: _downloadsHelper.getIncompleteDownloads(),
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
return SliverList(
|
||||
delegate:
|
||||
SliverChildBuilderDelegate((BuildContext context, int index) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: CurrentDownloadListTile(
|
||||
downloadTask: snapshot.data![index],
|
||||
));
|
||||
}, childCount: snapshot.data!.length),
|
||||
);
|
||||
} else if (snapshot.hasError) {
|
||||
errorSnackbar(snapshot.error, context);
|
||||
return SliverList(
|
||||
delegate: SliverChildListDelegate([
|
||||
const Text("An error occured while getting current downloads")
|
||||
]));
|
||||
} else {
|
||||
return SliverList(delegate: SliverChildListDelegate([]));
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class CurrentDownloadListTile extends StatelessWidget {
|
||||
const CurrentDownloadListTile({Key? key, required this.downloadTask})
|
||||
: super(key: key);
|
||||
|
||||
final DownloadTask downloadTask;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
DownloadsHelper downloadsHelper = GetIt.instance<DownloadsHelper>();
|
||||
DownloadedSong? item =
|
||||
downloadsHelper.getJellyfinItemFromDownloadId(downloadTask.taskId);
|
||||
|
||||
return Stack(
|
||||
alignment: Alignment.bottomCenter,
|
||||
children: [
|
||||
LinearProgressIndicator(
|
||||
value: downloadTask.progress / 100,
|
||||
backgroundColor: Colors.transparent,
|
||||
),
|
||||
ListTile(
|
||||
leading: AlbumImage(item: item?.song),
|
||||
title: Text(item?.song.name ?? "???"),
|
||||
subtitle: Text(item?.song.albumArtist ?? "???"),
|
||||
)
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_downloader/flutter_downloader.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
|
||||
import '../../screens/downloads_error_screen.dart';
|
||||
import '../../services/downloads_helper.dart';
|
||||
|
||||
class DownloadErrorScreenButton extends StatefulWidget {
|
||||
const DownloadErrorScreenButton({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<DownloadErrorScreenButton> createState() =>
|
||||
_DownloadErrorScreenButtonState();
|
||||
}
|
||||
|
||||
class _DownloadErrorScreenButtonState extends State<DownloadErrorScreenButton> {
|
||||
final _downloadsHelper = GetIt.instance<DownloadsHelper>();
|
||||
late Future<List<DownloadTask>?> downloadErrorScreenButtonFuture;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
downloadErrorScreenButtonFuture =
|
||||
_downloadsHelper.getDownloadsWithStatus(DownloadTaskStatus.failed);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder<List<DownloadTask>?>(
|
||||
future: downloadErrorScreenButtonFuture,
|
||||
builder: (context, snapshot) {
|
||||
return IconButton(
|
||||
onPressed: () =>
|
||||
Navigator.of(context).pushNamed(DownloadsErrorScreen.routeName),
|
||||
icon: Icon(
|
||||
Icons.error,
|
||||
color: snapshot.data?.isNotEmpty ?? false
|
||||
? Theme.of(context).colorScheme.error
|
||||
: null,
|
||||
),
|
||||
tooltip: AppLocalizations.of(context)!.downloadErrors,
|
||||
);
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
|
||||
import '../../services/downloads_helper.dart';
|
||||
import '../../services/finamp_settings_helper.dart';
|
||||
|
||||
class DownloadMissingImagesButton extends StatefulWidget {
|
||||
const DownloadMissingImagesButton({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<DownloadMissingImagesButton> createState() =>
|
||||
_DownloadMissingImagesButtonState();
|
||||
}
|
||||
|
||||
class _DownloadMissingImagesButtonState
|
||||
extends State<DownloadMissingImagesButton> {
|
||||
final _downloadsHelper = GetIt.instance<DownloadsHelper>();
|
||||
|
||||
// The user shouldn't be able to check for missing downloads while offline
|
||||
bool _enabled = !FinampSettingsHelper.finampSettings.isOffline;
|
||||
|
||||
void downloadImages() async {
|
||||
// We don't want two checks to happen at once, so we disable the
|
||||
// button while a check is running (it shouldn't take long enough
|
||||
// for the user to be able to press twice, but you never know)
|
||||
setState(() {
|
||||
_enabled = false;
|
||||
});
|
||||
|
||||
final imagesDownloaded = await _downloadsHelper.downloadMissingImages();
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
ScaffoldMessenger.of(context).showSnackBar(SnackBar(
|
||||
content: Text(AppLocalizations.of(context)!
|
||||
.downloadedMissingImages(imagesDownloaded)),
|
||||
));
|
||||
|
||||
setState(() {
|
||||
_enabled = true;
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IconButton(
|
||||
onPressed: _enabled ? () => downloadImages() : null,
|
||||
icon: const Icon(Icons.image),
|
||||
tooltip: AppLocalizations.of(context)!.downloadMissingImages,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
import 'package:finamp/services/jellyfin_api_helper.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
|
||||
import '../../models/finamp_models.dart';
|
||||
import '../../services/downloads_helper.dart';
|
||||
import '../../models/jellyfin_models.dart';
|
||||
import '../album_image.dart';
|
||||
import '../confirmation_prompt_dialog.dart';
|
||||
import 'item_media_source_info.dart';
|
||||
import 'album_file_size.dart';
|
||||
|
||||
class DownloadedAlbumsList extends StatefulWidget {
|
||||
const DownloadedAlbumsList({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<DownloadedAlbumsList> createState() => _DownloadedAlbumsListState();
|
||||
}
|
||||
|
||||
class _DownloadedAlbumsListState extends State<DownloadedAlbumsList> {
|
||||
final DownloadsHelper downloadsHelper = GetIt.instance<DownloadsHelper>();
|
||||
|
||||
final JellyfinApiHelper jellyfinApiHelper = JellyfinApiHelper();
|
||||
|
||||
Future<void> deleteAlbum(
|
||||
BuildContext context, DownloadedParent downloadedParent) async {
|
||||
List<String> itemIds = [];
|
||||
for (BaseItemDto item in downloadedParent.downloadedChildren.values) {
|
||||
itemIds.add(item.id);
|
||||
}
|
||||
await downloadsHelper.deleteParentAndChildDownloads(
|
||||
jellyfinItemIds: itemIds, deletedFor: downloadedParent.item.id);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final Iterable<DownloadedParent> downloadedParents =
|
||||
downloadsHelper.downloadedParents;
|
||||
|
||||
return SliverList(
|
||||
delegate: SliverChildBuilderDelegate(
|
||||
(context, index) {
|
||||
DownloadedParent album = downloadedParents.elementAt(index);
|
||||
return ExpansionTile(
|
||||
key: PageStorageKey(album.item.id),
|
||||
leading: AlbumImage(item: album.item),
|
||||
title: Text(album.item.name ?? "Unknown Name"),
|
||||
trailing: IconButton(
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: () => showDialog(
|
||||
context: context,
|
||||
builder: (context) => ConfirmationPromptDialog(
|
||||
promptText: AppLocalizations.of(context)!
|
||||
.deleteDownloadsPrompt(
|
||||
album.item.name ?? "",
|
||||
album.item.type == "Playlist"
|
||||
? "playlist"
|
||||
: "album"),
|
||||
confirmButtonText: AppLocalizations.of(context)!
|
||||
.deleteDownloadsConfirmButtonText,
|
||||
abortButtonText: AppLocalizations.of(context)!
|
||||
.deleteDownloadsAbortButtonText,
|
||||
onConfirmed: () async {
|
||||
await deleteAlbum(context, album);
|
||||
setState(() {});
|
||||
},
|
||||
onAborted: () {},
|
||||
),
|
||||
),
|
||||
),
|
||||
subtitle: AlbumFileSize(
|
||||
downloadedParent: album,
|
||||
),
|
||||
children: [
|
||||
DownloadedSongsInAlbumList(
|
||||
children: album.downloadedChildren.values,
|
||||
parent: album,
|
||||
)
|
||||
],
|
||||
);
|
||||
},
|
||||
childCount: downloadedParents.length,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DownloadedSongsInAlbumList extends StatefulWidget {
|
||||
const DownloadedSongsInAlbumList(
|
||||
{Key? key, required this.children, required this.parent})
|
||||
: super(key: key);
|
||||
|
||||
final Iterable<BaseItemDto> children;
|
||||
final DownloadedParent parent;
|
||||
|
||||
@override
|
||||
State<DownloadedSongsInAlbumList> createState() =>
|
||||
_DownloadedSongsInAlbumListState();
|
||||
}
|
||||
|
||||
class _DownloadedSongsInAlbumListState
|
||||
extends State<DownloadedSongsInAlbumList> {
|
||||
final DownloadsHelper downloadsHelper = GetIt.instance<DownloadsHelper>();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(children: [
|
||||
//TODO use a list builder here
|
||||
for (final song in widget.children)
|
||||
ListTile(
|
||||
title: Text(song.name ?? "Unknown Name"),
|
||||
leading: AlbumImage(item: song),
|
||||
trailing: IconButton(
|
||||
icon: const Icon(Icons.delete),
|
||||
onPressed: () => showDialog(
|
||||
context: context,
|
||||
builder: (context) => ConfirmationPromptDialog(
|
||||
promptText: AppLocalizations.of(context)!
|
||||
.deleteDownloadsPrompt(
|
||||
song.name ?? "",
|
||||
"track"),
|
||||
confirmButtonText: AppLocalizations.of(context)!
|
||||
.deleteDownloadsConfirmButtonText,
|
||||
abortButtonText: AppLocalizations.of(context)!
|
||||
.deleteDownloadsAbortButtonText,
|
||||
onConfirmed: () async {
|
||||
await deleteSong(context, song);
|
||||
setState(() {});
|
||||
},
|
||||
onAborted: () {},
|
||||
),
|
||||
),
|
||||
),
|
||||
subtitle: ItemMediaSourceInfo(
|
||||
songId: song.id,
|
||||
),
|
||||
)
|
||||
]);
|
||||
}
|
||||
|
||||
Future<void> deleteSong(BuildContext context, BaseItemDto itemDto) async {
|
||||
widget.parent.downloadedChildren
|
||||
.removeWhere((key, value) => value == itemDto);
|
||||
await downloadsHelper.deleteSong(jellyfinItemId: itemDto.id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:file_sizes/file_sizes.dart';
|
||||
|
||||
import '../../services/downloads_helper.dart';
|
||||
import '../error_snackbar.dart';
|
||||
|
||||
class DownloadsFileSize extends StatefulWidget {
|
||||
const DownloadsFileSize({Key? key, required this.directory})
|
||||
: super(key: key);
|
||||
|
||||
final Directory directory;
|
||||
|
||||
@override
|
||||
State<DownloadsFileSize> createState() => _DownloadsFileSizeState();
|
||||
}
|
||||
|
||||
class _DownloadsFileSizeState extends State<DownloadsFileSize> {
|
||||
late Future<int> _downloadsFileSizeFuture;
|
||||
final DownloadsHelper _downloadsHelper = GetIt.instance<DownloadsHelper>();
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_downloadsFileSizeFuture = _downloadsHelper.getDirSize(widget.directory);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder(
|
||||
future: _downloadsFileSizeFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
return Text(
|
||||
FileSize.getSize(snapshot.data),
|
||||
style: const TextStyle(color: Colors.grey),
|
||||
);
|
||||
}
|
||||
if (snapshot.hasError) {
|
||||
errorSnackbar(snapshot.error, context);
|
||||
return const Text(
|
||||
"??? MB",
|
||||
style: TextStyle(color: Colors.red),
|
||||
);
|
||||
} else {
|
||||
return const Text(
|
||||
"...",
|
||||
style: TextStyle(color: Colors.grey),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
import 'package:auto_size_text/auto_size_text.dart';
|
||||
import 'package:finamp/services/downloads_helper.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_downloader/flutter_downloader.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
|
||||
import '../../services/download_update_stream.dart';
|
||||
import '../error_snackbar.dart';
|
||||
|
||||
const double downloadsOverviewCardLoadingHeight = 120;
|
||||
|
||||
class DownloadsOverview extends StatefulWidget {
|
||||
const DownloadsOverview({Key? key}) : super(key: key);
|
||||
|
||||
@override
|
||||
State<DownloadsOverview> createState() => _DownloadsOverviewState();
|
||||
}
|
||||
|
||||
class _DownloadsOverviewState extends State<DownloadsOverview> {
|
||||
late Future<List<DownloadTask>?> _downloadsOverviewFuture;
|
||||
final _downloadUpdateStream = GetIt.instance<DownloadUpdateStream>();
|
||||
final _downloadsHelper = GetIt.instance<DownloadsHelper>();
|
||||
|
||||
Map<String, DownloadTaskStatus>? _downloadTaskStatuses;
|
||||
|
||||
final Map<DownloadTaskStatus, int> _downloadCount = {
|
||||
DownloadTaskStatus.undefined: 0,
|
||||
DownloadTaskStatus.enqueued: 0,
|
||||
DownloadTaskStatus.running: 0,
|
||||
DownloadTaskStatus.complete: 0,
|
||||
DownloadTaskStatus.failed: 0,
|
||||
DownloadTaskStatus.canceled: 0,
|
||||
DownloadTaskStatus.paused: 0,
|
||||
};
|
||||
|
||||
bool _initialCountDone = false;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_downloadsOverviewFuture = FlutterDownloader.loadTasks();
|
||||
|
||||
// Like in DownloadedIndicator, we use our own listener instead of a
|
||||
// StreamBuilder to ensure that we capture all events.
|
||||
_downloadUpdateStream.stream.listen((event) {
|
||||
if (_downloadTaskStatuses != null &&
|
||||
_downloadTaskStatuses!.containsKey(event.id) &&
|
||||
_downloadTaskStatuses![event.id] != event.status) {
|
||||
setState(() {
|
||||
_downloadCount[_downloadTaskStatuses![event.id]!] =
|
||||
_downloadCount[_downloadTaskStatuses![event.id]]! - 1;
|
||||
_downloadCount[event.status] = _downloadCount[event.status]! + 1;
|
||||
_downloadTaskStatuses![event.id] = event.status;
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return FutureBuilder<List<DownloadTask>?>(
|
||||
future: _downloadsOverviewFuture,
|
||||
builder: (context, snapshot) {
|
||||
if (snapshot.hasData) {
|
||||
_downloadTaskStatuses ??= Map.fromEntries(
|
||||
snapshot.data!.map((e) => MapEntry(e.taskId, e.status)));
|
||||
|
||||
if (!_initialCountDone) {
|
||||
// Switch cases don't work for some reason
|
||||
for (var element in snapshot.data!) {
|
||||
if (element.status == DownloadTaskStatus.undefined) {
|
||||
_downloadCount[DownloadTaskStatus.undefined] =
|
||||
_downloadCount[DownloadTaskStatus.undefined]! + 1;
|
||||
} else if (element.status == DownloadTaskStatus.enqueued) {
|
||||
_downloadCount[DownloadTaskStatus.enqueued] =
|
||||
_downloadCount[DownloadTaskStatus.enqueued]! + 1;
|
||||
} else if (element.status == DownloadTaskStatus.running) {
|
||||
_downloadCount[DownloadTaskStatus.running] =
|
||||
_downloadCount[DownloadTaskStatus.running]! + 1;
|
||||
} else if (element.status == DownloadTaskStatus.complete) {
|
||||
_downloadCount[DownloadTaskStatus.complete] =
|
||||
_downloadCount[DownloadTaskStatus.complete]! + 1;
|
||||
} else if (element.status == DownloadTaskStatus.failed) {
|
||||
_downloadCount[DownloadTaskStatus.failed] =
|
||||
_downloadCount[DownloadTaskStatus.failed]! + 1;
|
||||
} else if (element.status == DownloadTaskStatus.canceled) {
|
||||
_downloadCount[DownloadTaskStatus.canceled] =
|
||||
_downloadCount[DownloadTaskStatus.canceled]! + 1;
|
||||
} else if (element.status == DownloadTaskStatus.paused) {
|
||||
_downloadCount[DownloadTaskStatus.paused] =
|
||||
_downloadCount[DownloadTaskStatus.paused]! + 1;
|
||||
}
|
||||
}
|
||||
_initialCountDone = true;
|
||||
}
|
||||
|
||||
// We have to awkwardly get two strings like this because Flutter's
|
||||
// internationalisation stuff doesn't support multiple plurals.
|
||||
// https://github.com/flutter/flutter/issues/86906
|
||||
final downloadedItemsString = AppLocalizations.of(context)!
|
||||
.downloadedItemsCount(_downloadsHelper.downloadedItems.length);
|
||||
final downloadedImagesString = AppLocalizations.of(context)!
|
||||
.downloadedImagesCount(_downloadsHelper.downloadedImages.length);
|
||||
|
||||
return Card(
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Center(
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
AutoSizeText(
|
||||
AppLocalizations.of(context)!
|
||||
.downloadCount(snapshot.data!.length),
|
||||
style: const TextStyle(fontSize: 28),
|
||||
maxLines: 1,
|
||||
),
|
||||
Text(
|
||||
AppLocalizations.of(context)!
|
||||
.downloadedItemsImagesCount(
|
||||
downloadedItemsString,
|
||||
downloadedImagesString),
|
||||
style: const TextStyle(color: Colors.grey),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
Expanded(
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.end,
|
||||
children: [
|
||||
Text(
|
||||
AppLocalizations.of(context)!.dlComplete(
|
||||
_downloadCount[DownloadTaskStatus.complete]!),
|
||||
style: const TextStyle(color: Colors.green),
|
||||
),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.dlFailed(
|
||||
_downloadCount[DownloadTaskStatus.failed]!),
|
||||
style: const TextStyle(color: Colors.red),
|
||||
),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.dlEnqueued(
|
||||
_downloadCount[DownloadTaskStatus.enqueued]!),
|
||||
style: const TextStyle(color: Colors.grey),
|
||||
),
|
||||
Text(
|
||||
AppLocalizations.of(context)!.dlRunning(
|
||||
_downloadCount[DownloadTaskStatus.running]!),
|
||||
style: const TextStyle(color: Colors.grey),
|
||||
),
|
||||
],
|
||||
),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
} else if (snapshot.hasError) {
|
||||
errorSnackbar(snapshot.error, context);
|
||||
return const SizedBox(
|
||||
height: downloadsOverviewCardLoadingHeight,
|
||||
child: Card(
|
||||
child: Icon(Icons.error),
|
||||
),
|
||||
);
|
||||
} else {
|
||||
return const SizedBox(
|
||||
height: downloadsOverviewCardLoadingHeight,
|
||||
child: Card(
|
||||
child: Center(child: CircularProgressIndicator.adaptive()),
|
||||
),
|
||||
);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:file_sizes/file_sizes.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
|
||||
import '../../services/downloads_helper.dart';
|
||||
import '../../models/jellyfin_models.dart';
|
||||
|
||||
class ItemMediaSourceInfo extends StatelessWidget {
|
||||
const ItemMediaSourceInfo({Key? key, required this.songId}) : super(key: key);
|
||||
|
||||
final String songId;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
DownloadsHelper downloadsHelper = GetIt.instance<DownloadsHelper>();
|
||||
MediaSourceInfo? mediaSourceInfo =
|
||||
downloadsHelper.getDownloadedSong(songId)?.mediaSourceInfo;
|
||||
|
||||
if (mediaSourceInfo == null) {
|
||||
return const Text("??? MB Unknown");
|
||||
} else {
|
||||
return Text(
|
||||
"${FileSize.getSize(mediaSourceInfo.size)} ${mediaSourceInfo.container?.toUpperCase()}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import 'package:finamp/models/finamp_models.dart';
|
||||
import 'package:finamp/models/jellyfin_models.dart';
|
||||
import 'package:finamp/services/downloads_helper.dart';
|
||||
import 'package:finamp/services/jellyfin_api_helper.dart';
|
||||
import 'package:finamp/services/sync_helper.dart';
|
||||
import 'package:flutter_downloader/flutter_downloader.dart';
|
||||
import 'package:flutter_gen/gen_l10n/app_localizations.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:logging/logging.dart';
|
||||
|
||||
class SyncDownloadedAlbumsOrPlaylistsButton extends StatelessWidget {
|
||||
SyncDownloadedAlbumsOrPlaylistsButton({super.key});
|
||||
|
||||
final _syncLogger = Logger("SyncDownloadedPlaylistsButton");
|
||||
final DownloadsHelper downloadsHelper = GetIt.instance<DownloadsHelper>();
|
||||
final _jellyfinApiData = GetIt.instance<JellyfinApiHelper>();
|
||||
|
||||
void syncPlaylists(BuildContext context) async {
|
||||
_syncLogger.info("Syncing downloaded playlists");
|
||||
|
||||
var syncHelper = DownloadsSyncHelper(_syncLogger);
|
||||
List<DownloadedParent> parents = downloadsHelper.downloadedParents.toList();
|
||||
|
||||
for (DownloadedParent parent in parents) {
|
||||
List<BaseItemDto>? items = await _jellyfinApiData.getItems(
|
||||
isGenres: false, parentItem: parent.item);
|
||||
|
||||
if (items == null) {
|
||||
_syncLogger.warning("Could not find any items for album or playlist id ${parent.item.id}");
|
||||
continue;
|
||||
}
|
||||
syncHelper.sync(context, parent.item, items);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return IconButton(
|
||||
onPressed: () => syncPlaylists(context),
|
||||
icon: const Icon(Icons.sync),
|
||||
tooltip: AppLocalizations.of(context)!.syncDownloadedPlaylists,
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user