diff --git a/lib/components/gallery_basic_info.dart b/lib/components/gallery_basic_info.dart index 8435e3f..81f1c35 100644 --- a/lib/components/gallery_basic_info.dart +++ b/lib/components/gallery_basic_info.dart @@ -26,7 +26,10 @@ class GalleryBasicInfo extends StatelessWidget { child: Row(children: [ Expanded( flex: 2, - child: Thumbnail(firstPage, fileId: fileId, gid: gMeta.gid)), + child: Container( + margin: const EdgeInsets.only(right: 8), + child: Thumbnail(firstPage, fileId: fileId, gid: gMeta.gid), + )), Expanded( flex: 3, child: Column( diff --git a/lib/components/gallery_info.dart b/lib/components/gallery_info.dart index 67705bf..ba17042 100644 --- a/lib/components/gallery_info.dart +++ b/lib/components/gallery_info.dart @@ -5,6 +5,7 @@ import '../api/file.dart'; import '../api/gallery.dart'; import 'gallery_basic_info.dart'; import 'gallery_info_desktop.dart'; +import 'gallery_info_detail.dart'; import 'tags.dart'; import 'thumbnail_gridview.dart'; @@ -63,6 +64,8 @@ class _GalleryInfo extends State with ThemeModeWidget { gData: widget.gData, files: widget.files), const Divider(indent: 20, endIndent: 20), + GalleryInfoDetail(widget.gData.meta), + const Divider(indent: 20, endIndent: 20), ]), ) : SliverList( diff --git a/lib/components/gallery_info_desktop.dart b/lib/components/gallery_info_desktop.dart index 17ae0de..2bbefe6 100644 --- a/lib/components/gallery_info_desktop.dart +++ b/lib/components/gallery_info_desktop.dart @@ -125,7 +125,7 @@ class GalleryInfoDesktop extends StatelessWidget { style: TextStyle( color: cs.primary, fontSize: 12))), - Rate(gData.meta.rating), + Rate(gData.meta.rating, fontSize: 12), ], ) ])), diff --git a/lib/components/gallery_info_detail.dart b/lib/components/gallery_info_detail.dart new file mode 100644 index 0000000..109e71d --- /dev/null +++ b/lib/components/gallery_info_detail.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:go_router/go_router.dart'; +import 'package:intl/intl.dart'; +import '../api/gallery.dart'; +import '../dialog/gallery_details_page.dart'; +import '../main.dart'; +import '../utils/filesize.dart'; +import 'rate.dart'; + +class GalleryInfoDetail extends StatelessWidget { + const GalleryInfoDetail(this.meta, {Key? key}) : super(key: key); + final GMeta meta; + + @override + Widget build(BuildContext context) { + final cs = Theme.of(context).colorScheme; + final i18n = AppLocalizations.of(context)!; + final locale = MainApp.of(context).lang.toLocale().toString(); + return Container( + alignment: Alignment.center, + margin: const EdgeInsets.symmetric(horizontal: 40), + child: SizedBox( + height: 73, + child: Column(children: [ + Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + Text(i18n.pages(meta.filecount), + style: TextStyle(color: cs.secondary)), + Text(getFileSize(meta.filesize), + style: TextStyle(color: cs.secondary)), + ]), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Rate(meta.rating, fontSize: 14), + Text(DateFormat.yMd(locale).add_jms().format(meta.posted), + style: TextStyle(color: cs.secondary)), + ], + ), + TextButton( + onPressed: () { + context.push('/dialog/gallery/details/${meta.gid}', + extra: GalleryDetailsPageExtra(meta: meta)); + }, + child: Text(i18n.seeMoreInfo)), + ]), + ), + ); + } +} diff --git a/lib/components/rate.dart b/lib/components/rate.dart index 4c4bf76..f5663e7 100644 --- a/lib/components/rate.dart +++ b/lib/components/rate.dart @@ -1,8 +1,9 @@ import 'package:flutter/material.dart'; class Rate extends StatelessWidget { - const Rate(this.rate, {Key? key}) : super(key: key); + const Rate(this.rate, {Key? key, this.fontSize}) : super(key: key); final double rate; + final double? fontSize; @override Widget build(BuildContext context) { @@ -17,9 +18,10 @@ class Rate extends StatelessWidget { ? Icons.star_border : Icons.star_half, color: cs.primary, - size: 12, + size: fontSize, ), - Text(" $rate", style: TextStyle(color: cs.secondary, fontSize: 12)), + Text(" $rate", + style: TextStyle(color: cs.secondary, fontSize: fontSize)), ], ); } diff --git a/lib/dialog/gallery_details_page.dart b/lib/dialog/gallery_details_page.dart new file mode 100644 index 0000000..4fbecca --- /dev/null +++ b/lib/dialog/gallery_details_page.dart @@ -0,0 +1,128 @@ +import 'package:dio/dio.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; +import 'package:logging/logging.dart'; +import '../api/gallery.dart'; +import '../globals.dart'; + +final _log = Logger("GalleryDetailsPage"); + +class _KeyValue extends StatelessWidget { + const _KeyValue(this.name, this.value, {Key? key, this.fontSize}) + : super(key: key); + final String name; + final String value; + final double? fontSize; + + @override + Widget build(BuildContext context) { + final cs = Theme.of(context).colorScheme; + return Row(children: [ + SizedBox( + width: 80, + child: Text(name, + style: TextStyle(color: cs.primary, fontSize: fontSize))), + Expanded( + child: SelectableText(value, + style: TextStyle(color: cs.secondary, fontSize: fontSize)), + ) + ]); + } +} + +class GalleryDetailsPageExtra { + const GalleryDetailsPageExtra({this.meta}); + final GMeta? meta; +} + +class GalleryDetailsPage extends StatefulWidget { + const GalleryDetailsPage(this.gid, {Key? key, this.meta}) : super(key: key); + final int gid; + final GMeta? meta; + + @override + State createState() => _GalleryDetailsPage(); +} + +class _GalleryDetailsPage extends State { + GMeta? _meta; + CancelToken? _cancel; + bool _isLoading = false; + Object? _error; + + Future _fetchData() async { + try { + _cancel = CancelToken(); + _isLoading = true; + final data = (await api.getGallery(widget.gid)).unwrap(); + if (!_cancel!.isCancelled) { + setState(() { + _meta = data.meta; + _isLoading = false; + }); + } + } catch (e) { + if (!_cancel!.isCancelled) { + _log.severe("Failed to load gallery ${widget.gid}:", e); + setState(() { + _error = e; + _isLoading = false; + }); + } + } + } + + @override + void initState() { + _meta = widget.meta; + super.initState(); + } + + @override + void dispose() { + _cancel?.cancel(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + tryInitApi(context); + final isLoading = _meta == null && _error == null; + final i18n = AppLocalizations.of(context)!; + final maxWidth = MediaQuery.of(context).size.width; + if (isLoading && !_isLoading) _fetchData(); + return Container( + padding: maxWidth < 400 + ? const EdgeInsets.symmetric(vertical: 20, horizontal: 5) + : const EdgeInsets.all(20), + decoration: BoxDecoration(borderRadius: BorderRadius.circular(10)), + child: isLoading + ? const Center(child: CircularProgressIndicator()) + : _error != null + ? SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text("Error: $_error"), + ElevatedButton.icon( + onPressed: () { + _fetchData(); + setState(() { + _error = null; + }); + }, + icon: const Icon(Icons.refresh), + label: Text(i18n.retry)), + ], + )) + : SingleChildScrollView( + child: Column(children: [ + _KeyValue( + i18n.gid, + _meta!.gid.toString(), + fontSize: 14, + ), + ])), + ); + } +} diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 6f6b373..8325d52 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -86,5 +86,6 @@ "downloadComplete": "Download completed.", "downloadZipFailed": "Failed to download ZIP file.", "rating": "Rating", - "preventScreenCapture": "Prevent screen capture" + "preventScreenCapture": "Prevent screen capture", + "seeMoreInfo": "See more information" } diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index fabbefc..ea47e31 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -86,5 +86,6 @@ "downloadComplete": "下载完毕。", "downloadZipFailed": "Zip文件下载失败。", "rating": "评分", - "preventScreenCapture": "防止截屏" + "preventScreenCapture": "防止截屏", + "seeMoreInfo": "显示更多信息" } diff --git a/lib/main.dart b/lib/main.dart index df52018..f09769f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -10,6 +10,7 @@ import 'api/client.dart'; import 'create_root_user.dart'; import 'dialog/dialog_page.dart'; import 'dialog/download_zip_page.dart'; +import 'dialog/gallery_details_page.dart'; import 'galleries.dart'; import 'gallery.dart'; import 'globals.dart'; @@ -134,6 +135,27 @@ final _router = GoRouter( return "/gallery/${state.pathParameters["gid"]}"; } }), + GoRoute( + path: '/dialog/gallery/details/:gid', + pageBuilder: (context, state) { + final extra = state.extra as GalleryDetailsPageExtra?; + return DialogPage( + key: state.pageKey, + builder: (context) { + return GalleryDetailsPage( + int.parse(state.pathParameters["gid"]!), + meta: extra?.meta); + }); + }, + redirect: (context, state) { + try { + int.parse(state.pathParameters["gid"]!); + return null; + } catch (e) { + _routerLog.warning("Failed to parse gid:", e); + return "/"; + } + }), ], );