diff --git a/lib/http/api.dart b/lib/http/api.dart index 2b11af601..35ac12013 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -695,7 +695,9 @@ class Api { static const String removeMsg = '/session_svr/v1/session_svr/remove_session'; - static const String removeSysMsg = '/x/sys-msg/del_notify_list'; + static const String delSysMsg = '/x/sys-msg/del_notify_list'; + + static const String delMsgfeed = '/x/msgfeed/del'; static const String setTop = '/session_svr/v1/session_svr/set_top'; diff --git a/lib/http/msg.dart b/lib/http/msg.dart index 7ef58a684..d38e526c7 100644 --- a/lib/http/msg.dart +++ b/lib/http/msg.dart @@ -331,12 +331,41 @@ class MsgHttp { } } - static Future removeSysMsg( + static Future delMsgfeed( + int tp, dynamic id, ) async { String csrf = await Request.getCsrf(); var res = await Request().post( - HttpString.messageBaseUrl + Api.removeSysMsg, + Api.delMsgfeed, + data: { + 'tp': tp, + 'id': id, + 'build': 0, + 'mobi_app': 'web', + 'csrf_token': csrf, + 'csrf': csrf, + }, + options: Options( + contentType: Headers.formUrlEncodedContentType, + ), + ); + if (res.data['code'] == 0) { + return {'status': true}; + } else { + return { + 'status': false, + 'msg': res.data['message'], + }; + } + } + + static Future delSysMsg( + dynamic id, + ) async { + String csrf = await Request.getCsrf(); + var res = await Request().post( + HttpString.messageBaseUrl + Api.delSysMsg, queryParameters: { 'mobi_app': 'android', 'csrf': csrf, diff --git a/lib/pages/msg_feed_top/at_me/controller.dart b/lib/pages/msg_feed_top/at_me/controller.dart index aafd0d30c..de18054c9 100644 --- a/lib/pages/msg_feed_top/at_me/controller.dart +++ b/lib/pages/msg_feed_top/at_me/controller.dart @@ -3,6 +3,7 @@ import 'package:PiliPlus/pages/common/common_controller.dart'; import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/http/msg.dart'; import 'package:PiliPlus/models/msg/msgfeed_at_me.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; class AtMeController extends CommonController { int cursor = -1; @@ -40,4 +41,18 @@ class AtMeController extends CommonController { @override Future customGetData() => MsgHttp.msgFeedAtMe(cursor: cursor, cursorTime: cursorTime); + + Future onRemove(dynamic id, int index) async { + try { + var res = await MsgHttp.delMsgfeed(2, id); + if (res['status']) { + List list = (loadingState.value as Success).response; + list.removeAt(index); + loadingState.value = LoadingState.success(list); + SmartDialog.showToast('删除成功'); + } else { + SmartDialog.showToast(res['msg']); + } + } catch (_) {} + } } diff --git a/lib/pages/msg_feed_top/at_me/view.dart b/lib/pages/msg_feed_top/at_me/view.dart index 0606086de..54cef8c3e 100644 --- a/lib/pages/msg_feed_top/at_me/view.dart +++ b/lib/pages/msg_feed_top/at_me/view.dart @@ -1,3 +1,4 @@ +import 'package:PiliPlus/common/widgets/dialog.dart'; import 'package:PiliPlus/common/widgets/loading_widget.dart'; import 'package:PiliPlus/common/widgets/network_img_layer.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; @@ -47,42 +48,65 @@ class _AtMePageState extends State { if (index == loadingState.response.length - 1) { _atMeController.onLoadMore(); } + final item = loadingState.response[index]; return ListTile( onTap: () { - String? nativeUri = - loadingState.response[index].item?.nativeUri; + String? nativeUri = item.item?.nativeUri; if (nativeUri != null) { PiliScheme.routePushFromUrl(nativeUri); } }, + onLongPress: () { + showConfirmDialog( + context: context, + title: '确定删除该通知?', + onConfirm: () { + _atMeController.onRemove(item.id, index); + }, + ); + }, leading: GestureDetector( onTap: () { - Get.toNamed( - '/member?mid=${loadingState.response[index].user?.mid}'); + Get.toNamed('/member?mid=${item.user?.mid}'); }, child: NetworkImgLayer( width: 45, height: 45, type: 'avatar', - src: loadingState.response[index].user?.avatar, + src: item.user?.avatar, ), ), - title: Text( - "${loadingState.response[index].user?.nickname} " - "在${loadingState.response[index].item?.business}中@了我", - style: Theme.of(context).textTheme.bodyMedium!.copyWith( - color: Theme.of(context).colorScheme.primary, + title: Text.rich( + TextSpan( + children: [ + TextSpan( + text: "${item.user?.nickname}", + style: Theme.of(context) + .textTheme + .titleSmall! + .copyWith( + color: Theme.of(context).colorScheme.primary, + ), ), + TextSpan( + text: " 在${item.item?.business}中@了我", + style: + Theme.of(context).textTheme.titleSmall!.copyWith( + color: Theme.of(context) + .colorScheme + .onSurfaceVariant, + ), + ), + ], + ), ), subtitle: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - if ((loadingState.response[index].item?.sourceContent - as String?) - ?.isNotEmpty == + if ((item.item?.sourceContent as String?)?.isNotEmpty == true) ...[ const SizedBox(height: 4), - Text(loadingState.response[index].item?.sourceContent, + Text(item.item?.sourceContent, maxLines: 3, overflow: TextOverflow.ellipsis, style: Theme.of(context) @@ -94,20 +118,20 @@ class _AtMePageState extends State { ], const SizedBox(height: 4), Text( - Utils.dateFormat(loadingState.response[index].atTime), + Utils.dateFormat(item.atTime), style: Theme.of(context).textTheme.bodyMedium!.copyWith( + fontSize: 13, color: Theme.of(context).colorScheme.outline, ), ), ], ), - trailing: loadingState.response[index].item?.image != null && - loadingState.response[index].item?.image != "" + trailing: item.item?.image != null && item.item?.image != "" ? NetworkImgLayer( width: 45, height: 45, type: 'cover', - src: loadingState.response[index].item?.image, + src: item.item?.image, ) : null, ); diff --git a/lib/pages/msg_feed_top/like_me/controller.dart b/lib/pages/msg_feed_top/like_me/controller.dart index 1cb499ff9..658b3e8b7 100644 --- a/lib/pages/msg_feed_top/like_me/controller.dart +++ b/lib/pages/msg_feed_top/like_me/controller.dart @@ -4,6 +4,7 @@ import 'package:PiliPlus/pages/common/common_controller.dart'; import 'package:PiliPlus/http/msg.dart'; import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/models/msg/msgfeed_like_me.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; class LikeMeController extends CommonController { int cursor = -1; @@ -47,4 +48,23 @@ class LikeMeController extends CommonController { @override Future customGetData() => MsgHttp.msgFeedLikeMe(cursor: cursor, cursorTime: cursorTime); + + Future onRemove(dynamic id, int index, bool isLatest) async { + try { + var res = await MsgHttp.delMsgfeed(0, id); + if (res['status']) { + Pair, List> pair = + (loadingState.value as Success).response; + if (isLatest) { + pair.first.removeAt(index); + } else { + pair.second.removeAt(index); + } + loadingState.value = LoadingState.success(pair); + SmartDialog.showToast('删除成功'); + } else { + SmartDialog.showToast(res['msg']); + } + } catch (_) {} + } } diff --git a/lib/pages/msg_feed_top/like_me/view.dart b/lib/pages/msg_feed_top/like_me/view.dart index 52f6e5876..3bdab672f 100644 --- a/lib/pages/msg_feed_top/like_me/view.dart +++ b/lib/pages/msg_feed_top/like_me/view.dart @@ -1,3 +1,4 @@ +import 'package:PiliPlus/common/widgets/dialog.dart'; import 'package:PiliPlus/common/widgets/loading_widget.dart'; import 'package:PiliPlus/common/widgets/pair.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; @@ -42,165 +43,71 @@ class _LikeMePageState extends State { Success() => () { Pair, List> pair = loadingState.response; - - int length = pair.first.length + pair.second.length; - if (pair.first.isNotEmpty) { - length++; - } - if (pair.second.isNotEmpty) { - length++; - } - - LikeMeItems getCurrentItem(int index) { - if (pair.first.isEmpty) { - return pair.second[index - 1]; - } else { - return index <= pair.first.length - ? pair.first[index - 1] - : pair.second[index - pair.first.length - 2]; - } - } - - return length > 0 - ? ListView.separated( - itemCount: length, - padding: EdgeInsets.only( - bottom: MediaQuery.paddingOf(context).bottom + 80), - physics: const AlwaysScrollableScrollPhysics(), - itemBuilder: (context, int index) { - if (index == length - 1) { - _likeMeController.onLoadMore(); - } - - // title - if (index == 0) { - return Padding( - padding: const EdgeInsets.only(left: 16), - child: Text( - pair.first.isNotEmpty ? '最新' : '累计', - style: Theme.of(context) - .textTheme - .labelLarge! - .copyWith( - color: Theme.of(context).colorScheme.secondary, - ), - ), + List latest = pair.first; + List total = pair.second; + if (latest.isNotEmpty || total.isNotEmpty) { + return CustomScrollView( + physics: const AlwaysScrollableScrollPhysics(), + slivers: [ + if (latest.isNotEmpty) ...[ + _buildHeader('最新'), + SliverList.separated( + itemBuilder: (context, index) { + if (total.isEmpty && index == latest.length - 1) { + _likeMeController.onLoadMore(); + } + return _buildItem( + latest[index], + (id) { + _likeMeController.onRemove(id, index, true); + }, ); - } else if (pair.first.isNotEmpty && - index == pair.first.length + 1) { - return Padding( - padding: const EdgeInsets.only(left: 16), - child: Text( - "累计", - style: Theme.of(context) - .textTheme - .labelLarge! - .copyWith( - color: Theme.of(context).colorScheme.secondary, - ), - ), + }, + itemCount: latest.length, + separatorBuilder: (BuildContext context, int index) { + return Divider( + indent: 72, + endIndent: 20, + height: 6, + color: Colors.grey.withOpacity(0.1), ); - } - - // item - final item = getCurrentItem(index); - return ListTile( - onTap: () { - String? nativeUri = item.item?.nativeUri; - if (nativeUri != null) { - PiliScheme.routePushFromUrl(nativeUri); - } - }, - leading: Column( - children: [ - const Spacer(), - SizedBox( - width: 50, - height: 50, - child: Stack( - children: [ - for (var j = 0; - j < item.users!.length && j < 4; - j++) ...[ - Positioned( - left: 15 * (j % 2).toDouble(), - top: 15 * (j ~/ 2).toDouble(), - child: NetworkImgLayer( - width: - item.users!.length > 1 ? 30 : 45, - height: - item.users!.length > 1 ? 30 : 45, - type: 'avatar', - src: item.users![j].avatar, - )), - ] - ], - )), - const Spacer(), - ], - ), - title: Text( - // "${msgFeedLikeMeList[i].users!.map((e) => e.nickname).join("/")}" - "${item.users?[0].nickname}" - "${item.users!.length > 1 ? '、${item.users![1].nickname} 等' : ''} " - "${item.counts! > 1 ? '共 ${item.counts} 人' : ''}" - "赞了我的${item.item?.business}", - style: Theme.of(context).textTheme.titleSmall!.copyWith( - height: 1.5, - color: Theme.of(context).colorScheme.primary), - maxLines: 2, - overflow: TextOverflow.ellipsis, - ), - subtitle: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (item.item?.title?.isNotEmpty == true) ...[ - const SizedBox(height: 4), - Text(item.item!.title!, - maxLines: 3, - overflow: TextOverflow.ellipsis, - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith( - color: Theme.of(context) - .colorScheme - .outline, - height: 1.5)), - ], - const SizedBox(height: 4), - Text( - Utils.dateFormat(item.likeTime), - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith( - color: Theme.of(context).colorScheme.outline, - ), - ), - ], - ), - trailing: - item.item?.image != null && item.item?.image != "" - ? NetworkImgLayer( - width: 45, - height: 45, - type: 'cover', - src: item.item?.image, - ) - : null, - ); - }, - separatorBuilder: (BuildContext context, int index) { - return Divider( - indent: 72, - endIndent: 20, - height: 6, - color: Colors.grey.withOpacity(0.1), - ); - }, - ) - : scrollErrorWidget(callback: _likeMeController.onReload); + }, + ), + ], + if (total.isNotEmpty) ...[ + _buildHeader('累计'), + SliverList.separated( + itemBuilder: (context, index) { + if (index == total.length - 1) { + _likeMeController.onLoadMore(); + } + return _buildItem( + total[index], + (id) { + _likeMeController.onRemove(id, index, false); + }, + ); + }, + itemCount: total.length, + separatorBuilder: (BuildContext context, int index) { + return Divider( + indent: 72, + endIndent: 20, + height: 6, + color: Colors.grey.withOpacity(0.1), + ); + }, + ), + ], + SliverToBoxAdapter( + child: SizedBox( + height: MediaQuery.paddingOf(context).bottom + 80, + ), + ), + ], + ); + } + return scrollErrorWidget(callback: _likeMeController.onReload); }(), Error() => scrollErrorWidget( errMsg: loadingState.errMsg, @@ -209,4 +116,122 @@ class _LikeMePageState extends State { LoadingState() => throw UnimplementedError(), }; } + + Widget _buildHeader(String title) { + return SliverToBoxAdapter( + child: Padding( + padding: const EdgeInsets.only(left: 16), + child: Text( + title, + style: Theme.of(context).textTheme.labelLarge!.copyWith( + color: Theme.of(context).colorScheme.secondary, + ), + ), + ), + ); + } + + Widget _buildItem(LikeMeItems item, ValueChanged onRemove) { + return ListTile( + onTap: () { + String? nativeUri = item.item?.nativeUri; + if (nativeUri != null) { + PiliScheme.routePushFromUrl(nativeUri); + } + }, + onLongPress: () { + showConfirmDialog( + context: context, + title: '确定删除该通知?', + onConfirm: () { + onRemove(item.id); + }, + ); + }, + leading: Column( + children: [ + const Spacer(), + SizedBox( + width: 50, + height: 50, + child: Stack( + children: [ + for (var j = 0; + j < item.users!.length && j < 4; + j++) ...[ + Positioned( + left: 15 * (j % 2).toDouble(), + top: 15 * (j ~/ 2).toDouble(), + child: NetworkImgLayer( + width: item.users!.length > 1 ? 30 : 45, + height: item.users!.length > 1 ? 30 : 45, + type: 'avatar', + src: item.users![j].avatar, + )), + ] + ], + ), + ), + const Spacer(), + ], + ), + title: Text.rich( + TextSpan( + children: [ + TextSpan( + text: "${item.users![0].nickname}", + style: Theme.of(context).textTheme.titleSmall!.copyWith( + height: 1.5, color: Theme.of(context).colorScheme.primary), + ), + if (item.counts! > 1) + TextSpan( + text: ' 等${item.counts}人', + style: Theme.of(context) + .textTheme + .titleSmall! + .copyWith(fontSize: 12, height: 1.5), + ), + TextSpan( + text: " 赞了我的${item.item?.business}", + style: Theme.of(context).textTheme.titleSmall!.copyWith( + height: 1.5, + color: Theme.of(context).colorScheme.onSurfaceVariant, + ), + ), + ], + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + subtitle: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (item.item?.title?.isNotEmpty == true) ...[ + const SizedBox(height: 4), + Text(item.item!.title!, + maxLines: 3, + overflow: TextOverflow.ellipsis, + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + color: Theme.of(context).colorScheme.outline, height: 1.5)), + ], + const SizedBox(height: 4), + Text( + Utils.dateFormat(item.likeTime), + style: Theme.of(context).textTheme.bodyMedium!.copyWith( + fontSize: 13, + color: Theme.of(context).colorScheme.outline, + ), + ), + ], + ), + trailing: item.item?.image != null && item.item?.image != "" + ? NetworkImgLayer( + width: 45, + height: 45, + type: 'cover', + src: item.item?.image, + ) + : null, + ); + } } diff --git a/lib/pages/msg_feed_top/reply_me/controller.dart b/lib/pages/msg_feed_top/reply_me/controller.dart index d8fc47d06..6c620629d 100644 --- a/lib/pages/msg_feed_top/reply_me/controller.dart +++ b/lib/pages/msg_feed_top/reply_me/controller.dart @@ -3,6 +3,7 @@ import 'package:PiliPlus/pages/common/common_controller.dart'; import 'package:PiliPlus/utils/extension.dart'; import 'package:PiliPlus/http/msg.dart'; import 'package:PiliPlus/models/msg/msgfeed_reply_me.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; class ReplyMeController extends CommonController { int cursor = -1; @@ -40,4 +41,18 @@ class ReplyMeController extends CommonController { @override Future customGetData() => MsgHttp.msgFeedReplyMe(cursor: cursor, cursorTime: cursorTime); + + Future onRemove(dynamic id, int index) async { + try { + var res = await MsgHttp.delMsgfeed(1, id); + if (res['status']) { + List list = (loadingState.value as Success).response; + list.removeAt(index); + loadingState.value = LoadingState.success(list); + SmartDialog.showToast('删除成功'); + } else { + SmartDialog.showToast(res['msg']); + } + } catch (_) {} + } } diff --git a/lib/pages/msg_feed_top/reply_me/view.dart b/lib/pages/msg_feed_top/reply_me/view.dart index 31c835222..5f7b78a9a 100644 --- a/lib/pages/msg_feed_top/reply_me/view.dart +++ b/lib/pages/msg_feed_top/reply_me/view.dart @@ -1,3 +1,4 @@ +import 'package:PiliPlus/common/widgets/dialog.dart'; import 'package:PiliPlus/common/widgets/loading_widget.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/http/loading_state.dart'; @@ -59,6 +60,15 @@ class _ReplyMePageState extends State { ); } }, + onLongPress: () { + showConfirmDialog( + context: context, + title: '确定删除该通知?', + onConfirm: () { + _replyMeController.onRemove(item.id, index); + }, + ); + }, leading: GestureDetector( onTap: () { Get.toNamed('/member?mid=${item.user?.mid}'); @@ -70,13 +80,38 @@ class _ReplyMePageState extends State { src: item.user?.avatar, ), ), - title: Text( - "${item.user?.nickname} ${item.isMulti == 1 ? '等人' : ''}" - "回复了我的${item.item?.business} ${item.isMulti == 1 ? ',共${item.counts}条' : ''}", - style: Theme.of(context) - .textTheme - .bodyMedium! - .copyWith(color: Theme.of(context).colorScheme.primary), + title: Text.rich( + TextSpan( + children: [ + TextSpan( + text: "${item.user?.nickname}", + style: Theme.of(context) + .textTheme + .titleSmall! + .copyWith( + color: Theme.of(context).colorScheme.primary), + ), + if (item.isMulti == 1) + TextSpan( + text: " 等人", + style: Theme.of(context) + .textTheme + .titleSmall! + .copyWith(fontSize: 12), + ), + TextSpan( + text: + " 对我的${item.item?.business}发布了${item.counts}条评论", + style: Theme.of(context) + .textTheme + .titleSmall! + .copyWith( + color: Theme.of(context) + .colorScheme + .onSurfaceVariant), + ), + ], + ), ), subtitle: Column( mainAxisAlignment: MainAxisAlignment.start, @@ -117,6 +152,7 @@ class _ReplyMePageState extends State { Text( Utils.dateFormat(item.replyTime), style: Theme.of(context).textTheme.bodyMedium!.copyWith( + fontSize: 13, color: Theme.of(context).colorScheme.outline, ), ), diff --git a/lib/pages/msg_feed_top/sys_msg/controller.dart b/lib/pages/msg_feed_top/sys_msg/controller.dart index 83a16e9aa..8cfbff111 100644 --- a/lib/pages/msg_feed_top/sys_msg/controller.dart +++ b/lib/pages/msg_feed_top/sys_msg/controller.dart @@ -40,7 +40,7 @@ class SysMsgController extends CommonController { Future onRemove(dynamic id, int index) async { try { - var res = await MsgHttp.removeSysMsg(id); + var res = await MsgHttp.delSysMsg(id); if (res['status']) { List list = (loadingState.value as Success).response; list.removeAt(index); diff --git a/lib/pages/msg_feed_top/sys_msg/view.dart b/lib/pages/msg_feed_top/sys_msg/view.dart index f6f2e6e16..30a9e0a9e 100644 --- a/lib/pages/msg_feed_top/sys_msg/view.dart +++ b/lib/pages/msg_feed_top/sys_msg/view.dart @@ -1,5 +1,6 @@ import 'dart:convert'; +import 'package:PiliPlus/common/widgets/dialog.dart'; import 'package:PiliPlus/common/widgets/loading_widget.dart'; import 'package:PiliPlus/common/widgets/refresh_indicator.dart'; import 'package:PiliPlus/http/loading_state.dart'; @@ -52,7 +53,8 @@ class _SysMsgPageState extends State { _sysMsgController.onLoadMore(); } - String? content = loadingState.response[index].content; + final item = loadingState.response[index]; + String? content = item.content; if (content != null) { try { dynamic jsonContent = json.decode(content); @@ -64,36 +66,16 @@ class _SysMsgPageState extends State { return ListTile( onTap: () {}, onLongPress: () { - showDialog( - context: context, - builder: (context) => AlertDialog( - title: const Text('确定删除该通知?'), - actions: [ - TextButton( - onPressed: Get.back, - child: Text( - '取消', - style: TextStyle( - color: - Theme.of(context).colorScheme.outline, - ), - ), - ), - TextButton( - onPressed: () { - Get.back(); - _sysMsgController.onRemove( - loadingState.response[index].id, - index, - ); - }, - child: const Text('确定'), - ), - ], - )); + showConfirmDialog( + context: context, + title: '确定删除该通知?', + onConfirm: () { + _sysMsgController.onRemove(item.id, index); + }, + ); }, title: Text( - "${loadingState.response[index].title}", + "${item.title}", style: Theme.of(context).textTheme.titleMedium, ), subtitle: Column( @@ -114,13 +96,14 @@ class _SysMsgPageState extends State { SizedBox( width: double.infinity, child: Text( - "${loadingState.response[index].timeAt}", + "${item.timeAt}", maxLines: 1, overflow: TextOverflow.ellipsis, style: Theme.of(context) .textTheme .bodyMedium! .copyWith( + fontSize: 13, color: Theme.of(context).colorScheme.outline, ), textAlign: TextAlign.end,