Add select mode to gallery page

This commit is contained in:
2024-06-04 09:59:48 +00:00
committed by GitHub
parent 760f36cfb9
commit 28ead89629
5 changed files with 162 additions and 35 deletions

View File

@@ -12,11 +12,20 @@ import 'thumbnail_gridview.dart';
class GalleryInfo extends StatefulWidget { class GalleryInfo extends StatefulWidget {
const GalleryInfo(this.gData, const GalleryInfo(this.gData,
{super.key, this.files, this.refreshIndicatorKey, this.onRefresh}); {super.key,
this.files,
this.refreshIndicatorKey,
this.onRefresh,
this.isSelectMode = false,
this.onSelectChanged,
this.selected});
final GalleryData gData; final GalleryData gData;
final EhFiles? files; final EhFiles? files;
final GlobalKey<RefreshIndicatorState>? refreshIndicatorKey; final GlobalKey<RefreshIndicatorState>? refreshIndicatorKey;
final Future<void> Function()? onRefresh; final Future<void> Function()? onRefresh;
final bool isSelectMode;
final ValueChanged<bool>? onSelectChanged;
final List<String>? selected;
@override @override
State<GalleryInfo> createState() => _GalleryInfo(); State<GalleryInfo> createState() => _GalleryInfo();
@@ -66,14 +75,29 @@ class _GalleryInfo extends State<GalleryInfo> with ThemeModeWidget {
slivers: [ slivers: [
SliverAppBar( SliverAppBar(
leading: IconButton( leading: IconButton(
icon: const Icon(Icons.arrow_back), icon: Icon(widget.isSelectMode ? Icons.close : Icons.arrow_back),
onPressed: () { onPressed: () {
context.canPop() ? context.pop() : context.go("/"); if (widget.isSelectMode) {
if (widget.onSelectChanged != null) {
widget.onSelectChanged!(false);
}
} else {
context.canPop() ? context.pop() : context.go("/");
}
}, },
), ),
title: SelectableText(widget.gData.meta.preferredTitle, title: SelectableText(widget.gData.meta.preferredTitle,
maxLines: 1, minLines: 1), maxLines: 1, minLines: 1),
actions: [ actions: [
widget.isSelectMode || widget.onSelectChanged == null
? Container()
: IconButton(
onPressed: () {
if (widget.onSelectChanged != null) {
widget.onSelectChanged!(true);
}
},
icon: const Icon(Icons.check_box)),
buildThemeModeIcon(context), buildThemeModeIcon(context),
buildMoreVertSettingsButon(context), buildMoreVertSettingsButon(context),
], ],
@@ -113,7 +137,10 @@ class _GalleryInfo extends State<GalleryInfo> with ThemeModeWidget {
SliverGridDelegateWithFixedCrossAxisCount( SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: useMobile ? 2 : 5), crossAxisCount: useMobile ? 2 : 5),
files: widget.files, files: widget.files,
gid: widget.gData.meta.gid), gid: widget.gData.meta.gid,
isSelectMode: widget.isSelectMode,
selected: widget.selected,
onSelectedChange: () => setState(() {})),
], ],
); );
if (widget.refreshIndicatorKey != null && widget.onRefresh != null) { if (widget.refreshIndicatorKey != null && widget.onRefresh != null) {

View File

@@ -28,7 +28,10 @@ class Thumbnail extends StatefulWidget {
this.gid, this.gid,
this.index, this.index,
this.files, this.files,
this.gdata}) this.gdata,
this.isSelectMode = false,
this.isSelected = false,
this.onSelectedChange})
: _pMeta = pMeta, : _pMeta = pMeta,
_max = max ?? 1200, _max = max ?? 1200,
_width = width, _width = width,
@@ -43,6 +46,9 @@ class Thumbnail extends StatefulWidget {
final int? index; final int? index;
final EhFiles? files; final EhFiles? files;
final GalleryData? gdata; final GalleryData? gdata;
final bool isSelectMode;
final bool isSelected;
final ValueChanged<bool>? onSelectedChange;
int get height => _height != null int get height => _height != null
? _height! ? _height!
@@ -246,7 +252,8 @@ class _Thumbnail extends State<Thumbnail> {
} }
} }
bool get showNsfw => _showNsfw || (prefs.getBool("showNsfw") ?? false); bool get showNsfw =>
widget.isSelectMode || _showNsfw || (prefs.getBool("showNsfw") ?? false);
@override @override
void dispose() { void dispose() {
@@ -391,6 +398,7 @@ class _Thumbnail extends State<Thumbnail> {
}, },
child: timg) child: timg)
: timg; : timg;
final cs = Theme.of(context).colorScheme;
return SizedBox( return SizedBox(
width: widget.width.toDouble(), width: widget.width.toDouble(),
height: widget.height.toDouble(), height: widget.height.toDouble(),
@@ -431,7 +439,21 @@ class _Thumbnail extends State<Thumbnail> {
width: widget.width.toDouble(), width: widget.width.toDouble(),
height: widget.height.toDouble(), height: widget.height.toDouble(),
child: img), child: img),
moreVertMenu widget.isSelectMode ? Container() : moreVertMenu,
Visibility(
visible: widget.isSelectMode,
child: const ModalBarrier()),
widget.isSelectMode
? Center(
child: Checkbox(
value: widget.isSelected,
onChanged: (v) {
if (widget.onSelectedChange != null &&
v != null) {
widget.onSelectedChange!(v);
}
}))
: Container(),
]) ])
: Center( : Center(
child: Column( child: Column(

View File

@@ -6,11 +6,19 @@ import 'thumbnail.dart';
class ThumbnailGridView extends StatelessWidget { class ThumbnailGridView extends StatelessWidget {
const ThumbnailGridView(this.gdata, this.gridDelegate, const ThumbnailGridView(this.gdata, this.gridDelegate,
{super.key, this.files, this.gid}); {super.key,
this.files,
this.gid,
this.isSelectMode = false,
this.selected,
this.onSelectedChange});
final GalleryData gdata; final GalleryData gdata;
final int? gid; final int? gid;
final EhFiles? files; final EhFiles? files;
final SliverGridDelegate gridDelegate; final SliverGridDelegate gridDelegate;
final bool isSelectMode;
final List<String>? selected;
final Function? onSelectedChange;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@@ -27,13 +35,27 @@ class ThumbnailGridView extends StatelessWidget {
final key = Key("thumbnail$gid-${page.index}-$fileId"); final key = Key("thumbnail$gid-${page.index}-$fileId");
return Container( return Container(
padding: const EdgeInsets.all(4), padding: const EdgeInsets.all(4),
child: Thumbnail(page, child: Thumbnail(
key: key, page,
fileId: fileId, key: key,
gid: gid, fileId: fileId,
index: page.index, gid: gid,
files: files, index: page.index,
gdata: gdata)); files: files,
gdata: gdata,
isSelectMode: isSelectMode,
isSelected: selected?.contains(page.token) ?? false,
onSelectedChange: (v) {
if (v) {
selected?.add(page.token);
} else {
selected?.remove(page.token);
}
if (onSelectedChange != null) {
onSelectedChange!();
}
},
));
}); });
} }
} }

View File

@@ -143,6 +143,8 @@ enum MoreVertSettings {
markAsNsfw, markAsNsfw,
markAsSfw, markAsSfw,
taskManager, taskManager,
markAsAd,
markAsNonAd,
} }
void onMoreVertSettingsSelected(BuildContext context, MoreVertSettings value) { void onMoreVertSettingsSelected(BuildContext context, MoreVertSettings value) {
@@ -162,6 +164,12 @@ void onMoreVertSettingsSelected(BuildContext context, MoreVertSettings value) {
case MoreVertSettings.taskManager: case MoreVertSettings.taskManager:
context.push("/task_manager"); context.push("/task_manager");
break; break;
case MoreVertSettings.markAsAd:
GalleryPage.maybeOf(context)?.markAsAd(true);
break;
case MoreVertSettings.markAsNonAd:
GalleryPage.maybeOf(context)?.markAsAd(false);
break;
default: default:
break; break;
} }
@@ -171,24 +179,23 @@ List<PopupMenuEntry<MoreVertSettings>> buildMoreVertSettings(
BuildContext context) { BuildContext context) {
var list = <PopupMenuEntry<MoreVertSettings>>[]; var list = <PopupMenuEntry<MoreVertSettings>>[];
var path = GoRouterState.of(context).path; var path = GoRouterState.of(context).path;
final i18n = AppLocalizations.of(context)!;
if (auth.status != null && if (auth.status != null &&
auth.status!.noUser && auth.status!.noUser &&
prefs.getBool("skipCreateRootUser") == true && prefs.getBool("skipCreateRootUser") == true &&
path != "/create_root_user") { path != "/create_root_user") {
list.add(PopupMenuItem( list.add(PopupMenuItem(
value: MoreVertSettings.createRootUser, value: MoreVertSettings.createRootUser,
child: Text(AppLocalizations.of(context)!.createRootUser))); child: Text(i18n.createRootUser)));
} }
if (path == null || if (path == null ||
(path != "/settings" && !path!.startsWith("/settings/"))) { (path != "/settings" && !path!.startsWith("/settings/"))) {
list.add(PopupMenuItem( list.add(PopupMenuItem(
value: MoreVertSettings.settings, value: MoreVertSettings.settings, child: Text(i18n.settings)));
child: Text(AppLocalizations.of(context)!.settings)));
} }
if (path != "/task_manager" && auth.canManageTasks == true) { if (path != "/task_manager" && auth.canManageTasks == true) {
list.add(PopupMenuItem( list.add(PopupMenuItem(
value: MoreVertSettings.taskManager, value: MoreVertSettings.taskManager, child: Text(i18n.taskManager)));
child: Text(AppLocalizations.of(context)!.taskManager)));
} }
var showNsfw = prefs.getBool("showNsfw") ?? false; var showNsfw = prefs.getBool("showNsfw") ?? false;
list.add(PopupMenuItem( list.add(PopupMenuItem(
@@ -206,7 +213,7 @@ List<PopupMenuEntry<MoreVertSettings>> buildMoreVertSettings(
}); });
} }
}, },
title: Text(AppLocalizations.of(context)!.showNsfw), title: Text(i18n.showNsfw),
), ),
))); )));
var displayAd = prefs.getBool("displayAd") ?? false; var displayAd = prefs.getBool("displayAd") ?? false;
@@ -225,22 +232,30 @@ List<PopupMenuEntry<MoreVertSettings>> buildMoreVertSettings(
}); });
} }
}, },
title: Text(AppLocalizations.of(context)!.displayAd), title: Text(i18n.displayAd),
), ),
))); )));
if (path == "/gallery/:gid" && auth.canEditGallery == true) { if (path == "/gallery/:gid" && auth.canEditGallery == true) {
list.add(const PopupMenuDivider()); list.add(const PopupMenuDivider());
final isAllNsfw = GalleryPage.of(context).isAllNsfw; final gp = GalleryPage.of(context);
if (isAllNsfw != null) { if (!gp.isSelectMode && gp.isAllNsfw != null) {
list.add(PopupMenuItem( list.add(PopupMenuItem(
value: isAllNsfw value: gp.isAllNsfw!
? MoreVertSettings.markAsSfw ? MoreVertSettings.markAsSfw
: MoreVertSettings.markAsNsfw, : MoreVertSettings.markAsNsfw,
child: Text(isAllNsfw child: Text(gp.isAllNsfw! ? i18n.markAsSfw : i18n.markAsNsfw),
? AppLocalizations.of(context)!.markAsSfw
: AppLocalizations.of(context)!.markAsNsfw),
)); ));
} }
if (gp.isSelectMode) {
list.add(PopupMenuItem(
value: MoreVertSettings.markAsNsfw, child: Text(i18n.markAsNsfw)));
list.add(PopupMenuItem(
value: MoreVertSettings.markAsSfw, child: Text(i18n.markAsSfw)));
list.add(PopupMenuItem(
value: MoreVertSettings.markAsAd, child: Text(i18n.markAsAd)));
list.add(PopupMenuItem(
value: MoreVertSettings.markAsNonAd, child: Text(i18n.markAsNonAd)));
}
} }
return list; return list;
} }

View File

@@ -44,14 +44,25 @@ class _GalleryPage extends State<GalleryPage>
Object? _error; Object? _error;
CancelToken? _cancel; CancelToken? _cancel;
CancelToken? _markAsNsfwCancel; CancelToken? _markAsNsfwCancel;
CancelToken? _markAsAdCancel;
bool _isLoading = false; bool _isLoading = false;
bool _isSelectMode = false;
final List<String> _selected = [];
bool? get isAllNsfw => _data?.isAllNsfw; bool? get isAllNsfw => _data?.isAllNsfw;
bool get isSelectMode => _isSelectMode;
Future<void> markGalleryAsNsfw(bool isNsfw) async { Future<void> markGalleryAsNsfw(bool isNsfw) async {
try { try {
_markAsNsfwCancel = CancelToken(); _markAsNsfwCancel = CancelToken();
(await api.updateGalleryFileMeta(_gid, if (_isSelectMode) {
isNsfw: isNsfw, cancel: _markAsNsfwCancel)) if (_selected.isEmpty) return;
.unwrap(); (await api.updateFilesMeta(_selected.join(","),
isNsfw: isNsfw, cancel: _markAsNsfwCancel))
.unwrap();
} else {
(await api.updateGalleryFileMeta(_gid,
isNsfw: isNsfw, cancel: _markAsNsfwCancel))
.unwrap();
}
if (!_markAsNsfwCancel!.isCancelled) { if (!_markAsNsfwCancel!.isCancelled) {
_fetchData(); _fetchData();
} }
@@ -62,6 +73,23 @@ class _GalleryPage extends State<GalleryPage>
} }
} }
Future<void> markAsAd(bool isAd) async {
if (!_isSelectMode || _selected.isEmpty) return;
try {
_markAsAdCancel = CancelToken();
(await api.updateFilesMeta(_selected.join(","),
isAd: isAd, cancel: _markAsAdCancel))
.unwrap();
if (!_markAsAdCancel!.isCancelled) {
_fetchData();
}
} catch (e) {
if (!_markAsAdCancel!.isCancelled) {
_log.warning("Failed to mark gallery $_gid:", e);
}
}
}
Future<void> _fetchData() async { Future<void> _fetchData() async {
try { try {
_cancel = CancelToken(); _cancel = CancelToken();
@@ -76,6 +104,7 @@ class _GalleryPage extends State<GalleryPage>
setState(() { setState(() {
_files = fileData; _files = fileData;
_isLoading = false; _isLoading = false;
_selected.clear();
}); });
} }
} catch (e) { } catch (e) {
@@ -138,11 +167,22 @@ class _GalleryPage extends State<GalleryPage>
body: isLoading body: isLoading
? const Center(child: CircularProgressIndicator()) ? const Center(child: CircularProgressIndicator())
: _data != null : _data != null
? GalleryInfo(_data!, ? GalleryInfo(
files: _files, refreshIndicatorKey: _refreshIndicatorKey, _data!,
files: _files,
refreshIndicatorKey: _refreshIndicatorKey,
onRefresh: () async { onRefresh: () async {
await _fetchData(); await _fetchData();
}) },
onSelectChanged: (v) {
setState(() {
_isSelectMode = v;
if (v) _selected.clear();
});
},
isSelectMode: _isSelectMode,
selected: _selected,
)
: Center( : Center(
child: Text("Error: $_error"), child: Text("Error: $_error"),
)); ));
@@ -152,6 +192,7 @@ class _GalleryPage extends State<GalleryPage>
void dispose() { void dispose() {
_cancel?.cancel(); _cancel?.cancel();
_markAsNsfwCancel?.cancel(); _markAsNsfwCancel?.cancel();
_markAsAdCancel?.cancel();
super.dispose(); super.dispose();
} }
} }