feat: ui improvement

This commit is contained in:
Simon Ding
2024-08-08 10:56:03 +08:00
parent 64e98647a8
commit b34e39889c
6 changed files with 190 additions and 99 deletions

View File

@@ -161,15 +161,15 @@ class _SubmitSearchResultState extends ConsumerState<SubmitSearchResult> {
Navigator.of(context).pop(); Navigator.of(context).pop();
}, },
), ),
TextButton( LoadingTextButton(
style: TextButton.styleFrom( // style: TextButton.styleFrom(
textStyle: Theme.of(context).textTheme.labelLarge, // textStyle: Theme.of(context).textTheme.labelLarge,
), // ),
child: const Text('确定'), label: const Text('确定'),
onPressed: () async { onPressed: () async {
if (_formKey.currentState!.saveAndValidate()) { if (_formKey.currentState!.saveAndValidate()) {
final values = _formKey.currentState!.value; final values = _formKey.currentState!.value;
var f = ref await ref
.read(searchPageDataProvider(widget.query).notifier) .read(searchPageDataProvider(widget.query).notifier)
.submit2Watchlist( .submit2Watchlist(
widget.item.id!, widget.item.id!,
@@ -185,7 +185,6 @@ class _SubmitSearchResultState extends ConsumerState<SubmitSearchResult> {
Navigator.of(context).pop(); Navigator.of(context).pop();
showSnakeBar("添加成功:${widget.item.name}"); showSnakeBar("添加成功:${widget.item.name}");
}); });
showLoadingWithFuture(f);
} }
}, },
), ),

View File

@@ -22,12 +22,11 @@ Future<void> showSettingDialog(
), ),
actions: <Widget>[ actions: <Widget>[
showDelete showDelete
? TextButton( ? LoadingTextButton(
onPressed: () { onPressed: () async {
final f = onDelete().then((v) => Navigator.of(context).pop()); await onDelete().then((v) => Navigator.of(context).pop());
showLoadingWithFuture(f);
}, },
child: const Text( label: const Text(
'删除', '删除',
style: TextStyle(color: Colors.red), style: TextStyle(color: Colors.red),
)) ))
@@ -35,11 +34,10 @@ Future<void> showSettingDialog(
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
child: const Text('取消')), child: const Text('取消')),
TextButton( LoadingTextButton(
child: const Text('确定'), label: const Text('确定'),
onPressed: () { onPressed: () async {
final f = onSubmit().then((v) => Navigator.of(context).pop()); await onSubmit().then((v) => Navigator.of(context).pop());
showLoadingWithFuture(f);
}, },
), ),
], ],

View File

@@ -48,62 +48,55 @@ class _TvDetailsPageState extends ConsumerState<TvDetailsPage> {
Opacity( Opacity(
opacity: 0.7, opacity: 0.7,
child: ep.status == "downloading" child: ep.status == "downloading"
? const Tooltip( ? const IconButton(
message: "下载中", tooltip: "下载中",
child: IconButton(onPressed: null, icon: Icon(Icons.downloading)), onPressed: null,
) icon: Icon(Icons.downloading))
: (ep.status == "downloaded" : (ep.status == "downloaded"
? const Tooltip( ? const IconButton(
message: "已下载", tooltip: "已下载",
child: IconButton(onPressed: null, icon: Icon(Icons.download_done)), onPressed: null,
) icon: Icon(Icons.download_done))
: (ep.monitored == true : (ep.monitored == true
? Tooltip( ? IconButton(
message: "监控中", tooltip: "监控中",
onPressed: () {
ref
.read(mediaDetailsProvider(
widget.seriesId)
.notifier)
.changeMonitoringStatus(
ep.id!, false);
},
icon: const Icon(Icons.alarm))
: Opacity(
opacity: 0.7,
child: IconButton( child: IconButton(
tooltip: "未监控",
onPressed: () { onPressed: () {
ref ref
.read(mediaDetailsProvider( .read(mediaDetailsProvider(
widget.seriesId) widget.seriesId)
.notifier) .notifier)
.changeMonitoringStatus( .changeMonitoringStatus(
ep.id!, false); ep.id!, true);
}, },
icon: const Icon(Icons.alarm)), icon: const Icon(Icons.alarm_off)),
)
: Opacity(
opacity: 0.7,
child: Tooltip(
message: "未监控",
child: IconButton(
onPressed: () {
ref
.read(mediaDetailsProvider(
widget.seriesId)
.notifier)
.changeMonitoringStatus(
ep.id!, true);
},
icon: const Icon(Icons.alarm_off)),
),
)))), )))),
), ),
DataCell(Row( DataCell(Row(
children: [ children: [
Tooltip( LoadingIconButton(
message: "搜索下载对应剧集", tooltip: "搜索下载对应剧集",
child: IconButton( onPressed: () async {
onPressed: () { await ref
var f = ref .read(
.read(mediaDetailsProvider(widget.seriesId) mediaDetailsProvider(widget.seriesId).notifier)
.notifier) .searchAndDownload(widget.seriesId,
.searchAndDownload(widget.seriesId, ep.seasonNumber!, ep.episodeNumber!)
ep.seasonNumber!, ep.episodeNumber!) .then((v) => showSnakeBar("开始下载: $v"));
.then((v) => showSnakeBar("开始下载: $v")); },
showLoadingWithFuture(f); icon: Icons.download),
},
icon: const Icon(Icons.download)),
),
const SizedBox( const SizedBox(
width: 10, width: 10,
), ),
@@ -142,19 +135,17 @@ class _TvDetailsPageState extends ConsumerState<TvDetailsPage> {
DataColumn( DataColumn(
label: Row( label: Row(
children: [ children: [
Tooltip( LoadingIconButton(
message: "搜索下载全部剧集", tooltip: "搜索下载全部剧集",
child: IconButton( onPressed: () async {
onPressed: () { await ref
final f = ref .read(mediaDetailsProvider(widget.seriesId)
.read(mediaDetailsProvider(widget.seriesId) .notifier)
.notifier) .searchAndDownload(widget.seriesId, k, 0)
.searchAndDownload(widget.seriesId, k, 0) .then((v) => showSnakeBar("开始下载: $v"));
.then((v) => showSnakeBar("开始下载: $v")); //showLoadingWithFuture(f);
showLoadingWithFuture(f); },
}, icon: Icons.download),
icon: const Icon(Icons.download)),
),
const SizedBox( const SizedBox(
width: 10, width: 10,
), ),

View File

@@ -66,8 +66,7 @@ class _DetailCardState extends ConsumerState<DetailCard> {
const SizedBox( const SizedBox(
width: 30, width: 30,
), ),
Text( Text("${widget.details.storage!.name}:"),
"${widget.details.storage!.name}:"),
Text( Text(
"${widget.details.mediaType == "tv" ? widget.details.storage!.tvPath : widget.details.storage!.moviePath}" "${widget.details.mediaType == "tv" ? widget.details.storage!.tvPath : widget.details.storage!.moviePath}"
"${widget.details.targetDir}"), "${widget.details.targetDir}"),
@@ -90,13 +89,15 @@ class _DetailCardState extends ConsumerState<DetailCard> {
const Text(""), const Text(""),
Expanded( Expanded(
child: Text( child: Text(
overflow: TextOverflow.ellipsis, overflow: TextOverflow.visible,
maxLines: 9, maxLines: 9,
widget.details.overview ?? "", widget.details.overview ?? "",
)), )),
Row( Row(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [ children: [
editIcon(widget.details), downloadButton(),
editIcon(),
deleteIcon(), deleteIcon(),
], ],
) )
@@ -113,11 +114,10 @@ class _DetailCardState extends ConsumerState<DetailCard> {
} }
Widget deleteIcon() { Widget deleteIcon() {
return Tooltip( return IconButton(
message: widget.details.mediaType == "tv" ? "删除剧集" : "删除电影", tooltip: widget.details.mediaType == "tv" ? "删除剧集" : "删除电影",
child: IconButton( onPressed: () => showConfirmDialog(),
onPressed: () => showConfirmDialog(), icon: const Icon(Icons.delete)), icon: const Icon(Icons.delete));
);
} }
Future<void> showConfirmDialog() { Future<void> showConfirmDialog() {
@@ -150,19 +150,21 @@ class _DetailCardState extends ConsumerState<DetailCard> {
); );
} }
Widget editIcon(SeriesDetails details) { Widget editIcon() {
return IconButton( return IconButton(
onPressed: () => showEditDialog(details), icon: const Icon(Icons.edit)); tooltip: "编辑",
onPressed: () => showEditDialog(),
icon: const Icon(Icons.edit));
} }
showEditDialog(SeriesDetails details) { showEditDialog() {
final _formKey = GlobalKey<FormBuilderState>(); final _formKey = GlobalKey<FormBuilderState>();
return showDialog<void>( return showDialog<void>(
context: context, context: context,
barrierDismissible: true, barrierDismissible: true,
builder: (BuildContext context) { builder: (BuildContext context) {
return AlertDialog( return AlertDialog(
title: Text("编辑 ${details.name}"), title: Text("编辑 ${widget.details.name}"),
content: SelectionArea( content: SelectionArea(
child: SizedBox( child: SizedBox(
width: MediaQuery.of(context).size.width * 0.3, width: MediaQuery.of(context).size.width * 0.3,
@@ -171,11 +173,16 @@ class _DetailCardState extends ConsumerState<DetailCard> {
child: FormBuilder( child: FormBuilder(
key: _formKey, key: _formKey,
initialValue: { initialValue: {
"resolution": details.resolution, "resolution": widget.details.resolution,
"target_dir": details.targetDir, "target_dir": widget.details.targetDir,
"limiter": details.limiter != null "limiter": widget.details.limiter != null
? RangeValues(details.limiter!.sizeMin.toDouble()/1000/1000, ? RangeValues(
details.limiter!.sizeMax.toDouble()/1000/1000) widget.details.limiter!.sizeMin.toDouble() /
1000 /
1000,
widget.details.limiter!.sizeMax.toDouble() /
1000 /
1000)
: const RangeValues(0, 0) : const RangeValues(0, 0)
}, },
child: Column( child: Column(
@@ -204,23 +211,29 @@ class _DetailCardState extends ConsumerState<DetailCard> {
TextButton( TextButton(
onPressed: () => Navigator.of(context).pop(), onPressed: () => Navigator.of(context).pop(),
child: const Text("取消")), child: const Text("取消")),
TextButton( LoadingTextButton(
onPressed: () { onPressed: () async {
if (_formKey.currentState!.saveAndValidate()) { if (_formKey.currentState!.saveAndValidate()) {
final values = _formKey.currentState!.value; final values = _formKey.currentState!.value;
var f = ref await ref
.read(mediaDetailsProvider(widget.details.id.toString()) .read(mediaDetailsProvider(widget.details.id.toString())
.notifier) .notifier)
.edit(values["resolution"], values["target_dir"], .edit(values["resolution"], values["target_dir"],
values["limiter"]) values["limiter"])
.then((v) => Navigator.of(context).pop()); .then((v) => Navigator.of(context).pop());
showLoadingWithFuture(f);
} }
}, },
child: const Text("确认")) label: const Text("确认"))
], ],
); );
}, },
); );
} }
Widget downloadButton() {
return IconButton(
tooltip: widget.details.mediaType == "tv" ? "查找并下载所有监控剧集" : "查找并下载此电影",
onPressed: () {},
icon: const Icon(Icons.download_rounded));
}
} }

View File

@@ -59,10 +59,10 @@ class ResourceList extends ConsumerWidget {
: "-"))); : "-")));
} }
rows.add(DataCell(IconButton( rows.add(DataCell(LoadingIconButton(
icon: const Icon(Icons.download), icon: Icons.download,
onPressed: () async { onPressed: () async {
var f = ref await ref
.read(mediaTorrentsDataProvider(( .read(mediaTorrentsDataProvider((
mediaId: mediaId, mediaId: mediaId,
seasonNumber: seasonNum, seasonNumber: seasonNum,
@@ -70,7 +70,6 @@ class ResourceList extends ConsumerWidget {
)).notifier) )).notifier)
.download(torrent) .download(torrent)
.then((v) => showSnakeBar("开始下载:${torrent.name}")); .then((v) => showSnakeBar("开始下载:${torrent.name}"));
showLoadingWithFuture(f);
}, },
))); )));
return DataRow(cells: rows); return DataRow(cells: rows);

View File

@@ -1,6 +1,7 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_form_builder/flutter_form_builder.dart'; import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:ui/providers/APIs.dart'; import 'package:ui/providers/APIs.dart';
import 'package:ui/widgets/utils.dart';
class Commons { class Commons {
static InputDecoration requiredTextFieldStyle({ static InputDecoration requiredTextFieldStyle({
@@ -139,3 +140,93 @@ class _MySliderState extends State<MyRangeSlider> {
return "$v MB"; return "$v MB";
} }
} }
class LoadingIconButton extends StatefulWidget {
LoadingIconButton({required this.onPressed, required this.icon, this.tooltip});
final Future<void> Function() onPressed;
final IconData icon;
final String? tooltip;
@override
State<StatefulWidget> createState() {
return _LoadingIconButtonState();
}
}
class _LoadingIconButtonState extends State<LoadingIconButton> {
bool loading = false;
@override
Widget build(BuildContext context) {
return IconButton(
tooltip: widget.tooltip,
onPressed: loading
? null
: () async {
setState(() => loading = true);
try {
await widget.onPressed();
} catch (e) {
showSnakeBar("操作失败:$e");
} finally {
setState(() => loading = false);
}
},
icon: loading
? Container(
width: 24,
height: 24,
padding: const EdgeInsets.all(2.0),
child: const CircularProgressIndicator(
color: Colors.grey,
strokeWidth: 3,
),
)
: Icon(widget.icon));
}
}
class LoadingTextButton extends StatefulWidget {
LoadingTextButton({required this.onPressed, required this.label});
final Future<void> Function() onPressed;
final Widget label;
@override
State<StatefulWidget> createState() {
return _LoadingTextButtonState();
}
}
class _LoadingTextButtonState extends State<LoadingTextButton> {
bool loading = false;
@override
Widget build(BuildContext context) {
return TextButton.icon(
onPressed: loading
? null
: () async {
setState(() => loading = true);
try {
await widget.onPressed();
} catch (e) {
showSnakeBar("操作失败:$e");
} finally {
setState(() => loading = false);
}
},
icon: loading
? Container(
width: 24,
height: 24,
padding: const EdgeInsets.all(2.0),
child: const CircularProgressIndicator(
color: Colors.grey,
strokeWidth: 3,
),
)
: Text(""),
label: widget.label,
);
}
}