From e64910d5724042cdcb4c4641809dc717a90f8c6d Mon Sep 17 00:00:00 2001 From: lifegpc Date: Sat, 8 Mar 2025 17:11:16 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=B7=BB=E5=8A=A0=E7=94=BB=E5=BB=8A?= =?UTF-8?q?=E5=88=97=E8=A1=A8=E6=98=BE=E7=A4=BA=E6=A8=A1=E5=BC=8F=E5=92=8C?= =?UTF-8?q?=E5=90=88=E5=B9=B6=E5=8A=9F=E8=83=BD=EF=BC=8C=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E7=94=BB=E5=BB=8A=E5=88=97=E8=A1=A8=E5=8D=A1=E7=89=87=E7=BB=84?= =?UTF-8?q?=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/api/api_result.dart | 8 +++ lib/api/file.dart | 7 +- lib/api/gallery.dart | 17 ++++- lib/components/gallery_list_normal_card.dart | 75 ++++++++++++++++++++ lib/globals.dart | 6 ++ lib/l10n/app_en.arb | 3 +- lib/l10n/app_zh.arb | 3 +- lib/pages/galleries.dart | 29 ++++++++ lib/pages/settings/display.dart | 41 +++++++++++ 9 files changed, 183 insertions(+), 6 deletions(-) create mode 100644 lib/components/gallery_list_normal_card.dart diff --git a/lib/api/api_result.dart b/lib/api/api_result.dart index 9ec234b..a392fc5 100644 --- a/lib/api/api_result.dart +++ b/lib/api/api_result.dart @@ -27,6 +27,14 @@ class ApiResult { } } + T? unwrapOrNull() { + if (ok) { + return data; + } else { + return null; + } + } + (int, String) unwrapErr() { if (ok) { return throw 'unwrap_err called on ok ApiResult'; diff --git a/lib/api/file.dart b/lib/api/file.dart index 9f83bf7..fd8a01c 100644 --- a/lib/api/file.dart +++ b/lib/api/file.dart @@ -41,8 +41,8 @@ class EhFileExtend { } class EhFiles { - const EhFiles({required this.files}); - final Map> files; + EhFiles({required this.files}); + Map> files; factory EhFiles.fromJson(Map json) => EhFiles( files: (json).map( (k, e) => MapEntry( @@ -52,4 +52,7 @@ class EhFiles { .toList()), ), ); + void merge(EhFiles another) { + files.addAll(another.files); + } } diff --git a/lib/api/gallery.dart b/lib/api/gallery.dart index b4c3a58..f0c910a 100644 --- a/lib/api/gallery.dart +++ b/lib/api/gallery.dart @@ -239,10 +239,10 @@ class GMetaSearchInfo { } class GalleryThumbnails { - const GalleryThumbnails({ + GalleryThumbnails({ required this.thumbnails, }); - final Map> thumbnails; + Map> thumbnails; factory GalleryThumbnails.fromJson(Map json) => GalleryThumbnails( thumbnails: json.map((key, value) => MapEntry( @@ -251,4 +251,17 @@ class GalleryThumbnails { value as Map, (json) => ExtendedPMeta.fromJson(json as Map))))); + void merge(GalleryThumbnails another) { + another.thumbnails.forEach((key, value) { + if (thumbnails.containsKey(key)) { + if (value.ok) { + thumbnails[key] = value; + } else if (!thumbnails[key]!.ok) { + thumbnails[key] = value; + } + } else { + thumbnails[key] = value; + } + }); + } } diff --git a/lib/components/gallery_list_normal_card.dart b/lib/components/gallery_list_normal_card.dart new file mode 100644 index 0000000..b067858 --- /dev/null +++ b/lib/components/gallery_list_normal_card.dart @@ -0,0 +1,75 @@ +import 'package:flutter/material.dart'; +import 'package:go_router/go_router.dart'; +import '../api/file.dart'; +import '../api/gallery.dart'; +import '../globals.dart'; +import 'thumbnail.dart'; + +class GalleryListNormalCard extends StatefulWidget { + const GalleryListNormalCard(this.gMeta, {super.key, this.files, this.pMeta}); + final GMeta gMeta; + final ExtendedPMeta? pMeta; + final EhFiles? files; + + @override + State createState() => _GalleryListNormalCard(); +} + +class _GalleryListNormalCard extends State { + ExtendedPMeta? _pMeta; + EhFiles? _files; + @override + void initState() { + _pMeta = widget.pMeta; + _files = widget.files; + super.initState(); + } + + @override + Widget build(BuildContext context) { + final maxWidth = MediaQuery.of(context).size.width; + bool useMobile = maxWidth <= 810; + final max = + ((useMobile ? 300 : 400) * MediaQuery.of(context).devicePixelRatio) + .toInt(); + final fileId = + _pMeta != null ? _files?.files[_pMeta!.token]?.firstOrNull?.id : null; + final card = Card( + child: Row(children: [ + Expanded( + flex: useMobile ? 2 : 3, + child: _pMeta != null + ? Thumbnail(_pMeta!, + key: Key("thumbnail-conver-${widget.gMeta.gid}-$max"), + files: _files, + gid: widget.gMeta.gid, + fileId: fileId, + max: max) + : Container()), + Expanded( + flex: useMobile ? 3 : 7, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text(widget.gMeta.title), + Text(widget.gMeta.titleJpn), + Text(widget.gMeta.uploader), + Text(widget.gMeta.category), + Text(widget.gMeta.filecount.toString()), + Text(widget.gMeta.rating.toString()), + ]))) + ])); + final box = SizedBox( + height: useMobile ? 300 : 400, + width: useMobile ? null : 600, + child: card); + return InkWell( + onTap: () { + context.push('/gallery/${widget.gMeta.gid}', + extra: GalleryPageExtra(title: widget.gMeta.preferredTitle)); + }, + child: box); + } +} diff --git a/lib/globals.dart b/lib/globals.dart index 3a48939..2c736f2 100644 --- a/lib/globals.dart +++ b/lib/globals.dart @@ -451,6 +451,12 @@ enum ThumbnailSize { } } +enum GalleryListDisplayMode { + minimal, + normal, + extended; +} + final _authLog = Logger("AuthLog"); void clearAllStates(BuildContext context) { diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 5b89e97..7e6ec20 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -364,5 +364,6 @@ "time": "Time", "message": "Message", "type": "Type", - "level": "Level" + "level": "Level", + "galleryListDisplayMode": "Display mode for gallery list" } diff --git a/lib/l10n/app_zh.arb b/lib/l10n/app_zh.arb index 066e56c..5a66c34 100644 --- a/lib/l10n/app_zh.arb +++ b/lib/l10n/app_zh.arb @@ -364,5 +364,6 @@ "time": "时间", "message": "消息", "type": "类型", - "level": "级别" + "level": "级别", + "galleryListDisplayMode": "画廊列表的显示模式" } diff --git a/lib/pages/galleries.dart b/lib/pages/galleries.dart index d8b30d8..212a04b 100644 --- a/lib/pages/galleries.dart +++ b/lib/pages/galleries.dart @@ -5,7 +5,9 @@ import 'package:go_router/go_router.dart'; import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; import 'package:logging/logging.dart'; import '../api/client.dart'; +import '../api/file.dart'; import '../api/gallery.dart'; +import '../components/gallery_list_normal_card.dart'; import '../globals.dart'; import '../main.dart'; @@ -52,6 +54,8 @@ class _GalleriesPage extends State CancelToken? _tagCancel; bool _isFetchingTag = false; bool _fetchedTag = false; + late GalleryThumbnails _thumbnails; + late EhFiles _files; final PagingController _pagingController = PagingController(firstPageKey: 0); @@ -66,6 +70,22 @@ class _GalleriesPage extends State category: widget.category, tag: widget.tag)) .unwrap(); + final thumbnails = + (await api.getGalleriesThumbnail(list.map((e) => e.gid).toList())) + .unwrap(); + _thumbnails.merge(thumbnails); + final files = (await api.getFiles(list + .map((e) { + var thumbnail = _thumbnails.thumbnails[e.gid]; + if (thumbnail != null && thumbnail.ok) { + return thumbnail.unwrap().token; + } + }) + .where((t) => t != null) + .cast() + .toList())) + .unwrap(); + _files.merge(files); final isLastPage = list.length < _pageSize; if (isLastPage) { _pagingController.appendLastPage(list); @@ -118,6 +138,8 @@ class _GalleriesPage extends State _fetchPage(pageKey); }); _translatedTag = widget.translatedTag; + _thumbnails = GalleryThumbnails(thumbnails: {}); + _files = EhFiles(files: {}); listener.on("user_logined", _onStateChanged); listener.on("meilisearch_enabled", _onStateChanged); super.initState(); @@ -212,6 +234,13 @@ class _GalleriesPage extends State pagingController: _pagingController, builderDelegate: PagedChildBuilderDelegate( itemBuilder: (context, item, index) { + final displayMode = GalleryListDisplayMode + .values[prefs.getInt("galleryListDisplayMode") ?? 1]; + if (displayMode == GalleryListDisplayMode.normal) { + return GalleryListNormalCard(item, + files: _files, + pMeta: _thumbnails.thumbnails[item.gid]?.unwrapOrNull()); + } return ListTile( title: Text(item.preferredTitle), onTap: () { diff --git a/lib/pages/settings/display.dart b/lib/pages/settings/display.dart index 7c870a4..29ab01f 100644 --- a/lib/pages/settings/display.dart +++ b/lib/pages/settings/display.dart @@ -30,6 +30,8 @@ class _DisplaySettingsPage extends State ThumbnailSize _oriThumbnailSize = ThumbnailSize.medium; ThumbnailGenMethod _oriThumbnailMethod = ThumbnailGenMethod.unknown; ThumbnailAlign _oriThumbnailAlign = ThumbnailAlign.center; + GalleryListDisplayMode _oriGalleryListDisplayMode = + GalleryListDisplayMode.normal; bool _displayAd = false; Lang _lang = Lang.system; bool _preventScreenCapture = false; @@ -40,6 +42,8 @@ class _DisplaySettingsPage extends State ThumbnailSize _thumbnailSize = ThumbnailSize.medium; ThumbnailGenMethod _thumbnailMethod = ThumbnailGenMethod.unknown; ThumbnailAlign _thumbnailAlign = ThumbnailAlign.center; + GalleryListDisplayMode _galleryListDisplayMode = + GalleryListDisplayMode.normal; @override void initState() { super.initState(); @@ -127,6 +131,14 @@ class _DisplaySettingsPage extends State _oriThumbnailAlign = ThumbnailAlign.center; _thumbnailAlign = ThumbnailAlign.center; } + try { + _galleryListDisplayMode = GalleryListDisplayMode + .values[prefs.getInt("galleryListDisplayMode") ?? 1]; + _oriGalleryListDisplayMode = _galleryListDisplayMode; + } catch (e) { + _log.warning("Failed to get galleryListDisplayMode:", e); + _galleryListDisplayMode = GalleryListDisplayMode.normal; + } } void fallback(BuildContext context) { @@ -148,6 +160,7 @@ class _DisplaySettingsPage extends State _thumbnailSize = ThumbnailSize.medium; _thumbnailMethod = ThumbnailGenMethod.unknown; _thumbnailAlign = ThumbnailAlign.center; + _galleryListDisplayMode = GalleryListDisplayMode.normal; }); } @@ -240,6 +253,13 @@ class _DisplaySettingsPage extends State _oriThumbnailAlign = _thumbnailAlign; } } + if (_galleryListDisplayMode != _oriGalleryListDisplayMode) { + if (!await prefs.setInt( + "galleryListDisplayMode", _galleryListDisplayMode.index)) { + re = false; + _log.warning("Failed to save galleryListDisplayMode"); + } + } return re; } @@ -455,6 +475,27 @@ class _DisplaySettingsPage extends State helperText: i18n.thumbnailAlignHelp, helperMaxLines: 3, ))), + Container( + padding: + const EdgeInsets.symmetric(vertical: 8), + child: DropdownButtonFormField< + GalleryListDisplayMode>( + items: GalleryListDisplayMode.values + .map((e) => DropdownMenuItem( + value: e, child: Text(e.name))) + .toList(), + value: _galleryListDisplayMode, + onChanged: (value) { + if (value != null) { + setState(() { + _galleryListDisplayMode = value; + }); + } + }, + decoration: InputDecoration( + border: const OutlineInputBorder(), + labelText: i18n.galleryListDisplayMode, + ))), Row( mainAxisAlignment: MainAxisAlignment.center, children: [