feat: 消息添加未阅读数量

This commit is contained in:
orz12
2024-01-24 14:45:15 +08:00
parent 772a6a9843
commit 65e4d6f61e
5 changed files with 272 additions and 181 deletions

View File

@@ -328,6 +328,8 @@ class Api {
// 获取指定分组下的up // 获取指定分组下的up
static const String followUpGroup = '/x/relation/tag'; 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? /// 'https://api.vc.bilibili.com/session_svr/v1/session_svr/get_sessions?
/// session_type=1& /// session_type=1&

View File

@@ -5,6 +5,22 @@ import 'api.dart';
import 'init.dart'; import 'init.dart';
class MsgHttp { 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 { static Future sessionList({int? endTs}) async {
Map<String, dynamic> params = { Map<String, dynamic> params = {

View File

@@ -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<String, dynamic> 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;
}
}

View File

@@ -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:get/get.dart';
import 'package:pilipala/http/msg.dart'; import 'package:pilipala/http/msg.dart';
import 'package:pilipala/models/msg/account.dart'; import 'package:pilipala/models/msg/account.dart';
import 'package:pilipala/models/msg/session.dart'; import 'package:pilipala/models/msg/session.dart';
import '../../models/msg/msgfeed_unread.dart';
class WhisperController extends GetxController { class WhisperController extends GetxController {
RxList<SessionList> sessionList = <SessionList>[].obs; RxList<SessionList> sessionList = <SessionList>[].obs;
RxList<AccountListModel> accountList = <AccountListModel>[].obs; RxList<AccountListModel> accountList = <AccountListModel>[].obs;
bool isLoading = false; bool isLoading = false;
Rx<MsgFeedUnread> 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 { Future querySessionList(String? type) async {
if (isLoading) return; if (isLoading) return;

View File

@@ -22,6 +22,7 @@ class _WhisperPageState extends State<WhisperPage> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
_whisperController.queryMsgFeedUnread();
_futureBuilderFuture = _whisperController.querySessionList('init'); _futureBuilderFuture = _whisperController.querySessionList('init');
_scrollController.addListener(_scrollListener); _scrollController.addListener(_scrollListener);
} }
@@ -43,190 +44,190 @@ class _WhisperPageState extends State<WhisperPage> {
appBar: AppBar( appBar: AppBar(
title: const Text('消息'), title: const Text('消息'),
), ),
body: Column( body: RefreshIndicator(
children: [ onRefresh: () async {
// LayoutBuilder( await _whisperController.onRefresh();
// builder: (BuildContext context, BoxConstraints constraints) { },
// // 在这里根据父级容器的约束条件构建小部件树 child: SingleChildScrollView(
// return Padding( controller: _scrollController,
// padding: const EdgeInsets.only(left: 20, right: 20), child: Column(
// child: SizedBox( children: [
// height: constraints.maxWidth / 5, LayoutBuilder(
// child: GridView.count( builder: (BuildContext context, BoxConstraints constraints) {
// primary: false, // 在这里根据父级容器的约束条件构建小部件树
// crossAxisCount: 4, return Padding(
// padding: const EdgeInsets.all(0), padding: const EdgeInsets.only(left: 20, right: 20),
// childAspectRatio: 1.25, child: SizedBox(
// children: [ height: constraints.maxWidth / 5,
// Column( child: Obx(
// crossAxisAlignment: CrossAxisAlignment.center, () => GridView.count(
// mainAxisAlignment: MainAxisAlignment.center, primary: false,
// children: [ crossAxisCount: 4,
// SizedBox( padding: const EdgeInsets.all(0),
// width: 36, childAspectRatio: 1.25,
// height: 36, children:
// child: IconButton( _whisperController.msgFeedTop.value.map((item) {
// style: ButtonStyle( return Column(
// padding: crossAxisAlignment: CrossAxisAlignment.center,
// MaterialStateProperty.all(EdgeInsets.zero), mainAxisAlignment: MainAxisAlignment.center,
// backgroundColor: children: [
// MaterialStateProperty.resolveWith((states) { Badge(
// return Theme.of(context) isLabelVisible: item['value'] > 0,
// .colorScheme backgroundColor:
// .primary Theme.of(context).colorScheme.primary,
// .withOpacity(0.1); textColor: Theme.of(context)
// }), .colorScheme
// ), .onInverseSurface,
// onPressed: () {}, label: Text(" ${item['value']} "),
// icon: Icon( alignment: Alignment.topRight,
// Icons.message_outlined, child: SizedBox(
// size: 18, width: 36,
// color: Theme.of(context).colorScheme.primary, height: 36,
// ), child: IconButton(
// ), style: ButtonStyle(
// ), padding: MaterialStateProperty.all(
// const SizedBox(height: 6), EdgeInsets.zero),
// const Text('回复我的', style: TextStyle(fontSize: 13)) backgroundColor:
// ], MaterialStateProperty.resolveWith(
// ), (states) {
// ], return Theme.of(context)
// ), .colorScheme
// ), .primary
// ); .withOpacity(0.1);
// }, }),
// ), ),
Expanded( onPressed: () => Get.toNamed(
child: RefreshIndicator( item['route'],
onRefresh: () async { ),
await _whisperController.onRefresh(); icon: Icon(
}, item['icon'],
child: SingleChildScrollView( size: 18,
controller: _scrollController, color: Theme.of(context)
child: Column( .colorScheme
children: [ .primary,
FutureBuilder( ),
future: _futureBuilderFuture, ),
builder: (context, snapshot) { )),
if (snapshot.connectionState == ConnectionState.done) { const SizedBox(height: 6),
Map data = snapshot.data as Map; Text(item['name'],
if (data['status']) { style: const TextStyle(fontSize: 13))
List sessionList = _whisperController.sessionList; ],
return Obx( );
() => sessionList.isEmpty }).toList(),
? const SizedBox() ),
: ListView.separated( ),
itemCount: sessionList.length, ),
shrinkWrap: true, );
physics: }),
const NeverScrollableScrollPhysics(), FutureBuilder(
itemBuilder: (_, int i) { future: _futureBuilderFuture,
return ListTile( builder: (context, snapshot) {
onTap: () => Get.toNamed( if (snapshot.connectionState == ConnectionState.done) {
'/whisperDetail', Map data = snapshot.data as Map;
parameters: { if (data['status']) {
'talkerId': sessionList[i] List sessionList = _whisperController.sessionList;
.talkerId return Obx(
.toString(), () => sessionList.isEmpty
'name': sessionList[i] ? const SizedBox()
.accountInfo : ListView.separated(
.name, itemCount: sessionList.length,
'face': sessionList[i] shrinkWrap: true,
.accountInfo physics: const NeverScrollableScrollPhysics(),
.face, itemBuilder: (_, int i) {
'mid': sessionList[i] return ListTile(
.accountInfo onTap: () => Get.toNamed(
.mid '/whisperDetail',
.toString(), parameters: {
}, 'talkerId':
), sessionList[i].talkerId.toString(),
leading: Badge( 'name': sessionList[i].accountInfo.name,
isLabelVisible: 'face': sessionList[i].accountInfo.face,
sessionList[i].unreadCount > 0, 'mid': sessionList[i]
backgroundColor: Theme.of(context) .accountInfo
.colorScheme .mid
.primary, .toString(),
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),
);
}, },
), ),
); leading: Badge(
} else { isLabelVisible:
// 请求错误 sessionList[i].unreadCount > 0,
return const SizedBox(); backgroundColor:
} Theme.of(context).colorScheme.primary,
} else { textColor: Theme.of(context)
// 骨架屏 .colorScheme
return const SizedBox(); .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();
}
},
)
],
), ),
], ),
), ),
); );
} }