feat: 添加画廊列表显示模式和合并功能,优化画廊列表卡片组件

This commit is contained in:
2025-03-08 17:11:16 +08:00
parent 5f92d036fa
commit e64910d572
9 changed files with 183 additions and 6 deletions

View File

@@ -27,6 +27,14 @@ class ApiResult<T> {
} }
} }
T? unwrapOrNull() {
if (ok) {
return data;
} else {
return null;
}
}
(int, String) unwrapErr() { (int, String) unwrapErr() {
if (ok) { if (ok) {
return throw 'unwrap_err called on ok ApiResult'; return throw 'unwrap_err called on ok ApiResult';

View File

@@ -41,8 +41,8 @@ class EhFileExtend {
} }
class EhFiles { class EhFiles {
const EhFiles({required this.files}); EhFiles({required this.files});
final Map<String, List<EhFileBasic>> files; Map<String, List<EhFileBasic>> files;
factory EhFiles.fromJson(Map<String, dynamic> json) => EhFiles( factory EhFiles.fromJson(Map<String, dynamic> json) => EhFiles(
files: (json).map( files: (json).map(
(k, e) => MapEntry( (k, e) => MapEntry(
@@ -52,4 +52,7 @@ class EhFiles {
.toList()), .toList()),
), ),
); );
void merge(EhFiles another) {
files.addAll(another.files);
}
} }

View File

@@ -239,10 +239,10 @@ class GMetaSearchInfo {
} }
class GalleryThumbnails { class GalleryThumbnails {
const GalleryThumbnails({ GalleryThumbnails({
required this.thumbnails, required this.thumbnails,
}); });
final Map<int, ApiResult<ExtendedPMeta>> thumbnails; Map<int, ApiResult<ExtendedPMeta>> thumbnails;
factory GalleryThumbnails.fromJson(Map<String, dynamic> json) => factory GalleryThumbnails.fromJson(Map<String, dynamic> json) =>
GalleryThumbnails( GalleryThumbnails(
thumbnails: json.map((key, value) => MapEntry( thumbnails: json.map((key, value) => MapEntry(
@@ -251,4 +251,17 @@ class GalleryThumbnails {
value as Map<String, dynamic>, value as Map<String, dynamic>,
(json) => (json) =>
ExtendedPMeta.fromJson(json as Map<String, dynamic>))))); ExtendedPMeta.fromJson(json as Map<String, dynamic>)))));
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;
}
});
}
} }

View File

@@ -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<GalleryListNormalCard> createState() => _GalleryListNormalCard();
}
class _GalleryListNormalCard extends State<GalleryListNormalCard> {
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);
}
}

View File

@@ -451,6 +451,12 @@ enum ThumbnailSize {
} }
} }
enum GalleryListDisplayMode {
minimal,
normal,
extended;
}
final _authLog = Logger("AuthLog"); final _authLog = Logger("AuthLog");
void clearAllStates(BuildContext context) { void clearAllStates(BuildContext context) {

View File

@@ -364,5 +364,6 @@
"time": "Time", "time": "Time",
"message": "Message", "message": "Message",
"type": "Type", "type": "Type",
"level": "Level" "level": "Level",
"galleryListDisplayMode": "Display mode for gallery list"
} }

View File

@@ -364,5 +364,6 @@
"time": "时间", "time": "时间",
"message": "消息", "message": "消息",
"type": "类型", "type": "类型",
"level": "级别" "level": "级别",
"galleryListDisplayMode": "画廊列表的显示模式"
} }

View File

@@ -5,7 +5,9 @@ import 'package:go_router/go_router.dart';
import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart'; import 'package:infinite_scroll_pagination/infinite_scroll_pagination.dart';
import 'package:logging/logging.dart'; import 'package:logging/logging.dart';
import '../api/client.dart'; import '../api/client.dart';
import '../api/file.dart';
import '../api/gallery.dart'; import '../api/gallery.dart';
import '../components/gallery_list_normal_card.dart';
import '../globals.dart'; import '../globals.dart';
import '../main.dart'; import '../main.dart';
@@ -52,6 +54,8 @@ class _GalleriesPage extends State<GalleriesPage>
CancelToken? _tagCancel; CancelToken? _tagCancel;
bool _isFetchingTag = false; bool _isFetchingTag = false;
bool _fetchedTag = false; bool _fetchedTag = false;
late GalleryThumbnails _thumbnails;
late EhFiles _files;
final PagingController<int, GMeta> _pagingController = final PagingController<int, GMeta> _pagingController =
PagingController(firstPageKey: 0); PagingController(firstPageKey: 0);
@@ -66,6 +70,22 @@ class _GalleriesPage extends State<GalleriesPage>
category: widget.category, category: widget.category,
tag: widget.tag)) tag: widget.tag))
.unwrap(); .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<String>()
.toList()))
.unwrap();
_files.merge(files);
final isLastPage = list.length < _pageSize; final isLastPage = list.length < _pageSize;
if (isLastPage) { if (isLastPage) {
_pagingController.appendLastPage(list); _pagingController.appendLastPage(list);
@@ -118,6 +138,8 @@ class _GalleriesPage extends State<GalleriesPage>
_fetchPage(pageKey); _fetchPage(pageKey);
}); });
_translatedTag = widget.translatedTag; _translatedTag = widget.translatedTag;
_thumbnails = GalleryThumbnails(thumbnails: {});
_files = EhFiles(files: {});
listener.on("user_logined", _onStateChanged); listener.on("user_logined", _onStateChanged);
listener.on("meilisearch_enabled", _onStateChanged); listener.on("meilisearch_enabled", _onStateChanged);
super.initState(); super.initState();
@@ -212,6 +234,13 @@ class _GalleriesPage extends State<GalleriesPage>
pagingController: _pagingController, pagingController: _pagingController,
builderDelegate: PagedChildBuilderDelegate<GMeta>( builderDelegate: PagedChildBuilderDelegate<GMeta>(
itemBuilder: (context, item, index) { 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( return ListTile(
title: Text(item.preferredTitle), title: Text(item.preferredTitle),
onTap: () { onTap: () {

View File

@@ -30,6 +30,8 @@ class _DisplaySettingsPage extends State<DisplaySettingsPage>
ThumbnailSize _oriThumbnailSize = ThumbnailSize.medium; ThumbnailSize _oriThumbnailSize = ThumbnailSize.medium;
ThumbnailGenMethod _oriThumbnailMethod = ThumbnailGenMethod.unknown; ThumbnailGenMethod _oriThumbnailMethod = ThumbnailGenMethod.unknown;
ThumbnailAlign _oriThumbnailAlign = ThumbnailAlign.center; ThumbnailAlign _oriThumbnailAlign = ThumbnailAlign.center;
GalleryListDisplayMode _oriGalleryListDisplayMode =
GalleryListDisplayMode.normal;
bool _displayAd = false; bool _displayAd = false;
Lang _lang = Lang.system; Lang _lang = Lang.system;
bool _preventScreenCapture = false; bool _preventScreenCapture = false;
@@ -40,6 +42,8 @@ class _DisplaySettingsPage extends State<DisplaySettingsPage>
ThumbnailSize _thumbnailSize = ThumbnailSize.medium; ThumbnailSize _thumbnailSize = ThumbnailSize.medium;
ThumbnailGenMethod _thumbnailMethod = ThumbnailGenMethod.unknown; ThumbnailGenMethod _thumbnailMethod = ThumbnailGenMethod.unknown;
ThumbnailAlign _thumbnailAlign = ThumbnailAlign.center; ThumbnailAlign _thumbnailAlign = ThumbnailAlign.center;
GalleryListDisplayMode _galleryListDisplayMode =
GalleryListDisplayMode.normal;
@override @override
void initState() { void initState() {
super.initState(); super.initState();
@@ -127,6 +131,14 @@ class _DisplaySettingsPage extends State<DisplaySettingsPage>
_oriThumbnailAlign = ThumbnailAlign.center; _oriThumbnailAlign = ThumbnailAlign.center;
_thumbnailAlign = 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) { void fallback(BuildContext context) {
@@ -148,6 +160,7 @@ class _DisplaySettingsPage extends State<DisplaySettingsPage>
_thumbnailSize = ThumbnailSize.medium; _thumbnailSize = ThumbnailSize.medium;
_thumbnailMethod = ThumbnailGenMethod.unknown; _thumbnailMethod = ThumbnailGenMethod.unknown;
_thumbnailAlign = ThumbnailAlign.center; _thumbnailAlign = ThumbnailAlign.center;
_galleryListDisplayMode = GalleryListDisplayMode.normal;
}); });
} }
@@ -240,6 +253,13 @@ class _DisplaySettingsPage extends State<DisplaySettingsPage>
_oriThumbnailAlign = _thumbnailAlign; _oriThumbnailAlign = _thumbnailAlign;
} }
} }
if (_galleryListDisplayMode != _oriGalleryListDisplayMode) {
if (!await prefs.setInt(
"galleryListDisplayMode", _galleryListDisplayMode.index)) {
re = false;
_log.warning("Failed to save galleryListDisplayMode");
}
}
return re; return re;
} }
@@ -455,6 +475,27 @@ class _DisplaySettingsPage extends State<DisplaySettingsPage>
helperText: i18n.thumbnailAlignHelp, helperText: i18n.thumbnailAlignHelp,
helperMaxLines: 3, 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( Row(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [