Add private novel bookmarks.

Close #14
This commit is contained in:
2025-01-30 14:43:40 +08:00
parent 6530f2c57d
commit 37f84efe05
4 changed files with 148 additions and 83 deletions

View File

@@ -152,6 +152,7 @@ abstract class MultiPageLoadingState<T extends StatefulWidget, S extends Object>
void firstLoad() { void firstLoad() {
loadData(_page).then((value) { loadData(_page).then((value) {
if (!mounted) return;
if(value.success) { if(value.success) {
_page++; _page++;
setState(() { setState(() {

View File

@@ -35,15 +35,15 @@ extension NovelExt on Network {
return getNovelsWithNextUrl(url); return getNovelsWithNextUrl(url);
} }
Future<Res<List<Novel>>> getBookmarkedNovels(String uid) { Future<Res<List<Novel>>> getBookmarkedNovels(String uid, bool public) {
return getNovelsWithNextUrl( return getNovelsWithNextUrl(
"/v1/user/bookmarks/novel?user_id=$uid&restrict=public"); "/v1/user/bookmarks/novel?user_id=$uid&restrict=${public ? "public" : "private"}");
} }
Future<Res<bool>> favoriteNovel(String id) async { Future<Res<bool>> favoriteNovel(String id, bool public) async {
var res = await apiPost("/v2/novel/bookmark/add", data: { var res = await apiPost("/v2/novel/bookmark/add", data: {
"novel_id": id, "novel_id": id,
"restrict": "public", "restrict": public ? "public" : "private",
}); });
if (res.error) { if (res.error) {
return Res.fromErrorRes(res); return Res.fromErrorRes(res);

View File

@@ -3,6 +3,7 @@ import 'package:pixes/appdata.dart';
import 'package:pixes/components/grid.dart'; import 'package:pixes/components/grid.dart';
import 'package:pixes/components/loading.dart'; import 'package:pixes/components/loading.dart';
import 'package:pixes/components/novel.dart'; import 'package:pixes/components/novel.dart';
import 'package:pixes/components/segmented_button.dart';
import 'package:pixes/components/title_bar.dart'; import 'package:pixes/components/title_bar.dart';
import 'package:pixes/foundation/widget_utils.dart'; import 'package:pixes/foundation/widget_utils.dart';
import 'package:pixes/network/network.dart'; import 'package:pixes/network/network.dart';
@@ -17,11 +18,41 @@ class NovelBookmarksPage extends StatefulWidget {
class _NovelBookmarksPageState class _NovelBookmarksPageState
extends MultiPageLoadingState<NovelBookmarksPage, Novel> { extends MultiPageLoadingState<NovelBookmarksPage, Novel> {
bool public = true;
@override
Widget? buildFrame(BuildContext context, Widget child) {
return Column(
children: [
TitleBar(
title: "Bookmarks".tl,
action: SegmentedButton(
options: [
SegmentedButtonOption("public", "Public".tl),
SegmentedButtonOption("private", "Private".tl),
],
onPressed: (key) {
var newPublic = key == "public";
if (newPublic != public) {
public = newPublic;
nextUrl = null;
reset();
}
},
value: public ? "public" : "private",
),
),
Expanded(
child: child,
)
],
);
}
@override @override
Widget buildContent(BuildContext context, List<Novel> data) { Widget buildContent(BuildContext context, List<Novel> data) {
return Column( return Column(
children: [ children: [
TitleBar(title: "Bookmarks".tl),
Expanded( Expanded(
child: GridViewWithFixedItemHeight( child: GridViewWithFixedItemHeight(
itemCount: data.length, itemCount: data.length,
@@ -45,7 +76,7 @@ class _NovelBookmarksPageState
Future<Res<List<Novel>>> loadData(int page) async { Future<Res<List<Novel>>> loadData(int page) async {
if (nextUrl == "end") return Res.error("No more data"); if (nextUrl == "end") return Res.error("No more data");
var res = nextUrl == null var res = nextUrl == null
? await Network().getBookmarkedNovels(appdata.account!.user.id) ? await Network().getBookmarkedNovels(appdata.account!.user.id, public)
: await Network().getNovelsWithNextUrl(nextUrl!); : await Network().getNovelsWithNextUrl(nextUrl!);
nextUrl = res.subData ?? "end"; nextUrl = res.subData ?? "end";
return res; return res;

View File

@@ -121,18 +121,18 @@ class _NovelPageState extends State<NovelPage> {
padding: const EdgeInsets.only(bottom: 10), padding: const EdgeInsets.only(bottom: 10),
child: Row( child: Row(
children: [ children: [
const SizedBox( const SizedBox(width: 2),
width: 2,
),
Expanded( Expanded(
child: Container( child: Container(
height: 68, height: 68,
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all( border: Border.all(
color: ColorScheme.of(context).outlineVariant, color: ColorScheme.of(context).outlineVariant,
width: 0.6), width: 0.6,
borderRadius: BorderRadius.circular(4)), ),
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), borderRadius: BorderRadius.circular(4),
),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: Row( child: Row(
children: [ children: [
Column( Column(
@@ -148,31 +148,31 @@ class _NovelPageState extends State<NovelPage> {
) )
], ],
), ),
const SizedBox( const SizedBox(width: 8),
width: 12,
),
Text( Text(
widget.novel.totalViews.toString(), widget.novel.totalViews.toString(),
style: TextStyle( style: TextStyle(
color: ColorScheme.of(context).primary, color: ColorScheme.of(context).primary,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
fontSize: 18), fontSize: 18,
),
) )
], ],
), ),
), ),
), ),
const SizedBox( const SizedBox(width: 16),
width: 16,
),
Expanded( Expanded(
child: Container( child: Container(
height: 68, height: 68,
decoration: BoxDecoration( decoration: BoxDecoration(
border: Border.all( border: Border.all(
color: ColorScheme.of(context).outlineVariant, width: 0.6), color: ColorScheme.of(context).outlineVariant,
borderRadius: BorderRadius.circular(4)), width: 0.6,
padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), ),
borderRadius: BorderRadius.circular(4),
),
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: Row( child: Row(
children: [ children: [
Column( Column(
@@ -188,22 +188,19 @@ class _NovelPageState extends State<NovelPage> {
) )
], ],
), ),
const SizedBox( const SizedBox(width: 8),
width: 12,
),
Text( Text(
widget.novel.totalBookmarks.toString(), widget.novel.totalBookmarks.toString(),
style: TextStyle( style: TextStyle(
color: ColorScheme.of(context).primary, color: ColorScheme.of(context).primary,
fontWeight: FontWeight.w500, fontWeight: FontWeight.w500,
fontSize: 18), fontSize: 18,
),
) )
], ],
), ),
)), )),
const SizedBox( const SizedBox(width: 2),
width: 2,
),
], ],
), ),
); );
@@ -241,25 +238,30 @@ class _NovelPageState extends State<NovelPage> {
), ),
), ),
const SizedBox(width: 12), const SizedBox(width: 12),
Column( Expanded(
crossAxisAlignment: CrossAxisAlignment.start, child: Column(
mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.start,
children: [ mainAxisAlignment: MainAxisAlignment.center,
Text(widget.novel.author.name, children: [
Text(
widget.novel.author.name,
style: const TextStyle( style: const TextStyle(
fontSize: 14, fontSize: 14,
fontWeight: FontWeight.bold, fontWeight: FontWeight.bold,
)), ),
Text( maxLines: 1,
widget.novel.createDate.toString().substring(0, 10), overflow: TextOverflow.ellipsis,
style: TextStyle(
fontSize: 12,
color: ColorScheme.of(context).outline,
), ),
), Text(
], widget.novel.createDate.toString().substring(0, 10),
style: TextStyle(
fontSize: 12,
color: ColorScheme.of(context).outline,
),
),
],
),
), ),
const Spacer(),
const Icon(MdIcons.chevron_right) const Icon(MdIcons.chevron_right)
], ],
), ),
@@ -271,15 +273,43 @@ class _NovelPageState extends State<NovelPage> {
bool isAddingFavorite = false; bool isAddingFavorite = false;
var favoriteFlyout = FlyoutController();
Widget buildActions() { Widget buildActions() {
void favorite() async { void favorite() async {
if (isAddingFavorite) return; if (isAddingFavorite) return;
bool? public;
if (!widget.novel.isBookmarked) {
await favoriteFlyout.showFlyout(
builder: (context) {
return MenuFlyout(
items: [
MenuFlyoutItem(
text: Text("Public".tl),
onPressed: () {
public = true;
},
),
MenuFlyoutItem(
text: Text("Private".tl),
onPressed: () {
public = false;
},
),
],
);
},
);
if (public == null) {
return;
}
}
setState(() { setState(() {
isAddingFavorite = true; isAddingFavorite = true;
}); });
var res = widget.novel.isBookmarked var res = widget.novel.isBookmarked
? await Network().deleteFavoriteNovel(widget.novel.id.toString()) ? await Network().deleteFavoriteNovel(widget.novel.id.toString())
: await Network().favoriteNovel(widget.novel.id.toString()); : await Network().favoriteNovel(widget.novel.id.toString(), public!);
if (res.error) { if (res.error) {
if (mounted) { if (mounted) {
context.showToast(message: res.errorMessage ?? "Network Error"); context.showToast(message: res.errorMessage ?? "Network Error");
@@ -337,38 +367,41 @@ class _NovelPageState extends State<NovelPage> {
context.to(() => NovelReadingPage(widget.novel)); context.to(() => NovelReadingPage(widget.novel));
}), }),
const SizedBox(width: 16), const SizedBox(width: 16),
Button( FlyoutTarget(
onPressed: favorite, controller: favoriteFlyout,
child: Row( child: Button(
mainAxisAlignment: constrains.maxWidth > 420 onPressed: favorite,
? MainAxisAlignment.start child: Row(
: MainAxisAlignment.center, mainAxisAlignment: constrains.maxWidth > 420
children: [ ? MainAxisAlignment.start
if (isAddingFavorite) : MainAxisAlignment.center,
const SizedBox( children: [
width: 18, if (isAddingFavorite)
height: 18, const SizedBox(
child: ProgressRing( width: 18,
strokeWidth: 2, height: 18,
), child: ProgressRing(
) strokeWidth: 2,
else if (widget.novel.isBookmarked) ),
Icon( )
MdIcons.favorite, else if (widget.novel.isBookmarked)
size: 18, Icon(
color: ColorScheme.of(context).error, MdIcons.favorite,
) size: 18,
else color: ColorScheme.of(context).error,
const Icon(MdIcons.favorite_outline, size: 18), )
if (constrains.maxWidth > 420) else
const SizedBox(width: 12), const Icon(MdIcons.favorite_outline, size: 18),
if (constrains.maxWidth > 420) Text("Favorite".tl) if (constrains.maxWidth > 420)
], const SizedBox(width: 12),
) if (constrains.maxWidth > 420) Text("Favorite".tl)
.fixWidth(shouldFillSpace ],
? width / 4 - 4 - kFluentButtonPadding )
: 64) .fixWidth(shouldFillSpace
.fixHeight(32), ? width / 4 - 4 - kFluentButtonPadding
: 64)
.fixHeight(32),
),
), ),
const SizedBox(width: 8), const SizedBox(width: 8),
Button( Button(