diff --git a/lib/http/api.dart b/lib/http/api.dart index 50106dae..c10ced10 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -321,4 +321,38 @@ class Api { // 获取指定分组下的up static const String followUpGroup = '/x/relation/tag'; + + /// 私聊 + /// 'https://api.vc.bilibili.com/session_svr/v1/session_svr/get_sessions? + /// session_type=1& + /// group_fold=1& + /// unfollow_fold=0& + /// sort_rule=2& + /// build=0& + /// mobi_app=web& + /// w_rid=8641d157fb9a9255eb2159f316ee39e2& + /// wts=1697305010 + + static const String sessionList = + 'https://api.vc.bilibili.com/session_svr/v1/session_svr/get_sessions'; + + /// 私聊用户信息 + /// uids + /// build=0&mobi_app=web + static const String sessionAccountList = + 'https://api.vc.bilibili.com/account/v1/user/cards'; + + /// https://api.vc.bilibili.com/svr_sync/v1/svr_sync/fetch_session_msgs? + /// talker_id=400787461& + /// session_type=1& + /// size=20& + /// sender_device_id=1& + /// build=0& + /// mobi_app=web& + /// web_location=333.1296& + /// w_rid=cfe3bf58c9fe181bbf4dd6c75175e6b0& + /// wts=1697350697 + + static const String sessionMsg = + 'https://api.vc.bilibili.com/svr_sync/v1/svr_sync/fetch_session_msgs'; } diff --git a/lib/http/msg.dart b/lib/http/msg.dart new file mode 100644 index 00000000..41cdefe8 --- /dev/null +++ b/lib/http/msg.dart @@ -0,0 +1,80 @@ +import 'package:pilipala/http/api.dart'; +import 'package:pilipala/http/init.dart'; +import 'package:pilipala/models/msg/account.dart'; +import 'package:pilipala/models/msg/session.dart'; +import 'package:pilipala/utils/wbi_sign.dart'; + +class MsgHttp { + // 会话列表 + static Future sessionList() async { + Map params = await WbiSign().makSign({ + 'session_type': 1, + 'group_fold': 1, + 'unfollow_fold': 0, + 'sort_rule': 2, + 'build': 0, + 'mobi_app': 'web', + }); + var res = await Request().get(Api.sessionList, data: params); + if (res.data['code'] == 0) { + return { + 'status': true, + 'data': SessionDataModel.fromJson(res.data['data']), + }; + } else { + return { + 'status': false, + 'date': [], + 'msg': res.data['message'], + }; + } + } + + static Future accountList(uids) async { + var res = await Request().get(Api.sessionAccountList, data: { + 'uids': uids, + 'build': 0, + 'mobi_app': 'web', + }); + if (res.data['code'] == 0) { + return { + 'status': true, + 'data': res.data['data'] + .map((e) => AccountListModel.fromJson(e)) + .toList(), + }; + } else { + return { + 'status': false, + 'date': [], + 'msg': res.data['message'], + }; + } + } + + static Future sessionMsg({ + int? talkerId, + }) async { + Map params = await WbiSign().makSign({ + 'talker_id': talkerId, + 'session_type': 1, + 'size': 20, + 'sender_device_id': 1, + 'build': 0, + 'mobi_app': 'web', + }); + var res = await Request().get(Api.sessionMsg, data: params); + if (res.data['code'] == 0) { + return { + 'status': true, + 'data': SessionMsgDataModel.fromJson(res.data['data']), + }; + } else { + return { + 'status': false, + 'date': [], + 'msg': res.data['message'], + }; + } + } +} diff --git a/lib/models/msg/account.dart b/lib/models/msg/account.dart new file mode 100644 index 00000000..bedd49b5 --- /dev/null +++ b/lib/models/msg/account.dart @@ -0,0 +1,80 @@ +class AccountListModel { + AccountListModel({ + this.mid, + this.name, + this.sex, + this.face, + this.sign, + this.rank, + this.level, + this.silence, + this.vip, + this.pendant, + this.nameplate, + this.official, + this.birthday, + this.isFakeAccount, + this.isDeleted, + this.inRegAudit, + this.faceNft, + this.faceNftNew, + this.isSeniorMember, + this.digitalId, + this.digitalType, + this.attestation, + this.expertInfo, + this.honours, + }); + + int? mid; + String? name; + String? sex; + String? face; + String? sign; + int? rank; + int? level; + int? silence; + Map? vip; + Map? pendant; + Map? nameplate; + Map? official; + int? birthday; + int? isFakeAccount; + int? isDeleted; + int? inRegAudit; + int? faceNft; + int? faceNftNew; + int? isSeniorMember; + String? digitalId; + int? digitalType; + Map? attestation; + Map? expertInfo; + Map? honours; + + AccountListModel.fromJson(Map json) { + mid = json['mid']; + name = json['name'] ?? ''; + sex = json['sex']; + face = json['face']; + sign = json['sign']; + rank = json['rank']; + level = json['level']; + silence = json['silence']; + vip = json['vip']; + pendant = json['pendant']; + nameplate = json['nameplate']; + official = json['official']; + birthday = json['birthday']; + isFakeAccount = json['is_fake_account']; + isDeleted = json['is_deleted']; + inRegAudit = json['in_reg_audit']; + faceNft = json['face_nft']; + faceNftNew = json['face_nft_new']; + isSeniorMember = json['is_senior_member']; + digitalId = json['digital_id']; + digitalType = json['digital_type']; + attestation = json['attestation']; + expertInfo = json['expert_info']; + honours = json['honours']; + } +} diff --git a/lib/models/msg/session.dart b/lib/models/msg/session.dart new file mode 100644 index 00000000..683dd94a --- /dev/null +++ b/lib/models/msg/session.dart @@ -0,0 +1,226 @@ +import 'dart:convert'; + +import 'package:pilipala/models/msg/account.dart'; + +class SessionDataModel { + SessionDataModel({ + this.sessionList, + this.hasMore, + }); + + List? sessionList; + int? hasMore; + + SessionDataModel.fromJson(Map json) { + sessionList = json['session_list'] + .map((e) => SessionList.fromJson(e)) + .toList(); + hasMore = json['has_more']; + } +} + +class SessionList { + SessionList({ + this.talkerId, + this.sessionType, + this.atSeqno, + this.topTs, + this.groupName, + this.groupCover, + this.isFollow, + this.isDnd, + this.ackSeqno, + this.ackTs, + this.sessionTs, + this.unreadCount, + this.lastMsg, + this.groupType, + this.canFold, + this.status, + this.maxSeqno, + this.newPushMsg, + this.setting, + this.isGuardian, + this.isIntercept, + this.isTrust, + this.systemMsgType, + this.liveStatus, + this.bizMsgUnreadCount, + // this.userLabel, + }); + + int? talkerId; + int? sessionType; + int? atSeqno; + int? topTs; + String? groupName; + String? groupCover; + int? isFollow; + int? isDnd; + int? ackSeqno; + int? ackTs; + int? sessionTs; + int? unreadCount; + LastMsg? lastMsg; + int? groupType; + int? canFold; + int? status; + int? maxSeqno; + int? newPushMsg; + int? setting; + int? isGuardian; + int? isIntercept; + int? isTrust; + int? systemMsgType; + int? liveStatus; + int? bizMsgUnreadCount; + // int? userLabel; + AccountListModel? accountInfo; + + SessionList.fromJson(Map json) { + talkerId = json["talker_id"]; + sessionType = json["session_type"]; + atSeqno = json["at_seqno"]; + topTs = json["top_ts"]; + groupName = json["group_name"]; + groupCover = json["group_cover"]; + isFollow = json["is_follow"]; + isDnd = json["is_dnd"]; + ackSeqno = json["ack_seqno"]; + ackTs = json["ack_ts"]; + sessionTs = json["session_ts"]; + unreadCount = json["unread_count"]; + lastMsg = + json["last_msg"] != null ? LastMsg.fromJson(json["last_msg"]) : null; + groupType = json["group_type"]; + canFold = json["can_fold"]; + status = json["status"]; + maxSeqno = json["max_seqno"]; + newPushMsg = json["new_push_msg"]; + setting = json["setting"]; + isGuardian = json["is_guardian"]; + isIntercept = json["is_intercept"]; + isTrust = json["is_trust"]; + systemMsgType = json["system_msg_type"]; + liveStatus = json["live_status"]; + bizMsgUnreadCount = json["biz_msg_unread_count"]; + // userLabel = json["user_label"]; + } +} + +class LastMsg { + LastMsg({ + this.senderIid, + this.receiverType, + this.receiverId, + this.msgType, + this.content, + this.msgSeqno, + this.timestamp, + this.atUids, + this.msgKey, + this.msgStatus, + this.notifyCode, + this.newFaceVersion, + }); + + int? senderIid; + int? receiverType; + int? receiverId; + int? msgType; + Map? content; + int? msgSeqno; + int? timestamp; + String? atUids; + int? msgKey; + int? msgStatus; + String? notifyCode; + int? newFaceVersion; + + LastMsg.fromJson(Map json) { + senderIid = json['sender_uid']; + receiverType = json['receiver_type']; + receiverId = json['receiver_id']; + msgType = json['msg_type']; + content = jsonDecode(json['content']); + msgSeqno = json['msg_seqno']; + timestamp = json['timestamp']; + atUids = json['at_uids']; + msgKey = json['msg_key']; + msgStatus = json['msg_status']; + notifyCode = json['notify_code']; + newFaceVersion = json['new_face_version']; + } +} + +class SessionMsgDataModel { + SessionMsgDataModel({ + this.messages, + this.hasMore, + this.minSeqno, + this.maxSeqno, + this.eInfos, + }); + + List? messages; + int? hasMore; + int? minSeqno; + int? maxSeqno; + List? eInfos; + + SessionMsgDataModel.fromJson(Map json) { + messages = json['messages'] + .map((e) => MessageItem.fromJson(e)) + .toList(); + hasMore = json['has_more']; + minSeqno = json['min_seqno']; + maxSeqno = json['max_seqno']; + eInfos = json['e_infos']; + } +} + +class MessageItem { + MessageItem({ + this.senderUid, + this.receiverType, + this.receiverId, + this.msgType, + this.content, + this.msgSeqno, + this.timestamp, + this.atUids, + this.msgKey, + this.msgStatus, + this.notifyCode, + this.newFaceVersion, + }); + + int? senderUid; + int? receiverType; + int? receiverId; + int? msgType; + Map? content; + int? msgSeqno; + int? timestamp; + List? atUids; + int? msgKey; + int? msgStatus; + String? notifyCode; + int? newFaceVersion; + + MessageItem.fromJson(Map json) { + senderUid = json['sender_uid']; + receiverType = json['receiver_type']; + receiverId = json['receiver_id']; + // 1 文本 2 图片 18 系统提示 10 系统通知 + msgType = json['msg_type']; + content = jsonDecode(json['content']); + msgSeqno = json['msg_seqno']; + timestamp = json['timestamp']; + atUids = json['at_uids']; + msgKey = json['msg_key']; + msgStatus = json['msg_status']; + notifyCode = json['notify_code']; + newFaceVersion = json['new_face_version']; + } +} diff --git a/lib/pages/home/view.dart b/lib/pages/home/view.dart index 3cdf81c7..dc55b4f5 100644 --- a/lib/pages/home/view.dart +++ b/lib/pages/home/view.dart @@ -129,7 +129,11 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { child: Row( children: [ const Expanded(child: SearchPage()), - const SizedBox(width: 10), + const SizedBox(width: 6), + IconButton( + onPressed: () => Get.toNamed('/whisper'), + icon: const Icon(Icons.notifications_none)), + const SizedBox(width: 6), Obx( () => ctr!.userLogin.value ? Stack( diff --git a/lib/pages/whisper/controller.dart b/lib/pages/whisper/controller.dart new file mode 100644 index 00000000..3e0ae2bc --- /dev/null +++ b/lib/pages/whisper/controller.dart @@ -0,0 +1,50 @@ +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'; + +class WhisperController extends GetxController { + RxList sessionList = [].obs; + RxList accountList = [].obs; + + Future querySessionList() async { + var res = await MsgHttp.sessionList(); + if (res['data'].sessionList.isNotEmpty) { + await queryAccountList(res['data'].sessionList); + // 将 accountList 转换为 Map 结构 + Map accountMap = {}; + for (var j in accountList) { + accountMap[j.mid!] = j; + } + + // 遍历 sessionList,通过 mid 查找并赋值 accountInfo + for (var i in res['data'].sessionList) { + var accountInfo = accountMap[i.talkerId]; + if (accountInfo != null) { + i.accountInfo = accountInfo; + } + if (i.talkerId == 844424930131966) { + i.accountInfo = AccountListModel( + name: 'UP主小助手', + face: + 'https://message.biliimg.com/bfs/im/489a63efadfb202366c2f88853d2217b5ddc7a13.png', + ); + } + } + } + if (res['status']) { + sessionList.value = res['data'].sessionList; + } + + return res; + } + + Future queryAccountList(sessionList) async { + List midsList = sessionList.map((e) => e.talkerId!).toList(); + var res = await MsgHttp.accountList(midsList.join(',')); + if (res['status']) { + accountList.value = res['data']; + } + return res; + } +} diff --git a/lib/pages/whisper/index.dart b/lib/pages/whisper/index.dart new file mode 100644 index 00000000..6d2ce533 --- /dev/null +++ b/lib/pages/whisper/index.dart @@ -0,0 +1,4 @@ +library whisper; + +export './controller.dart'; +export './view.dart'; diff --git a/lib/pages/whisper/view.dart b/lib/pages/whisper/view.dart new file mode 100644 index 00000000..7de3fa75 --- /dev/null +++ b/lib/pages/whisper/view.dart @@ -0,0 +1,178 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:pilipala/common/widgets/network_img_layer.dart'; +import 'package:pilipala/utils/utils.dart'; + +import 'controller.dart'; + +class WhisperPage extends StatefulWidget { + const WhisperPage({super.key}); + + @override + State createState() => _WhisperPageState(); +} + +class _WhisperPageState extends State { + late final WhisperController _whisperController = + Get.put(WhisperController()); + late Future _futureBuilderFuture; + + @override + void initState() { + super.initState(); + _futureBuilderFuture = _whisperController.querySessionList(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + scrolledUnderElevation: 0, + ), + 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: 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.builder( + itemCount: sessionList.length, + shrinkWrap: true, + itemBuilder: (_, int i) { + return ListTile( + onTap: () { + Get.toNamed( + '/whisperDetail?talkerId=${sessionList[i].talkerId}&name=${sessionList[i].accountInfo.name}'); + }, + leading: NetworkImgLayer( + width: 45, + height: 45, + type: 'avatar', + src: sessionList[i].accountInfo.face, + ), + title: Text( + sessionList[i].accountInfo.name, + style: + Theme.of(context).textTheme.titleSmall, + ), + subtitle: Text( + sessionList[i].lastMsg.content['text'] ?? + sessionList[i] + .lastMsg + .content['content'] ?? + sessionList[i] + .lastMsg + .content['title'], + 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), + ), + ); + }, + ), + ); + } else { + // 请求错误 + return SizedBox(); + } + } else { + // 骨架屏 + return SizedBox(); + } + }, + ), + ), + + // ListTile( + // onTap: () {}, + // leading: CircleAvatar(), + // title: Text('钱瑞昌'), + // subtitle: Text('没事', style: Theme.of(context).textTheme.bodySmall), + // trailing: Text('昨天'), + // ), + // ListTile( + // onTap: () {}, + // leading: CircleAvatar(), + // title: Text('李天'), + // subtitle: + // Text('明天有空吗', style: Theme.of(context).textTheme.bodySmall), + // trailing: Text('现在'), + // ) + ], + ), + ); + } +} diff --git a/lib/pages/whisperDetail/controller.dart b/lib/pages/whisperDetail/controller.dart new file mode 100644 index 00000000..ef25cfd4 --- /dev/null +++ b/lib/pages/whisperDetail/controller.dart @@ -0,0 +1,24 @@ +import 'package:get/get.dart'; +import 'package:pilipala/http/msg.dart'; +import 'package:pilipala/models/msg/session.dart'; + +class WhisperDetailController extends GetxController { + late int talkerId; + RxString name = ''.obs; + RxList messageList = [].obs; + + @override + void onInit() { + super.onInit(); + talkerId = int.parse(Get.parameters['talkerId']!); + name.value = Get.parameters['name']!; + } + + Future querySessionMsg() async { + var res = await MsgHttp.sessionMsg(talkerId: talkerId); + if (res['status']) { + messageList.value = res['data'].messages; + } + return res; + } +} diff --git a/lib/pages/whisperDetail/index.dart b/lib/pages/whisperDetail/index.dart new file mode 100644 index 00000000..79b4ea19 --- /dev/null +++ b/lib/pages/whisperDetail/index.dart @@ -0,0 +1,4 @@ +library whisper_detail; + +export './controller.dart'; +export './view.dart'; diff --git a/lib/pages/whisperDetail/view.dart b/lib/pages/whisperDetail/view.dart new file mode 100644 index 00000000..b2aba5da --- /dev/null +++ b/lib/pages/whisperDetail/view.dart @@ -0,0 +1,167 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:pilipala/pages/whisperDetail/controller.dart'; + +import 'widget/chat_item.dart'; + +class WhisperDetailPage extends StatefulWidget { + const WhisperDetailPage({super.key}); + + @override + State createState() => _WhisperDetailPageState(); +} + +class _WhisperDetailPageState extends State { + final WhisperDetailController _whisperDetailController = + Get.put(WhisperDetailController()); + late Future _futureBuilderFuture; + + @override + void initState() { + super.initState(); + _futureBuilderFuture = _whisperDetailController.querySessionMsg(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + automaticallyImplyLeading: false, + scrolledUnderElevation: 0, + title: SizedBox( + width: double.infinity, + height: 50, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox( + width: 34, + height: 34, + child: IconButton( + style: ButtonStyle( + padding: MaterialStateProperty.all(EdgeInsets.zero), + backgroundColor: + MaterialStateProperty.resolveWith((states) { + return Theme.of(context) + .colorScheme + .primaryContainer + .withOpacity(0.6); + }), + ), + onPressed: () => Get.back(), + icon: Icon( + Icons.arrow_back_outlined, + size: 18, + color: Theme.of(context).colorScheme.primary, + ), + ), + ), + Obx( + () => Text( + _whisperDetailController.name.value, + style: Theme.of(context).textTheme.titleMedium, + ), + ), + const SizedBox(width: 36, height: 36), + ], + ), + ), + ), + body: FutureBuilder( + future: _futureBuilderFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + Map data = snapshot.data as Map; + if (data['status']) { + List messageList = _whisperDetailController.messageList; + return Obx( + () => messageList.isEmpty + ? const SizedBox() + : ListView.builder( + itemCount: messageList.length, + shrinkWrap: true, + reverse: true, + itemBuilder: (_, int i) { + if (i == 0) { + return Column( + children: [ + ChatItem(item: messageList[i]), + const SizedBox(height: 12), + ], + ); + } else { + return ChatItem(item: messageList[i]); + } + }, + ), + ); + } else { + // 请求错误 + return const SizedBox(); + } + } else { + // 骨架屏 + return const SizedBox(); + } + }, + ), + bottomNavigationBar: Container( + width: double.infinity, + height: MediaQuery.of(context).padding.bottom + 70, + padding: EdgeInsets.only( + left: 8, + right: 12, + bottom: MediaQuery.of(context).padding.bottom, + ), + decoration: BoxDecoration( + color: Colors.white, + border: Border( + top: BorderSide( + width: 4, + color: Theme.of(context).colorScheme.primary.withOpacity(0.1), + ), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // IconButton( + // onPressed: () {}, + // icon: Icon( + // Icons.add_circle_outline, + // color: Theme.of(context).colorScheme.outline, + // ), + // ), + IconButton( + onPressed: () {}, + icon: Icon( + Icons.emoji_emotions_outlined, + color: Theme.of(context).colorScheme.outline, + ), + ), + // Expanded( + // child: Container( + // height: 42, + // decoration: BoxDecoration( + // color: Theme.of(context).colorScheme.primary.withOpacity(0.1), + // borderRadius: BorderRadius.circular(40.0), + // ), + // child: TextField( + // readOnly: true, + // style: Theme.of(context).textTheme.titleMedium, + // decoration: const InputDecoration( + // border: InputBorder.none, // 移除默认边框 + // hintText: '请输入内容', // 提示文本 + // contentPadding: EdgeInsets.symmetric( + // horizontal: 12.0, vertical: 12.0), // 内边距 + // ), + // ), + // ), + // ), + const SizedBox(width: 16), + ], + ), + ), + ); + } +} diff --git a/lib/pages/whisperDetail/widget/chat_item.dart b/lib/pages/whisperDetail/widget/chat_item.dart new file mode 100644 index 00000000..512300ce --- /dev/null +++ b/lib/pages/whisperDetail/widget/chat_item.dart @@ -0,0 +1,189 @@ +// ignore_for_file: must_be_immutable + +import 'package:flutter/material.dart'; +import 'package:pilipala/common/widgets/network_img_layer.dart'; +import 'package:pilipala/utils/utils.dart'; + +class ChatItem extends StatelessWidget { + dynamic item; + + ChatItem({ + super.key, + this.item, + }); + + @override + Widget build(BuildContext context) { + bool isOwner = item.senderUid == 17340771; + bool isPic = item.msgType == 2; + bool isText = item.msgType == 1; + bool isSystem = + item.msgType == 18 || item.msgType == 10 || item.msgType == 13; + int msgType = item.msgType; + Map content = item.content ?? ''; + return isSystem + ? (msgType == 10 + ? SystemNotice(item: item) + : msgType == 13 + ? SystemNotice2(item: item) + : const SizedBox()) + : Row( + children: [ + if (!isOwner) const SizedBox(width: 12), + if (isOwner) const Spacer(), + Container( + constraints: const BoxConstraints( + maxWidth: 300.0, // 设置最大宽度为200.0 + ), + decoration: BoxDecoration( + color: isOwner + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.secondaryContainer, + borderRadius: BorderRadius.only( + topLeft: const Radius.circular(16), + topRight: const Radius.circular(16), + bottomLeft: Radius.circular(isOwner ? 16 : 6), + bottomRight: Radius.circular(isOwner ? 6 : 16), + ), + ), + margin: const EdgeInsets.only(top: 12), + padding: EdgeInsets.only( + top: 8, + bottom: 6, + left: isPic ? 8 : 12, + right: isPic ? 8 : 12, + ), + child: Column( + crossAxisAlignment: isOwner + ? CrossAxisAlignment.end + : CrossAxisAlignment.start, + children: [ + isText + ? Text( + content['content'], + style: TextStyle( + color: isOwner + ? Theme.of(context).colorScheme.onPrimary + : Theme.of(context) + .colorScheme + .onSecondaryContainer), + ) + : isPic + ? NetworkImgLayer( + width: 220, + height: + 220 * content['height'] / content['width'], + src: content['url'], + ) + : const SizedBox(), + SizedBox(height: isPic ? 7 : 2), + Text( + Utils.dateFormat(item.timestamp), + style: Theme.of(context).textTheme.labelSmall!.copyWith( + color: isOwner + ? Theme.of(context) + .colorScheme + .onPrimary + .withOpacity(0.8) + : Theme.of(context) + .colorScheme + .onSecondaryContainer + .withOpacity(0.8)), + ) + ], + ), + ), + if (!isOwner) const Spacer(), + if (isOwner) const SizedBox(width: 12), + ], + ); + } +} + +class SystemNotice extends StatelessWidget { + dynamic item; + SystemNotice({super.key, this.item}); + + @override + Widget build(BuildContext context) { + Map content = item.content ?? ''; + return Row( + children: [ + const SizedBox(width: 12), + Container( + constraints: const BoxConstraints( + maxWidth: 300.0, // 设置最大宽度为200.0 + ), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.secondaryContainer, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + bottomLeft: Radius.circular(6), + bottomRight: Radius.circular(16), + ), + ), + margin: const EdgeInsets.only(top: 12), + padding: const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(content['title'], + style: Theme.of(context) + .textTheme + .titleMedium! + .copyWith(fontWeight: FontWeight.bold)), + Text( + Utils.dateFormat(item.timestamp), + style: Theme.of(context) + .textTheme + .labelSmall! + .copyWith(color: Theme.of(context).colorScheme.outline), + ) + ], + ), + Divider( + color: Theme.of(context).colorScheme.primary.withOpacity(0.1), + ), + Text( + content['text'], + ) + ], + ), + ), + const Spacer(), + ], + ); + } +} + +class SystemNotice2 extends StatelessWidget { + dynamic item; + SystemNotice2({super.key, this.item}); + + @override + Widget build(BuildContext context) { + Map content = item.content ?? ''; + return Row( + children: [ + const SizedBox(width: 12), + Container( + constraints: const BoxConstraints( + maxWidth: 300.0, // 设置最大宽度为200.0 + ), + margin: const EdgeInsets.only(top: 12), + padding: const EdgeInsets.only(bottom: 6), + child: NetworkImgLayer( + width: 320, + height: 150, + src: content['pic_url'], + ), + ), + const Spacer(), + ], + ); + } +} diff --git a/lib/router/app_pages.dart b/lib/router/app_pages.dart index 544217f9..afcfea72 100644 --- a/lib/router/app_pages.dart +++ b/lib/router/app_pages.dart @@ -37,6 +37,8 @@ import 'package:pilipala/pages/video/detail/replyReply/index.dart'; import 'package:pilipala/pages/webview/index.dart'; import 'package:pilipala/pages/setting/index.dart'; import 'package:pilipala/pages/media/index.dart'; +import 'package:pilipala/pages/whisper/index.dart'; +import 'package:pilipala/pages/whisperDetail/index.dart'; import 'package:pilipala/utils/storage.dart'; Box setting = GStrorage.setting; @@ -122,6 +124,11 @@ class Routes { CustomGetPage(name: '/playSpeedSet', page: () => const PlaySpeedPage()), // 收藏搜索 CustomGetPage(name: '/favSearch', page: () => const FavSearchPage()), + // 消息页面 + CustomGetPage(name: '/whisper', page: () => const WhisperPage()), + // 私信详情 + CustomGetPage( + name: '/whisperDetail', page: () => const WhisperDetailPage()), ]; }