Merge branch 'main' into design
This commit is contained in:
@ -326,6 +326,51 @@ class Api {
|
|||||||
// 获取指定分组下的up
|
// 获取指定分组下的up
|
||||||
static const String followUpGroup = '/x/relation/tag';
|
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';
|
||||||
|
|
||||||
|
/// 标记已读 POST
|
||||||
|
/// talker_id:
|
||||||
|
/// session_type: 1
|
||||||
|
/// ack_seqno: 920224140918926
|
||||||
|
/// build: 0
|
||||||
|
/// mobi_app: web
|
||||||
|
/// csrf_token:
|
||||||
|
/// csrf:
|
||||||
|
static const String updateAck =
|
||||||
|
'https://api.vc.bilibili.com/session_svr/v1/session_svr/update_ack';
|
||||||
|
|
||||||
// 获取某个动态详情
|
// 获取某个动态详情
|
||||||
// timezone_offset=-480
|
// timezone_offset=-480
|
||||||
// id=849312409672744983
|
// id=849312409672744983
|
||||||
|
85
lib/http/msg.dart
Normal file
85
lib/http/msg.dart
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
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({int? endTs}) async {
|
||||||
|
Map<String, dynamic> params = {
|
||||||
|
'session_type': 1,
|
||||||
|
'group_fold': 1,
|
||||||
|
'unfollow_fold': 0,
|
||||||
|
'sort_rule': 2,
|
||||||
|
'build': 0,
|
||||||
|
'mobi_app': 'web',
|
||||||
|
};
|
||||||
|
if (endTs != null) {
|
||||||
|
params['end_ts'] = endTs;
|
||||||
|
}
|
||||||
|
|
||||||
|
Map signParams = await WbiSign().makSign(params);
|
||||||
|
var res = await Request().get(Api.sessionList, data: signParams);
|
||||||
|
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<AccountListModel>((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'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
80
lib/models/msg/account.dart
Normal file
80
lib/models/msg/account.dart
Normal file
@ -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<String, dynamic> 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'];
|
||||||
|
}
|
||||||
|
}
|
226
lib/models/msg/session.dart
Normal file
226
lib/models/msg/session.dart
Normal file
@ -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<String, dynamic> json) {
|
||||||
|
sessionList = json['session_list']
|
||||||
|
?.map<SessionList>((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<String, dynamic> 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<String, dynamic> 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<MessageItem>? messages;
|
||||||
|
int? hasMore;
|
||||||
|
int? minSeqno;
|
||||||
|
int? maxSeqno;
|
||||||
|
List? eInfos;
|
||||||
|
|
||||||
|
SessionMsgDataModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
messages = json['messages']
|
||||||
|
.map<MessageItem>((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<String, dynamic> 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'];
|
||||||
|
}
|
||||||
|
}
|
@ -139,7 +139,13 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
child: Row(
|
child: Row(
|
||||||
children: [
|
children: [
|
||||||
const Expanded(child: SearchPage()),
|
const Expanded(child: SearchPage()),
|
||||||
const SizedBox(width: 10),
|
if (ctr!.userLogin.value) ...[
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
IconButton(
|
||||||
|
onPressed: () => Get.toNamed('/whisper'),
|
||||||
|
icon: const Icon(Icons.notifications_none))
|
||||||
|
],
|
||||||
|
const SizedBox(width: 6),
|
||||||
Obx(
|
Obx(
|
||||||
() => ctr!.userLogin.value
|
() => ctr!.userLogin.value
|
||||||
? Stack(
|
? Stack(
|
||||||
|
65
lib/pages/whisper/controller.dart
Normal file
65
lib/pages/whisper/controller.dart
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
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> sessionList = <SessionList>[].obs;
|
||||||
|
RxList<AccountListModel> accountList = <AccountListModel>[].obs;
|
||||||
|
bool isLoading = false;
|
||||||
|
|
||||||
|
Future querySessionList(String? type) async {
|
||||||
|
if (isLoading) return;
|
||||||
|
var res = await MsgHttp.sessionList(
|
||||||
|
endTs: type == 'onLoad' ? sessionList.last.sessionTs : null);
|
||||||
|
if (res['data'].sessionList != null && res['data'].sessionList.isNotEmpty) {
|
||||||
|
await queryAccountList(res['data'].sessionList);
|
||||||
|
// 将 accountList 转换为 Map 结构
|
||||||
|
Map<int, dynamic> 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'] && res['data'].sessionList != null) {
|
||||||
|
if (type == 'onLoad') {
|
||||||
|
sessionList.addAll(res['data'].sessionList);
|
||||||
|
} else {
|
||||||
|
sessionList.value = res['data'].sessionList;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isLoading = false;
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future onLoad() async {
|
||||||
|
querySessionList('onLoad');
|
||||||
|
}
|
||||||
|
|
||||||
|
Future onRefresh() async {
|
||||||
|
querySessionList('onRefresh');
|
||||||
|
}
|
||||||
|
}
|
4
lib/pages/whisper/index.dart
Normal file
4
lib/pages/whisper/index.dart
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
library whisper;
|
||||||
|
|
||||||
|
export './controller.dart';
|
||||||
|
export './view.dart';
|
230
lib/pages/whisper/view.dart
Normal file
230
lib/pages/whisper/view.dart
Normal file
@ -0,0 +1,230 @@
|
|||||||
|
import 'package:easy_debounce/easy_throttle.dart';
|
||||||
|
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<WhisperPage> createState() => _WhisperPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _WhisperPageState extends State<WhisperPage> {
|
||||||
|
late final WhisperController _whisperController =
|
||||||
|
Get.put(WhisperController());
|
||||||
|
late Future _futureBuilderFuture;
|
||||||
|
final ScrollController _scrollController = ScrollController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_futureBuilderFuture = _whisperController.querySessionList('init');
|
||||||
|
_scrollController.addListener(_scrollListener);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future _scrollListener() async {
|
||||||
|
if (_scrollController.position.pixels >=
|
||||||
|
_scrollController.position.maxScrollExtent - 200) {
|
||||||
|
EasyThrottle.throttle('my-throttler', const Duration(milliseconds: 800),
|
||||||
|
() async {
|
||||||
|
await _whisperController.onLoad();
|
||||||
|
_whisperController.isLoading = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
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: false,
|
||||||
|
backgroundColor: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.primary,
|
||||||
|
label: Text(sessionList[i]
|
||||||
|
.unreadCount
|
||||||
|
.toString()),
|
||||||
|
alignment: Alignment.bottomRight,
|
||||||
|
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 SizedBox();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 骨架屏
|
||||||
|
return SizedBox();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
28
lib/pages/whisperDetail/controller.dart
Normal file
28
lib/pages/whisperDetail/controller.dart
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
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;
|
||||||
|
late String name;
|
||||||
|
late String face;
|
||||||
|
late String mid;
|
||||||
|
RxList<MessageItem> messageList = <MessageItem>[].obs;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
super.onInit();
|
||||||
|
talkerId = int.parse(Get.parameters['talkerId']!);
|
||||||
|
name = Get.parameters['name']!;
|
||||||
|
face = Get.parameters['face']!;
|
||||||
|
mid = Get.parameters['mid']!;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future querySessionMsg() async {
|
||||||
|
var res = await MsgHttp.sessionMsg(talkerId: talkerId);
|
||||||
|
if (res['status']) {
|
||||||
|
messageList.value = res['data'].messages;
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
4
lib/pages/whisperDetail/index.dart
Normal file
4
lib/pages/whisperDetail/index.dart
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
library whisper_detail;
|
||||||
|
|
||||||
|
export './controller.dart';
|
||||||
|
export './view.dart';
|
193
lib/pages/whisperDetail/view.dart
Normal file
193
lib/pages/whisperDetail/view.dart
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
import 'package:pilipala/pages/whisperDetail/controller.dart';
|
||||||
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
|
|
||||||
|
import 'widget/chat_item.dart';
|
||||||
|
|
||||||
|
class WhisperDetailPage extends StatefulWidget {
|
||||||
|
const WhisperDetailPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<WhisperDetailPage> createState() => _WhisperDetailPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _WhisperDetailPageState extends State<WhisperDetailPage> {
|
||||||
|
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,
|
||||||
|
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.onPrimaryContainer,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
feedBack();
|
||||||
|
Get.toNamed(
|
||||||
|
'/member?mid=${_whisperDetailController.mid}',
|
||||||
|
arguments: {
|
||||||
|
'face': _whisperDetailController.face,
|
||||||
|
'heroTag': null
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Row(
|
||||||
|
children: [
|
||||||
|
NetworkImgLayer(
|
||||||
|
width: 34,
|
||||||
|
height: 34,
|
||||||
|
type: 'avatar',
|
||||||
|
src: _whisperDetailController.face,
|
||||||
|
),
|
||||||
|
const SizedBox(width: 6),
|
||||||
|
Text(
|
||||||
|
_whisperDetailController.name,
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
// resizeToAvoidBottomInset: true,
|
||||||
|
bottomNavigationBar: Container(
|
||||||
|
width: double.infinity,
|
||||||
|
height: MediaQuery.of(context).padding.bottom + 70,
|
||||||
|
padding: EdgeInsets.only(
|
||||||
|
left: 8,
|
||||||
|
right: 12,
|
||||||
|
top: 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,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
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: 45,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color:
|
||||||
|
Theme.of(context).colorScheme.primary.withOpacity(0.08),
|
||||||
|
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: 16.0, vertical: 12.0), // 内边距
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
190
lib/pages/whisperDetail/widget/chat_item.dart
Normal file
190
lib/pages/whisperDetail/widget/chat_item.dart
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
// 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 isAchive = item.msgType == 11;
|
||||||
|
bool isArticle = item.msgType == 12;
|
||||||
|
|
||||||
|
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
|
||||||
|
.withOpacity(0.4),
|
||||||
|
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: [
|
||||||
|
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.05),
|
||||||
|
),
|
||||||
|
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(),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -42,6 +42,8 @@ import 'package:pilipala/pages/video/detail/replyReply/index.dart';
|
|||||||
import 'package:pilipala/pages/webview/index.dart';
|
import 'package:pilipala/pages/webview/index.dart';
|
||||||
import 'package:pilipala/pages/setting/index.dart';
|
import 'package:pilipala/pages/setting/index.dart';
|
||||||
import 'package:pilipala/pages/media/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';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
Box setting = GStrorage.setting;
|
Box setting = GStrorage.setting;
|
||||||
@ -127,6 +129,11 @@ class Routes {
|
|||||||
CustomGetPage(name: '/playSpeedSet', page: () => const PlaySpeedPage()),
|
CustomGetPage(name: '/playSpeedSet', page: () => const PlaySpeedPage()),
|
||||||
// 收藏搜索
|
// 收藏搜索
|
||||||
CustomGetPage(name: '/favSearch', page: () => const FavSearchPage()),
|
CustomGetPage(name: '/favSearch', page: () => const FavSearchPage()),
|
||||||
|
// 消息页面
|
||||||
|
CustomGetPage(name: '/whisper', page: () => const WhisperPage()),
|
||||||
|
// 私信详情
|
||||||
|
CustomGetPage(
|
||||||
|
name: '/whisperDetail', page: () => const WhisperDetailPage()),
|
||||||
// 登录页面
|
// 登录页面
|
||||||
CustomGetPage(name: '/loginPage', page: () => const LoginPage()),
|
CustomGetPage(name: '/loginPage', page: () => const LoginPage()),
|
||||||
// 用户动态
|
// 用户动态
|
||||||
|
Reference in New Issue
Block a user