Merge branch 'main' into design
This commit is contained in:
@ -535,4 +535,17 @@ class Api {
|
|||||||
|
|
||||||
/// 搜索结果计数
|
/// 搜索结果计数
|
||||||
static const String searchCount = '/x/web-interface/wbi/search/all/v2';
|
static const String searchCount = '/x/web-interface/wbi/search/all/v2';
|
||||||
|
|
||||||
|
/// 关闭会话
|
||||||
|
static const String removeSession =
|
||||||
|
'${HttpString.tUrl}/session_svr/v1/session_svr/remove_session';
|
||||||
|
|
||||||
|
/// 消息未读数
|
||||||
|
static const String unread = '${HttpString.tUrl}/x/im/web/msgfeed/unread';
|
||||||
|
|
||||||
|
/// 回复我的
|
||||||
|
static const String messageReplyAPi = '/x/msgfeed/reply';
|
||||||
|
|
||||||
|
/// 收到的赞
|
||||||
|
static const String messageLikeAPi = '/x/msgfeed/like';
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,8 @@
|
|||||||
|
import 'dart:convert';
|
||||||
import 'dart:math';
|
import 'dart:math';
|
||||||
|
import 'package:dio/dio.dart';
|
||||||
|
import 'package:pilipala/models/msg/like.dart';
|
||||||
|
import 'package:pilipala/models/msg/reply.dart';
|
||||||
import '../models/msg/account.dart';
|
import '../models/msg/account.dart';
|
||||||
import '../models/msg/session.dart';
|
import '../models/msg/session.dart';
|
||||||
import '../utils/wbi_sign.dart';
|
import '../utils/wbi_sign.dart';
|
||||||
@ -122,68 +126,48 @@ class MsgHttp {
|
|||||||
'data': res.data['data'],
|
'data': res.data['data'],
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {'status': false, 'date': [], 'msg': res.data['message']};
|
||||||
'status': false,
|
|
||||||
'date': [],
|
|
||||||
'msg': "message: ${res.data['message']},"
|
|
||||||
" msg: ${res.data['msg']},"
|
|
||||||
" code: ${res.data['code']}",
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 发送私信
|
// 发送私信
|
||||||
static Future sendMsg({
|
static Future sendMsg({
|
||||||
int? senderUid,
|
required int senderUid,
|
||||||
int? receiverId,
|
required int receiverId,
|
||||||
int? receiverType,
|
int? receiverType,
|
||||||
int? msgType,
|
int? msgType,
|
||||||
dynamic content,
|
dynamic content,
|
||||||
}) async {
|
}) async {
|
||||||
String csrf = await Request.getCsrf();
|
String csrf = await Request.getCsrf();
|
||||||
Map<String, dynamic> params = await WbiSign().makSign({
|
var res = await Request().post(
|
||||||
'msg[sender_uid]': senderUid,
|
Api.sendMsg,
|
||||||
'msg[receiver_id]': receiverId,
|
data: {
|
||||||
'msg[receiver_type]': receiverType ?? 1,
|
'msg[sender_uid]': senderUid,
|
||||||
'msg[msg_type]': msgType ?? 1,
|
'msg[receiver_id]': receiverId,
|
||||||
'msg[msg_status]': 0,
|
'msg[receiver_type]': 1,
|
||||||
'msg[dev_id]': getDevId(),
|
'msg[msg_type]': 1,
|
||||||
'msg[timestamp]': DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
'msg[msg_status]': 0,
|
||||||
'msg[new_face_version]': 0,
|
'msg[content]': jsonEncode(content),
|
||||||
'msg[content]': content,
|
'msg[timestamp]': DateTime.now().millisecondsSinceEpoch ~/ 1000,
|
||||||
'from_firework': 0,
|
'msg[new_face_version]': 0,
|
||||||
'build': 0,
|
'msg[dev_id]': getDevId(),
|
||||||
'mobi_app': 'web',
|
'from_firework': 0,
|
||||||
'csrf_token': csrf,
|
'build': 0,
|
||||||
'csrf': csrf,
|
'mobi_app': 'web',
|
||||||
});
|
'csrf_token': csrf,
|
||||||
var res =
|
'csrf': csrf,
|
||||||
await Request().post(Api.sendMsg, queryParameters: <String, dynamic>{
|
},
|
||||||
...params,
|
options: Options(
|
||||||
'csrf_token': csrf,
|
contentType: Headers.formUrlEncodedContentType,
|
||||||
'csrf': csrf,
|
),
|
||||||
}, data: {
|
);
|
||||||
'w_sender_uid': params['msg[sender_uid]'],
|
|
||||||
'w_receiver_id': params['msg[receiver_id]'],
|
|
||||||
'w_dev_id': params['msg[dev_id]'],
|
|
||||||
'w_rid': params['w_rid'],
|
|
||||||
'wts': params['wts'],
|
|
||||||
'csrf_token': csrf,
|
|
||||||
'csrf': csrf,
|
|
||||||
});
|
|
||||||
if (res.data['code'] == 0) {
|
if (res.data['code'] == 0) {
|
||||||
return {
|
return {
|
||||||
'status': true,
|
'status': true,
|
||||||
'data': res.data['data'],
|
'data': res.data['data'],
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
return {
|
return {'status': false, 'date': [], 'msg': res.data['message']};
|
||||||
'status': false,
|
|
||||||
'date': [],
|
|
||||||
'msg': "message: ${res.data['message']},"
|
|
||||||
" msg: ${res.data['msg']},"
|
|
||||||
" code: ${res.data['code']}",
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -220,4 +204,87 @@ class MsgHttp {
|
|||||||
}
|
}
|
||||||
return s.join();
|
return s.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Future removeSession({
|
||||||
|
int? talkerId,
|
||||||
|
}) async {
|
||||||
|
String csrf = await Request.getCsrf();
|
||||||
|
Map params = await WbiSign().makSign({
|
||||||
|
'talker_id': talkerId,
|
||||||
|
'session_type': 1,
|
||||||
|
'build': 0,
|
||||||
|
'mobi_app': 'web',
|
||||||
|
'csrf_token': csrf,
|
||||||
|
'csrf': csrf
|
||||||
|
});
|
||||||
|
var res = await Request().get(Api.removeSession, data: params);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': res.data['data'],
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {'status': false, 'date': [], 'msg': res.data['message']};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Future unread() async {
|
||||||
|
var res = await Request().get(Api.unread);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': res.data['data'],
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {'status': false, 'date': [], 'msg': res.data['message']};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 回复我的
|
||||||
|
static Future messageReply({
|
||||||
|
int? id,
|
||||||
|
int? replyTime,
|
||||||
|
}) async {
|
||||||
|
var params = {
|
||||||
|
if (id != null) 'id': id,
|
||||||
|
if (replyTime != null) 'reply_time': replyTime,
|
||||||
|
};
|
||||||
|
var res = await Request().get(Api.messageReplyAPi, data: params);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
try {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': MessageReplyModel.fromJson(res.data['data']),
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
return {'status': false, 'date': [], 'msg': err.toString()};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {'status': false, 'date': [], 'msg': res.data['message']};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 收到的赞
|
||||||
|
static Future messageLike({
|
||||||
|
int? id,
|
||||||
|
int? likeTime,
|
||||||
|
}) async {
|
||||||
|
var params = {
|
||||||
|
if (id != null) 'id': id,
|
||||||
|
if (likeTime != null) 'like_time': likeTime,
|
||||||
|
};
|
||||||
|
var res = await Request().get(Api.messageLikeAPi, data: params);
|
||||||
|
if (res.data['code'] == 0) {
|
||||||
|
try {
|
||||||
|
return {
|
||||||
|
'status': true,
|
||||||
|
'data': MessageLikeModel.fromJson(res.data['data']),
|
||||||
|
};
|
||||||
|
} catch (err) {
|
||||||
|
return {'status': false, 'date': [], 'msg': err.toString()};
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return {'status': false, 'date': [], 'msg': res.data['message']};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,14 @@ enum SubtitleType {
|
|||||||
zhHans,
|
zhHans,
|
||||||
// 英文(美国)
|
// 英文(美国)
|
||||||
enUS,
|
enUS,
|
||||||
|
// 中文繁体
|
||||||
|
zhTW,
|
||||||
|
//
|
||||||
|
en,
|
||||||
|
//
|
||||||
|
pt,
|
||||||
|
//
|
||||||
|
es,
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SubtitleTypeExtension on SubtitleType {
|
extension SubtitleTypeExtension on SubtitleType {
|
||||||
@ -24,6 +32,14 @@ extension SubtitleTypeExtension on SubtitleType {
|
|||||||
return '中文(简体)';
|
return '中文(简体)';
|
||||||
case SubtitleType.enUS:
|
case SubtitleType.enUS:
|
||||||
return '英文(美国)';
|
return '英文(美国)';
|
||||||
|
case SubtitleType.zhTW:
|
||||||
|
return '中文(繁体)';
|
||||||
|
case SubtitleType.en:
|
||||||
|
return '英文';
|
||||||
|
case SubtitleType.pt:
|
||||||
|
return '葡萄牙语';
|
||||||
|
case SubtitleType.es:
|
||||||
|
return '西班牙语';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -41,6 +57,14 @@ extension SubtitleIdExtension on SubtitleType {
|
|||||||
return 'zh-Hans';
|
return 'zh-Hans';
|
||||||
case SubtitleType.enUS:
|
case SubtitleType.enUS:
|
||||||
return 'en-US';
|
return 'en-US';
|
||||||
|
case SubtitleType.zhTW:
|
||||||
|
return 'zh-TW';
|
||||||
|
case SubtitleType.en:
|
||||||
|
return 'en';
|
||||||
|
case SubtitleType.pt:
|
||||||
|
return 'pt';
|
||||||
|
case SubtitleType.es:
|
||||||
|
return 'es';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -58,6 +82,14 @@ extension SubtitleCodeExtension on SubtitleType {
|
|||||||
return 4;
|
return 4;
|
||||||
case SubtitleType.enUS:
|
case SubtitleType.enUS:
|
||||||
return 5;
|
return 5;
|
||||||
|
case SubtitleType.zhTW:
|
||||||
|
return 6;
|
||||||
|
case SubtitleType.en:
|
||||||
|
return 7;
|
||||||
|
case SubtitleType.pt:
|
||||||
|
return 8;
|
||||||
|
case SubtitleType.es:
|
||||||
|
return 9;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
183
lib/models/msg/like.dart
Normal file
183
lib/models/msg/like.dart
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
class MessageLikeModel {
|
||||||
|
MessageLikeModel({
|
||||||
|
this.latest,
|
||||||
|
this.total,
|
||||||
|
});
|
||||||
|
|
||||||
|
Latest? latest;
|
||||||
|
Total? total;
|
||||||
|
|
||||||
|
factory MessageLikeModel.fromJson(Map<String, dynamic> json) =>
|
||||||
|
MessageLikeModel(
|
||||||
|
latest: json["latest"] == null ? null : Latest.fromJson(json["latest"]),
|
||||||
|
total: json["total"] == null ? null : Total.fromJson(json["total"]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Latest {
|
||||||
|
Latest({
|
||||||
|
this.items,
|
||||||
|
this.lastViewAt,
|
||||||
|
});
|
||||||
|
|
||||||
|
List? items;
|
||||||
|
int? lastViewAt;
|
||||||
|
|
||||||
|
factory Latest.fromJson(Map<String, dynamic> json) => Latest(
|
||||||
|
items: json["items"],
|
||||||
|
lastViewAt: json["last_view_at"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Total {
|
||||||
|
Total({
|
||||||
|
this.cursor,
|
||||||
|
this.items,
|
||||||
|
});
|
||||||
|
|
||||||
|
Cursor? cursor;
|
||||||
|
List<MessageLikeItem>? items;
|
||||||
|
|
||||||
|
factory Total.fromJson(Map<String, dynamic> json) => Total(
|
||||||
|
cursor: Cursor.fromJson(json['cursor']),
|
||||||
|
items: json["items"] == null
|
||||||
|
? []
|
||||||
|
: json["items"].map<MessageLikeItem>((e) {
|
||||||
|
return MessageLikeItem.fromJson(e);
|
||||||
|
}).toList(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class Cursor {
|
||||||
|
Cursor({
|
||||||
|
this.id,
|
||||||
|
this.isEnd,
|
||||||
|
this.time,
|
||||||
|
});
|
||||||
|
|
||||||
|
int? id;
|
||||||
|
bool? isEnd;
|
||||||
|
int? time;
|
||||||
|
|
||||||
|
factory Cursor.fromJson(Map<String, dynamic> json) => Cursor(
|
||||||
|
id: json['id'],
|
||||||
|
isEnd: json['is_end'],
|
||||||
|
time: json['time'],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class MessageLikeItem {
|
||||||
|
MessageLikeItem({
|
||||||
|
this.id,
|
||||||
|
this.users,
|
||||||
|
this.item,
|
||||||
|
this.counts,
|
||||||
|
this.likeTime,
|
||||||
|
this.noticeState,
|
||||||
|
this.isExpand = false,
|
||||||
|
});
|
||||||
|
|
||||||
|
int? id;
|
||||||
|
List<MessageLikeUser>? users;
|
||||||
|
MessageLikeItemItem? item;
|
||||||
|
int? counts;
|
||||||
|
int? likeTime;
|
||||||
|
int? noticeState;
|
||||||
|
bool isExpand;
|
||||||
|
|
||||||
|
factory MessageLikeItem.fromJson(Map<String, dynamic> json) =>
|
||||||
|
MessageLikeItem(
|
||||||
|
id: json["id"],
|
||||||
|
users: json["users"] == null
|
||||||
|
? []
|
||||||
|
: json["users"].map<MessageLikeUser>((e) {
|
||||||
|
return MessageLikeUser.fromJson(e);
|
||||||
|
}).toList(),
|
||||||
|
item: json["item"] == null
|
||||||
|
? null
|
||||||
|
: MessageLikeItemItem.fromJson(json["item"]),
|
||||||
|
counts: json["counts"],
|
||||||
|
likeTime: json["like_time"],
|
||||||
|
noticeState: json["notice_state"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class MessageLikeUser {
|
||||||
|
MessageLikeUser({
|
||||||
|
this.mid,
|
||||||
|
this.fans,
|
||||||
|
this.nickname,
|
||||||
|
this.avatar,
|
||||||
|
this.midLink,
|
||||||
|
this.follow,
|
||||||
|
});
|
||||||
|
|
||||||
|
int? mid;
|
||||||
|
int? fans;
|
||||||
|
String? nickname;
|
||||||
|
String? avatar;
|
||||||
|
String? midLink;
|
||||||
|
bool? follow;
|
||||||
|
|
||||||
|
factory MessageLikeUser.fromJson(Map<String, dynamic> json) =>
|
||||||
|
MessageLikeUser(
|
||||||
|
mid: json["mid"],
|
||||||
|
fans: json["fans"],
|
||||||
|
nickname: json["nickname"],
|
||||||
|
avatar: json["avatar"],
|
||||||
|
midLink: json["mid_link"],
|
||||||
|
follow: json["follow"],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
class MessageLikeItemItem {
|
||||||
|
MessageLikeItemItem({
|
||||||
|
this.itemId,
|
||||||
|
this.pid,
|
||||||
|
this.type,
|
||||||
|
this.business,
|
||||||
|
this.businessId,
|
||||||
|
this.replyBusinessId,
|
||||||
|
this.likeBusinessId,
|
||||||
|
this.title,
|
||||||
|
this.desc,
|
||||||
|
this.image,
|
||||||
|
this.uri,
|
||||||
|
this.detailName,
|
||||||
|
this.nativeUri,
|
||||||
|
this.ctime,
|
||||||
|
});
|
||||||
|
|
||||||
|
int? itemId;
|
||||||
|
int? pid;
|
||||||
|
String? type;
|
||||||
|
String? business;
|
||||||
|
int? businessId;
|
||||||
|
int? replyBusinessId;
|
||||||
|
int? likeBusinessId;
|
||||||
|
String? title;
|
||||||
|
String? desc;
|
||||||
|
String? image;
|
||||||
|
String? uri;
|
||||||
|
String? detailName;
|
||||||
|
String? nativeUri;
|
||||||
|
int? ctime;
|
||||||
|
|
||||||
|
factory MessageLikeItemItem.fromJson(Map<String, dynamic> json) =>
|
||||||
|
MessageLikeItemItem(
|
||||||
|
itemId: json["item_id"],
|
||||||
|
pid: json["pid"],
|
||||||
|
type: json["type"],
|
||||||
|
business: json["business"],
|
||||||
|
businessId: json["business_id"],
|
||||||
|
replyBusinessId: json["reply_business_id"],
|
||||||
|
likeBusinessId: json["like_business_id"],
|
||||||
|
title: json["title"],
|
||||||
|
desc: json["desc"],
|
||||||
|
image: json["image"],
|
||||||
|
uri: json["uri"],
|
||||||
|
detailName: json["detail_name"],
|
||||||
|
nativeUri: json["native_uri"],
|
||||||
|
ctime: json["ctime"],
|
||||||
|
);
|
||||||
|
}
|
||||||
168
lib/models/msg/reply.dart
Normal file
168
lib/models/msg/reply.dart
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
class MessageReplyModel {
|
||||||
|
MessageReplyModel({
|
||||||
|
this.cursor,
|
||||||
|
this.items,
|
||||||
|
});
|
||||||
|
|
||||||
|
Cursor? cursor;
|
||||||
|
List<MessageReplyItem>? items;
|
||||||
|
|
||||||
|
MessageReplyModel.fromJson(Map<String, dynamic> json) {
|
||||||
|
cursor = Cursor.fromJson(json['cursor']);
|
||||||
|
items = json["items"] != null
|
||||||
|
? json["items"].map<MessageReplyItem>((e) {
|
||||||
|
return MessageReplyItem.fromJson(e);
|
||||||
|
}).toList()
|
||||||
|
: [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class Cursor {
|
||||||
|
Cursor({
|
||||||
|
this.id,
|
||||||
|
this.isEnd,
|
||||||
|
this.time,
|
||||||
|
});
|
||||||
|
|
||||||
|
int? id;
|
||||||
|
bool? isEnd;
|
||||||
|
int? time;
|
||||||
|
|
||||||
|
Cursor.fromJson(Map<String, dynamic> json) {
|
||||||
|
id = json['id'];
|
||||||
|
isEnd = json['is_end'];
|
||||||
|
time = json['time'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class MessageReplyItem {
|
||||||
|
MessageReplyItem({
|
||||||
|
this.count,
|
||||||
|
this.id,
|
||||||
|
this.isMulti,
|
||||||
|
this.item,
|
||||||
|
this.replyTime,
|
||||||
|
this.user,
|
||||||
|
});
|
||||||
|
|
||||||
|
int? count;
|
||||||
|
int? id;
|
||||||
|
int? isMulti;
|
||||||
|
ReplyContentItem? item;
|
||||||
|
int? replyTime;
|
||||||
|
ReplyUser? user;
|
||||||
|
|
||||||
|
MessageReplyItem.fromJson(Map<String, dynamic> json) {
|
||||||
|
count = json['count'];
|
||||||
|
id = json['id'];
|
||||||
|
isMulti = json['is_multi'];
|
||||||
|
item = ReplyContentItem.fromJson(json["item"]);
|
||||||
|
replyTime = json['reply_time'];
|
||||||
|
user = ReplyUser.fromJson(json['user']);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReplyContentItem {
|
||||||
|
ReplyContentItem({
|
||||||
|
this.subjectId,
|
||||||
|
this.rootId,
|
||||||
|
this.sourceId,
|
||||||
|
this.targetId,
|
||||||
|
this.type,
|
||||||
|
this.businessId,
|
||||||
|
this.business,
|
||||||
|
this.title,
|
||||||
|
this.desc,
|
||||||
|
this.image,
|
||||||
|
this.uri,
|
||||||
|
this.nativeUri,
|
||||||
|
this.detailTitle,
|
||||||
|
this.rootReplyContent,
|
||||||
|
this.sourceContent,
|
||||||
|
this.targetReplyContent,
|
||||||
|
this.atDetails,
|
||||||
|
this.topicDetails,
|
||||||
|
this.hideReplyButton,
|
||||||
|
this.hideLikeButton,
|
||||||
|
this.likeState,
|
||||||
|
this.danmu,
|
||||||
|
this.message,
|
||||||
|
});
|
||||||
|
|
||||||
|
int? subjectId;
|
||||||
|
int? rootId;
|
||||||
|
int? sourceId;
|
||||||
|
int? targetId;
|
||||||
|
String? type;
|
||||||
|
int? businessId;
|
||||||
|
String? business;
|
||||||
|
String? title;
|
||||||
|
String? desc;
|
||||||
|
String? image;
|
||||||
|
String? uri;
|
||||||
|
String? nativeUri;
|
||||||
|
String? detailTitle;
|
||||||
|
String? rootReplyContent;
|
||||||
|
String? sourceContent;
|
||||||
|
String? targetReplyContent;
|
||||||
|
List? atDetails;
|
||||||
|
List? topicDetails;
|
||||||
|
bool? hideReplyButton;
|
||||||
|
bool? hideLikeButton;
|
||||||
|
int? likeState;
|
||||||
|
String? danmu;
|
||||||
|
String? message;
|
||||||
|
|
||||||
|
ReplyContentItem.fromJson(Map<String, dynamic> json) {
|
||||||
|
subjectId = json['subject_id'];
|
||||||
|
rootId = json['root_id'];
|
||||||
|
sourceId = json['source_id'];
|
||||||
|
targetId = json['target_id'];
|
||||||
|
type = json['type'];
|
||||||
|
businessId = json['business_id'];
|
||||||
|
business = json['business'];
|
||||||
|
title = json['title'];
|
||||||
|
desc = json['desc'];
|
||||||
|
image = json['image'];
|
||||||
|
uri = json['uri'];
|
||||||
|
nativeUri = json['native_uri'];
|
||||||
|
detailTitle = json['detail_title'];
|
||||||
|
rootReplyContent = json['root_reply_content'];
|
||||||
|
sourceContent = json['source_content'];
|
||||||
|
targetReplyContent = json['target_reply_content'];
|
||||||
|
atDetails = json['at_details'];
|
||||||
|
topicDetails = json['topic_details'];
|
||||||
|
hideReplyButton = json['hide_reply_button'];
|
||||||
|
hideLikeButton = json['hide_like_button'];
|
||||||
|
likeState = json['like_state'];
|
||||||
|
danmu = json['danmu'];
|
||||||
|
message = json['message'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReplyUser {
|
||||||
|
ReplyUser({
|
||||||
|
this.mid,
|
||||||
|
this.fans,
|
||||||
|
this.nickname,
|
||||||
|
this.avatar,
|
||||||
|
this.midLink,
|
||||||
|
this.follow,
|
||||||
|
});
|
||||||
|
|
||||||
|
int? mid;
|
||||||
|
int? fans;
|
||||||
|
String? nickname;
|
||||||
|
String? avatar;
|
||||||
|
String? midLink;
|
||||||
|
bool? follow;
|
||||||
|
|
||||||
|
ReplyUser.fromJson(Map<String, dynamic> json) {
|
||||||
|
mid = json['mid'];
|
||||||
|
fans = json['fans'];
|
||||||
|
nickname = json['nickname'];
|
||||||
|
avatar = json['avatar'];
|
||||||
|
midLink = json['mid_link'];
|
||||||
|
follow = json['follow'];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -225,6 +225,8 @@ class BangumiIntroController extends GetxController {
|
|||||||
videoDetailCtr.oid.value = aid;
|
videoDetailCtr.oid.value = aid;
|
||||||
videoDetailCtr.cover.value = cover;
|
videoDetailCtr.cover.value = cover;
|
||||||
videoDetailCtr.queryVideoUrl();
|
videoDetailCtr.queryVideoUrl();
|
||||||
|
videoDetailCtr.getSubtitle();
|
||||||
|
videoDetailCtr.setSubtitleContent();
|
||||||
// 重新请求评论
|
// 重新请求评论
|
||||||
try {
|
try {
|
||||||
/// 未渲染回复组件时可能异常
|
/// 未渲染回复组件时可能异常
|
||||||
|
|||||||
@ -208,7 +208,17 @@ class ProfilePanel extends StatelessWidget {
|
|||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Expanded(
|
Expanded(
|
||||||
child: TextButton(
|
child: TextButton(
|
||||||
onPressed: () {},
|
onPressed: () {
|
||||||
|
Get.toNamed(
|
||||||
|
'/whisperDetail',
|
||||||
|
parameters: {
|
||||||
|
'name': memberInfo.name!,
|
||||||
|
'face': memberInfo.face!,
|
||||||
|
'mid': memberInfo.mid.toString(),
|
||||||
|
'heroTag': ctr.heroTag!,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
style: TextButton.styleFrom(
|
style: TextButton.styleFrom(
|
||||||
backgroundColor: Theme.of(context)
|
backgroundColor: Theme.of(context)
|
||||||
.colorScheme
|
.colorScheme
|
||||||
|
|||||||
3
lib/pages/message/at/controller.dart
Normal file
3
lib/pages/message/at/controller.dart
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
class MessageAtController extends GetxController {}
|
||||||
4
lib/pages/message/at/index.dart
Normal file
4
lib/pages/message/at/index.dart
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
library message_at;
|
||||||
|
|
||||||
|
export './controller.dart';
|
||||||
|
export './view.dart';
|
||||||
19
lib/pages/message/at/view.dart
Normal file
19
lib/pages/message/at/view.dart
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class MessageAtPage extends StatefulWidget {
|
||||||
|
const MessageAtPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MessageAtPage> createState() => _MessageAtPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MessageAtPageState extends State<MessageAtPage> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('@我的'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
30
lib/pages/message/like/controller.dart
Normal file
30
lib/pages/message/like/controller.dart
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/http/msg.dart';
|
||||||
|
import 'package:pilipala/models/msg/like.dart';
|
||||||
|
|
||||||
|
class MessageLikeController extends GetxController {
|
||||||
|
Cursor? cursor;
|
||||||
|
RxList<MessageLikeItem> likeItems = <MessageLikeItem>[].obs;
|
||||||
|
|
||||||
|
Future queryMessageLike({String type = 'init'}) async {
|
||||||
|
if (cursor != null && cursor!.isEnd == true) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
var params = {
|
||||||
|
if (type == 'onLoad') 'id': cursor!.id,
|
||||||
|
if (type == 'onLoad') 'likeTime': cursor!.time,
|
||||||
|
};
|
||||||
|
var res = await MsgHttp.messageLike(
|
||||||
|
id: params['id'], likeTime: params['likeTime']);
|
||||||
|
if (res['status']) {
|
||||||
|
cursor = res['data'].total.cursor;
|
||||||
|
likeItems.addAll(res['data'].total.items);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
Future expandedUsersAvatar(i) async {
|
||||||
|
likeItems[i].isExpand = !likeItems[i].isExpand;
|
||||||
|
likeItems.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
4
lib/pages/message/like/index.dart
Normal file
4
lib/pages/message/like/index.dart
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
library message_like;
|
||||||
|
|
||||||
|
export './controller.dart';
|
||||||
|
export './view.dart';
|
||||||
319
lib/pages/message/like/view.dart
Normal file
319
lib/pages/message/like/view.dart
Normal file
@ -0,0 +1,319 @@
|
|||||||
|
import 'package:easy_debounce/easy_throttle.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
import 'package:pilipala/http/search.dart';
|
||||||
|
import 'package:pilipala/models/msg/like.dart';
|
||||||
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
|
import 'controller.dart';
|
||||||
|
|
||||||
|
class MessageLikePage extends StatefulWidget {
|
||||||
|
const MessageLikePage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MessageLikePage> createState() => _MessageLikePageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MessageLikePageState extends State<MessageLikePage> {
|
||||||
|
final MessageLikeController _messageLikeCtr =
|
||||||
|
Get.put(MessageLikeController());
|
||||||
|
late Future _futureBuilderFuture;
|
||||||
|
final ScrollController scrollController = ScrollController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_futureBuilderFuture = _messageLikeCtr.queryMessageLike();
|
||||||
|
scrollController.addListener(
|
||||||
|
() async {
|
||||||
|
if (scrollController.position.pixels >=
|
||||||
|
scrollController.position.maxScrollExtent - 200) {
|
||||||
|
EasyThrottle.throttle('follow', const Duration(seconds: 1), () {
|
||||||
|
_messageLikeCtr.queryMessageLike(type: 'onLoad');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
scrollController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('收到的赞'),
|
||||||
|
),
|
||||||
|
body: RefreshIndicator(
|
||||||
|
onRefresh: () async {
|
||||||
|
await _messageLikeCtr.queryMessageLike(type: 'init');
|
||||||
|
},
|
||||||
|
child: FutureBuilder(
|
||||||
|
future: _futureBuilderFuture,
|
||||||
|
builder: (BuildContext context, AsyncSnapshot snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
|
if (snapshot.data == null) {
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
if (snapshot.data['status']) {
|
||||||
|
final likeItems = _messageLikeCtr.likeItems;
|
||||||
|
return Obx(
|
||||||
|
() => ListView.separated(
|
||||||
|
controller: scrollController,
|
||||||
|
itemBuilder: (context, index) => LikeItem(
|
||||||
|
item: likeItems[index],
|
||||||
|
index: index,
|
||||||
|
messageLikeCtr: _messageLikeCtr,
|
||||||
|
),
|
||||||
|
itemCount: likeItems.length,
|
||||||
|
separatorBuilder: (BuildContext context, int index) {
|
||||||
|
return Divider(
|
||||||
|
indent: 66,
|
||||||
|
endIndent: 14,
|
||||||
|
height: 1,
|
||||||
|
color: Colors.grey.withOpacity(0.1),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// 请求错误
|
||||||
|
return CustomScrollView(
|
||||||
|
slivers: [
|
||||||
|
HttpError(
|
||||||
|
errMsg: snapshot.data['msg'],
|
||||||
|
fn: () {
|
||||||
|
setState(() {
|
||||||
|
_futureBuilderFuture =
|
||||||
|
_messageLikeCtr.queryMessageLike();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LikeItem extends StatelessWidget {
|
||||||
|
final MessageLikeItem item;
|
||||||
|
final int index;
|
||||||
|
final MessageLikeController messageLikeCtr;
|
||||||
|
|
||||||
|
const LikeItem(
|
||||||
|
{super.key,
|
||||||
|
required this.item,
|
||||||
|
required this.index,
|
||||||
|
required this.messageLikeCtr});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
Color outline = Theme.of(context).colorScheme.outline;
|
||||||
|
final nickNameList = item.users!.map((e) => e.nickname).take(2).toList();
|
||||||
|
int usersLen = item.users!.length > 3 ? 3 : item.users!.length;
|
||||||
|
final String bvid = item.item!.uri!.split('/').last;
|
||||||
|
// 页码
|
||||||
|
final String page =
|
||||||
|
item.item!.nativeUri!.split('page=').last.split('&').first;
|
||||||
|
// 根评论id
|
||||||
|
final String commentRootId =
|
||||||
|
item.item!.nativeUri!.split('comment_root_id=').last.split('&').first;
|
||||||
|
// 二级评论id
|
||||||
|
final String commentSecondaryId =
|
||||||
|
item.item!.nativeUri!.split('comment_secondary_id=').last;
|
||||||
|
|
||||||
|
return InkWell(
|
||||||
|
onTap: () async {
|
||||||
|
try {
|
||||||
|
final int cid = await SearchHttp.ab2c(bvid: bvid);
|
||||||
|
final String heroTag = Utils.makeHeroTag(bvid);
|
||||||
|
Get.toNamed<dynamic>(
|
||||||
|
'/video?bvid=$bvid&cid=$cid',
|
||||||
|
arguments: <String, String?>{
|
||||||
|
'pic': '',
|
||||||
|
'heroTag': heroTag,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} catch (_) {
|
||||||
|
SmartDialog.showToast('视频可能失效了');
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
Padding(
|
||||||
|
padding: const EdgeInsets.all(14),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
GestureDetector(
|
||||||
|
behavior: HitTestBehavior.translucent,
|
||||||
|
onTap: () {
|
||||||
|
if (usersLen == 1) {
|
||||||
|
final String heroTag =
|
||||||
|
Utils.makeHeroTag(item.users!.first.mid);
|
||||||
|
Get.toNamed('/member?mid=${item.users!.first.mid}',
|
||||||
|
arguments: {
|
||||||
|
'face': item.users!.first.avatar,
|
||||||
|
'heroTag': heroTag
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
messageLikeCtr.expandedUsersAvatar(index);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
// 多个头像层叠
|
||||||
|
child: SizedBox(
|
||||||
|
width: 50,
|
||||||
|
height: 50,
|
||||||
|
child: Stack(
|
||||||
|
children: [
|
||||||
|
for (var i = 0; i < usersLen; i++)
|
||||||
|
Positioned(
|
||||||
|
top: i % 2 * (50 / (usersLen >= 2 ? 2 : 1)),
|
||||||
|
left: i / 2 * (50 / (usersLen >= 2 ? 2 : 1)),
|
||||||
|
child: NetworkImgLayer(
|
||||||
|
width: 50 / (usersLen >= 2 ? 2 : 1),
|
||||||
|
height: 50 / (usersLen >= 2 ? 2 : 1),
|
||||||
|
type: 'avatar',
|
||||||
|
src: item.users![i].avatar,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text.rich(TextSpan(children: [
|
||||||
|
TextSpan(text: nickNameList.join('、')),
|
||||||
|
const TextSpan(text: ' '),
|
||||||
|
if (item.users!.length > 1)
|
||||||
|
TextSpan(
|
||||||
|
text: '等总计${item.users!.length}人',
|
||||||
|
style: TextStyle(color: outline),
|
||||||
|
),
|
||||||
|
TextSpan(
|
||||||
|
text: '赞了我的评论',
|
||||||
|
style: TextStyle(color: outline),
|
||||||
|
),
|
||||||
|
])),
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Text(
|
||||||
|
Utils.dateFormat(item.likeTime!, formatType: 'detail'),
|
||||||
|
style: TextStyle(color: outline),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 25),
|
||||||
|
if (item.item!.type! == 'reply')
|
||||||
|
Container(
|
||||||
|
width: 60,
|
||||||
|
height: 60,
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
child: Text(
|
||||||
|
item.item!.title!,
|
||||||
|
maxLines: 4,
|
||||||
|
style: const TextStyle(fontSize: 12, letterSpacing: 0.3),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (item.item!.type! == 'video')
|
||||||
|
NetworkImgLayer(
|
||||||
|
width: 60,
|
||||||
|
height: 60,
|
||||||
|
src: item.item!.image,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
top: 0,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
child: AnimatedContainer(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
width: item.isExpand ? Get.size.width - 74 : 0,
|
||||||
|
color: Theme.of(context).colorScheme.secondaryContainer,
|
||||||
|
child: ListView.builder(
|
||||||
|
itemCount: item.users!.length,
|
||||||
|
scrollDirection: Axis.horizontal,
|
||||||
|
itemBuilder: (BuildContext context, int i) {
|
||||||
|
return Padding(
|
||||||
|
padding: EdgeInsets.fromLTRB(i == 0 ? 12 : 4, 8, 4, 0),
|
||||||
|
child: Column(
|
||||||
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
final String heroTag =
|
||||||
|
Utils.makeHeroTag(item.users![i].mid);
|
||||||
|
Get.toNamed(
|
||||||
|
'/member?mid=${item.users![i].mid}',
|
||||||
|
arguments: {
|
||||||
|
'face': item.users![i].avatar,
|
||||||
|
'heroTag': heroTag
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: NetworkImgLayer(
|
||||||
|
width: 42,
|
||||||
|
height: 42,
|
||||||
|
type: 'avatar',
|
||||||
|
src: item.users![i].avatar,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
SizedBox(
|
||||||
|
width: 68,
|
||||||
|
child: Text(
|
||||||
|
textAlign: TextAlign.center,
|
||||||
|
item.users![i].nickname!,
|
||||||
|
maxLines: 1,
|
||||||
|
overflow: TextOverflow.clip,
|
||||||
|
style: TextStyle(color: outline),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
Positioned(
|
||||||
|
top: 0,
|
||||||
|
left: 0,
|
||||||
|
bottom: 0,
|
||||||
|
child: GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
messageLikeCtr.expandedUsersAvatar(index);
|
||||||
|
},
|
||||||
|
child: AnimatedContainer(
|
||||||
|
duration: const Duration(milliseconds: 300),
|
||||||
|
width: item.isExpand ? 74 : 0,
|
||||||
|
color: Colors.black.withOpacity(0.3),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
25
lib/pages/message/reply/controller.dart
Normal file
25
lib/pages/message/reply/controller.dart
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/http/msg.dart';
|
||||||
|
import 'package:pilipala/models/msg/reply.dart';
|
||||||
|
|
||||||
|
class MessageReplyController extends GetxController {
|
||||||
|
Cursor? cursor;
|
||||||
|
RxList<MessageReplyItem> replyItems = <MessageReplyItem>[].obs;
|
||||||
|
|
||||||
|
Future queryMessageReply({String type = 'init'}) async {
|
||||||
|
if (cursor != null && cursor!.isEnd == true) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
var params = {
|
||||||
|
if (type == 'onLoad') 'id': cursor!.id,
|
||||||
|
if (type == 'onLoad') 'replyTime': cursor!.time,
|
||||||
|
};
|
||||||
|
var res = await MsgHttp.messageReply(
|
||||||
|
id: params['id'], replyTime: params['replyTime']);
|
||||||
|
if (res['status']) {
|
||||||
|
cursor = res['data'].cursor;
|
||||||
|
replyItems.addAll(res['data'].items);
|
||||||
|
}
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
4
lib/pages/message/reply/index.dart
Normal file
4
lib/pages/message/reply/index.dart
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
library message_reply;
|
||||||
|
|
||||||
|
export './controller.dart';
|
||||||
|
export './view.dart';
|
||||||
272
lib/pages/message/reply/view.dart
Normal file
272
lib/pages/message/reply/view.dart
Normal file
@ -0,0 +1,272 @@
|
|||||||
|
import 'package:easy_debounce/easy_throttle.dart';
|
||||||
|
import 'package:flutter/gestures.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/common/widgets/http_error.dart';
|
||||||
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
import 'package:pilipala/http/search.dart';
|
||||||
|
import 'package:pilipala/models/msg/reply.dart';
|
||||||
|
import 'package:pilipala/utils/utils.dart';
|
||||||
|
|
||||||
|
import 'controller.dart';
|
||||||
|
|
||||||
|
class MessageReplyPage extends StatefulWidget {
|
||||||
|
const MessageReplyPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MessageReplyPage> createState() => _MessageReplyPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MessageReplyPageState extends State<MessageReplyPage> {
|
||||||
|
final MessageReplyController _messageReplyCtr =
|
||||||
|
Get.put(MessageReplyController());
|
||||||
|
late Future _futureBuilderFuture;
|
||||||
|
final ScrollController scrollController = ScrollController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
_futureBuilderFuture = _messageReplyCtr.queryMessageReply();
|
||||||
|
scrollController.addListener(
|
||||||
|
() async {
|
||||||
|
if (scrollController.position.pixels >=
|
||||||
|
scrollController.position.maxScrollExtent - 200) {
|
||||||
|
EasyThrottle.throttle('follow', const Duration(seconds: 1), () {
|
||||||
|
_messageReplyCtr.queryMessageReply(type: 'onLoad');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
scrollController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('回复我的'),
|
||||||
|
),
|
||||||
|
body: RefreshIndicator(
|
||||||
|
onRefresh: () async {
|
||||||
|
await _messageReplyCtr.queryMessageReply(type: 'init');
|
||||||
|
},
|
||||||
|
child: FutureBuilder(
|
||||||
|
future: _futureBuilderFuture,
|
||||||
|
builder: (BuildContext context, AsyncSnapshot snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
|
if (snapshot.data == null) {
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
if (snapshot.data['status']) {
|
||||||
|
final replyItems = _messageReplyCtr.replyItems;
|
||||||
|
return Obx(
|
||||||
|
() => ListView.separated(
|
||||||
|
controller: scrollController,
|
||||||
|
itemBuilder: (context, index) =>
|
||||||
|
ReplyItem(item: replyItems[index]),
|
||||||
|
itemCount: replyItems.length,
|
||||||
|
separatorBuilder: (BuildContext context, int index) {
|
||||||
|
return Divider(
|
||||||
|
indent: 66,
|
||||||
|
endIndent: 14,
|
||||||
|
height: 1,
|
||||||
|
color: Colors.grey.withOpacity(0.1),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
// 请求错误
|
||||||
|
return CustomScrollView(
|
||||||
|
slivers: [
|
||||||
|
HttpError(
|
||||||
|
errMsg: snapshot.data['msg'],
|
||||||
|
fn: () {
|
||||||
|
setState(() {
|
||||||
|
_futureBuilderFuture =
|
||||||
|
_messageReplyCtr.queryMessageReply();
|
||||||
|
});
|
||||||
|
},
|
||||||
|
)
|
||||||
|
],
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return const SizedBox();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class ReplyItem extends StatelessWidget {
|
||||||
|
final MessageReplyItem item;
|
||||||
|
|
||||||
|
const ReplyItem({super.key, required this.item});
|
||||||
|
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
Color outline = Theme.of(context).colorScheme.outline;
|
||||||
|
final String heroTag = Utils.makeHeroTag(item.user!.mid);
|
||||||
|
final String bvid = item.item!.uri!.split('/').last;
|
||||||
|
// 页码
|
||||||
|
final String page =
|
||||||
|
item.item!.nativeUri!.split('page=').last.split('&').first;
|
||||||
|
// 根评论id
|
||||||
|
final String commentRootId =
|
||||||
|
item.item!.nativeUri!.split('comment_root_id=').last.split('&').first;
|
||||||
|
// 二级评论id
|
||||||
|
final String commentSecondaryId =
|
||||||
|
item.item!.nativeUri!.split('comment_secondary_id=').last;
|
||||||
|
|
||||||
|
return InkWell(
|
||||||
|
onTap: () async {
|
||||||
|
final int cid = await SearchHttp.ab2c(bvid: bvid);
|
||||||
|
final String heroTag = Utils.makeHeroTag(bvid);
|
||||||
|
Get.toNamed<dynamic>(
|
||||||
|
'/video?bvid=$bvid&cid=$cid',
|
||||||
|
arguments: <String, String?>{
|
||||||
|
'pic': '',
|
||||||
|
'heroTag': heroTag,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
},
|
||||||
|
child: Padding(
|
||||||
|
padding: const EdgeInsets.all(14),
|
||||||
|
child: Row(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
GestureDetector(
|
||||||
|
onTap: () {
|
||||||
|
Get.toNamed('/member?mid=${item.user!.mid}',
|
||||||
|
arguments: {'face': item.user!.avatar, 'heroTag': heroTag});
|
||||||
|
},
|
||||||
|
child: Hero(
|
||||||
|
tag: heroTag,
|
||||||
|
child: NetworkImgLayer(
|
||||||
|
width: 42,
|
||||||
|
height: 42,
|
||||||
|
type: 'avatar',
|
||||||
|
src: item.user!.avatar,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
|
Expanded(
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
Text.rich(TextSpan(children: [
|
||||||
|
TextSpan(text: item.user!.nickname!),
|
||||||
|
const TextSpan(text: ' '),
|
||||||
|
if (item.item!.type! == 'video')
|
||||||
|
TextSpan(
|
||||||
|
text: '对我的视频发表了评论', style: TextStyle(color: outline)),
|
||||||
|
if (item.item!.type! == 'reply')
|
||||||
|
TextSpan(
|
||||||
|
text: '回复了我的评论',
|
||||||
|
style: TextStyle(color: outline),
|
||||||
|
),
|
||||||
|
])),
|
||||||
|
const SizedBox(height: 6),
|
||||||
|
Text.rich(
|
||||||
|
maxLines: 2,
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
style: const TextStyle(letterSpacing: 0.3),
|
||||||
|
buildContent(context, item.item)),
|
||||||
|
if (item.item!.targetReplyContent != '') ...[
|
||||||
|
const SizedBox(height: 2),
|
||||||
|
Text(
|
||||||
|
item.item!.targetReplyContent!,
|
||||||
|
style: TextStyle(color: outline),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
const SizedBox(height: 4),
|
||||||
|
Row(
|
||||||
|
children: [
|
||||||
|
Text(
|
||||||
|
Utils.dateFormat(item.replyTime!, formatType: 'detail'),
|
||||||
|
style: TextStyle(color: outline),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 16),
|
||||||
|
Text('回复', style: TextStyle(color: outline)),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
const SizedBox(width: 25),
|
||||||
|
if (item.item!.type! == 'reply')
|
||||||
|
Container(
|
||||||
|
width: 60,
|
||||||
|
height: 80,
|
||||||
|
padding: const EdgeInsets.all(4),
|
||||||
|
child: Text(
|
||||||
|
item.item!.rootReplyContent!,
|
||||||
|
maxLines: 4,
|
||||||
|
style: const TextStyle(fontSize: 12, letterSpacing: 0.3),
|
||||||
|
overflow: TextOverflow.ellipsis,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
if (item.item!.type! == 'video')
|
||||||
|
NetworkImgLayer(
|
||||||
|
width: 60,
|
||||||
|
height: 60,
|
||||||
|
src: item.item!.image,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
InlineSpan buildContent(BuildContext context, item) {
|
||||||
|
List? atDetails = item!.atDetails;
|
||||||
|
final List<InlineSpan> spanChilds = <InlineSpan>[];
|
||||||
|
if (atDetails!.isNotEmpty) {
|
||||||
|
final String patternStr =
|
||||||
|
atDetails.map<String>((e) => '@${e['nickname']}').toList().join('|');
|
||||||
|
final RegExp regExp = RegExp(patternStr);
|
||||||
|
item.sourceContent!.splitMapJoin(
|
||||||
|
regExp,
|
||||||
|
onMatch: (Match match) {
|
||||||
|
spanChilds.add(
|
||||||
|
TextSpan(
|
||||||
|
text: match.group(0),
|
||||||
|
recognizer: TapGestureRecognizer()
|
||||||
|
..onTap = () {
|
||||||
|
var currentUser = atDetails
|
||||||
|
.where((e) => e['nickname'] == match.group(0)!.substring(1))
|
||||||
|
.first;
|
||||||
|
Get.toNamed('/member?mid=${currentUser['mid']}', arguments: {
|
||||||
|
'face': currentUser['avatar'],
|
||||||
|
});
|
||||||
|
},
|
||||||
|
style: TextStyle(color: Theme.of(context).colorScheme.primary),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
onNonMatch: (String nonMatch) {
|
||||||
|
spanChilds.add(
|
||||||
|
TextSpan(text: nonMatch),
|
||||||
|
);
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
spanChilds.add(
|
||||||
|
TextSpan(text: item.sourceContent),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return TextSpan(children: spanChilds);
|
||||||
|
}
|
||||||
3
lib/pages/message/system/controller.dart
Normal file
3
lib/pages/message/system/controller.dart
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import 'package:get/get.dart';
|
||||||
|
|
||||||
|
class MessageSystemController extends GetxController {}
|
||||||
4
lib/pages/message/system/index.dart
Normal file
4
lib/pages/message/system/index.dart
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
library message_system;
|
||||||
|
|
||||||
|
export './controller.dart';
|
||||||
|
export './view.dart';
|
||||||
19
lib/pages/message/system/view.dart
Normal file
19
lib/pages/message/system/view.dart
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
import 'package:flutter/material.dart';
|
||||||
|
|
||||||
|
class MessageSystemPage extends StatefulWidget {
|
||||||
|
const MessageSystemPage({super.key});
|
||||||
|
|
||||||
|
@override
|
||||||
|
State<MessageSystemPage> createState() => _MessageSystemPageState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MessageSystemPageState extends State<MessageSystemPage> {
|
||||||
|
@override
|
||||||
|
Widget build(BuildContext context) {
|
||||||
|
return Scaffold(
|
||||||
|
appBar: AppBar(
|
||||||
|
title: const Text('系统通知'),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -436,6 +436,7 @@ class VideoIntroController extends GetxController {
|
|||||||
videoDetailCtr.cover.value = cover;
|
videoDetailCtr.cover.value = cover;
|
||||||
videoDetailCtr.queryVideoUrl();
|
videoDetailCtr.queryVideoUrl();
|
||||||
videoDetailCtr.getSubtitle();
|
videoDetailCtr.getSubtitle();
|
||||||
|
videoDetailCtr.setSubtitleContent();
|
||||||
// 重新请求评论
|
// 重新请求评论
|
||||||
try {
|
try {
|
||||||
/// 未渲染回复组件时可能异常
|
/// 未渲染回复组件时可能异常
|
||||||
|
|||||||
@ -645,7 +645,7 @@ InlineSpan buildContent(
|
|||||||
'',
|
'',
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
Uri uri = Uri.parse(matchStr);
|
Uri uri = Uri.parse(matchStr.replaceAll('/?', '?'));
|
||||||
SchemeEntity scheme = SchemeEntity(
|
SchemeEntity scheme = SchemeEntity(
|
||||||
scheme: uri.scheme,
|
scheme: uri.scheme,
|
||||||
host: uri.host,
|
host: uri.host,
|
||||||
|
|||||||
@ -117,6 +117,7 @@ class _VideoReplyNewDialogState extends State<VideoReplyNewDialog>
|
|||||||
final String newText = currentText.substring(0, cursorPosition) +
|
final String newText = currentText.substring(0, cursorPosition) +
|
||||||
emote.text! +
|
emote.text! +
|
||||||
currentText.substring(cursorPosition);
|
currentText.substring(cursorPosition);
|
||||||
|
message.value = newText;
|
||||||
_replyContentController.value = TextEditingValue(
|
_replyContentController.value = TextEditingValue(
|
||||||
text: newText,
|
text: newText,
|
||||||
selection:
|
selection:
|
||||||
|
|||||||
@ -515,6 +515,13 @@ class _VideoDetailPageState extends State<VideoDetailPage>
|
|||||||
showEposideCb: () => vdCtr.videoType == SearchType.video
|
showEposideCb: () => vdCtr.videoType == SearchType.video
|
||||||
? videoIntroController.showEposideHandler()
|
? videoIntroController.showEposideHandler()
|
||||||
: bangumiIntroController.showEposideHandler(),
|
: bangumiIntroController.showEposideHandler(),
|
||||||
|
fullScreenCb: (bool status) {
|
||||||
|
if (status) {
|
||||||
|
videoHeight.value = Get.size.height;
|
||||||
|
} else {
|
||||||
|
videoHeight.value = defaultVideoHeight;
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
|
|||||||
@ -433,42 +433,47 @@ class _HeaderControlState extends State<HeaderControl> {
|
|||||||
return AlertDialog(
|
return AlertDialog(
|
||||||
title: const Text('选择字幕'),
|
title: const Text('选择字幕'),
|
||||||
contentPadding: const EdgeInsets.fromLTRB(0, 12, 0, 18),
|
contentPadding: const EdgeInsets.fromLTRB(0, 12, 0, 18),
|
||||||
content: StatefulBuilder(builder: (context, StateSetter setState) {
|
content: StatefulBuilder(
|
||||||
return len == 0
|
builder: (context, StateSetter setState) {
|
||||||
? const SizedBox(
|
return len == 0
|
||||||
height: 60,
|
? const SizedBox(
|
||||||
child: Center(
|
height: 60,
|
||||||
child: Text('没有字幕'),
|
child: Center(
|
||||||
),
|
child: Text('没有字幕'),
|
||||||
)
|
|
||||||
: Column(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
RadioListTile(
|
|
||||||
value: -1,
|
|
||||||
title: const Text('关闭字幕'),
|
|
||||||
groupValue: tempThemeValue,
|
|
||||||
onChanged: (value) {
|
|
||||||
tempThemeValue = value!;
|
|
||||||
widget.controller?.toggleSubtitle(value);
|
|
||||||
Get.back();
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
...widget.videoDetailCtr!.subtitles
|
)
|
||||||
.map((e) => RadioListTile(
|
: SingleChildScrollView(
|
||||||
value: e.code,
|
child: Column(
|
||||||
title: Text(e.title),
|
mainAxisSize: MainAxisSize.min,
|
||||||
groupValue: tempThemeValue,
|
children: [
|
||||||
onChanged: (value) {
|
RadioListTile(
|
||||||
tempThemeValue = value!;
|
value: -1,
|
||||||
widget.controller?.toggleSubtitle(value);
|
title: const Text('关闭字幕'),
|
||||||
Get.back();
|
groupValue: tempThemeValue,
|
||||||
},
|
onChanged: (value) {
|
||||||
))
|
tempThemeValue = value!;
|
||||||
.toList(),
|
widget.controller?.toggleSubtitle(value);
|
||||||
],
|
Get.back();
|
||||||
);
|
},
|
||||||
}),
|
),
|
||||||
|
...widget.videoDetailCtr!.subtitles
|
||||||
|
.map((e) => RadioListTile(
|
||||||
|
value: e.code,
|
||||||
|
title: Text(e.title),
|
||||||
|
groupValue: tempThemeValue,
|
||||||
|
onChanged: (value) {
|
||||||
|
tempThemeValue = value!;
|
||||||
|
widget.controller
|
||||||
|
?.toggleSubtitle(value);
|
||||||
|
Get.back();
|
||||||
|
},
|
||||||
|
))
|
||||||
|
.toList(),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
import 'package:flutter/material.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';
|
||||||
@ -7,6 +8,38 @@ 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;
|
||||||
|
RxList noticesList = [
|
||||||
|
{
|
||||||
|
'icon': Icons.message_outlined,
|
||||||
|
'title': '回复我的',
|
||||||
|
'path': '/messageReply',
|
||||||
|
'count': 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'icon': Icons.alternate_email,
|
||||||
|
'title': '@ 我的',
|
||||||
|
'path': '/messageAt',
|
||||||
|
'count': 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'icon': Icons.thumb_up_outlined,
|
||||||
|
'title': '收到的赞',
|
||||||
|
'path': '/messageLike',
|
||||||
|
'count': 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
'icon': Icons.notifications_none_outlined,
|
||||||
|
'title': '系统通知',
|
||||||
|
'path': '/messageSystem',
|
||||||
|
'count': 0,
|
||||||
|
}
|
||||||
|
].obs;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void onInit() {
|
||||||
|
unread();
|
||||||
|
super.onInit();
|
||||||
|
}
|
||||||
|
|
||||||
Future querySessionList(String? type) async {
|
Future querySessionList(String? type) async {
|
||||||
if (isLoading) return;
|
if (isLoading) return;
|
||||||
@ -62,4 +95,31 @@ class WhisperController extends GetxController {
|
|||||||
Future onRefresh() async {
|
Future onRefresh() async {
|
||||||
querySessionList('onRefresh');
|
querySessionList('onRefresh');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void refreshLastMsg(int talkerId, String content) {
|
||||||
|
final SessionList currentItem =
|
||||||
|
sessionList.where((p0) => p0.talkerId == talkerId).first;
|
||||||
|
currentItem.lastMsg!.content['content'] = content;
|
||||||
|
sessionList.removeWhere((p0) => p0.talkerId == talkerId);
|
||||||
|
sessionList.insert(0, currentItem);
|
||||||
|
sessionList.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除会话
|
||||||
|
void removeSessionMsg(int talkerId) {
|
||||||
|
sessionList.removeWhere((p0) => p0.talkerId == talkerId);
|
||||||
|
sessionList.refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 消息未读数
|
||||||
|
void unread() async {
|
||||||
|
var res = await MsgHttp.unread();
|
||||||
|
if (res['status']) {
|
||||||
|
noticesList[0]['count'] = res['data']['reply'];
|
||||||
|
noticesList[1]['count'] = res['data']['at'];
|
||||||
|
noticesList[2]['count'] = res['data']['like'];
|
||||||
|
noticesList[3]['count'] = res['data']['sys_msg'];
|
||||||
|
noticesList.refresh();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,6 +1,7 @@
|
|||||||
import 'package:easy_debounce/easy_throttle.dart';
|
import 'package:easy_debounce/easy_throttle.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
|
import 'package:pilipala/common/constants.dart';
|
||||||
import 'package:pilipala/common/skeleton/skeleton.dart';
|
import 'package:pilipala/common/skeleton/skeleton.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
@ -44,147 +45,152 @@ class _WhisperPageState extends State<WhisperPage> {
|
|||||||
appBar: AppBar(
|
appBar: AppBar(
|
||||||
title: const Text('消息'),
|
title: const Text('消息'),
|
||||||
),
|
),
|
||||||
body: Column(
|
body: RefreshIndicator(
|
||||||
children: [
|
onRefresh: () async {
|
||||||
// LayoutBuilder(
|
_whisperController.unread();
|
||||||
// builder: (BuildContext context, BoxConstraints constraints) {
|
await _whisperController.onRefresh();
|
||||||
// // 在这里根据父级容器的约束条件构建小部件树
|
},
|
||||||
// return Padding(
|
child: SingleChildScrollView(
|
||||||
// padding: const EdgeInsets.only(left: 20, right: 20),
|
controller: _scrollController,
|
||||||
// child: SizedBox(
|
child: Column(
|
||||||
// height: constraints.maxWidth / 5,
|
children: [
|
||||||
// child: GridView.count(
|
LayoutBuilder(
|
||||||
// primary: false,
|
builder: (BuildContext context, BoxConstraints constraints) {
|
||||||
// crossAxisCount: 4,
|
// 在这里根据父级容器的约束条件构建小部件树
|
||||||
// padding: const EdgeInsets.all(0),
|
return Padding(
|
||||||
// childAspectRatio: 1.25,
|
padding: const EdgeInsets.only(left: 20, right: 20),
|
||||||
// children: [
|
child: SizedBox(
|
||||||
// Column(
|
height: constraints.maxWidth / 4,
|
||||||
// crossAxisAlignment: CrossAxisAlignment.center,
|
child: Obx(
|
||||||
// mainAxisAlignment: MainAxisAlignment.center,
|
() => GridView.count(
|
||||||
// children: [
|
primary: false,
|
||||||
// SizedBox(
|
crossAxisCount: 4,
|
||||||
// width: 36,
|
padding: const EdgeInsets.all(0),
|
||||||
// height: 36,
|
children: [
|
||||||
// child: IconButton(
|
..._whisperController.noticesList.map((element) {
|
||||||
// style: ButtonStyle(
|
return InkWell(
|
||||||
// padding:
|
onTap: () {
|
||||||
// MaterialStateProperty.all(EdgeInsets.zero),
|
Get.toNamed(element['path']);
|
||||||
// backgroundColor:
|
|
||||||
// MaterialStateProperty.resolveWith((states) {
|
if (element['count'] > 0) {
|
||||||
// return Theme.of(context)
|
element['count'] = 0;
|
||||||
// .colorScheme
|
}
|
||||||
// .primary
|
_whisperController.noticesList.refresh();
|
||||||
// .withOpacity(0.1);
|
},
|
||||||
// }),
|
onLongPress: () {},
|
||||||
// ),
|
borderRadius: StyleString.mdRadius,
|
||||||
// onPressed: () {},
|
child: Column(
|
||||||
// icon: Icon(
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
// Icons.message_outlined,
|
children: [
|
||||||
// size: 18,
|
Badge(
|
||||||
// color: Theme.of(context).colorScheme.primary,
|
isLabelVisible: element['count'] > 0,
|
||||||
// ),
|
label: Text(element['count'] > 99
|
||||||
// ),
|
? '99+'
|
||||||
// ),
|
: element['count'].toString()),
|
||||||
// const SizedBox(height: 6),
|
child: Padding(
|
||||||
// const Text('回复我的', style: TextStyle(fontSize: 13))
|
padding: const EdgeInsets.all(10),
|
||||||
// ],
|
child: Icon(
|
||||||
// ),
|
element['icon'],
|
||||||
// ],
|
size: 21,
|
||||||
// ),
|
color: Theme.of(context)
|
||||||
// ),
|
.colorScheme
|
||||||
// );
|
.primary,
|
||||||
// },
|
),
|
||||||
// ),
|
),
|
||||||
Expanded(
|
),
|
||||||
child: RefreshIndicator(
|
const SizedBox(height: 4),
|
||||||
onRefresh: () async {
|
Text(element['title'])
|
||||||
await _whisperController.onRefresh();
|
],
|
||||||
},
|
|
||||||
child: SingleChildScrollView(
|
|
||||||
controller: _scrollController,
|
|
||||||
child: FutureBuilder(
|
|
||||||
future: _futureBuilderFuture,
|
|
||||||
builder: (context, snapshot) {
|
|
||||||
if (snapshot.connectionState == ConnectionState.done) {
|
|
||||||
Map? data = snapshot.data;
|
|
||||||
if (data != null && data['status']) {
|
|
||||||
RxList sessionList = _whisperController.sessionList;
|
|
||||||
return Obx(
|
|
||||||
() => sessionList.isEmpty
|
|
||||||
? const SizedBox()
|
|
||||||
: ListView.separated(
|
|
||||||
itemCount: sessionList.length,
|
|
||||||
shrinkWrap: true,
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
|
||||||
itemBuilder: (_, int i) {
|
|
||||||
return SessionItem(
|
|
||||||
sessionItem: sessionList[i],
|
|
||||||
changeFucCall: () =>
|
|
||||||
sessionList.refresh(),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
separatorBuilder:
|
|
||||||
(BuildContext context, int index) {
|
|
||||||
return Divider(
|
|
||||||
indent: 72,
|
|
||||||
endIndent: 20,
|
|
||||||
height: 6,
|
|
||||||
color: Colors.grey.withOpacity(0.1),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
}).toList(),
|
||||||
// 请求错误
|
],
|
||||||
return Center(
|
),
|
||||||
child: Text(data?['msg'] ?? '请求异常'),
|
),
|
||||||
);
|
),
|
||||||
}
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
FutureBuilder(
|
||||||
|
future: _futureBuilderFuture,
|
||||||
|
builder: (context, snapshot) {
|
||||||
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
|
Map? data = snapshot.data;
|
||||||
|
if (data != null && data['status']) {
|
||||||
|
RxList sessionList = _whisperController.sessionList;
|
||||||
|
return Obx(
|
||||||
|
() => sessionList.isEmpty
|
||||||
|
? const SizedBox()
|
||||||
|
: ListView.separated(
|
||||||
|
itemCount: sessionList.length,
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
itemBuilder: (_, int i) {
|
||||||
|
return SessionItem(
|
||||||
|
sessionItem: sessionList[i],
|
||||||
|
changeFucCall: () => sessionList.refresh(),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
separatorBuilder:
|
||||||
|
(BuildContext context, int index) {
|
||||||
|
return Divider(
|
||||||
|
indent: 72,
|
||||||
|
endIndent: 20,
|
||||||
|
height: 6,
|
||||||
|
color: Colors.grey.withOpacity(0.1),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
// 骨架屏
|
// 请求错误
|
||||||
return ListView.builder(
|
return Center(
|
||||||
itemCount: 15,
|
child: Text(data?['msg'] ?? '请求异常'),
|
||||||
shrinkWrap: true,
|
|
||||||
physics: const NeverScrollableScrollPhysics(),
|
|
||||||
itemBuilder: (context, int i) {
|
|
||||||
return Skeleton(
|
|
||||||
child: ListTile(
|
|
||||||
leading: Container(
|
|
||||||
width: 45,
|
|
||||||
height: 45,
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.onInverseSurface,
|
|
||||||
borderRadius: BorderRadius.circular(25),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
title: Container(
|
|
||||||
width: 100,
|
|
||||||
height: 14,
|
|
||||||
color: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.onInverseSurface,
|
|
||||||
),
|
|
||||||
subtitle: Container(
|
|
||||||
width: 80,
|
|
||||||
height: 14,
|
|
||||||
color: Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.onInverseSurface,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
},
|
} else {
|
||||||
),
|
// 骨架屏
|
||||||
|
return ListView.builder(
|
||||||
|
itemCount: 15,
|
||||||
|
shrinkWrap: true,
|
||||||
|
physics: const NeverScrollableScrollPhysics(),
|
||||||
|
itemBuilder: (context, int i) {
|
||||||
|
return Skeleton(
|
||||||
|
child: ListTile(
|
||||||
|
leading: Container(
|
||||||
|
width: 45,
|
||||||
|
height: 45,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onInverseSurface,
|
||||||
|
borderRadius: BorderRadius.circular(25),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
title: Container(
|
||||||
|
width: 100,
|
||||||
|
height: 14,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onInverseSurface,
|
||||||
|
),
|
||||||
|
subtitle: Container(
|
||||||
|
width: 80,
|
||||||
|
height: 14,
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.onInverseSurface,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -202,7 +208,10 @@ class SessionItem extends StatelessWidget {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final String heroTag = Utils.makeHeroTag(sessionItem.accountInfo.mid);
|
||||||
final content = sessionItem.lastMsg.content;
|
final content = sessionItem.lastMsg.content;
|
||||||
|
final msgStatus = sessionItem.lastMsg.msgStatus;
|
||||||
|
|
||||||
return ListTile(
|
return ListTile(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
sessionItem.unreadCount = 0;
|
sessionItem.unreadCount = 0;
|
||||||
@ -214,6 +223,7 @@ class SessionItem extends StatelessWidget {
|
|||||||
'name': sessionItem.accountInfo.name,
|
'name': sessionItem.accountInfo.name,
|
||||||
'face': sessionItem.accountInfo.face,
|
'face': sessionItem.accountInfo.face,
|
||||||
'mid': sessionItem.accountInfo.mid.toString(),
|
'mid': sessionItem.accountInfo.mid.toString(),
|
||||||
|
'heroTag': heroTag,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
},
|
},
|
||||||
@ -221,22 +231,27 @@ class SessionItem extends StatelessWidget {
|
|||||||
isLabelVisible: sessionItem.unreadCount > 0,
|
isLabelVisible: sessionItem.unreadCount > 0,
|
||||||
label: Text(sessionItem.unreadCount.toString()),
|
label: Text(sessionItem.unreadCount.toString()),
|
||||||
alignment: Alignment.topRight,
|
alignment: Alignment.topRight,
|
||||||
child: NetworkImgLayer(
|
child: Hero(
|
||||||
width: 45,
|
tag: heroTag,
|
||||||
height: 45,
|
child: NetworkImgLayer(
|
||||||
type: 'avatar',
|
width: 45,
|
||||||
src: sessionItem.accountInfo.face,
|
height: 45,
|
||||||
|
type: 'avatar',
|
||||||
|
src: sessionItem.accountInfo.face,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
title: Text(sessionItem.accountInfo.name),
|
title: Text(sessionItem.accountInfo.name),
|
||||||
subtitle: Text(
|
subtitle: Text(
|
||||||
content != null && content != ''
|
msgStatus == 1
|
||||||
? (content['text'] ??
|
? '你撤回了一条消息'
|
||||||
content['content'] ??
|
: content != null && content != ''
|
||||||
content['title'] ??
|
? (content['text'] ??
|
||||||
content['reply_content'] ??
|
content['content'] ??
|
||||||
'不支持的消息类型')
|
content['title'] ??
|
||||||
: '不支持的消息类型',
|
content['reply_content'] ??
|
||||||
|
'不支持的消息类型')
|
||||||
|
: '不支持的消息类型',
|
||||||
maxLines: 1,
|
maxLines: 1,
|
||||||
overflow: TextOverflow.ellipsis,
|
overflow: TextOverflow.ellipsis,
|
||||||
style: Theme.of(context)
|
style: Theme.of(context)
|
||||||
@ -245,10 +260,10 @@ class SessionItem extends StatelessWidget {
|
|||||||
.copyWith(color: Theme.of(context).colorScheme.outline)),
|
.copyWith(color: Theme.of(context).colorScheme.outline)),
|
||||||
trailing: Text(
|
trailing: Text(
|
||||||
Utils.dateFormat(sessionItem.lastMsg.timestamp),
|
Utils.dateFormat(sessionItem.lastMsg.timestamp),
|
||||||
style: Theme.of(context)
|
style: TextStyle(
|
||||||
.textTheme
|
fontSize: Theme.of(context).textTheme.labelSmall!.fontSize,
|
||||||
.labelSmall!
|
color: Theme.of(context).colorScheme.outline,
|
||||||
.copyWith(color: Theme.of(context).colorScheme.outline),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,30 +1,40 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:convert';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/http/msg.dart';
|
import 'package:pilipala/http/msg.dart';
|
||||||
import 'package:pilipala/models/msg/session.dart';
|
import 'package:pilipala/models/msg/session.dart';
|
||||||
|
import 'package:pilipala/pages/whisper/index.dart';
|
||||||
import '../../utils/feed_back.dart';
|
import '../../utils/feed_back.dart';
|
||||||
import '../../utils/storage.dart';
|
import '../../utils/storage.dart';
|
||||||
|
|
||||||
class WhisperDetailController extends GetxController {
|
class WhisperDetailController extends GetxController {
|
||||||
late int talkerId;
|
int? talkerId;
|
||||||
late String name;
|
late String name;
|
||||||
late String face;
|
late String face;
|
||||||
late String mid;
|
late String mid;
|
||||||
|
late String heroTag;
|
||||||
RxList<MessageItem> messageList = <MessageItem>[].obs;
|
RxList<MessageItem> messageList = <MessageItem>[].obs;
|
||||||
//表情转换图片规则
|
//表情转换图片规则
|
||||||
List<dynamic>? eInfos;
|
RxList<dynamic> eInfos = [].obs;
|
||||||
final TextEditingController replyContentController = TextEditingController();
|
final TextEditingController replyContentController = TextEditingController();
|
||||||
Box userInfoCache = GStrorage.userInfo;
|
Box userInfoCache = GStrorage.userInfo;
|
||||||
|
List emoteList = [];
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void onInit() {
|
void onInit() {
|
||||||
super.onInit();
|
super.onInit();
|
||||||
talkerId = int.parse(Get.parameters['talkerId']!);
|
if (Get.parameters.containsKey('talkerId')) {
|
||||||
|
talkerId = int.parse(Get.parameters['talkerId']!);
|
||||||
|
} else {
|
||||||
|
talkerId = int.parse(Get.parameters['mid']!);
|
||||||
|
}
|
||||||
name = Get.parameters['name']!;
|
name = Get.parameters['name']!;
|
||||||
face = Get.parameters['face']!;
|
face = Get.parameters['face']!;
|
||||||
mid = Get.parameters['mid']!;
|
mid = Get.parameters['mid']!;
|
||||||
|
heroTag = Get.parameters['heroTag']!;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future querySessionMsg() async {
|
Future querySessionMsg() async {
|
||||||
@ -34,7 +44,7 @@ class WhisperDetailController extends GetxController {
|
|||||||
if (messageList.isNotEmpty) {
|
if (messageList.isNotEmpty) {
|
||||||
ackSessionMsg();
|
ackSessionMsg();
|
||||||
if (res['data'].eInfos != null) {
|
if (res['data'].eInfos != null) {
|
||||||
eInfos = res['data'].eInfos;
|
eInfos.value = res['data'].eInfos;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -73,9 +83,64 @@ class WhisperDetailController extends GetxController {
|
|||||||
msgType: 1,
|
msgType: 1,
|
||||||
);
|
);
|
||||||
if (result['status']) {
|
if (result['status']) {
|
||||||
SmartDialog.showToast('发送成功');
|
String content = jsonDecode(result['data']['msg_content'])['content'];
|
||||||
|
messageList.insert(
|
||||||
|
0,
|
||||||
|
MessageItem(
|
||||||
|
msgSeqno: result['data']['msg_key'],
|
||||||
|
senderUid: userInfo.mid,
|
||||||
|
receiverId: int.parse(mid),
|
||||||
|
content: {'content': content},
|
||||||
|
msgType: 1,
|
||||||
|
timestamp: DateTime.now().millisecondsSinceEpoch,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
eInfos.addAll(emoteList);
|
||||||
|
replyContentController.clear();
|
||||||
|
try {
|
||||||
|
late final WhisperController whisperController =
|
||||||
|
Get.find<WhisperController>();
|
||||||
|
whisperController.refreshLastMsg(talkerId!, message);
|
||||||
|
} catch (_) {}
|
||||||
} else {
|
} else {
|
||||||
SmartDialog.showToast(result['msg']);
|
SmartDialog.showToast(result['msg']);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void removeSession(context) {
|
||||||
|
showDialog(
|
||||||
|
context: context,
|
||||||
|
builder: (BuildContext context) {
|
||||||
|
return AlertDialog(
|
||||||
|
clipBehavior: Clip.hardEdge,
|
||||||
|
title: const Text('提示'),
|
||||||
|
content: const Text('确认清空会话内容并移除会话?'),
|
||||||
|
actions: [
|
||||||
|
TextButton(
|
||||||
|
onPressed: Get.back,
|
||||||
|
child: Text(
|
||||||
|
'取消',
|
||||||
|
style: TextStyle(color: Theme.of(context).colorScheme.outline),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
TextButton(
|
||||||
|
onPressed: () async {
|
||||||
|
var res = await MsgHttp.removeSession(talkerId: talkerId);
|
||||||
|
if (res['status']) {
|
||||||
|
SmartDialog.showToast('操作成功');
|
||||||
|
try {
|
||||||
|
late final WhisperController whisperController =
|
||||||
|
Get.find<WhisperController>();
|
||||||
|
whisperController.removeSessionMsg(talkerId!);
|
||||||
|
Get.back();
|
||||||
|
} catch (_) {}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
child: const Text('确认'),
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,9 +1,12 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
|
import 'dart:math';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
import 'package:pilipala/common/widgets/network_img_layer.dart';
|
||||||
|
import 'package:pilipala/models/video/reply/emote.dart';
|
||||||
import 'package:pilipala/pages/emote/index.dart';
|
import 'package:pilipala/pages/emote/index.dart';
|
||||||
|
import 'package:pilipala/pages/video/detail/reply_new/toolbar_icon_button.dart';
|
||||||
import 'package:pilipala/pages/whisper_detail/controller.dart';
|
import 'package:pilipala/pages/whisper_detail/controller.dart';
|
||||||
import 'package:pilipala/utils/feed_back.dart';
|
import 'package:pilipala/utils/feed_back.dart';
|
||||||
import '../../utils/storage.dart';
|
import '../../utils/storage.dart';
|
||||||
@ -24,9 +27,9 @@ class _WhisperDetailPageState extends State<WhisperDetailPage>
|
|||||||
late TextEditingController _replyContentController;
|
late TextEditingController _replyContentController;
|
||||||
final FocusNode replyContentFocusNode = FocusNode();
|
final FocusNode replyContentFocusNode = FocusNode();
|
||||||
final _debouncer = Debouncer(milliseconds: 200); // 设置延迟时间
|
final _debouncer = Debouncer(milliseconds: 200); // 设置延迟时间
|
||||||
late double emoteHeight = 0.0;
|
late double emoteHeight = 230.0;
|
||||||
double keyboardHeight = 0.0; // 键盘高度
|
double keyboardHeight = 0.0; // 键盘高度
|
||||||
String toolbarType = 'input';
|
RxString toolbarType = ''.obs;
|
||||||
Box userInfoCache = GStrorage.userInfo;
|
Box userInfoCache = GStrorage.userInfo;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -41,9 +44,7 @@ class _WhisperDetailPageState extends State<WhisperDetailPage>
|
|||||||
_focuslistener() {
|
_focuslistener() {
|
||||||
replyContentFocusNode.addListener(() {
|
replyContentFocusNode.addListener(() {
|
||||||
if (replyContentFocusNode.hasFocus) {
|
if (replyContentFocusNode.hasFocus) {
|
||||||
setState(() {
|
toolbarType.value = 'input';
|
||||||
toolbarType = 'input';
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -52,7 +53,7 @@ class _WhisperDetailPageState extends State<WhisperDetailPage>
|
|||||||
void didChangeMetrics() {
|
void didChangeMetrics() {
|
||||||
super.didChangeMetrics();
|
super.didChangeMetrics();
|
||||||
final String routePath = Get.currentRoute;
|
final String routePath = Get.currentRoute;
|
||||||
if (mounted && routePath.startsWith('/whisper_detail')) {
|
if (mounted && routePath.startsWith('/whisperDetail')) {
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
// 键盘高度
|
// 键盘高度
|
||||||
final viewInsets = EdgeInsets.fromViewPadding(
|
final viewInsets = EdgeInsets.fromViewPadding(
|
||||||
@ -61,8 +62,11 @@ class _WhisperDetailPageState extends State<WhisperDetailPage>
|
|||||||
if (mounted) {
|
if (mounted) {
|
||||||
if (keyboardHeight == 0) {
|
if (keyboardHeight == 0) {
|
||||||
setState(() {
|
setState(() {
|
||||||
emoteHeight = keyboardHeight =
|
keyboardHeight =
|
||||||
keyboardHeight == 0.0 ? viewInsets.bottom : keyboardHeight;
|
keyboardHeight == 0.0 ? viewInsets.bottom : keyboardHeight;
|
||||||
|
if (keyboardHeight != 0) {
|
||||||
|
emoteHeight = keyboardHeight;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -79,6 +83,23 @@ class _WhisperDetailPageState extends State<WhisperDetailPage>
|
|||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void onChooseEmote(PackageItem package, Emote emote) {
|
||||||
|
_whisperDetailController.emoteList.add(
|
||||||
|
{'text': emote.text, 'url': emote.url},
|
||||||
|
);
|
||||||
|
final int cursorPosition =
|
||||||
|
max(_replyContentController.selection.baseOffset, 0);
|
||||||
|
final String currentText = _replyContentController.text;
|
||||||
|
final String newText = currentText.substring(0, cursorPosition) +
|
||||||
|
emote.text! +
|
||||||
|
currentText.substring(cursorPosition);
|
||||||
|
_replyContentController.value = TextEditingValue(
|
||||||
|
text: newText,
|
||||||
|
selection:
|
||||||
|
TextSelection.collapsed(offset: cursorPosition + emote.text!.length),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return Scaffold(
|
return Scaffold(
|
||||||
@ -88,30 +109,20 @@ class _WhisperDetailPageState extends State<WhisperDetailPage>
|
|||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: 50,
|
height: 50,
|
||||||
child: Row(
|
child: Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
|
||||||
children: [
|
children: [
|
||||||
SizedBox(
|
SizedBox(
|
||||||
width: 34,
|
width: 34,
|
||||||
height: 34,
|
height: 34,
|
||||||
child: IconButton(
|
child: IconButton(
|
||||||
style: ButtonStyle(
|
|
||||||
padding: MaterialStateProperty.all(EdgeInsets.zero),
|
|
||||||
backgroundColor: MaterialStateProperty.resolveWith(
|
|
||||||
(Set<MaterialState> states) {
|
|
||||||
return Theme.of(context)
|
|
||||||
.colorScheme
|
|
||||||
.primaryContainer
|
|
||||||
.withOpacity(0.6);
|
|
||||||
}),
|
|
||||||
),
|
|
||||||
onPressed: () => Get.back(),
|
onPressed: () => Get.back(),
|
||||||
icon: Icon(
|
icon: Icon(
|
||||||
Icons.arrow_back_outlined,
|
Icons.arrow_back_ios,
|
||||||
size: 18,
|
size: 18,
|
||||||
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
color: Theme.of(context).colorScheme.onPrimaryContainer,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
const SizedBox(width: 10),
|
||||||
GestureDetector(
|
GestureDetector(
|
||||||
onTap: () {
|
onTap: () {
|
||||||
feedBack();
|
feedBack();
|
||||||
@ -125,13 +136,16 @@ class _WhisperDetailPageState extends State<WhisperDetailPage>
|
|||||||
},
|
},
|
||||||
child: Row(
|
child: Row(
|
||||||
children: <Widget>[
|
children: <Widget>[
|
||||||
NetworkImgLayer(
|
Hero(
|
||||||
width: 34,
|
tag: _whisperDetailController.heroTag,
|
||||||
height: 34,
|
child: NetworkImgLayer(
|
||||||
type: 'avatar',
|
width: 34,
|
||||||
src: _whisperDetailController.face,
|
height: 34,
|
||||||
|
type: 'avatar',
|
||||||
|
src: _whisperDetailController.face,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
const SizedBox(width: 6),
|
const SizedBox(width: 10),
|
||||||
Text(
|
Text(
|
||||||
_whisperDetailController.name,
|
_whisperDetailController.name,
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
@ -143,155 +157,171 @@ class _WhisperDetailPageState extends State<WhisperDetailPage>
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
actions: [
|
||||||
|
PopupMenuButton(
|
||||||
|
icon: const Icon(Icons.more_vert_outlined, size: 20),
|
||||||
|
itemBuilder: (BuildContext context) => <PopupMenuEntry>[
|
||||||
|
PopupMenuItem(
|
||||||
|
onTap: () => _whisperDetailController.removeSession(context),
|
||||||
|
child: const Text('关闭会话'),
|
||||||
|
)
|
||||||
|
],
|
||||||
|
),
|
||||||
|
const SizedBox(width: 14)
|
||||||
|
],
|
||||||
),
|
),
|
||||||
body: GestureDetector(
|
body: Column(
|
||||||
onTap: () {
|
children: [
|
||||||
FocusScope.of(context).unfocus();
|
Expanded(
|
||||||
setState(() {
|
child: GestureDetector(
|
||||||
keyboardHeight = 0;
|
onTap: () {
|
||||||
});
|
FocusScope.of(context).unfocus();
|
||||||
},
|
toolbarType.value = '';
|
||||||
child: FutureBuilder(
|
},
|
||||||
future: _futureBuilderFuture,
|
child: FutureBuilder(
|
||||||
builder: (BuildContext context, snapshot) {
|
future: _futureBuilderFuture,
|
||||||
if (snapshot.connectionState == ConnectionState.done) {
|
builder: (BuildContext context, snapshot) {
|
||||||
if (snapshot.data == null) {
|
if (snapshot.connectionState == ConnectionState.done) {
|
||||||
return const SizedBox();
|
if (snapshot.data == null) {
|
||||||
}
|
return const SizedBox();
|
||||||
final Map data = snapshot.data as Map;
|
}
|
||||||
if (data['status']) {
|
final Map data = snapshot.data as Map;
|
||||||
List messageList = _whisperDetailController.messageList;
|
if (data['status']) {
|
||||||
return Obx(
|
List messageList = _whisperDetailController.messageList;
|
||||||
() => messageList.isEmpty
|
return Obx(
|
||||||
? const SizedBox()
|
() => messageList.isEmpty
|
||||||
: ListView.builder(
|
? const SizedBox()
|
||||||
itemCount: messageList.length,
|
: Align(
|
||||||
shrinkWrap: true,
|
alignment: Alignment.topCenter,
|
||||||
reverse: true,
|
child: ListView.builder(
|
||||||
itemBuilder: (_, int i) {
|
itemCount: messageList.length,
|
||||||
if (i == 0) {
|
shrinkWrap: true,
|
||||||
return Column(
|
reverse: true,
|
||||||
children: [
|
itemBuilder: (_, int i) {
|
||||||
ChatItem(
|
if (i == 0) {
|
||||||
item: messageList[i],
|
return Column(
|
||||||
e_infos: _whisperDetailController.eInfos),
|
children: [
|
||||||
const SizedBox(height: 12),
|
ChatItem(
|
||||||
],
|
item: messageList[i],
|
||||||
);
|
e_infos: _whisperDetailController
|
||||||
} else {
|
.eInfos),
|
||||||
return ChatItem(
|
const SizedBox(height: 20),
|
||||||
item: messageList[i],
|
],
|
||||||
e_infos: _whisperDetailController.eInfos);
|
);
|
||||||
}
|
} else {
|
||||||
},
|
return ChatItem(
|
||||||
),
|
item: messageList[i],
|
||||||
);
|
e_infos:
|
||||||
} else {
|
_whisperDetailController.eInfos);
|
||||||
// 请求错误
|
}
|
||||||
return const SizedBox();
|
},
|
||||||
}
|
),
|
||||||
} else {
|
),
|
||||||
// 骨架屏
|
);
|
||||||
return const SizedBox();
|
} else {
|
||||||
}
|
// 请求错误
|
||||||
},
|
return const SizedBox();
|
||||||
),
|
}
|
||||||
),
|
} else {
|
||||||
// resizeToAvoidBottomInset: true,
|
// 骨架屏
|
||||||
bottomNavigationBar: Container(
|
return const SizedBox();
|
||||||
width: double.infinity,
|
}
|
||||||
height: MediaQuery.of(context).padding.bottom + 70 + keyboardHeight,
|
},
|
||||||
padding: EdgeInsets.only(
|
),
|
||||||
left: 8,
|
|
||||||
right: 12,
|
|
||||||
top: 12,
|
|
||||||
bottom: MediaQuery.of(context).padding.bottom,
|
|
||||||
),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
border: Border(
|
|
||||||
top: BorderSide(
|
|
||||||
width: 4,
|
|
||||||
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
Obx(
|
||||||
child: Column(
|
() => Container(
|
||||||
children: [
|
padding: EdgeInsets.fromLTRB(
|
||||||
Row(
|
8,
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
12,
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
12,
|
||||||
children: [
|
toolbarType.value == ''
|
||||||
// IconButton(
|
? MediaQuery.of(context).padding.bottom + 6
|
||||||
// onPressed: () {},
|
: 6,
|
||||||
// icon: Icon(
|
),
|
||||||
// Icons.add_circle_outline,
|
decoration: BoxDecoration(
|
||||||
// color: Theme.of(context).colorScheme.outline,
|
border: Border(
|
||||||
// ),
|
top: BorderSide(
|
||||||
// ),
|
width: 1,
|
||||||
IconButton(
|
color: Colors.grey.withOpacity(0.15),
|
||||||
onPressed: () {
|
|
||||||
// if (toolbarType == 'input') {
|
|
||||||
// setState(() {
|
|
||||||
// toolbarType = 'emote';
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// FocusScope.of(context).unfocus();
|
|
||||||
},
|
|
||||||
icon: Icon(
|
|
||||||
Icons.emoji_emotions_outlined,
|
|
||||||
color: Theme.of(context).colorScheme.outline,
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
Expanded(
|
),
|
||||||
child: Container(
|
child: Row(
|
||||||
height: 45,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
decoration: BoxDecoration(
|
children: [
|
||||||
color: Theme.of(context)
|
ToolbarIconButton(
|
||||||
.colorScheme
|
onPressed: () {
|
||||||
.primary
|
if (toolbarType.value == '') {
|
||||||
.withOpacity(0.08),
|
toolbarType.value = 'emote';
|
||||||
borderRadius: BorderRadius.circular(40.0),
|
} else if (toolbarType.value == 'input') {
|
||||||
),
|
FocusScope.of(context).unfocus();
|
||||||
child: TextField(
|
toolbarType.value = 'emote';
|
||||||
readOnly: true,
|
} else if (toolbarType.value == 'emote') {
|
||||||
style: Theme.of(context).textTheme.titleMedium,
|
FocusScope.of(context).requestFocus();
|
||||||
controller: _replyContentController,
|
}
|
||||||
autofocus: false,
|
},
|
||||||
focusNode: replyContentFocusNode,
|
icon: const Icon(Icons.emoji_emotions_outlined, size: 22),
|
||||||
decoration: const InputDecoration(
|
toolbarType: toolbarType.value,
|
||||||
border: InputBorder.none, // 移除默认边框
|
selected: false,
|
||||||
hintText: '开发中 ...', // 提示文本
|
),
|
||||||
contentPadding: EdgeInsets.symmetric(
|
const SizedBox(width: 4),
|
||||||
horizontal: 16.0, vertical: 12.0), // 内边距
|
Expanded(
|
||||||
|
child: Container(
|
||||||
|
height: 45,
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.outline
|
||||||
|
.withOpacity(0.05),
|
||||||
|
borderRadius: BorderRadius.circular(40.0),
|
||||||
|
),
|
||||||
|
child: TextField(
|
||||||
|
style: Theme.of(context).textTheme.titleMedium,
|
||||||
|
controller: _replyContentController,
|
||||||
|
autofocus: false,
|
||||||
|
focusNode: replyContentFocusNode,
|
||||||
|
decoration: const InputDecoration(
|
||||||
|
border: InputBorder.none, // 移除默认边框
|
||||||
|
hintText: '文明发言 ~', // 提示文本
|
||||||
|
contentPadding: EdgeInsets.symmetric(
|
||||||
|
horizontal: 16.0, vertical: 12.0), // 内边距
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
IconButton(
|
||||||
IconButton(
|
onPressed: _whisperDetailController.sendMsg,
|
||||||
// onPressed: _whisperDetailController.sendMsg,
|
icon: Icon(
|
||||||
onPressed: null,
|
Icons.send,
|
||||||
icon: Icon(
|
color: Theme.of(context).colorScheme.outline,
|
||||||
Icons.send,
|
),
|
||||||
color: Theme.of(context).colorScheme.outline,
|
|
||||||
),
|
),
|
||||||
),
|
],
|
||||||
// const SizedBox(width: 16),
|
),
|
||||||
],
|
|
||||||
),
|
),
|
||||||
AnimatedSize(
|
),
|
||||||
curve: Curves.easeInOut,
|
Obx(
|
||||||
duration: const Duration(milliseconds: 300),
|
() => AnimatedSize(
|
||||||
|
curve: Curves.linear,
|
||||||
|
duration: const Duration(milliseconds: 200),
|
||||||
child: SizedBox(
|
child: SizedBox(
|
||||||
width: double.infinity,
|
width: double.infinity,
|
||||||
height: toolbarType == 'input' ? keyboardHeight : emoteHeight,
|
height: toolbarType.value == 'input'
|
||||||
|
? keyboardHeight
|
||||||
|
: toolbarType.value == 'emote'
|
||||||
|
? emoteHeight
|
||||||
|
: 0,
|
||||||
child: EmotePanel(
|
child: EmotePanel(
|
||||||
onChoose: (package, emote) => {},
|
onChoose: (package, emote) => onChooseEmote(package, emote),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
],
|
),
|
||||||
),
|
],
|
||||||
),
|
),
|
||||||
|
resizeToAvoidBottomInset: false,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
// ignore_for_file: must_be_immutable
|
// ignore_for_file: must_be_immutable
|
||||||
|
// ignore_for_file: constant_identifier_names
|
||||||
|
|
||||||
import 'dart:convert';
|
import 'dart:convert';
|
||||||
|
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
@ -9,7 +9,6 @@ import 'package:pilipala/common/widgets/network_img_layer.dart';
|
|||||||
import 'package:pilipala/utils/route_push.dart';
|
import 'package:pilipala/utils/route_push.dart';
|
||||||
import 'package:pilipala/utils/utils.dart';
|
import 'package:pilipala/utils/utils.dart';
|
||||||
import 'package:pilipala/utils/storage.dart';
|
import 'package:pilipala/utils/storage.dart';
|
||||||
|
|
||||||
import '../../../http/search.dart';
|
import '../../../http/search.dart';
|
||||||
|
|
||||||
enum MsgType {
|
enum MsgType {
|
||||||
@ -69,9 +68,13 @@ class ChatItem extends StatelessWidget {
|
|||||||
Color textColor(BuildContext context) {
|
Color textColor(BuildContext context) {
|
||||||
return isOwner
|
return isOwner
|
||||||
? Theme.of(context).colorScheme.onPrimary
|
? Theme.of(context).colorScheme.onPrimary
|
||||||
: Theme.of(context).colorScheme.onSecondaryContainer;
|
: Theme.of(context).colorScheme.onBackground;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const double safeDistanceval = 6;
|
||||||
|
const double borderRadiusVal = 12;
|
||||||
|
const double paddingVal = 10;
|
||||||
|
|
||||||
Widget richTextMessage(BuildContext context) {
|
Widget richTextMessage(BuildContext context) {
|
||||||
var text = content['content'];
|
var text = content['content'];
|
||||||
if (e_infos != null) {
|
if (e_infos != null) {
|
||||||
@ -386,73 +389,97 @@ class ChatItem extends StatelessWidget {
|
|||||||
? messageContent(context)
|
? messageContent(context)
|
||||||
: isRevoke
|
: isRevoke
|
||||||
? const SizedBox()
|
? const SizedBox()
|
||||||
: Row(
|
: Container(
|
||||||
children: [
|
padding: const EdgeInsets.only(top: 6, bottom: 6),
|
||||||
if (!isOwner) const SizedBox(width: 12),
|
decoration: BoxDecoration(
|
||||||
if (isOwner) const Spacer(),
|
border: Border(
|
||||||
Container(
|
left: item.msgStatus == 1 && !isOwner
|
||||||
constraints: const BoxConstraints(
|
? BorderSide(
|
||||||
maxWidth: 300.0, // 设置最大宽度为200.0
|
width: 4, color: Theme.of(context).dividerColor)
|
||||||
),
|
: BorderSide.none,
|
||||||
decoration: BoxDecoration(
|
right: item.msgStatus == 1 && isOwner
|
||||||
color: isOwner
|
? BorderSide(
|
||||||
? Theme.of(context).colorScheme.primary
|
width: 4, color: Theme.of(context).primaryColor)
|
||||||
: Theme.of(context).colorScheme.secondaryContainer,
|
: BorderSide.none,
|
||||||
borderRadius: BorderRadius.only(
|
)),
|
||||||
topLeft: const Radius.circular(16),
|
child: Row(
|
||||||
topRight: const Radius.circular(16),
|
mainAxisAlignment: !isOwner
|
||||||
bottomLeft: Radius.circular(isOwner ? 16 : 6),
|
? MainAxisAlignment.start
|
||||||
bottomRight: Radius.circular(isOwner ? 6 : 16),
|
: MainAxisAlignment.end,
|
||||||
|
crossAxisAlignment: CrossAxisAlignment.center,
|
||||||
|
children: [
|
||||||
|
const SizedBox(width: safeDistanceval),
|
||||||
|
Container(
|
||||||
|
constraints: const BoxConstraints(
|
||||||
|
maxWidth: 300.0, // 设置最大宽度为200.0
|
||||||
|
),
|
||||||
|
decoration: BoxDecoration(
|
||||||
|
color: isOwner
|
||||||
|
? Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.primary
|
||||||
|
.withAlpha(180)
|
||||||
|
: Theme.of(context)
|
||||||
|
.colorScheme
|
||||||
|
.outlineVariant
|
||||||
|
.withOpacity(0.6)
|
||||||
|
.withAlpha(125),
|
||||||
|
borderRadius: BorderRadius.only(
|
||||||
|
topLeft: const Radius.circular(borderRadiusVal),
|
||||||
|
topRight: const Radius.circular(borderRadiusVal),
|
||||||
|
bottomLeft:
|
||||||
|
Radius.circular(isOwner ? borderRadiusVal : 2),
|
||||||
|
bottomRight:
|
||||||
|
Radius.circular(isOwner ? 2 : borderRadiusVal),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
margin: const EdgeInsets.only(
|
||||||
|
left: 8,
|
||||||
|
right: 8,
|
||||||
|
),
|
||||||
|
padding: const EdgeInsets.all(paddingVal),
|
||||||
|
child: Column(
|
||||||
|
crossAxisAlignment: isOwner
|
||||||
|
? CrossAxisAlignment.end
|
||||||
|
: CrossAxisAlignment.start,
|
||||||
|
children: [
|
||||||
|
messageContent(context),
|
||||||
|
SizedBox(height: isPic ? 7 : 4),
|
||||||
|
Row(
|
||||||
|
mainAxisSize: MainAxisSize.min,
|
||||||
|
children: [
|
||||||
|
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)),
|
||||||
|
),
|
||||||
|
item.msgStatus == 1
|
||||||
|
? Text(
|
||||||
|
' 已撤回',
|
||||||
|
style: Theme.of(context)
|
||||||
|
.textTheme
|
||||||
|
.labelSmall!,
|
||||||
|
)
|
||||||
|
: const SizedBox()
|
||||||
|
],
|
||||||
|
)
|
||||||
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
margin: const EdgeInsets.only(top: 12),
|
const SizedBox(width: safeDistanceval),
|
||||||
padding: EdgeInsets.only(
|
],
|
||||||
top: 8,
|
),
|
||||||
bottom: 6,
|
|
||||||
left: isPic ? 8 : 12,
|
|
||||||
right: isPic ? 8 : 12,
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: isOwner
|
|
||||||
? CrossAxisAlignment.end
|
|
||||||
: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
messageContent(context),
|
|
||||||
SizedBox(height: isPic ? 7 : 2),
|
|
||||||
Row(
|
|
||||||
mainAxisSize: MainAxisSize.min,
|
|
||||||
children: [
|
|
||||||
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)),
|
|
||||||
),
|
|
||||||
item.msgStatus == 1
|
|
||||||
? Text(
|
|
||||||
' 已撤回',
|
|
||||||
style:
|
|
||||||
Theme.of(context).textTheme.labelSmall!,
|
|
||||||
)
|
|
||||||
: const SizedBox()
|
|
||||||
],
|
|
||||||
)
|
|
||||||
],
|
|
||||||
),
|
|
||||||
),
|
|
||||||
if (!isOwner) const Spacer(),
|
|
||||||
if (isOwner) const SizedBox(width: 12),
|
|
||||||
],
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -39,6 +39,7 @@ class PLVideoPlayer extends StatefulWidget {
|
|||||||
this.customWidget,
|
this.customWidget,
|
||||||
this.customWidgets,
|
this.customWidgets,
|
||||||
this.showEposideCb,
|
this.showEposideCb,
|
||||||
|
this.fullScreenCb,
|
||||||
super.key,
|
super.key,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -52,6 +53,7 @@ class PLVideoPlayer extends StatefulWidget {
|
|||||||
final Widget? customWidget;
|
final Widget? customWidget;
|
||||||
final List<Widget>? customWidgets;
|
final List<Widget>? customWidgets;
|
||||||
final Function? showEposideCb;
|
final Function? showEposideCb;
|
||||||
|
final Function? fullScreenCb;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
State<PLVideoPlayer> createState() => _PLVideoPlayerState();
|
State<PLVideoPlayer> createState() => _PLVideoPlayerState();
|
||||||
@ -335,7 +337,10 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
fuc: () => _.triggerFullScreen(status: !_.isFullScreen.value),
|
fuc: () {
|
||||||
|
_.triggerFullScreen(status: !_.isFullScreen.value);
|
||||||
|
widget.fullScreenCb?.call(!_.isFullScreen.value);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
};
|
};
|
||||||
final List<Widget> list = [];
|
final List<Widget> list = [];
|
||||||
@ -940,7 +945,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
begin: 0.0,
|
begin: 0.0,
|
||||||
end: _hideSeekBackwardButton.value ? 0.0 : 1.0,
|
end: _hideSeekBackwardButton.value ? 0.0 : 1.0,
|
||||||
),
|
),
|
||||||
duration: const Duration(milliseconds: 200),
|
duration: const Duration(milliseconds: 150),
|
||||||
builder: (BuildContext context, double value,
|
builder: (BuildContext context, double value,
|
||||||
Widget? child) =>
|
Widget? child) =>
|
||||||
Opacity(
|
Opacity(
|
||||||
@ -983,7 +988,7 @@ class _PLVideoPlayerState extends State<PLVideoPlayer>
|
|||||||
begin: 0.0,
|
begin: 0.0,
|
||||||
end: _hideSeekForwardButton.value ? 0.0 : 1.0,
|
end: _hideSeekForwardButton.value ? 0.0 : 1.0,
|
||||||
),
|
),
|
||||||
duration: const Duration(milliseconds: 200),
|
duration: const Duration(milliseconds: 150),
|
||||||
builder: (BuildContext context, double value,
|
builder: (BuildContext context, double value,
|
||||||
Widget? child) =>
|
Widget? child) =>
|
||||||
Opacity(
|
Opacity(
|
||||||
|
|||||||
@ -30,14 +30,14 @@ class BackwardSeekIndicatorState extends State<BackwardSeekIndicator> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
timer = Timer(const Duration(milliseconds: 400), () {
|
timer = Timer(const Duration(milliseconds: 200), () {
|
||||||
widget.onSubmitted.call(value);
|
widget.onSubmitted.call(value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void increment() {
|
void increment() {
|
||||||
timer?.cancel();
|
timer?.cancel();
|
||||||
timer = Timer(const Duration(milliseconds: 400), () {
|
timer = Timer(const Duration(milliseconds: 200), () {
|
||||||
widget.onSubmitted.call(value);
|
widget.onSubmitted.call(value);
|
||||||
});
|
});
|
||||||
widget.onChanged.call(value);
|
widget.onChanged.call(value);
|
||||||
|
|||||||
@ -30,14 +30,14 @@ class ForwardSeekIndicatorState extends State<ForwardSeekIndicator> {
|
|||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
timer = Timer(const Duration(milliseconds: 400), () {
|
timer = Timer(const Duration(milliseconds: 200), () {
|
||||||
widget.onSubmitted.call(value);
|
widget.onSubmitted.call(value);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
void increment() {
|
void increment() {
|
||||||
timer?.cancel();
|
timer?.cancel();
|
||||||
timer = Timer(const Duration(milliseconds: 400), () {
|
timer = Timer(const Duration(milliseconds: 200), () {
|
||||||
widget.onSubmitted.call(value);
|
widget.onSubmitted.call(value);
|
||||||
});
|
});
|
||||||
widget.onChanged.call(value);
|
widget.onChanged.call(value);
|
||||||
|
|||||||
@ -4,6 +4,10 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:hive/hive.dart';
|
import 'package:hive/hive.dart';
|
||||||
import 'package:pilipala/pages/follow_search/view.dart';
|
import 'package:pilipala/pages/follow_search/view.dart';
|
||||||
|
import 'package:pilipala/pages/message/at/index.dart';
|
||||||
|
import 'package:pilipala/pages/message/like/index.dart';
|
||||||
|
import 'package:pilipala/pages/message/reply/index.dart';
|
||||||
|
import 'package:pilipala/pages/message/system/index.dart';
|
||||||
import 'package:pilipala/pages/setting/pages/logs.dart';
|
import 'package:pilipala/pages/setting/pages/logs.dart';
|
||||||
|
|
||||||
import '../pages/about/index.dart';
|
import '../pages/about/index.dart';
|
||||||
@ -178,6 +182,15 @@ class Routes {
|
|||||||
// 操作菜单
|
// 操作菜单
|
||||||
CustomGetPage(
|
CustomGetPage(
|
||||||
name: '/actionMenuSet', page: () => const ActionMenuSetPage()),
|
name: '/actionMenuSet', page: () => const ActionMenuSetPage()),
|
||||||
|
// 回复我的
|
||||||
|
CustomGetPage(name: '/messageReply', page: () => const MessageReplyPage()),
|
||||||
|
// @我的
|
||||||
|
CustomGetPage(name: '/messageAt', page: () => const MessageAtPage()),
|
||||||
|
// 收到的赞
|
||||||
|
CustomGetPage(name: '/messageLike', page: () => const MessageLikePage()),
|
||||||
|
// 系统通知
|
||||||
|
CustomGetPage(
|
||||||
|
name: '/messageSystem', page: () => const MessageSystemPage()),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import 'package:appscheme/appscheme.dart';
|
import 'package:appscheme/appscheme.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
|
||||||
import 'package:get/get.dart';
|
import 'package:get/get.dart';
|
||||||
import 'package:pilipala/utils/route_push.dart';
|
import 'package:pilipala/utils/route_push.dart';
|
||||||
@ -38,60 +39,82 @@ class PiliSchame {
|
|||||||
final String host = value.host;
|
final String host = value.host;
|
||||||
final String path = value.path;
|
final String path = value.path;
|
||||||
if (scheme == 'bilibili') {
|
if (scheme == 'bilibili') {
|
||||||
if (host == 'root') {
|
switch (host) {
|
||||||
Navigator.popUntil(
|
case 'root':
|
||||||
Get.context!, (Route<dynamic> route) => route.isFirst);
|
Navigator.popUntil(
|
||||||
} else if (host == 'space') {
|
Get.context!, (Route<dynamic> route) => route.isFirst);
|
||||||
final String mid = path.split('/').last;
|
break;
|
||||||
Get.toNamed<dynamic>(
|
case 'space':
|
||||||
'/member?mid=$mid',
|
final String mid = path.split('/').last;
|
||||||
arguments: <String, dynamic>{'face': null},
|
Get.toNamed<dynamic>(
|
||||||
);
|
'/member?mid=$mid',
|
||||||
} else if (host == 'video') {
|
arguments: <String, dynamic>{'face': null},
|
||||||
String pathQuery = path.split('/').last;
|
|
||||||
final numericRegex = RegExp(r'^[0-9]+$');
|
|
||||||
if (numericRegex.hasMatch(pathQuery)) {
|
|
||||||
pathQuery = 'AV$pathQuery';
|
|
||||||
}
|
|
||||||
Map map = IdUtils.matchAvorBv(input: pathQuery);
|
|
||||||
if (map.containsKey('AV')) {
|
|
||||||
_videoPush(map['AV'], null);
|
|
||||||
} else if (map.containsKey('BV')) {
|
|
||||||
_videoPush(null, map['BV']);
|
|
||||||
} else {
|
|
||||||
SmartDialog.showToast('投稿匹配失败');
|
|
||||||
}
|
|
||||||
} else if (host == 'live') {
|
|
||||||
final String roomId = path.split('/').last;
|
|
||||||
Get.toNamed<dynamic>('/liveRoom?roomid=$roomId',
|
|
||||||
arguments: <String, String?>{'liveItem': null, 'heroTag': roomId});
|
|
||||||
} else if (host == 'bangumi') {
|
|
||||||
if (path.startsWith('/season')) {
|
|
||||||
final String seasonId = path.split('/').last;
|
|
||||||
RoutePush.bangumiPush(int.parse(seasonId), null);
|
|
||||||
}
|
|
||||||
} else if (host == 'opus') {
|
|
||||||
if (path.startsWith('/detail')) {
|
|
||||||
var opusId = path.split('/').last;
|
|
||||||
Get.toNamed(
|
|
||||||
'/webview',
|
|
||||||
parameters: {
|
|
||||||
'url': 'https://www.bilibili.com/opus/$opusId',
|
|
||||||
'type': 'url',
|
|
||||||
'pageTitle': '',
|
|
||||||
},
|
|
||||||
);
|
);
|
||||||
}
|
break;
|
||||||
} else if (host == 'search') {
|
case 'video':
|
||||||
Get.toNamed('/searchResult', parameters: {'keyword': ''});
|
String pathQuery = path.split('/').last;
|
||||||
} else if (host == 'article') {
|
final numericRegex = RegExp(r'^[0-9]+$');
|
||||||
final String id = path.split('/').last.split('?').first;
|
if (numericRegex.hasMatch(pathQuery)) {
|
||||||
Get.toNamed('/htmlRender', parameters: {
|
pathQuery = 'AV$pathQuery';
|
||||||
'url': 'https://www.bilibili.com/read/cv$id',
|
}
|
||||||
'title': 'cv$id',
|
Map map = IdUtils.matchAvorBv(input: pathQuery);
|
||||||
'id': 'cv$id',
|
if (map.containsKey('AV')) {
|
||||||
'dynamicType': 'read'
|
_videoPush(map['AV'], null);
|
||||||
});
|
} else if (map.containsKey('BV')) {
|
||||||
|
_videoPush(null, map['BV']);
|
||||||
|
} else {
|
||||||
|
SmartDialog.showToast('投稿匹配失败');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'live':
|
||||||
|
final String roomId = path.split('/').last;
|
||||||
|
Get.toNamed<dynamic>(
|
||||||
|
'/liveRoom?roomid=$roomId',
|
||||||
|
arguments: <String, String?>{'liveItem': null, 'heroTag': roomId},
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case 'bangumi':
|
||||||
|
if (path.startsWith('/season')) {
|
||||||
|
final String seasonId = path.split('/').last;
|
||||||
|
RoutePush.bangumiPush(int.parse(seasonId), null);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'opus':
|
||||||
|
if (path.startsWith('/detail')) {
|
||||||
|
var opusId = path.split('/').last;
|
||||||
|
Get.toNamed(
|
||||||
|
'/webview',
|
||||||
|
parameters: {
|
||||||
|
'url': 'https://www.bilibili.com/opus/$opusId',
|
||||||
|
'type': 'url',
|
||||||
|
'pageTitle': '',
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'search':
|
||||||
|
Get.toNamed('/searchResult', parameters: {'keyword': ''});
|
||||||
|
break;
|
||||||
|
case 'article':
|
||||||
|
final String id = path.split('/').last.split('?').first;
|
||||||
|
Get.toNamed('/htmlRender', parameters: {
|
||||||
|
'url': 'https://www.bilibili.com/read/cv$id',
|
||||||
|
'title': 'cv$id',
|
||||||
|
'id': 'cv$id',
|
||||||
|
'dynamicType': 'read'
|
||||||
|
});
|
||||||
|
break;
|
||||||
|
case 'pgc':
|
||||||
|
if (path.contains('ep')) {
|
||||||
|
final String lastPathSegment = path.split('/').last;
|
||||||
|
RoutePush.bangumiPush(
|
||||||
|
null, int.parse(lastPathSegment.split('?').first));
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
SmartDialog.showToast('未匹配地址,请联系开发者');
|
||||||
|
Clipboard.setData(ClipboardData(text: value.toJson().toString()));
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (scheme == 'https') {
|
if (scheme == 'https') {
|
||||||
@ -196,9 +219,7 @@ class PiliSchame {
|
|||||||
parameters: {'url': redirectUrl, 'type': 'url', 'pageTitle': ''},
|
parameters: {'url': redirectUrl, 'type': 'url', 'pageTitle': ''},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
} else if (path != null) {
|
||||||
|
|
||||||
if (path != null) {
|
|
||||||
final String area = path.split('/').last;
|
final String area = path.split('/').last;
|
||||||
switch (area) {
|
switch (area) {
|
||||||
case 'bangumi':
|
case 'bangumi':
|
||||||
|
|||||||
Reference in New Issue
Block a user