diff --git a/lib/http/api.dart b/lib/http/api.dart index 532ca3419..05fc022f6 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -328,6 +328,8 @@ class Api { // 获取指定分组下的up static const String followUpGroup = '/x/relation/tag'; + static const String msgFeedUnread = '/x/msgfeed/unread'; + /// 私聊 /// 'https://api.vc.bilibili.com/session_svr/v1/session_svr/get_sessions? /// session_type=1& diff --git a/lib/http/msg.dart b/lib/http/msg.dart index 70af5b559..e460852a0 100644 --- a/lib/http/msg.dart +++ b/lib/http/msg.dart @@ -5,6 +5,22 @@ import 'api.dart'; import 'init.dart'; class MsgHttp { + + static Future msgFeedUnread() async { + var res = await Request().get(Api.msgFeedUnread); + if (res.data['code'] == 0) { + return { + 'status': true, + 'data': res.data['data'], + }; + } else { + return { + 'status': false, + 'date': [], + 'msg': res.data['message'], + }; + } + } // 会话列表 static Future sessionList({int? endTs}) async { Map params = { diff --git a/lib/models/msg/msgfeed_unread.dart b/lib/models/msg/msgfeed_unread.dart new file mode 100644 index 000000000..df3622ee5 --- /dev/null +++ b/lib/models/msg/msgfeed_unread.dart @@ -0,0 +1,26 @@ +class MsgFeedUnread { + MsgFeedUnread({ + this.at = 0, + this.chat = 0, + this.like = 0, + this.reply = 0, + this.sys_msg = 0, + this.up = 0, + }); + + int at = 0; + int chat = 0; + int like = 0; + int reply = 0; + int sys_msg = 0; + int up = 0; + + MsgFeedUnread.fromJson(Map json) { + at = json['at'] ?? 0; + chat = json['chat'] ?? 0; + like = json['like'] ?? 0; + reply = json['reply'] ?? 0; + sys_msg = json['sys_msg'] ?? 0; + up = json['up'] ?? 0; + } +} diff --git a/lib/pages/whisper/controller.dart b/lib/pages/whisper/controller.dart index c82cab5b7..c88f96efd 100644 --- a/lib/pages/whisper/controller.dart +++ b/lib/pages/whisper/controller.dart @@ -1,12 +1,58 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:pilipala/http/msg.dart'; import 'package:pilipala/models/msg/account.dart'; import 'package:pilipala/models/msg/session.dart'; +import '../../models/msg/msgfeed_unread.dart'; + class WhisperController extends GetxController { RxList sessionList = [].obs; RxList accountList = [].obs; bool isLoading = false; + Rx msgFeedUnread = MsgFeedUnread().obs; + RxList msgFeedTop = [ + { + "name":"回复我的", + "icon":Icons.message_outlined, + "route": "/", + "value": 0 + }, + { + "name":"@我", + "icon":Icons.alternate_email_outlined, + "route": "/", + "value": 0 + }, + { + "name":"收到的赞", + "icon":Icons.favorite_border_outlined, + "route": "/", + "value": 0 + }, + { + "name":"系统通知", + "icon":Icons.notifications_none_outlined, + "route": "/", + "value": 0 + }, + ].obs; + + Future queryMsgFeedUnread() async { + var res = await MsgHttp.msgFeedUnread(); + if (res['status']) { + msgFeedUnread.value = MsgFeedUnread.fromJson(res['data']); + msgFeedTop.value[0]["value"] = msgFeedUnread.value.reply; + msgFeedTop.value[1]["value"] = msgFeedUnread.value.at; + msgFeedTop.value[2]["value"] = msgFeedUnread.value.like; + msgFeedTop.value[3]["value"] = msgFeedUnread.value.sys_msg; + // 触发更新 + msgFeedTop.refresh(); + } else { + SmartDialog.showToast(res['msg']); + } + } Future querySessionList(String? type) async { if (isLoading) return; diff --git a/lib/pages/whisper/view.dart b/lib/pages/whisper/view.dart index b26b9b863..c2f4aa4c7 100644 --- a/lib/pages/whisper/view.dart +++ b/lib/pages/whisper/view.dart @@ -22,6 +22,7 @@ class _WhisperPageState extends State { @override void initState() { super.initState(); + _whisperController.queryMsgFeedUnread(); _futureBuilderFuture = _whisperController.querySessionList('init'); _scrollController.addListener(_scrollListener); } @@ -43,190 +44,190 @@ class _WhisperPageState extends State { appBar: AppBar( title: const Text('消息'), ), - body: Column( - children: [ - // LayoutBuilder( - // builder: (BuildContext context, BoxConstraints constraints) { - // // 在这里根据父级容器的约束条件构建小部件树 - // return Padding( - // padding: const EdgeInsets.only(left: 20, right: 20), - // child: SizedBox( - // height: constraints.maxWidth / 5, - // child: GridView.count( - // primary: false, - // crossAxisCount: 4, - // padding: const EdgeInsets.all(0), - // childAspectRatio: 1.25, - // children: [ - // Column( - // crossAxisAlignment: CrossAxisAlignment.center, - // mainAxisAlignment: MainAxisAlignment.center, - // children: [ - // SizedBox( - // width: 36, - // height: 36, - // child: IconButton( - // style: ButtonStyle( - // padding: - // MaterialStateProperty.all(EdgeInsets.zero), - // backgroundColor: - // MaterialStateProperty.resolveWith((states) { - // return Theme.of(context) - // .colorScheme - // .primary - // .withOpacity(0.1); - // }), - // ), - // onPressed: () {}, - // icon: Icon( - // Icons.message_outlined, - // size: 18, - // color: Theme.of(context).colorScheme.primary, - // ), - // ), - // ), - // const SizedBox(height: 6), - // const Text('回复我的', style: TextStyle(fontSize: 13)) - // ], - // ), - // ], - // ), - // ), - // ); - // }, - // ), - Expanded( - child: RefreshIndicator( - onRefresh: () async { - await _whisperController.onRefresh(); - }, - child: SingleChildScrollView( - controller: _scrollController, - child: Column( - children: [ - FutureBuilder( - future: _futureBuilderFuture, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - Map data = snapshot.data as Map; - if (data['status']) { - List sessionList = _whisperController.sessionList; - return Obx( - () => sessionList.isEmpty - ? const SizedBox() - : ListView.separated( - itemCount: sessionList.length, - shrinkWrap: true, - physics: - const NeverScrollableScrollPhysics(), - itemBuilder: (_, int i) { - return ListTile( - onTap: () => Get.toNamed( - '/whisperDetail', - parameters: { - 'talkerId': sessionList[i] - .talkerId - .toString(), - 'name': sessionList[i] - .accountInfo - .name, - 'face': sessionList[i] - .accountInfo - .face, - 'mid': sessionList[i] - .accountInfo - .mid - .toString(), - }, - ), - leading: Badge( - isLabelVisible: - sessionList[i].unreadCount > 0, - backgroundColor: Theme.of(context) - .colorScheme - .primary, - textColor: Theme.of(context) - .colorScheme - .onInverseSurface, - label: Text( - " ${sessionList[i].unreadCount.toString()} "), - alignment: Alignment.topRight, - child: NetworkImgLayer( - width: 45, - height: 45, - type: 'avatar', - src: sessionList[i] - .accountInfo - .face, - ), - ), - title: Text( - sessionList[i].accountInfo.name), - subtitle: Text( - sessionList[i] - .lastMsg - .content['text'] ?? - sessionList[i] - .lastMsg - .content['content'] ?? - sessionList[i] - .lastMsg - .content['title'] ?? - sessionList[i] - .lastMsg - .content[ - 'reply_content'] ?? - '', - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: Theme.of(context) - .textTheme - .labelMedium! - .copyWith( - color: Theme.of(context) - .colorScheme - .outline)), - trailing: Text( - Utils.dateFormat(sessionList[i] - .lastMsg - .timestamp), - style: Theme.of(context) - .textTheme - .labelSmall! - .copyWith( - color: Theme.of(context) - .colorScheme - .outline), - ), - ); - }, - separatorBuilder: - (BuildContext context, int index) { - return Divider( - indent: 72, - endIndent: 20, - height: 6, - color: Colors.grey.withOpacity(0.1), - ); + body: RefreshIndicator( + onRefresh: () async { + await _whisperController.onRefresh(); + }, + child: SingleChildScrollView( + controller: _scrollController, + child: Column( + children: [ + LayoutBuilder( + builder: (BuildContext context, BoxConstraints constraints) { + // 在这里根据父级容器的约束条件构建小部件树 + return Padding( + padding: const EdgeInsets.only(left: 20, right: 20), + child: SizedBox( + height: constraints.maxWidth / 5, + child: Obx( + () => GridView.count( + primary: false, + crossAxisCount: 4, + padding: const EdgeInsets.all(0), + childAspectRatio: 1.25, + children: + _whisperController.msgFeedTop.value.map((item) { + return Column( + crossAxisAlignment: CrossAxisAlignment.center, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Badge( + isLabelVisible: item['value'] > 0, + backgroundColor: + Theme.of(context).colorScheme.primary, + textColor: Theme.of(context) + .colorScheme + .onInverseSurface, + label: Text(" ${item['value']} "), + alignment: Alignment.topRight, + child: SizedBox( + width: 36, + height: 36, + child: IconButton( + style: ButtonStyle( + padding: MaterialStateProperty.all( + EdgeInsets.zero), + backgroundColor: + MaterialStateProperty.resolveWith( + (states) { + return Theme.of(context) + .colorScheme + .primary + .withOpacity(0.1); + }), + ), + onPressed: () => Get.toNamed( + item['route'], + ), + icon: Icon( + item['icon'], + size: 18, + color: Theme.of(context) + .colorScheme + .primary, + ), + ), + )), + const SizedBox(height: 6), + Text(item['name'], + style: const TextStyle(fontSize: 13)) + ], + ); + }).toList(), + ), + ), + ), + ); + }), + FutureBuilder( + future: _futureBuilderFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + Map data = snapshot.data as Map; + if (data['status']) { + List sessionList = _whisperController.sessionList; + return Obx( + () => sessionList.isEmpty + ? const SizedBox() + : ListView.separated( + itemCount: sessionList.length, + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemBuilder: (_, int i) { + return ListTile( + onTap: () => Get.toNamed( + '/whisperDetail', + parameters: { + 'talkerId': + sessionList[i].talkerId.toString(), + 'name': sessionList[i].accountInfo.name, + 'face': sessionList[i].accountInfo.face, + 'mid': sessionList[i] + .accountInfo + .mid + .toString(), }, ), - ); - } else { - // 请求错误 - return const SizedBox(); - } - } else { - // 骨架屏 - return const SizedBox(); - } - }, - ) - ], - ), - ), - ), + leading: Badge( + isLabelVisible: + sessionList[i].unreadCount > 0, + backgroundColor: + Theme.of(context).colorScheme.primary, + textColor: Theme.of(context) + .colorScheme + .onInverseSurface, + label: Text( + " ${sessionList[i].unreadCount.toString()} "), + alignment: Alignment.topRight, + child: NetworkImgLayer( + width: 45, + height: 45, + type: 'avatar', + src: sessionList[i].accountInfo.face, + ), + ), + title: + Text(sessionList[i].accountInfo.name), + subtitle: Text( + sessionList[i] + .lastMsg + .content['text'] ?? + sessionList[i] + .lastMsg + .content['content'] ?? + sessionList[i] + .lastMsg + .content['title'] ?? + sessionList[i] + .lastMsg + .content['reply_content'] ?? + '', + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context) + .textTheme + .labelMedium! + .copyWith( + color: Theme.of(context) + .colorScheme + .outline)), + trailing: Text( + Utils.dateFormat( + sessionList[i].lastMsg.timestamp), + style: Theme.of(context) + .textTheme + .labelSmall! + .copyWith( + color: Theme.of(context) + .colorScheme + .outline), + ), + ); + }, + separatorBuilder: + (BuildContext context, int index) { + return Divider( + indent: 72, + endIndent: 20, + height: 6, + color: Colors.grey.withOpacity(0.1), + ); + }, + ), + ); + } else { + // 请求错误 + return const SizedBox(); + } + } else { + // 骨架屏 + return const SizedBox(); + } + }, + ) + ], ), - ], + ), ), ); }