完成添加收藏的功能

main
落雨楓 2 years ago
parent 4a0076d97d
commit 5ad50bbc58

@ -73,7 +73,7 @@ class MWResponse<T> {
class MWApi {
static Future<MWResponse<Map<String, dynamic>>> get(String action,
{Map<String, dynamic>? params}) async {
{Map<String, dynamic>? params, bool returnRoot = false}) async {
Map<String, String> paramsStr =
params?.map((key, value) => MapEntry(key, value.toString())) ?? {};
paramsStr.addAll({
@ -109,11 +109,11 @@ class MWApi {
}
}
return parseMWResponse(action, resText);
return parseMWResponse(action, resText, returnRoot);
}
static Future<MWResponse<Map<String, dynamic>>> post(String action,
{Map<String, dynamic>? params, String? withToken}) async {
static Future<MWResponse<Map<String, dynamic>>> post<T>(String action,
{Map<String, dynamic>? params, String? withToken, bool returnRoot = false}) async {
params ??= {};
params.addAll({
"action": action,
@ -154,10 +154,11 @@ class MWApi {
}
}
return parseMWResponse(action, resText);
return parseMWResponse(action, resText, returnRoot);
}
static MWResponse<Map<String, dynamic>> parseMWResponse(String action, String resJson) {
static MWResponse<Map<String, dynamic>> parseMWResponse(
String action, String resJson, bool returnRoot) {
var resData = jsonDecode(resJson);
if (resData is! Map<String, dynamic>) {
throw MWApiEmptyBodyException(resData);
@ -169,12 +170,22 @@ class MWApi {
}
//
if (!resData.containsKey(action) || resData[action] is! Map<String, dynamic>) {
throw MWApiEmptyBodyException(resData);
MWResponse<Map<String, dynamic>> mwRes;
if (returnRoot) {
var filteredResData = <String, dynamic>{}..addAll(resData);
filteredResData
..remove("error")
..remove("warnings")
..remove("batchcomplete");
mwRes = MWResponse(filteredResData);
} else {
if (!resData.containsKey(action) || resData[action] is! Map<String, dynamic>) {
throw MWApiEmptyBodyException(resData);
}
mwRes = MWResponse(resData[action] as Map<String, dynamic>);
}
MWResponse<Map<String, dynamic>> mwRes = MWResponse(resData[action] as Map<String, dynamic>);
// warnings
if (resData.containsKey("warnings") && resData["warnings"] is Map<String, dynamic>) {
var warnings = resData["warnings"] as Map<String, dynamic>;

@ -3,21 +3,28 @@ import 'package:isekai_wiki/api/response/watch.dart';
import 'mw_api.dart';
class MWApiWatch {
static Future<MWResponse<List<WatchActionResponseList>>> watchPage(List<String> titles,
{bool unwatch = false}) async {
var query = {"titles": titles.join("|")};
static Future<MWResponse<List<WatchActionResponseList>>> watchPage(
{List<String>? titles, List<int>? pageIds, bool unwatch = false}) async {
Map<String, dynamic> query = {};
if (titles != null) {
query["titles"] = titles.join("|");
}
if (pageIds != null) {
query["pageids"] = pageIds.join("|");
}
if (unwatch) {
query["unwatch"] = "1";
}
var mwRes = await MWApi.post("watch", params: query, withToken: "watch");
var mwRes = await MWApi.post("watch", params: query, withToken: "watch", returnRoot: true);
var data = WatchActionResponse.fromJson(mwRes.data);
return mwRes.replaceData(data.watch);
}
static Future<MWResponse<List<WatchActionResponseList>>> unwatchPage(List<String> titles) {
return watchPage(titles, unwatch: true);
static Future<MWResponse<List<WatchActionResponseList>>> unwatchPage(
{List<String>? titles, List<int>? pageIds, bool unwatch = false}) {
return watchPage(titles: titles, pageIds: pageIds, unwatch: true);
}
}

@ -12,11 +12,11 @@ class WatchActionResponseList with _$WatchActionResponseList {
factory WatchActionResponseList(
{required int ns,
required String title,
String? watch,
String? unwatch}) = _WatchActionResponseList;
bool? watched,
bool? unwatched}) = _WatchActionResponseList;
bool get isWatch {
return watch != null;
bool get isWatched {
return watched == true;
}
factory WatchActionResponseList.fromJson(Map<String, dynamic> json) =>

@ -9,8 +9,7 @@ import 'package:get/get.dart';
const double _DEFAULT_SCALE_MIN_VALUE = 0.96;
const double _DEFAULT_OPACITY_MIN_VALUE = 0.90;
final Curve _DEFAULT_SCALE_CURVE =
CurveSpring(); // ignore: non_constant_identifier_names
final Curve _DEFAULT_SCALE_CURVE = CurveSpring(); // ignore: non_constant_identifier_names
const Curve _DEFAULT_OPACITY_CURVE = Curves.ease;
const Duration _DEFAULT_DURATION = Duration(milliseconds: 250);
@ -53,8 +52,7 @@ class ScaleTap extends StatefulWidget {
_ScaleTapState createState() => _ScaleTapState();
}
class _ScaleTapState extends State<ScaleTap>
with SingleTickerProviderStateMixin {
class _ScaleTapState extends State<ScaleTap> with SingleTickerProviderStateMixin {
late AnimationController _animationController;
late Animation<double> _scale;
late Animation<double> _opacity;
@ -73,8 +71,7 @@ class _ScaleTapState extends State<ScaleTap>
_animationController = AnimationController(vsync: this);
_scale = Tween<double>(begin: 1.0, end: 1.0).animate(_animationController);
_opacity =
Tween<double>(begin: 1.0, end: 1.0).animate(_animationController);
_opacity = Tween<double>(begin: 1.0, end: 1.0).animate(_animationController);
}
@override
@ -91,18 +88,14 @@ class _ScaleTapState extends State<ScaleTap>
begin: _scale.value,
end: scale,
).animate(CurvedAnimation(
curve: widget.scaleCurve ??
ScaleTapConfig.scaleCurve ??
_DEFAULT_SCALE_CURVE,
curve: widget.scaleCurve ?? ScaleTapConfig.scaleCurve ?? _DEFAULT_SCALE_CURVE,
parent: _animationController,
));
_opacity = Tween<double>(
begin: _opacity.value,
end: opacity,
).animate(CurvedAnimation(
curve: widget.opacityCurve ??
ScaleTapConfig.opacityCurve ??
_DEFAULT_OPACITY_CURVE,
curve: widget.opacityCurve ?? ScaleTapConfig.opacityCurve ?? _DEFAULT_OPACITY_CURVE,
parent: _animationController,
));
_animationController.reset();
@ -114,8 +107,7 @@ class _ScaleTapState extends State<ScaleTap>
return await anim(
scale: 1.0,
opacity: 1.0,
duration:
widget.duration ?? ScaleTapConfig.duration ?? _DEFAULT_DURATION,
duration: widget.duration ?? ScaleTapConfig.duration ?? _DEFAULT_DURATION,
);
}
}
@ -127,12 +119,9 @@ class _ScaleTapState extends State<ScaleTap>
_scaleAnimating = true;
await anim(
scale: widget.scaleMinValue ??
ScaleTapConfig.scaleMinValue ??
_DEFAULT_SCALE_MIN_VALUE,
opacity: widget.opacityMinValue ??
ScaleTapConfig.opacityMinValue ??
_DEFAULT_OPACITY_MIN_VALUE,
scale: widget.scaleMinValue ?? ScaleTapConfig.scaleMinValue ?? _DEFAULT_SCALE_MIN_VALUE,
opacity:
widget.opacityMinValue ?? ScaleTapConfig.opacityMinValue ?? _DEFAULT_OPACITY_MIN_VALUE,
duration: widget.duration ?? ScaleTapConfig.duration ?? _DEFAULT_DURATION,
);
@ -152,8 +141,7 @@ class _ScaleTapState extends State<ScaleTap>
@override
Widget build(BuildContext context) {
final bool isTapEnabled =
widget.onPressed != null || widget.onLongPress != null;
final bool isTapEnabled = widget.onPressed != null || widget.onLongPress != null;
return AnimatedBuilder(
animation: _animationController,
@ -219,9 +207,10 @@ class ScaleTapIgnore extends StatelessWidget {
c.ignoredAreaPressing = true;
},
child: GestureDetector(
child: child,
behavior: HitTestBehavior.opaque,
onTap: () {},
onLongPress: () {},
child: child,
),
);
}

@ -11,8 +11,7 @@ import 'package:skeletons/skeletons.dart';
import '../styles.dart';
typedef AddFavoriteCallback = Future<bool> Function(
PageInfo pageInfo, bool localIsFavorite, bool showToast);
typedef AddFavoriteCallback = Future<bool> Function(PageInfo pageInfo, bool localIsFavorite);
typedef PageInfoCallback = Future<void> Function(PageInfo pageInfo);
@ -41,7 +40,7 @@ class PageCard extends StatelessWidget {
Future<bool> handleFavoriteClick(bool localIsFavorite) async {
if (pageInfo != null && onSetFavorite != null) {
return await onSetFavorite!.call(pageInfo!, localIsFavorite, false);
return await onSetFavorite!.call(pageInfo!, !localIsFavorite);
} else {
return false;
}
@ -49,13 +48,13 @@ class PageCard extends StatelessWidget {
Future<void> handleAddFavoriteMenuItemClick() async {
if (pageInfo != null && onSetFavorite != null) {
await onSetFavorite!.call(pageInfo!, true, true);
await onSetFavorite!.call(pageInfo!, true);
}
}
Future<void> handleRemoveFavoriteMenuItemClick() async {
if (pageInfo != null && onSetFavorite != null) {
await onSetFavorite!.call(pageInfo!, false, true);
await onSetFavorite!.call(pageInfo!, false);
}
}
@ -249,7 +248,7 @@ class PageCard extends StatelessWidget {
: PullDownMenuItem(
title: '收藏',
icon: CupertinoIcons.heart,
onTap: handleRemoveFavoriteMenuItemClick,
onTap: handleAddFavoriteMenuItemClick,
),
const PullDownMenuDivider(),
PullDownMenuItem(

@ -132,12 +132,11 @@ class RecentPageList extends StatelessWidget {
}
Widget _buildPageCard(
int index, PageInfo pageInfo, RecentPageListController c, FavoriteListController fc) {
int index, PageInfo pageInfo, RecentPageListController c, FavoriteListController flc) {
return PageCard(
key: ValueKey("rpl-card-$index"),
pageInfo: c.pageList[index],
isFavorite: fc.isFavorite(c.pageList[index]),
onSetFavorite: fc.setFavorite,
isFavorite: flc.isFavorite(c.pageList[index]),
onSetFavorite: flc.setFavoriteImmediate,
);
}
@ -161,7 +160,7 @@ class RecentPageList extends StatelessWidget {
//
if (index < c.pageList.length) {
return _buildPageCard(index, c.pageList[index], c, flc);
return Obx(() => _buildPageCard(index, c.pageList[index], c, flc));
} else if (index == c.pageList.length) {
//
return Padding(

@ -64,8 +64,8 @@ Future<void> postInit() async {
Future<void> main() async {
await init();
Get.put(LifeCycleController());
Get.put(SiteConfigController());
Get.put(LifeCycleController());
runApp(const IsekaiWikiApp());

@ -1,10 +1,18 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/foundation.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:get/get.dart';
import 'package:isekai_wiki/api/mw/watch.dart';
import 'package:isekai_wiki/api/response/page_info.dart';
import 'package:isekai_wiki/api/response/watch.dart';
import 'package:isekai_wiki/models/user.dart';
import 'package:isekai_wiki/utils/dialog.dart';
import 'package:isekai_wiki/utils/error.dart';
class FavoriteListController extends GetxController {
var pageIds = RxList<int>();
bool syncRunning = false;
var favPageIds = RxList<int>();
void updateFromPageList(List<PageInfo> list) {
List<int> addList = [];
@ -19,14 +27,14 @@ class FavoriteListController extends GetxController {
}
}
var newPageIds = pageIds.where((pageId) => !removeList.contains(pageId)).toList();
var newPageIds = favPageIds.where((pageId) => !removeList.contains(pageId)).toList();
for (var pageId in addList) {
if (!newPageIds.contains(pageId)) {
newPageIds.add(pageId);
}
}
pageIds.value = newPageIds;
favPageIds.value = newPageIds;
}
void updateFromWatchList(List<PageInfo> list) {
@ -36,29 +44,103 @@ class FavoriteListController extends GetxController {
}
for (var pageId in addList) {
if (!pageIds.contains(pageId)) {
pageIds.add(pageId);
if (!favPageIds.contains(pageId)) {
favPageIds.add(pageId);
}
}
}
bool isFavorite(PageInfo pageInfo) {
return pageIds.contains(pageInfo.pageid);
return favPageIds.contains(pageInfo.pageid);
}
Future<bool> setFavorite(PageInfo pageInfo, bool isFavorite, bool showToast) async {
Future<void> setFavorite(PageInfo pageInfo, bool isFavorite, bool showToast) async {
syncRunning = true;
List<WatchActionResponseList> watchRes = [];
if (isFavorite) {
//
try {
var res = await MWApiWatch.watchPage(pageIds: [pageInfo.pageid]);
watchRes = res.data;
//
if (!favPageIds.contains(pageInfo.pageid)) {
favPageIds.add(pageInfo.pageid);
}
} catch (err, stack) {
alert(Get.overlayContext!, ErrorUtils.getErrorMessage(err), title: "错误");
if (kDebugMode) {
print("Exception in logout: $err");
stack.printError();
}
}
} else {
//
try {
var res = await MWApiWatch.unwatchPage(pageIds: [pageInfo.pageid]);
watchRes = res.data;
} catch (err, stack) {
alert(Get.overlayContext!, ErrorUtils.getErrorMessage(err), title: "错误");
if (kDebugMode) {
print("Exception in logout: $err");
stack.printError();
}
}
}
if (watchRes.isNotEmpty) {
if (watchRes[0].isWatched) {
//
if (!favPageIds.contains(pageInfo.pageid)) {
favPageIds.add(pageInfo.pageid);
}
if (showToast) {
Fluttertoast.showToast(
msg: "已将《${pageInfo.displayTitle}》添加到收藏列表",
toastLength: Toast.LENGTH_LONG,
gravity: ToastGravity.BOTTOM,
timeInSecForIosWeb: 1,
fontSize: 16.0,
backgroundColor: CupertinoColors.black.withOpacity(0.8),
textColor: CupertinoColors.white,
);
}
} else {
//
if (favPageIds.contains(pageInfo.pageid)) {
favPageIds.remove(pageInfo.pageid);
}
if (showToast) {
Fluttertoast.showToast(
msg: "已将《${pageInfo.displayTitle}》从收藏列表中移除",
toastLength: Toast.LENGTH_LONG,
gravity: ToastGravity.BOTTOM,
timeInSecForIosWeb: 1,
fontSize: 16.0,
backgroundColor: CupertinoColors.black.withOpacity(0.8),
textColor: CupertinoColors.white,
);
}
}
}
syncRunning = false;
}
///
Future<bool> setFavoriteImmediate(PageInfo pageInfo, bool isFavorite) async {
//
var uc = Get.find<UserController>();
if (!uc.isLoggedIn) {
alert(Get.overlayContext!, "使用收藏功能需要登录", title: "提示");
return isFavorite;
return !isFavorite;
}
if (isFavorite) {
//
} else {
//
if (syncRunning) {
alert(Get.overlayContext!, "正在同步收藏列表,请稍后再试", title: "提示");
return !isFavorite;
}
return !isFavorite;
setFavorite(pageInfo, isFavorite, true);
// UI
return isFavorite;
}
}

@ -137,6 +137,7 @@ class SiteConfigController extends GetxController {
loadFromEntity(siteConfigData);
isAppActive = true;
Global.isAppActive = true;
saveToStorage();
}
}

@ -15,6 +15,13 @@ packages:
url: "https://pub.flutter-io.cn"
source: hosted
version: "5.2.0"
animated_snack_bar:
dependency: "direct main"
description:
name: animated_snack_bar
url: "https://pub.flutter-io.cn"
source: hosted
version: "0.3.0"
animations:
dependency: "direct main"
description:
@ -385,6 +392,13 @@ packages:
description: flutter
source: sdk
version: "0.0.0"
fluttertoast:
dependency: "direct main"
description:
name: fluttertoast
url: "https://pub.flutter-io.cn"
source: hosted
version: "8.1.2"
freezed:
dependency: "direct dev"
description:

@ -31,8 +31,8 @@ environment:
dependencies:
flutter:
sdk: flutter
flutter_localizations: # Add this line
sdk: flutter # Add this line
flutter_localizations:
sdk: flutter
# The following adds the Cupertino Icons font to your application.
# Use with the CupertinoIcons class for iOS style icons.
@ -48,6 +48,8 @@ dependencies:
like_button: ^2.0.4
skeletons: ^0.0.3
modal_bottom_sheet: ^2.1.2
fluttertoast: ^8.1.2
animated_snack_bar: ^0.3.0
responsive_builder: ^0.4.3
url_launcher: ^6.1.7
flutter_web_browser: ^0.17.1

Loading…
Cancel
Save