freezer/lib/ui/downloads_screen.dart

379 lines
11 KiB
Dart
Raw Normal View History

2020-10-10 20:51:20 +00:00
import 'dart:io';
2020-06-23 19:23:12 +00:00
import 'package:filesize/filesize.dart';
import 'package:flutter/material.dart';
import 'package:freezer/api/download.dart';
import 'package:freezer/translations.i18n.dart';
import 'package:freezer/ui/elements.dart';
2020-10-10 20:51:20 +00:00
import 'package:path/path.dart' as p;
import 'package:path_provider/path_provider.dart';
2020-06-23 19:23:12 +00:00
import 'cached_image.dart';
2020-10-10 20:51:20 +00:00
import 'dart:async';
class DownloadsScreen extends StatefulWidget {
@override
_DownloadsScreenState createState() => _DownloadsScreenState();
}
2020-06-23 19:23:12 +00:00
class _DownloadsScreenState extends State<DownloadsScreen> {
List<Download> downloads = [];
2021-11-01 16:41:25 +00:00
late StreamSubscription _stateSubscription;
2020-06-23 19:23:12 +00:00
//Sublists
2021-09-01 12:38:32 +00:00
List<Download> get downloading => downloads
.where((d) =>
d.state == DownloadState.DOWNLOADING || d.state == DownloadState.POST)
.toList();
List<Download> get queued =>
downloads.where((d) => d.state == DownloadState.NONE).toList();
List<Download> get failed => downloads
.where((d) =>
d.state == DownloadState.ERROR ||
d.state == DownloadState.DEEZER_ERROR)
.toList();
List<Download> get finished =>
downloads.where((d) => d.state == DownloadState.DONE).toList();
2020-06-23 19:23:12 +00:00
Future _load() async {
//Load downloads
List<Download> _d = await downloadManager.getDownloads();
setState(() {
downloads = _d;
});
2020-06-23 19:23:12 +00:00
}
@override
void initState() {
_load();
//Subscribe to state update
_stateSubscription = downloadManager.serviceEvents.stream.listen((e) {
//State change = update
if (e['action'] == 'onStateChange') {
setState(() => downloadManager.running = downloadManager.running);
}
//Progress change
if (e['action'] == 'onProgress') {
setState(() {
for (Map su in e['data']) {
2021-09-01 12:38:32 +00:00
downloads
.firstWhere((d) => d.id == su['id'], orElse: () => Download())
.updateFromJson(su);
}
});
}
});
super.initState();
2020-06-23 19:23:12 +00:00
}
2020-09-01 14:41:15 +00:00
@override
void dispose() {
2021-11-01 16:41:25 +00:00
_stateSubscription.cancel();
super.dispose();
}
2020-09-01 14:41:15 +00:00
2020-06-23 19:23:12 +00:00
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: FreezerAppBar(
2021-09-01 12:38:32 +00:00
'Downloads'.i18n,
2020-09-01 14:41:15 +00:00
actions: [
IconButton(
2021-09-01 12:38:32 +00:00
icon: Icon(
Icons.delete_sweep,
semanticLabel: "Clear all".i18n,
),
onPressed: () async {
await downloadManager.removeDownloads(DownloadState.ERROR);
2021-09-01 12:38:32 +00:00
await downloadManager
.removeDownloads(DownloadState.DEEZER_ERROR);
await downloadManager.removeDownloads(DownloadState.DONE);
await _load();
},
),
2020-09-01 14:41:15 +00:00
IconButton(
2021-09-01 12:38:32 +00:00
icon: Icon(
2021-11-01 16:41:25 +00:00
downloadManager.running ? Icons.stop : Icons.play_arrow,
2021-09-01 12:38:32 +00:00
semanticLabel:
2021-11-01 16:41:25 +00:00
downloadManager.running ? "Stop".i18n : "Start".i18n,
2021-09-01 12:38:32 +00:00
),
2020-09-01 14:41:15 +00:00
onPressed: () {
setState(() {
2021-11-01 16:41:25 +00:00
if (downloadManager.running)
downloadManager.stop();
else
downloadManager.start();
});
2020-09-01 14:41:15 +00:00
},
)
],
),
body: ListView(
children: [
//Now downloading
Container(height: 2.0),
2021-09-01 12:38:32 +00:00
Column(
children: List.generate(
downloading.length,
(int i) => DownloadTile(
downloading[i],
updateCallback: () => _load(),
))),
Container(height: 8.0),
//Queued
if (queued.length > 0)
Text(
'Queued'.i18n,
textAlign: TextAlign.center,
2021-09-01 12:38:32 +00:00
style: TextStyle(fontSize: 24.0, fontWeight: FontWeight.bold),
),
2021-09-01 12:38:32 +00:00
Column(
children: List.generate(
queued.length,
(int i) => DownloadTile(
queued[i],
updateCallback: () => _load(),
))),
if (queued.length > 0)
ListTile(
title: Text('Clear queue'.i18n),
leading: Icon(Icons.delete),
onTap: () async {
await downloadManager.removeDownloads(DownloadState.NONE);
await _load();
},
),
//Failed
if (failed.length > 0)
Text(
'Failed'.i18n,
textAlign: TextAlign.center,
2021-09-01 12:38:32 +00:00
style: TextStyle(fontSize: 24.0, fontWeight: FontWeight.bold),
),
2021-09-01 12:38:32 +00:00
Column(
children: List.generate(
failed.length,
(int i) => DownloadTile(
failed[i],
updateCallback: () => _load(),
))),
//Restart failed
if (failed.length > 0)
ListTile(
title: Text('Restart failed downloads'.i18n),
leading: Icon(Icons.restore),
onTap: () async {
await downloadManager.retryDownloads();
await _load();
},
),
if (failed.length > 0)
ListTile(
title: Text('Clear failed'.i18n),
leading: Icon(Icons.delete),
onTap: () async {
await downloadManager.removeDownloads(DownloadState.ERROR);
2021-09-01 12:38:32 +00:00
await downloadManager
.removeDownloads(DownloadState.DEEZER_ERROR);
await _load();
},
),
//Finished
if (finished.length > 0)
Text(
'Done'.i18n,
textAlign: TextAlign.center,
2021-09-01 12:38:32 +00:00
style: TextStyle(fontSize: 24.0, fontWeight: FontWeight.bold),
),
2021-09-01 12:38:32 +00:00
Column(
children: List.generate(
finished.length,
(int i) => DownloadTile(
finished[i],
updateCallback: () => _load(),
))),
if (finished.length > 0)
ListTile(
title: Text('Clear downloads history'.i18n),
leading: Icon(Icons.delete),
onTap: () async {
await downloadManager.removeDownloads(DownloadState.DONE);
await _load();
},
),
2020-09-01 14:41:15 +00:00
],
2021-09-01 12:38:32 +00:00
));
2020-06-23 19:23:12 +00:00
}
2020-09-01 14:41:15 +00:00
}
class DownloadTile extends StatelessWidget {
final Download download;
2021-09-01 12:38:32 +00:00
final Function? updateCallback;
DownloadTile(this.download, {this.updateCallback});
String subtitle() {
String out = '';
2020-10-10 20:51:20 +00:00
2021-09-01 12:38:32 +00:00
if (download.state != DownloadState.DOWNLOADING &&
download.state != DownloadState.POST) {
2020-10-10 20:51:20 +00:00
//Download type
2021-09-01 12:38:32 +00:00
if (download.private!)
out += 'Offline'.i18n;
else
out += 'External'.i18n;
2020-10-10 20:51:20 +00:00
out += ' | ';
}
if (download.state == DownloadState.POST) {
return 'Post processing...'.i18n;
}
//Quality
if (download.quality == 9) out += 'FLAC';
if (download.quality == 3) out += 'MP3 320kbps';
if (download.quality == 1) out += 'MP3 128kbps';
2020-10-10 20:51:20 +00:00
//Downloading show progress
if (download.state == DownloadState.DOWNLOADING) {
2021-09-01 12:38:32 +00:00
out +=
' | ${filesize(download.received, 2)} / ${filesize(download.filesize, 2)}';
double progress =
download.received!.toDouble() / download.filesize!.toDouble();
out += ' ${(progress * 100.0).toStringAsFixed(2)}%';
2020-10-10 20:51:20 +00:00
}
return out;
}
Future onClick(BuildContext context) async {
2021-09-01 12:38:32 +00:00
if (download.state != DownloadState.DOWNLOADING &&
download.state != DownloadState.POST) {
showDialog(
2021-09-01 12:38:32 +00:00
context: context,
builder: (context) {
return AlertDialog(
title: Text('Delete'.i18n),
content:
Text('Are you sure you want to delete this download?'.i18n),
actions: [
TextButton(
child: Text('Cancel'.i18n),
onPressed: () => Navigator.of(context).pop(),
),
TextButton(
child: Text('Delete'.i18n),
onPressed: () async {
await downloadManager.removeDownload(download.id);
if (updateCallback != null) updateCallback!();
Navigator.of(context).pop();
},
)
],
);
});
}
}
2020-09-01 14:41:15 +00:00
//Trailing icon with state
Widget trailing() {
switch (download.state) {
case DownloadState.NONE:
return Icon(
Icons.query_builder,
);
case DownloadState.DOWNLOADING:
2021-09-01 12:38:32 +00:00
return Icon(Icons.download_rounded);
case DownloadState.POST:
2021-09-01 12:38:32 +00:00
return Icon(Icons.miscellaneous_services);
case DownloadState.DONE:
return Icon(
Icons.done,
color: Colors.green,
);
case DownloadState.DEEZER_ERROR:
2021-09-01 12:38:32 +00:00
return Icon(Icons.error, color: Colors.blue);
case DownloadState.ERROR:
2021-09-01 12:38:32 +00:00
return Icon(Icons.error, color: Colors.red);
default:
return Container();
}
}
@override
Widget build(BuildContext context) {
return Column(
children: [
ListTile(
2021-09-01 12:38:32 +00:00
title: Text(download.title!),
leading: CachedImage(url: download.image),
2021-09-01 12:38:32 +00:00
subtitle:
Text(subtitle(), maxLines: 1, overflow: TextOverflow.ellipsis),
trailing: trailing(),
onTap: () => onClick(context),
),
if (download.state == DownloadState.DOWNLOADING)
LinearProgressIndicator(value: download.progress),
2021-09-01 12:38:32 +00:00
if (download.state == DownloadState.POST) LinearProgressIndicator(),
],
);
}
2020-10-10 20:51:20 +00:00
}
class DownloadLogViewer extends StatefulWidget {
@override
_DownloadLogViewerState createState() => _DownloadLogViewerState();
}
class _DownloadLogViewerState extends State<DownloadLogViewer> {
List<String> data = [];
//Load log from file
Future _load() async {
2021-09-01 12:38:32 +00:00
String path =
p.join((await getExternalStorageDirectory())!.path, 'download.log');
2020-10-10 20:51:20 +00:00
File file = File(path);
if (await file.exists()) {
String _d = await file.readAsString();
setState(() {
data = _d.replaceAll("\r", "").split("\n");
});
}
}
//Get color by log type
2021-09-01 12:38:32 +00:00
Color? color(String line) {
2020-10-10 20:51:20 +00:00
if (line.startsWith('E:')) return Colors.red;
if (line.startsWith('W:')) return Colors.orange[600];
return null;
}
@override
void initState() {
_load();
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
2021-09-01 12:38:32 +00:00
appBar: FreezerAppBar('Download Log'.i18n),
body: ListView.builder(
itemCount: data.length,
itemBuilder: (context, i) {
return Padding(
padding: EdgeInsets.all(8.0),
child: Text(
data[i],
style: TextStyle(fontSize: 14.0, color: color(data[i])),
2020-10-10 20:51:20 +00:00
),
2021-09-01 12:38:32 +00:00
);
},
));
2020-10-10 20:51:20 +00:00
}
}