mirror of
https://github.com/lifegpc/eh_downloader_flutter.git
synced 2026-06-06 05:49:03 +08:00
feat: 添加画廊列表显示模式和合并功能,优化画廊列表卡片组件
This commit is contained in:
@@ -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';
|
||||||
|
|||||||
@@ -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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
75
lib/components/gallery_list_normal_card.dart
Normal file
75
lib/components/gallery_list_normal_card.dart
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -364,5 +364,6 @@
|
|||||||
"time": "时间",
|
"time": "时间",
|
||||||
"message": "消息",
|
"message": "消息",
|
||||||
"type": "类型",
|
"type": "类型",
|
||||||
"level": "级别"
|
"level": "级别",
|
||||||
|
"galleryListDisplayMode": "画廊列表的显示模式"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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: () {
|
||||||
|
|||||||
@@ -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: [
|
||||||
|
|||||||
Reference in New Issue
Block a user