feat: display loading animation

This commit is contained in:
Simon Ding
2024-07-30 14:02:24 +08:00
parent e2bba8ec71
commit 233970ef39
11 changed files with 121 additions and 124 deletions

View File

@@ -69,7 +69,7 @@ func (s *Server) SuggestedSeriesFolderName(c *gin.Context) (interface{}, error)
year := strings.Split(d.FirstAirDate, "-")[0] year := strings.Split(d.FirstAirDate, "-")[0]
if utils.ContainsChineseChar(originalName) { if utils.ContainsChineseChar(originalName) || name == originalName {
name = originalName name = originalName
} else { } else {
name = fmt.Sprintf("%s %s", name, originalName) name = fmt.Sprintf("%s %s", name, originalName)
@@ -95,9 +95,7 @@ func (s *Server) SuggestedMovieFolderName(c *gin.Context) (interface{}, error) {
originalName := d1.OriginalTitle originalName := d1.OriginalTitle
year := strings.Split(d1.ReleaseDate, "-")[0] year := strings.Split(d1.ReleaseDate, "-")[0]
name = fmt.Sprintf("%s %s", name, originalName) if utils.ContainsChineseChar(originalName) || name == originalName {
if utils.ContainsChineseChar(originalName) {
name = originalName name = originalName
} else { } else {
name = fmt.Sprintf("%s %s", name, originalName) name = fmt.Sprintf("%s %s", name, originalName)

View File

@@ -2,8 +2,8 @@ import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:percent_indicator/circular_percent_indicator.dart'; import 'package:percent_indicator/circular_percent_indicator.dart';
import 'package:ui/providers/activity.dart'; import 'package:ui/providers/activity.dart';
import 'package:ui/widgets/utils.dart';
import 'package:ui/widgets/progress_indicator.dart'; import 'package:ui/widgets/progress_indicator.dart';
import 'package:ui/widgets/widgets.dart';
class ActivityPage extends ConsumerStatefulWidget { class ActivityPage extends ConsumerStatefulWidget {
const ActivityPage({super.key}); const ActivityPage({super.key});
@@ -68,7 +68,7 @@ class _ActivityPageState extends ConsumerState<ActivityPage>
DataColumn(label: Text("名称")), DataColumn(label: Text("名称")),
DataColumn(label: Text("开始时间")), DataColumn(label: Text("开始时间")),
DataColumn(label: Text("状态")), DataColumn(label: Text("状态")),
DataColumn(label: Text("操作")) DataColumn(label: Text("操作"))
], ],
source: ActivityDataSource( source: ActivityDataSource(
activities: activities, activities: activities,
@@ -85,11 +85,10 @@ class _ActivityPageState extends ConsumerState<ActivityPage>
Function(int) onDelete() { Function(int) onDelete() {
return (id) { return (id) {
ref final f = ref
.read(activitiesDataProvider("active").notifier) .read(activitiesDataProvider("active").notifier)
.deleteActivity(id) .deleteActivity(id);
.then((v) => Utils.showSnakeBar("删除成功")) showLoadingWithFuture(f);
.onError((error, trace) => Utils.showSnakeBar("删除失败:$error"));
}; };
} }
} }

View File

@@ -5,9 +5,10 @@ import 'package:ui/providers/APIs.dart';
import 'package:ui/providers/activity.dart'; import 'package:ui/providers/activity.dart';
import 'package:ui/providers/series_details.dart'; import 'package:ui/providers/series_details.dart';
import 'package:ui/providers/settings.dart'; import 'package:ui/providers/settings.dart';
import 'package:ui/widgets/utils.dart';
import 'package:ui/welcome_page.dart'; import 'package:ui/welcome_page.dart';
import 'package:ui/widgets/utils.dart';
import 'package:ui/widgets/progress_indicator.dart'; import 'package:ui/widgets/progress_indicator.dart';
import 'package:ui/widgets/widgets.dart';
class MovieDetailsPage extends ConsumerStatefulWidget { class MovieDetailsPage extends ConsumerStatefulWidget {
static const route = "/movie/:id"; static const route = "/movie/:id";
@@ -44,7 +45,9 @@ class _MovieDetailsPageState extends ConsumerState<MovieDetailsPage> {
image: DecorationImage( image: DecorationImage(
fit: BoxFit.cover, fit: BoxFit.cover,
opacity: 0.3, opacity: 0.3,
colorFilter: ColorFilter.mode(Colors.black.withOpacity(0.3), BlendMode.dstATop), colorFilter: ColorFilter.mode(
Colors.black.withOpacity(0.3),
BlendMode.dstATop),
image: NetworkImage( image: NetworkImage(
"${APIs.imagesUrl}/${details.id}/backdrop.jpg", "${APIs.imagesUrl}/${details.id}/backdrop.jpg",
))), ))),
@@ -101,7 +104,7 @@ class _MovieDetailsPageState extends ConsumerState<MovieDetailsPage> {
), ),
const Text(""), const Text(""),
Text( Text(
details.overview!, details.overview??"",
), ),
], ],
)), )),
@@ -109,16 +112,12 @@ class _MovieDetailsPageState extends ConsumerState<MovieDetailsPage> {
children: [ children: [
IconButton( IconButton(
onPressed: () { onPressed: () {
ref var f = ref
.read(mediaDetailsProvider( .read(mediaDetailsProvider(
widget.id) widget.id)
.notifier) .notifier)
.delete() .delete().then((v) => context.go(WelcomePage.routeMoivie));
.then((v) => context showLoadingWithFuture(f);
.go(WelcomePage.routeMoivie))
.onError((error, trace) =>
Utils.showSnakeBar(
"删除失败:$error"));
}, },
icon: const Icon(Icons.delete)) icon: const Icon(Icons.delete))
], ],
@@ -252,17 +251,18 @@ class _NestedTabBarState extends ConsumerState<NestedTabBar>
DataCell(IconButton( DataCell(IconButton(
icon: const Icon(Icons.download), icon: const Icon(Icons.download),
onPressed: () { onPressed: () {
ref final f = ref
.read(mediaTorrentsDataProvider(( .read(mediaTorrentsDataProvider((
mediaId: widget.id, mediaId: widget.id,
seasonNumber: 0, seasonNumber: 0,
episodeNumber: 0 episodeNumber: 0
)).notifier) )).notifier)
.download(torrent) .download(torrent)
.then((v) => Utils.showSnakeBar( .then((v) => showSnakeBar(
"开始下载:${torrent.name}")) "开始下载:${torrent.name}"));
.onError((error, trace) => // .onError((error, trace) =>
Utils.showSnakeBar("操作失败: $error")); // Utils.showSnakeBar("操作失败: $error"));
showLoadingWithFuture(f);
}, },
)) ))
]); ]);

View File

@@ -4,8 +4,9 @@ import 'package:go_router/go_router.dart';
import 'package:ui/providers/APIs.dart'; import 'package:ui/providers/APIs.dart';
import 'package:ui/providers/settings.dart'; import 'package:ui/providers/settings.dart';
import 'package:ui/providers/welcome_data.dart'; import 'package:ui/providers/welcome_data.dart';
import 'package:ui/widgets/utils.dart';
import 'package:ui/widgets/progress_indicator.dart'; import 'package:ui/widgets/progress_indicator.dart';
import 'package:ui/widgets/utils.dart';
import 'package:ui/widgets/widgets.dart';
class SearchPage extends ConsumerStatefulWidget { class SearchPage extends ConsumerStatefulWidget {
const SearchPage({super.key, this.query}); const SearchPage({super.key, this.query});
@@ -155,9 +156,9 @@ class _SearchPageState extends ConsumerState<SearchPage> {
String resSelected = "1080p"; String resSelected = "1080p";
int storageSelected = 0; int storageSelected = 0;
var storage = ref.watch(storageSettingProvider); var storage = ref.watch(storageSettingProvider);
var name = ref.watch(suggestNameDataProvider((id: item.id!, mediaType: item.mediaType!))); var name = ref.watch(suggestNameDataProvider(
(id: item.id!, mediaType: item.mediaType!)));
bool downloadHistoryEpisodes = false; bool downloadHistoryEpisodes = false;
bool buttonTapped = false;
var pathController = TextEditingController(); var pathController = TextEditingController();
return AlertDialog( return AlertDialog(
@@ -275,14 +276,7 @@ class _SearchPageState extends ConsumerState<SearchPage> {
), ),
child: const Text('确定'), child: const Text('确定'),
onPressed: () async { onPressed: () async {
if (buttonTapped) { var f = ref
return;
}
setState(() {
buttonTapped = true;
});
await ref
.read(searchPageDataProvider(widget.query ?? "") .read(searchPageDataProvider(widget.query ?? "")
.notifier) .notifier)
.submit2Watchlist( .submit2Watchlist(
@@ -293,14 +287,10 @@ class _SearchPageState extends ConsumerState<SearchPage> {
pathController.text, pathController.text,
downloadHistoryEpisodes) downloadHistoryEpisodes)
.then((v) { .then((v) {
Utils.showSnakeBar("添加成功");
Navigator.of(context).pop(); Navigator.of(context).pop();
}).onError((error, trace) { showSnakeBar("添加成功:${item.name}");
Utils.showSnakeBar("添加失败:$error");
});
setState(() {
buttonTapped = false;
}); });
showLoadingWithFuture(f);
}, },
), ),
], ],

View File

@@ -18,7 +18,7 @@ class AuthSettings extends ConsumerStatefulWidget {
} }
class _AuthState extends ConsumerState<AuthSettings> { class _AuthState extends ConsumerState<AuthSettings> {
final _formKey2 = GlobalKey<FormBuilderState>(); final _formKey2 = GlobalKey<FormBuilderState>();
bool? _enableAuth; bool? _enableAuth;
@override @override
@@ -84,12 +84,11 @@ class _AuthState extends ConsumerState<AuthSettings> {
var f = ref var f = ref
.read(authSettingProvider.notifier) .read(authSettingProvider.notifier)
.updateAuthSetting(_enableAuth!, .updateAuthSetting(_enableAuth!,
values["user"], values["password"]); values["user"], values["password"])
f.then((v) { .then((v) {
Utils.showSnakeBar("更新成功"); showSnakeBar("更新成功");
}).onError((e, s) {
Utils.showSnakeBar("更新失败:$e");
}); });
showLoadingWithFuture(f);
} }
})) }))
], ],
@@ -98,5 +97,4 @@ class _AuthState extends ConsumerState<AuthSettings> {
error: (err, trace) => Text("$err"), error: (err, trace) => Text("$err"),
loading: () => const MyProgressIndicator()); loading: () => const MyProgressIndicator());
} }
}
}

View File

@@ -1,8 +1,13 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:ui/widgets/utils.dart'; import 'package:ui/widgets/widgets.dart';
Future<void> showSettingDialog(BuildContext context,String title, bool showDelete, Widget body, Future<void> showSettingDialog(
Future Function() onSubmit, Future Function() onDelete) { BuildContext context,
String title,
bool showDelete,
Widget body,
Future Function() onSubmit,
Future Function() onDelete) {
return showDialog<void>( return showDialog<void>(
context: context, context: context,
barrierDismissible: true, barrierDismissible: true,
@@ -19,13 +24,8 @@ Future<void> showSettingDialog(BuildContext context,String title, bool showDelet
showDelete showDelete
? TextButton( ? TextButton(
onPressed: () { onPressed: () {
final f = onDelete(); final f = onDelete().then((v) => Navigator.of(context).pop());
f.then((v) { showLoadingWithFuture(f);
Utils.showSnakeBar("删除成功");
Navigator.of(context).pop();
}).onError((e, s) {
Utils.showSnakeBar("删除失败:$e");
});
}, },
child: const Text( child: const Text(
'删除', '删除',
@@ -38,15 +38,8 @@ Future<void> showSettingDialog(BuildContext context,String title, bool showDelet
TextButton( TextButton(
child: const Text('确定'), child: const Text('确定'),
onPressed: () { onPressed: () {
final f = onSubmit(); final f = onSubmit().then((v) => Navigator.of(context).pop());
f.then((v) { showLoadingWithFuture(f);
Utils.showSnakeBar("操作成功");
Navigator.of(context).pop();
}).onError((e, s) {
if (e.toString() != "validation_error") {
Utils.showSnakeBar("操作失败:$e");
}
});
}, },
), ),
], ],

View File

@@ -3,8 +3,8 @@ import 'package:flutter_form_builder/flutter_form_builder.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:form_builder_validators/form_builder_validators.dart'; import 'package:form_builder_validators/form_builder_validators.dart';
import 'package:ui/providers/settings.dart'; import 'package:ui/providers/settings.dart';
import 'package:ui/widgets/utils.dart';
import 'package:ui/widgets/progress_indicator.dart'; import 'package:ui/widgets/progress_indicator.dart';
import 'package:ui/widgets/utils.dart';
import 'package:ui/widgets/widgets.dart'; import 'package:ui/widgets/widgets.dart';
class GeneralSettings extends ConsumerStatefulWidget { class GeneralSettings extends ConsumerStatefulWidget {
@@ -17,9 +17,8 @@ class GeneralSettings extends ConsumerStatefulWidget {
} }
} }
class _GeneralState extends ConsumerState<GeneralSettings> { class _GeneralState extends ConsumerState<GeneralSettings> {
final _formKey = GlobalKey<FormBuilderState>(); final _formKey = GlobalKey<FormBuilderState>();
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -96,12 +95,8 @@ class _GeneralState extends ConsumerState<GeneralSettings> {
tmdbApiKey: values["tmdb_api"], tmdbApiKey: values["tmdb_api"],
downloadDIr: values["download_dir"], downloadDIr: values["download_dir"],
logLevel: values["log_level"], logLevel: values["log_level"],
proxy: values["proxy"])); proxy: values["proxy"])).then((v) => showSnakeBar("更新成功"));
f.then((v) { showLoadingWithFuture(f);
Utils.showSnakeBar("更新成功");
}).onError((e, s) {
Utils.showSnakeBar("更新失败:$e");
});
} }
}), }),
), ),
@@ -113,5 +108,4 @@ class _GeneralState extends ConsumerState<GeneralSettings> {
error: (err, trace) => Text("$err"), error: (err, trace) => Text("$err"),
loading: () => const MyProgressIndicator()); loading: () => const MyProgressIndicator());
} }
}
}

View File

@@ -4,9 +4,10 @@ import 'package:go_router/go_router.dart';
import 'package:ui/providers/APIs.dart'; import 'package:ui/providers/APIs.dart';
import 'package:ui/providers/series_details.dart'; import 'package:ui/providers/series_details.dart';
import 'package:ui/providers/settings.dart'; import 'package:ui/providers/settings.dart';
import 'package:ui/widgets/utils.dart';
import 'package:ui/welcome_page.dart'; import 'package:ui/welcome_page.dart';
import 'package:ui/widgets/utils.dart';
import 'package:ui/widgets/progress_indicator.dart'; import 'package:ui/widgets/progress_indicator.dart';
import 'package:ui/widgets/widgets.dart';
class TvDetailsPage extends ConsumerStatefulWidget { class TvDetailsPage extends ConsumerStatefulWidget {
static const route = "/series/:id"; static const route = "/series/:id";
@@ -70,14 +71,12 @@ class _TvDetailsPageState extends ConsumerState<TvDetailsPage> {
message: "搜索下载对应剧集", message: "搜索下载对应剧集",
child: IconButton( child: IconButton(
onPressed: () { onPressed: () {
ref var f = ref
.read(mediaDetailsProvider(widget.seriesId) .read(mediaDetailsProvider(widget.seriesId)
.notifier) .notifier)
.searchAndDownload(widget.seriesId, .searchAndDownload(widget.seriesId,
ep.seasonNumber!, ep.episodeNumber!) ep.seasonNumber!, ep.episodeNumber!);
.then((v) => Utils.showSnakeBar("开始下载: $v")) showLoadingWithFuture(f);
.onError((error, trace) =>
Utils.showSnakeBar("操作失败: $error"));
}, },
icon: const Icon(Icons.download)), icon: const Icon(Icons.download)),
), ),
@@ -120,13 +119,11 @@ class _TvDetailsPageState extends ConsumerState<TvDetailsPage> {
message: "搜索下载全部剧集", message: "搜索下载全部剧集",
child: IconButton( child: IconButton(
onPressed: () { onPressed: () {
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) => Utils.showSnakeBar("开始下载: $v")) showLoadingWithFuture(f);
.onError((error, trace) =>
Utils.showSnakeBar("操作失败: $error"));
}, },
icon: const Icon(Icons.download)), icon: const Icon(Icons.download)),
), ),
@@ -154,7 +151,8 @@ class _TvDetailsPageState extends ConsumerState<TvDetailsPage> {
image: DecorationImage( image: DecorationImage(
fit: BoxFit.cover, fit: BoxFit.cover,
opacity: 0.3, opacity: 0.3,
colorFilter: ColorFilter.mode(Colors.black.withOpacity(0.3), BlendMode.dstATop), colorFilter: ColorFilter.mode(
Colors.black.withOpacity(0.3), BlendMode.dstATop),
image: NetworkImage( image: NetworkImage(
"${APIs.imagesUrl}/${details.id}/backdrop.jpg"))), "${APIs.imagesUrl}/${details.id}/backdrop.jpg"))),
child: Padding( child: Padding(
@@ -166,9 +164,8 @@ class _TvDetailsPageState extends ConsumerState<TvDetailsPage> {
child: Padding( child: Padding(
padding: const EdgeInsets.all(10), padding: const EdgeInsets.all(10),
child: Image.network( child: Image.network(
"${APIs.imagesUrl}/${details.id}/poster.jpg", "${APIs.imagesUrl}/${details.id}/poster.jpg",
fit: BoxFit.contain fit: BoxFit.contain),
),
), ),
), ),
Flexible( Flexible(
@@ -217,16 +214,12 @@ class _TvDetailsPageState extends ConsumerState<TvDetailsPage> {
children: [ children: [
IconButton( IconButton(
onPressed: () { onPressed: () {
ref var f = ref
.read(mediaDetailsProvider( .read(mediaDetailsProvider(
widget.seriesId) widget.seriesId)
.notifier) .notifier)
.delete() .delete().then((v) => context.go(WelcomePage.routeTv));
.then((v) => showLoadingWithFuture(f);
context.go(WelcomePage.routeTv))
.onError((error, trace) =>
Utils.showSnakeBar(
"删除失败: $error"));
}, },
icon: const Icon(Icons.delete)) icon: const Icon(Icons.delete))
], ],
@@ -270,7 +263,8 @@ class _TvDetailsPageState extends ConsumerState<TvDetailsPage> {
data: (v) { data: (v) {
return SingleChildScrollView( return SingleChildScrollView(
child: DataTable( child: DataTable(
dataTextStyle: const TextStyle(fontSize: 12, height: 0), dataTextStyle:
const TextStyle(fontSize: 12, height: 0),
columns: const [ columns: const [
DataColumn(label: Text("名称")), DataColumn(label: Text("名称")),
DataColumn(label: Text("大小")), DataColumn(label: Text("大小")),
@@ -289,19 +283,14 @@ class _TvDetailsPageState extends ConsumerState<TvDetailsPage> {
DataCell(IconButton( DataCell(IconButton(
icon: const Icon(Icons.download), icon: const Icon(Icons.download),
onPressed: () async { onPressed: () async {
await ref var f = ref
.read(mediaTorrentsDataProvider(( .read(mediaTorrentsDataProvider((
mediaId: id, mediaId: id,
seasonNumber: season, seasonNumber: season,
episodeNumber: episode episodeNumber: episode
)).notifier) )).notifier)
.download(torrent) .download(torrent).then((v) => showSnakeBar("开始下载:${torrent.name}"));
.then((v) { showLoadingWithFuture(f);
Navigator.of(context).pop();
Utils.showSnakeBar(
"开始下载:${torrent.name}");
}).onError((error, trace) =>
Utils.showSnakeBar("下载失败:$error"));
}, },
)) ))
]); ]);

View File

@@ -74,7 +74,7 @@ class WelcomePage extends ConsumerWidget {
), ),
Text( Text(
item.name!, item.name!,
style: const TextStyle( style: const TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
height: 2.5), height: 2.5),

View File

@@ -32,7 +32,9 @@ class Utils {
); );
} }
static showSnakeBar(String msg) { }
showSnakeBar(String msg) {
final context = APIs.navigatorKey.currentContext; final context = APIs.navigatorKey.currentContext;
if (context != null) { if (context != null) {
ScaffoldMessenger.of(context).showSnackBar(SnackBar( ScaffoldMessenger.of(context).showSnackBar(SnackBar(
@@ -42,17 +44,6 @@ class Utils {
} }
} }
static bool showError(BuildContext context, AsyncSnapshot snapshot) {
final isErrored = snapshot.hasError &&
snapshot.connectionState != ConnectionState.waiting;
if (isErrored) {
Utils.showSnakeBar("当前操作出错: ${snapshot.error}");
return true;
}
return false;
}
}
extension FileFormatter on num { extension FileFormatter on num {
String readableFileSize({bool base1024 = true}) { String readableFileSize({bool base1024 = true}) {
final base = base1024 ? 1024 : 1000; final base = base1024 ? 1024 : 1000;

View File

@@ -1,4 +1,5 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:ui/providers/APIs.dart';
class Commons { class Commons {
static InputDecoration requiredTextFieldStyle({ static InputDecoration requiredTextFieldStyle({
@@ -41,3 +42,47 @@ class SettingsCard extends StatelessWidget {
); );
} }
} }
showLoadingWithFuture(Future f) {
final context = APIs.navigatorKey.currentContext;
if (context == null) {
return;
}
showDialog(
context: context,
barrierDismissible: false, //点击遮罩不关闭对话框
builder: (context) {
return FutureBuilder(
future: f,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
if (snapshot.hasError) {
return AlertDialog(
content: Text("处理失败:${snapshot.error}"),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: const Text(""))
],
);
}
Navigator.of(context).pop();
return Container();
} else {
return const AlertDialog(
content: Column(
mainAxisSize: MainAxisSize.min,
children: <Widget>[
CircularProgressIndicator(),
Padding(
padding: EdgeInsets.only(top: 26.0),
child: Text("正在处理,请稍后..."),
)
],
),
);
}
});
},
);
}