Merge branch 'main' into fix

This commit is contained in:
guozhigq
2023-12-17 19:35:02 +08:00
39 changed files with 1437 additions and 169 deletions

View File

@ -333,7 +333,9 @@ class VideoStat extends StatelessWidget {
color: Theme.of(context).colorScheme.outline, color: Theme.of(context).colorScheme.outline,
), ),
children: [ children: [
if (videoItem.stat.view != '-')
TextSpan(text: '${videoItem.stat.view}观看'), TextSpan(text: '${videoItem.stat.view}观看'),
if (videoItem.stat.danmu != '-')
TextSpan(text: '${videoItem.stat.danmu}弹幕'), TextSpan(text: '${videoItem.stat.danmu}弹幕'),
], ],
), ),

View File

@ -326,6 +326,51 @@ class Api {
// 获取指定分组下的up // 获取指定分组下的up
static const String followUpGroup = '/x/relation/tag'; static const String followUpGroup = '/x/relation/tag';
/// 私聊
/// 'https://api.vc.bilibili.com/session_svr/v1/session_svr/get_sessions?
/// session_type=1&
/// group_fold=1&
/// unfollow_fold=0&
/// sort_rule=2&
/// build=0&
/// mobi_app=web&
/// w_rid=8641d157fb9a9255eb2159f316ee39e2&
/// wts=1697305010
static const String sessionList =
'https://api.vc.bilibili.com/session_svr/v1/session_svr/get_sessions';
/// 私聊用户信息
/// uids
/// build=0&mobi_app=web
static const String sessionAccountList =
'https://api.vc.bilibili.com/account/v1/user/cards';
/// https://api.vc.bilibili.com/svr_sync/v1/svr_sync/fetch_session_msgs?
/// talker_id=400787461&
/// session_type=1&
/// size=20&
/// sender_device_id=1&
/// build=0&
/// mobi_app=web&
/// web_location=333.1296&
/// w_rid=cfe3bf58c9fe181bbf4dd6c75175e6b0&
/// wts=1697350697
static const String sessionMsg =
'https://api.vc.bilibili.com/svr_sync/v1/svr_sync/fetch_session_msgs';
/// 标记已读 POST
/// talker_id:
/// session_type: 1
/// ack_seqno: 920224140918926
/// build: 0
/// mobi_app: web
/// csrf_token:
/// csrf:
static const String updateAck =
'https://api.vc.bilibili.com/session_svr/v1/session_svr/update_ack';
// 获取某个动态详情 // 获取某个动态详情
// timezone_offset=-480 // timezone_offset=-480
// id=849312409672744983 // id=849312409672744983
@ -410,6 +455,7 @@ class Api {
static const getMemberSeasonsApi = '/x/polymer/web-space/home/seasons_series'; static const getMemberSeasonsApi = '/x/polymer/web-space/home/seasons_series';
/// 获赞数 播放数 /// 获赞数 播放数
/// mid
static const getMemberViewApi = '/x/space/upstat'; static const getMemberViewApi = '/x/space/upstat';
/// 查询某个专栏 /// 查询某个专栏

View File

@ -403,7 +403,7 @@ class MemberHttp {
'csrf': await Request.getCsrf(), 'csrf': await Request.getCsrf(),
}); });
await Future.delayed(const Duration(milliseconds: 300)); await Future.delayed(const Duration(milliseconds: 300));
qrcodePoll(authCodeRes['data']); await qrcodePoll(authCodeRes['data']);
if (res.data['code'] == 0) { if (res.data['code'] == 0) {
return {'status': true, 'data': [], 'msg': '操作成功'}; return {'status': true, 'data': [], 'msg': '操作成功'};
} else { } else {
@ -447,4 +447,18 @@ class MemberHttp {
}; };
} }
} }
// 获取up播放数、点赞数
static Future memberView({required int mid}) async {
var res = await Request().get(Api.getMemberViewApi, data: {'mid': mid});
if (res.data['code'] == 0) {
return {'status': true, 'data': res.data['data']};
} else {
return {
'status': false,
'data': [],
'msg': res.data['message'],
};
}
}
} }

85
lib/http/msg.dart Normal file
View File

@ -0,0 +1,85 @@
import 'package:pilipala/http/api.dart';
import 'package:pilipala/http/init.dart';
import 'package:pilipala/models/msg/account.dart';
import 'package:pilipala/models/msg/session.dart';
import 'package:pilipala/utils/wbi_sign.dart';
class MsgHttp {
// 会话列表
static Future sessionList({int? endTs}) async {
Map<String, dynamic> params = {
'session_type': 1,
'group_fold': 1,
'unfollow_fold': 0,
'sort_rule': 2,
'build': 0,
'mobi_app': 'web',
};
if (endTs != null) {
params['end_ts'] = endTs;
}
Map signParams = await WbiSign().makSign(params);
var res = await Request().get(Api.sessionList, data: signParams);
if (res.data['code'] == 0) {
return {
'status': true,
'data': SessionDataModel.fromJson(res.data['data']),
};
} else {
return {
'status': false,
'date': [],
'msg': res.data['message'],
};
}
}
static Future accountList(uids) async {
var res = await Request().get(Api.sessionAccountList, data: {
'uids': uids,
'build': 0,
'mobi_app': 'web',
});
if (res.data['code'] == 0) {
return {
'status': true,
'data': res.data['data']
.map<AccountListModel>((e) => AccountListModel.fromJson(e))
.toList(),
};
} else {
return {
'status': false,
'date': [],
'msg': res.data['message'],
};
}
}
static Future sessionMsg({
int? talkerId,
}) async {
Map params = await WbiSign().makSign({
'talker_id': talkerId,
'session_type': 1,
'size': 20,
'sender_device_id': 1,
'build': 0,
'mobi_app': 'web',
});
var res = await Request().get(Api.sessionMsg, data: params);
if (res.data['code'] == 0) {
return {
'status': true,
'data': SessionMsgDataModel.fromJson(res.data['data']),
};
} else {
return {
'status': false,
'date': [],
'msg': res.data['message'],
};
}
}
}

View File

@ -199,7 +199,7 @@ class UserHttp {
} }
} }
// 获取用户凭证 // 获取用户凭证 失效
static Future thirdLogin() async { static Future thirdLogin() async {
var res = await Request().get( var res = await Request().get(
'https://passport.bilibili.com/login/app/third', 'https://passport.bilibili.com/login/app/third',

View File

@ -118,7 +118,7 @@ class RcmdStat {
RcmdStat.fromJson(Map<String, dynamic> json) { RcmdStat.fromJson(Map<String, dynamic> json) {
view = json["cover_left_text_1"]; view = json["cover_left_text_1"];
danmu = json['cover_left_text_2']; danmu = json['cover_left_text_2'] ?? '-';
} }
} }

View File

@ -0,0 +1,80 @@
class AccountListModel {
AccountListModel({
this.mid,
this.name,
this.sex,
this.face,
this.sign,
this.rank,
this.level,
this.silence,
this.vip,
this.pendant,
this.nameplate,
this.official,
this.birthday,
this.isFakeAccount,
this.isDeleted,
this.inRegAudit,
this.faceNft,
this.faceNftNew,
this.isSeniorMember,
this.digitalId,
this.digitalType,
this.attestation,
this.expertInfo,
this.honours,
});
int? mid;
String? name;
String? sex;
String? face;
String? sign;
int? rank;
int? level;
int? silence;
Map? vip;
Map? pendant;
Map? nameplate;
Map? official;
int? birthday;
int? isFakeAccount;
int? isDeleted;
int? inRegAudit;
int? faceNft;
int? faceNftNew;
int? isSeniorMember;
String? digitalId;
int? digitalType;
Map? attestation;
Map? expertInfo;
Map? honours;
AccountListModel.fromJson(Map<String, dynamic> json) {
mid = json['mid'];
name = json['name'] ?? '';
sex = json['sex'];
face = json['face'];
sign = json['sign'];
rank = json['rank'];
level = json['level'];
silence = json['silence'];
vip = json['vip'];
pendant = json['pendant'];
nameplate = json['nameplate'];
official = json['official'];
birthday = json['birthday'];
isFakeAccount = json['is_fake_account'];
isDeleted = json['is_deleted'];
inRegAudit = json['in_reg_audit'];
faceNft = json['face_nft'];
faceNftNew = json['face_nft_new'];
isSeniorMember = json['is_senior_member'];
digitalId = json['digital_id'];
digitalType = json['digital_type'];
attestation = json['attestation'];
expertInfo = json['expert_info'];
honours = json['honours'];
}
}

226
lib/models/msg/session.dart Normal file
View File

@ -0,0 +1,226 @@
import 'dart:convert';
import 'package:pilipala/models/msg/account.dart';
class SessionDataModel {
SessionDataModel({
this.sessionList,
this.hasMore,
});
List? sessionList;
int? hasMore;
SessionDataModel.fromJson(Map<String, dynamic> json) {
sessionList = json['session_list']
?.map<SessionList>((e) => SessionList.fromJson(e))
.toList();
hasMore = json['has_more'];
}
}
class SessionList {
SessionList({
this.talkerId,
this.sessionType,
this.atSeqno,
this.topTs,
this.groupName,
this.groupCover,
this.isFollow,
this.isDnd,
this.ackSeqno,
this.ackTs,
this.sessionTs,
this.unreadCount,
this.lastMsg,
this.groupType,
this.canFold,
this.status,
this.maxSeqno,
this.newPushMsg,
this.setting,
this.isGuardian,
this.isIntercept,
this.isTrust,
this.systemMsgType,
this.liveStatus,
this.bizMsgUnreadCount,
// this.userLabel,
});
int? talkerId;
int? sessionType;
int? atSeqno;
int? topTs;
String? groupName;
String? groupCover;
int? isFollow;
int? isDnd;
int? ackSeqno;
int? ackTs;
int? sessionTs;
int? unreadCount;
LastMsg? lastMsg;
int? groupType;
int? canFold;
int? status;
int? maxSeqno;
int? newPushMsg;
int? setting;
int? isGuardian;
int? isIntercept;
int? isTrust;
int? systemMsgType;
int? liveStatus;
int? bizMsgUnreadCount;
// int? userLabel;
AccountListModel? accountInfo;
SessionList.fromJson(Map<String, dynamic> json) {
talkerId = json["talker_id"];
sessionType = json["session_type"];
atSeqno = json["at_seqno"];
topTs = json["top_ts"];
groupName = json["group_name"];
groupCover = json["group_cover"];
isFollow = json["is_follow"];
isDnd = json["is_dnd"];
ackSeqno = json["ack_seqno"];
ackTs = json["ack_ts"];
sessionTs = json["session_ts"];
unreadCount = json["unread_count"];
lastMsg =
json["last_msg"] != null ? LastMsg.fromJson(json["last_msg"]) : null;
groupType = json["group_type"];
canFold = json["can_fold"];
status = json["status"];
maxSeqno = json["max_seqno"];
newPushMsg = json["new_push_msg"];
setting = json["setting"];
isGuardian = json["is_guardian"];
isIntercept = json["is_intercept"];
isTrust = json["is_trust"];
systemMsgType = json["system_msg_type"];
liveStatus = json["live_status"];
bizMsgUnreadCount = json["biz_msg_unread_count"];
// userLabel = json["user_label"];
}
}
class LastMsg {
LastMsg({
this.senderIid,
this.receiverType,
this.receiverId,
this.msgType,
this.content,
this.msgSeqno,
this.timestamp,
this.atUids,
this.msgKey,
this.msgStatus,
this.notifyCode,
this.newFaceVersion,
});
int? senderIid;
int? receiverType;
int? receiverId;
int? msgType;
Map? content;
int? msgSeqno;
int? timestamp;
String? atUids;
int? msgKey;
int? msgStatus;
String? notifyCode;
int? newFaceVersion;
LastMsg.fromJson(Map<String, dynamic> json) {
senderIid = json['sender_uid'];
receiverType = json['receiver_type'];
receiverId = json['receiver_id'];
msgType = json['msg_type'];
content = jsonDecode(json['content']);
msgSeqno = json['msg_seqno'];
timestamp = json['timestamp'];
atUids = json['at_uids'];
msgKey = json['msg_key'];
msgStatus = json['msg_status'];
notifyCode = json['notify_code'];
newFaceVersion = json['new_face_version'];
}
}
class SessionMsgDataModel {
SessionMsgDataModel({
this.messages,
this.hasMore,
this.minSeqno,
this.maxSeqno,
this.eInfos,
});
List<MessageItem>? messages;
int? hasMore;
int? minSeqno;
int? maxSeqno;
List? eInfos;
SessionMsgDataModel.fromJson(Map<String, dynamic> json) {
messages = json['messages']
.map<MessageItem>((e) => MessageItem.fromJson(e))
.toList();
hasMore = json['has_more'];
minSeqno = json['min_seqno'];
maxSeqno = json['max_seqno'];
eInfos = json['e_infos'];
}
}
class MessageItem {
MessageItem({
this.senderUid,
this.receiverType,
this.receiverId,
this.msgType,
this.content,
this.msgSeqno,
this.timestamp,
this.atUids,
this.msgKey,
this.msgStatus,
this.notifyCode,
this.newFaceVersion,
});
int? senderUid;
int? receiverType;
int? receiverId;
int? msgType;
Map? content;
int? msgSeqno;
int? timestamp;
List? atUids;
int? msgKey;
int? msgStatus;
String? notifyCode;
int? newFaceVersion;
MessageItem.fromJson(Map<String, dynamic> json) {
senderUid = json['sender_uid'];
receiverType = json['receiver_type'];
receiverId = json['receiver_id'];
// 1 文本 2 图片 18 系统提示 10 系统通知
msgType = json['msg_type'];
content = jsonDecode(json['content']);
msgSeqno = json['msg_seqno'];
timestamp = json['timestamp'];
atUids = json['at_uids'];
msgKey = json['msg_key'];
msgStatus = json['msg_status'];
notifyCode = json['notify_code'];
newFaceVersion = json['new_face_version'];
}
}

View File

@ -1,5 +1,3 @@
import 'dart:developer';
import 'package:pilipala/models/video/play/quality.dart'; import 'package:pilipala/models/video/play/quality.dart';
class PlayUrlModel { class PlayUrlModel {

View File

@ -1,6 +1,3 @@
import 'dart:io';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';

View File

@ -5,7 +5,6 @@ import 'package:get/get.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:pilipala/common/constants.dart'; import 'package:pilipala/common/constants.dart';
import 'package:pilipala/common/widgets/badge.dart'; import 'package:pilipala/common/widgets/badge.dart';
import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/common/widgets/stat/danmu.dart'; import 'package:pilipala/common/widgets/stat/danmu.dart';
import 'package:pilipala/common/widgets/stat/view.dart'; import 'package:pilipala/common/widgets/stat/view.dart';

View File

@ -1,8 +1,6 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/models/dynamics/result.dart';
import 'package:pilipala/pages/preview/index.dart';
// 富文本 // 富文本
InlineSpan richNode(item, context) { InlineSpan richNode(item, context) {
@ -11,13 +9,11 @@ InlineSpan richNode(item, context) {
TextStyle authorStyle = TextStyle authorStyle =
TextStyle(color: Theme.of(context).colorScheme.primary); TextStyle(color: Theme.of(context).colorScheme.primary);
List<InlineSpan> spanChilds = []; List<InlineSpan> spanChilds = [];
String contentType = 'desc';
dynamic richTextNodes; dynamic richTextNodes;
if (item.modules.moduleDynamic.desc != null) { if (item.modules.moduleDynamic.desc != null) {
richTextNodes = item.modules.moduleDynamic.desc.richTextNodes; richTextNodes = item.modules.moduleDynamic.desc.richTextNodes;
} else if (item.modules.moduleDynamic.major != null) { } else if (item.modules.moduleDynamic.major != null) {
contentType = 'major';
// 动态页面 richTextNodes 层级可能与主页动态层级不同 // 动态页面 richTextNodes 层级可能与主页动态层级不同
richTextNodes = richTextNodes =
item.modules.moduleDynamic.major.opus.summary.richTextNodes; item.modules.moduleDynamic.major.opus.summary.richTextNodes;

View File

@ -1,5 +1,4 @@
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/models/follow/result.dart'; import 'package:pilipala/models/follow/result.dart';

View File

@ -1,5 +1,3 @@
import 'dart:math';
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';

View File

@ -11,7 +11,6 @@ import 'package:pilipala/models/bangumi/info.dart';
import 'package:pilipala/models/common/business_type.dart'; import 'package:pilipala/models/common/business_type.dart';
import 'package:pilipala/models/common/search_type.dart'; import 'package:pilipala/models/common/search_type.dart';
import 'package:pilipala/models/live/item.dart'; import 'package:pilipala/models/live/item.dart';
import 'package:pilipala/pages/history/index.dart';
import 'package:pilipala/pages/history_search/index.dart'; import 'package:pilipala/pages/history_search/index.dart';
import 'package:pilipala/utils/feed_back.dart'; import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/id_utils.dart'; import 'package:pilipala/utils/id_utils.dart';

View File

@ -3,7 +3,6 @@ import 'dart:async';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/pages/main/index.dart';
import 'package:pilipala/pages/mine/index.dart'; import 'package:pilipala/pages/mine/index.dart';
import 'package:pilipala/pages/search/index.dart'; import 'package:pilipala/pages/search/index.dart';
import 'package:pilipala/utils/feed_back.dart'; import 'package:pilipala/utils/feed_back.dart';
@ -140,7 +139,13 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget {
child: Row( child: Row(
children: [ children: [
const Expanded(child: SearchPage()), const Expanded(child: SearchPage()),
const SizedBox(width: 10), if (ctr!.userLogin.value) ...[
const SizedBox(width: 6),
IconButton(
onPressed: () => Get.toNamed('/whisper'),
icon: const Icon(Icons.notifications_none))
],
const SizedBox(width: 6),
Obx( Obx(
() => ctr!.userLogin.value () => ctr!.userLogin.value
? Stack( ? Stack(

View File

@ -9,7 +9,6 @@ import 'package:pilipala/common/widgets/html_render.dart';
import 'package:pilipala/common/widgets/http_error.dart'; import 'package:pilipala/common/widgets/http_error.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/models/common/reply_type.dart'; import 'package:pilipala/models/common/reply_type.dart';
import 'package:pilipala/pages/mine/index.dart';
import 'package:pilipala/pages/video/detail/reply/widgets/reply_item.dart'; import 'package:pilipala/pages/video/detail/reply/widgets/reply_item.dart';
import 'package:pilipala/pages/video/detail/replyNew/index.dart'; import 'package:pilipala/pages/video/detail/replyNew/index.dart';
import 'package:pilipala/pages/video/detail/replyReply/index.dart'; import 'package:pilipala/pages/video/detail/replyReply/index.dart';

View File

@ -72,6 +72,7 @@ class _LivePageState extends State<LivePage>
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
super.build(context);
return Container( return Container(
clipBehavior: Clip.hardEdge, clipBehavior: Clip.hardEdge,
margin: const EdgeInsets.only( margin: const EdgeInsets.only(

View File

@ -52,7 +52,6 @@ class _LiveRoomPageState extends State<LiveRoomPage> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final videoHeight = MediaQuery.of(context).size.width * 9 / 16;
Widget childWhenDisabled = Scaffold( Widget childWhenDisabled = Scaffold(
primary: true, primary: true,
appBar: AppBar( appBar: AppBar(

View File

@ -3,7 +3,6 @@ import 'dart:io';
import 'package:floating/floating.dart'; import 'package:floating/floating.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:pilipala/models/video/play/url.dart'; import 'package:pilipala/models/video/play/url.dart';
import 'package:pilipala/pages/liveRoom/index.dart'; import 'package:pilipala/pages/liveRoom/index.dart';
@ -43,10 +42,6 @@ class _BottomControlState extends State<BottomControl> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
const textStyle = TextStyle(
color: Colors.white,
fontSize: 12,
);
return AppBar( return AppBar(
backgroundColor: Colors.transparent, backgroundColor: Colors.transparent,
foregroundColor: Colors.white, foregroundColor: Colors.white,

View File

@ -14,7 +14,7 @@ import 'package:share_plus/share_plus.dart';
class MemberController extends GetxController { class MemberController extends GetxController {
late int mid; late int mid;
Rx<MemberInfoModel> memberInfo = MemberInfoModel().obs; Rx<MemberInfoModel> memberInfo = MemberInfoModel().obs;
Map? userStat; late Map userStat;
RxString face = ''.obs; RxString face = ''.obs;
String? heroTag; String? heroTag;
Box userInfoCache = GStrorage.userInfo; Box userInfoCache = GStrorage.userInfo;
@ -40,6 +40,7 @@ class MemberController extends GetxController {
// 获取用户信息 // 获取用户信息
Future<Map<String, dynamic>> getInfo() async { Future<Map<String, dynamic>> getInfo() async {
await getMemberStat(); await getMemberStat();
await getMemberView();
var res = await MemberHttp.memberInfo(mid: mid); var res = await MemberHttp.memberInfo(mid: mid);
if (res['status']) { if (res['status']) {
memberInfo.value = res['data']; memberInfo.value = res['data'];
@ -57,6 +58,15 @@ class MemberController extends GetxController {
return res; return res;
} }
// 获取用户播放数 获赞数
Future<Map<String, dynamic>> getMemberView() async {
var res = await MemberHttp.memberView(mid: mid);
if (res['status']) {
userStat.addAll(res['data']);
}
return res;
}
// 关注/取关up // 关注/取关up
Future actionRelationMod() async { Future actionRelationMod() async {
if (userInfo == null) { if (userInfo == null) {

View File

@ -137,8 +137,14 @@ Widget profile(ctr, {loadingStatus = false}) {
), ),
Column( Column(
children: [ children: [
const Text('-', Text(
style: TextStyle(fontWeight: FontWeight.bold)), !loadingStatus
? Utils.numFormat(
ctr.userStat!['likes'],
)
: '-',
style: const TextStyle(
fontWeight: FontWeight.bold)),
Text( Text(
'获赞', '获赞',
style: TextStyle( style: TextStyle(

View File

@ -160,25 +160,25 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
} }
Widget _searchSuggest() { Widget _searchSuggest() {
SSearchController _ssCtr = _searchController; SSearchController ssCtr = _searchController;
return Obx( return Obx(
() => _ssCtr.searchSuggestList.isNotEmpty && () => ssCtr.searchSuggestList.isNotEmpty &&
_ssCtr.searchSuggestList.first.term != null && ssCtr.searchSuggestList.first.term != null &&
_ssCtr.controller.value.text != '' ssCtr.controller.value.text != ''
? ListView.builder( ? ListView.builder(
physics: const NeverScrollableScrollPhysics(), physics: const NeverScrollableScrollPhysics(),
shrinkWrap: true, shrinkWrap: true,
itemCount: _ssCtr.searchSuggestList.length, itemCount: ssCtr.searchSuggestList.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
return InkWell( return InkWell(
customBorder: RoundedRectangleBorder( customBorder: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4), borderRadius: BorderRadius.circular(4),
), ),
onTap: () => _ssCtr onTap: () => ssCtr
.onClickKeyword(_ssCtr.searchSuggestList[index].term!), .onClickKeyword(ssCtr.searchSuggestList[index].term!),
child: Padding( child: Padding(
padding: const EdgeInsets.only(left: 20, top: 9, bottom: 9), padding: const EdgeInsets.only(left: 20, top: 9, bottom: 9),
child: _ssCtr.searchSuggestList[index].textRich, child: ssCtr.searchSuggestList[index].textRich,
), ),
); );
}, },
@ -235,6 +235,7 @@ class _SearchPageState extends State<SearchPage> with RouteAware {
return Obx( return Obx(
() => HotKeyword( () => HotKeyword(
width: width, width: width,
// ignore: invalid_use_of_protected_member
hotSearchList: _searchController.hotSearchList.value, hotSearchList: _searchController.hotSearchList.value,
onClick: (keyword) async { onClick: (keyword) async {
_searchController.searchFocusNode.unfocus(); _searchController.searchFocusNode.unfocus();

View File

@ -83,6 +83,7 @@ class _SearchPanelState extends State<SearchPanel>
case SearchType.video: case SearchType.video:
return SearchVideoPanel( return SearchVideoPanel(
ctr: _searchPanelController, ctr: _searchPanelController,
// ignore: invalid_use_of_protected_member
list: list.value, list: list.value,
); );
case SearchType.media_bangumi: case SearchType.media_bangumi:

View File

@ -1,6 +1,8 @@
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:hive/hive.dart'; import 'package:hive/hive.dart';
import 'package:pilipala/pages/setting/widgets/switch_item.dart';
import 'package:pilipala/plugin/pl_player/index.dart';
import 'package:pilipala/plugin/pl_player/models/play_speed.dart'; import 'package:pilipala/plugin/pl_player/models/play_speed.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
@ -13,9 +15,11 @@ class PlaySpeedPage extends StatefulWidget {
class _PlaySpeedPageState extends State<PlaySpeedPage> { class _PlaySpeedPageState extends State<PlaySpeedPage> {
Box videoStorage = GStrorage.video; Box videoStorage = GStrorage.video;
Box settingStorage = GStrorage.setting;
late double playSpeedDefault; late double playSpeedDefault;
late double longPressSpeedDefault; late double longPressSpeedDefault;
late List customSpeedsList; late List customSpeedsList;
late bool enableAutoLongPressSpeed;
List<Map<dynamic, dynamic>> sheetMenu = [ List<Map<dynamic, dynamic>> sheetMenu = [
{ {
'id': 1, 'id': 1,
@ -24,6 +28,7 @@ class _PlaySpeedPageState extends State<PlaySpeedPage> {
Icons.speed, Icons.speed,
size: 21, size: 21,
), ),
'show': true,
}, },
{ {
'id': 2, 'id': 2,
@ -32,6 +37,7 @@ class _PlaySpeedPageState extends State<PlaySpeedPage> {
Icons.speed_sharp, Icons.speed_sharp,
size: 21, size: 21,
), ),
'show': true,
}, },
{ {
'id': -1, 'id': -1,
@ -40,6 +46,7 @@ class _PlaySpeedPageState extends State<PlaySpeedPage> {
Icons.delete_outline, Icons.delete_outline,
size: 21, size: 21,
), ),
'show': true,
}, },
]; ];
@ -55,6 +62,15 @@ class _PlaySpeedPageState extends State<PlaySpeedPage> {
// 自定义倍速 // 自定义倍速
customSpeedsList = customSpeedsList =
videoStorage.get(VideoBoxKey.customSpeedsList, defaultValue: []); videoStorage.get(VideoBoxKey.customSpeedsList, defaultValue: []);
enableAutoLongPressSpeed = settingStorage
.get(SettingBoxKey.enableAutoLongPressSpeed, defaultValue: false);
if (enableAutoLongPressSpeed) {
Map newItem = sheetMenu[1];
newItem['show'] = false;
setState(() {
sheetMenu[1] = newItem;
});
}
} }
// 添加自定义倍速 // 添加自定义倍速
@ -120,7 +136,8 @@ class _PlaySpeedPageState extends State<PlaySpeedPage> {
//重要 //重要
itemCount: sheetMenu.length, itemCount: sheetMenu.length,
itemBuilder: (BuildContext context, int index) { itemBuilder: (BuildContext context, int index) {
return ListTile( return sheetMenu[index]['show']
? ListTile(
onTap: () { onTap: () {
Navigator.pop(context); Navigator.pop(context);
menuAction(type, i, sheetMenu[index]['id']); menuAction(type, i, sheetMenu[index]['id']);
@ -132,7 +149,8 @@ class _PlaySpeedPageState extends State<PlaySpeedPage> {
sheetMenu[index]['title'], sheetMenu[index]['title'],
style: Theme.of(context).textTheme.titleSmall, style: Theme.of(context).textTheme.titleSmall,
), ),
); )
: const SizedBox();
}, },
), ),
); );
@ -210,11 +228,27 @@ class _PlaySpeedPageState extends State<PlaySpeedPage> {
title: const Text('默认倍速'), title: const Text('默认倍速'),
subtitle: Text(playSpeedDefault.toString()), subtitle: Text(playSpeedDefault.toString()),
), ),
ListTile( SetSwitchItem(
title: '动态长按倍速',
subTitle: '根据默认倍速长按时自动双倍',
setKey: SettingBoxKey.enableAutoLongPressSpeed,
defaultVal: enableAutoLongPressSpeed,
callFn: (val) {
Map newItem = sheetMenu[1];
val ? newItem['show'] = false : newItem['show'] = true;
setState(() {
sheetMenu[1] = newItem;
enableAutoLongPressSpeed = val;
});
},
),
!enableAutoLongPressSpeed
? ListTile(
dense: false, dense: false,
title: const Text('默认长按倍速'), title: const Text('默认长按倍速'),
subtitle: Text(longPressSpeedDefault.toString()), subtitle: Text(longPressSpeedDefault.toString()),
), )
: const SizedBox(),
Padding( Padding(
padding: const EdgeInsets.only( padding: const EdgeInsets.only(
left: 14, left: 14,

View File

@ -12,7 +12,6 @@ import 'package:pilipala/common/widgets/stat/view.dart';
import 'package:pilipala/models/video_detail_res.dart'; import 'package:pilipala/models/video_detail_res.dart';
import 'package:pilipala/pages/video/detail/introduction/controller.dart'; import 'package:pilipala/pages/video/detail/introduction/controller.dart';
import 'package:pilipala/pages/video/detail/widgets/ai_detail.dart'; import 'package:pilipala/pages/video/detail/widgets/ai_detail.dart';
import 'package:pilipala/services/service_locator.dart';
import 'package:pilipala/utils/feed_back.dart'; import 'package:pilipala/utils/feed_back.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
import 'package:pilipala/utils/utils.dart'; import 'package:pilipala/utils/utils.dart';

View File

@ -27,6 +27,7 @@ class _PagesPanelState extends State<PagesPanel> {
late int currentIndex; late int currentIndex;
String heroTag = Get.arguments['heroTag']; String heroTag = Get.arguments['heroTag'];
late VideoDetailController _videoDetailController; late VideoDetailController _videoDetailController;
final ScrollController _scrollController = ScrollController();
@override @override
void initState() { void initState() {
@ -50,6 +51,12 @@ class _PagesPanelState extends State<PagesPanel> {
setState(() {}); setState(() {});
} }
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Column( return Column(
@ -80,23 +87,33 @@ class _PagesPanelState extends State<PagesPanel> {
onPressed: () { onPressed: () {
showBottomSheet( showBottomSheet(
context: context, context: context,
builder: (_) => Container( builder: (BuildContext context) {
return StatefulBuilder(builder:
(BuildContext context, StateSetter setState) {
WidgetsBinding.instance
.addPostFrameCallback((_) async {
await Future.delayed(
const Duration(milliseconds: 200));
_scrollController.jumpTo(currentIndex * 56);
});
return Container(
height: widget.sheetHeight, height: widget.sheetHeight,
color: Theme.of(context).colorScheme.background, color: Theme.of(context).colorScheme.background,
child: Column( child: Column(
children: [ children: [
Container( Container(
height: 45, height: 45,
padding: padding: const EdgeInsets.only(
const EdgeInsets.only(left: 14, right: 14), left: 14, right: 14),
child: Row( child: Row(
mainAxisAlignment: mainAxisAlignment:
MainAxisAlignment.spaceBetween, MainAxisAlignment.spaceBetween,
children: [ children: [
Text( Text(
'合集(${episodes.length}', '合集(${episodes.length}',
style: style: Theme.of(context)
Theme.of(context).textTheme.titleMedium, .textTheme
.titleMedium,
), ),
IconButton( IconButton(
icon: const Icon(Icons.close), icon: const Icon(Icons.close),
@ -114,29 +131,36 @@ class _PagesPanelState extends State<PagesPanel> {
Expanded( Expanded(
child: Material( child: Material(
child: ListView.builder( child: ListView.builder(
controller: _scrollController,
itemCount: episodes.length, itemCount: episodes.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
return InkWell( return ListTile(
onTap: () { onTap: () {
changeFucCall(episodes[index], index); changeFucCall(
episodes[index], index);
Get.back(); Get.back();
}, },
child: Padding( dense: false,
padding: const EdgeInsets.only( leading: index == currentIndex
top: 10, ? Image.asset(
bottom: 10, 'assets/images/live.gif',
left: 15, color: Theme.of(context)
right: 15), .colorScheme
child: Text( .primary,
height: 12,
)
: null,
title: Text(
episodes[index].pagePart!, episodes[index].pagePart!,
style: TextStyle( style: TextStyle(
fontSize: 14,
color: index == currentIndex color: index == currentIndex
? Theme.of(context) ? Theme.of(context)
.colorScheme .colorScheme
.primary .primary
: Theme.of(context) : Theme.of(context)
.colorScheme .colorScheme
.onSurface), .onSurface,
), ),
), ),
); );
@ -146,7 +170,9 @@ class _PagesPanelState extends State<PagesPanel> {
), ),
], ],
), ),
), );
});
},
); );
}, },
child: Text( child: Text(

View File

@ -28,6 +28,7 @@ class _SeasonPanelState extends State<SeasonPanel> {
late int currentIndex; late int currentIndex;
String heroTag = Get.arguments['heroTag']; String heroTag = Get.arguments['heroTag'];
late VideoDetailController _videoDetailController; late VideoDetailController _videoDetailController;
final ScrollController _scrollController = ScrollController();
@override @override
void initState() { void initState() {
@ -73,6 +74,12 @@ class _SeasonPanelState extends State<SeasonPanel> {
setState(() {}); setState(() {});
} }
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Builder(builder: (context) { return Builder(builder: (context) {
@ -90,7 +97,14 @@ class _SeasonPanelState extends State<SeasonPanel> {
child: InkWell( child: InkWell(
onTap: () => showBottomSheet( onTap: () => showBottomSheet(
context: context, context: context,
builder: (_) => Container( builder: (BuildContext context) {
return StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
WidgetsBinding.instance.addPostFrameCallback((_) async {
await Future.delayed(const Duration(milliseconds: 200));
_scrollController.jumpTo(currentIndex * 56);
});
return Container(
height: widget.sheetHeight, height: widget.sheetHeight,
color: Theme.of(context).colorScheme.background, color: Theme.of(context).colorScheme.background,
child: Column( child: Column(
@ -114,29 +128,39 @@ class _SeasonPanelState extends State<SeasonPanel> {
), ),
Divider( Divider(
height: 1, height: 1,
color: Theme.of(context).dividerColor.withOpacity(0.1), color:
Theme.of(context).dividerColor.withOpacity(0.1),
), ),
Expanded( Expanded(
child: Material( child: Material(
child: ListView.builder( child: ListView.builder(
controller: _scrollController,
itemCount: episodes.length, itemCount: episodes.length,
itemBuilder: (context, index) { itemBuilder: (context, index) {
return InkWell( return ListTile(
onTap: () => onTap: () =>
changeFucCall(episodes[index], index), changeFucCall(episodes[index], index),
child: Padding( dense: false,
padding: const EdgeInsets.only( leading: index == currentIndex
top: 10, bottom: 10, left: 15, right: 15), ? Image.asset(
child: Text( 'assets/images/live.gif',
color: Theme.of(context)
.colorScheme
.primary,
height: 12,
)
: null,
title: Text(
episodes[index].title!, episodes[index].title!,
style: TextStyle( style: TextStyle(
fontSize: 14,
color: index == currentIndex color: index == currentIndex
? Theme.of(context) ? Theme.of(context)
.colorScheme .colorScheme
.primary .primary
: Theme.of(context) : Theme.of(context)
.colorScheme .colorScheme
.onSurface), .onSurface,
), ),
), ),
); );
@ -146,7 +170,9 @@ class _SeasonPanelState extends State<SeasonPanel> {
), ),
], ],
), ),
), );
});
},
), ),
child: Padding( child: Padding(
padding: const EdgeInsets.fromLTRB(8, 12, 8, 12), padding: const EdgeInsets.fromLTRB(8, 12, 8, 12),

View File

@ -6,6 +6,7 @@ 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/init.dart'; import 'package:pilipala/http/init.dart';
import 'package:pilipala/http/member.dart';
import 'package:pilipala/http/user.dart'; import 'package:pilipala/http/user.dart';
import 'package:pilipala/pages/home/index.dart'; import 'package:pilipala/pages/home/index.dart';
import 'package:pilipala/pages/media/index.dart'; import 'package:pilipala/pages/media/index.dart';
@ -102,7 +103,6 @@ class WebviewController extends GetxController {
try { try {
await SetCookie.onSet(); await SetCookie.onSet();
var result = await UserHttp.userInfo(); var result = await UserHttp.userInfo();
UserHttp.thirdLogin();
if (result['status'] && result['data'].isLogin) { if (result['status'] && result['data'].isLogin) {
SmartDialog.showToast('登录成功'); SmartDialog.showToast('登录成功');
try { try {
@ -115,6 +115,7 @@ class WebviewController extends GetxController {
MediaController mediaCtr = Get.find<MediaController>(); MediaController mediaCtr = Get.find<MediaController>();
mediaCtr.mid = result['data'].mid; mediaCtr.mid = result['data'].mid;
await LoginUtils.refreshLoginStatus(true); await LoginUtils.refreshLoginStatus(true);
MemberHttp.cookieToKey();
} catch (err) { } catch (err) {
SmartDialog.show(builder: (context) { SmartDialog.show(builder: (context) {
return AlertDialog( return AlertDialog(

View File

@ -0,0 +1,65 @@
import 'package:get/get.dart';
import 'package:pilipala/http/msg.dart';
import 'package:pilipala/models/msg/account.dart';
import 'package:pilipala/models/msg/session.dart';
class WhisperController extends GetxController {
RxList<SessionList> sessionList = <SessionList>[].obs;
RxList<AccountListModel> accountList = <AccountListModel>[].obs;
bool isLoading = false;
Future querySessionList(String? type) async {
if (isLoading) return;
var res = await MsgHttp.sessionList(
endTs: type == 'onLoad' ? sessionList.last.sessionTs : null);
if (res['data'].sessionList != null && res['data'].sessionList.isNotEmpty) {
await queryAccountList(res['data'].sessionList);
// 将 accountList 转换为 Map 结构
Map<int, dynamic> accountMap = {};
for (var j in accountList) {
accountMap[j.mid!] = j;
}
// 遍历 sessionList通过 mid 查找并赋值 accountInfo
for (var i in res['data'].sessionList) {
var accountInfo = accountMap[i.talkerId];
if (accountInfo != null) {
i.accountInfo = accountInfo;
}
if (i.talkerId == 844424930131966) {
i.accountInfo = AccountListModel(
name: 'UP主小助手',
face:
'https://message.biliimg.com/bfs/im/489a63efadfb202366c2f88853d2217b5ddc7a13.png',
);
}
}
}
if (res['status'] && res['data'].sessionList != null) {
if (type == 'onLoad') {
sessionList.addAll(res['data'].sessionList);
} else {
sessionList.value = res['data'].sessionList;
}
}
isLoading = false;
return res;
}
Future queryAccountList(sessionList) async {
List midsList = sessionList.map((e) => e.talkerId!).toList();
var res = await MsgHttp.accountList(midsList.join(','));
if (res['status']) {
accountList.value = res['data'];
}
return res;
}
Future onLoad() async {
querySessionList('onLoad');
}
Future onRefresh() async {
querySessionList('onRefresh');
}
}

View File

@ -0,0 +1,4 @@
library whisper;
export './controller.dart';
export './view.dart';

230
lib/pages/whisper/view.dart Normal file
View File

@ -0,0 +1,230 @@
import 'package:easy_debounce/easy_throttle.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/utils/utils.dart';
import 'controller.dart';
class WhisperPage extends StatefulWidget {
const WhisperPage({super.key});
@override
State<WhisperPage> createState() => _WhisperPageState();
}
class _WhisperPageState extends State<WhisperPage> {
late final WhisperController _whisperController =
Get.put(WhisperController());
late Future _futureBuilderFuture;
final ScrollController _scrollController = ScrollController();
@override
void initState() {
super.initState();
_futureBuilderFuture = _whisperController.querySessionList('init');
_scrollController.addListener(_scrollListener);
}
Future _scrollListener() async {
if (_scrollController.position.pixels >=
_scrollController.position.maxScrollExtent - 200) {
EasyThrottle.throttle('my-throttler', const Duration(milliseconds: 800),
() async {
await _whisperController.onLoad();
_whisperController.isLoading = true;
});
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('消息'),
),
body: Column(
children: [
// LayoutBuilder(
// builder: (BuildContext context, BoxConstraints constraints) {
// // 在这里根据父级容器的约束条件构建小部件树
// return Padding(
// padding: const EdgeInsets.only(left: 20, right: 20),
// child: SizedBox(
// height: constraints.maxWidth / 5,
// child: GridView.count(
// primary: false,
// crossAxisCount: 4,
// padding: const EdgeInsets.all(0),
// childAspectRatio: 1.25,
// children: [
// Column(
// crossAxisAlignment: CrossAxisAlignment.center,
// mainAxisAlignment: MainAxisAlignment.center,
// children: [
// SizedBox(
// width: 36,
// height: 36,
// child: IconButton(
// style: ButtonStyle(
// padding:
// MaterialStateProperty.all(EdgeInsets.zero),
// backgroundColor:
// MaterialStateProperty.resolveWith((states) {
// return Theme.of(context)
// .colorScheme
// .primary
// .withOpacity(0.1);
// }),
// ),
// onPressed: () {},
// icon: Icon(
// Icons.message_outlined,
// size: 18,
// color: Theme.of(context).colorScheme.primary,
// ),
// ),
// ),
// const SizedBox(height: 6),
// const Text('回复我的', style: TextStyle(fontSize: 13))
// ],
// ),
// ],
// ),
// ),
// );
// },
// ),
Expanded(
child: RefreshIndicator(
onRefresh: () async {
await _whisperController.onRefresh();
},
child: SingleChildScrollView(
controller: _scrollController,
child: Column(
children: [
FutureBuilder(
future: _futureBuilderFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
Map data = snapshot.data as Map;
if (data['status']) {
List sessionList = _whisperController.sessionList;
return Obx(
() => sessionList.isEmpty
? const SizedBox()
: ListView.separated(
itemCount: sessionList.length,
shrinkWrap: true,
physics:
const NeverScrollableScrollPhysics(),
itemBuilder: (_, int i) {
return ListTile(
onTap: () => Get.toNamed(
'/whisperDetail',
parameters: {
'talkerId': sessionList[i]
.talkerId
.toString(),
'name': sessionList[i]
.accountInfo
.name,
'face': sessionList[i]
.accountInfo
.face,
'mid': sessionList[i]
.accountInfo
.mid
.toString(),
},
),
leading: Badge(
isLabelVisible: false,
backgroundColor: Theme.of(context)
.colorScheme
.primary,
label: Text(sessionList[i]
.unreadCount
.toString()),
alignment: Alignment.bottomRight,
child: NetworkImgLayer(
width: 45,
height: 45,
type: 'avatar',
src: sessionList[i]
.accountInfo
.face,
),
),
title: Text(
sessionList[i].accountInfo.name),
subtitle: Text(
sessionList[i]
.lastMsg
.content['text'] ??
sessionList[i]
.lastMsg
.content['content'] ??
sessionList[i]
.lastMsg
.content['title'] ??
sessionList[i]
.lastMsg
.content[
'reply_content'] ??
'',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: Theme.of(context)
.textTheme
.labelMedium!
.copyWith(
color: Theme.of(context)
.colorScheme
.outline)),
trailing: Text(
Utils.dateFormat(sessionList[i]
.lastMsg
.timestamp),
style: Theme.of(context)
.textTheme
.labelSmall!
.copyWith(
color: Theme.of(context)
.colorScheme
.outline),
),
);
},
separatorBuilder:
(BuildContext context, int index) {
return Divider(
indent: 72,
endIndent: 20,
height: 6,
color: Colors.grey.withOpacity(0.1),
);
},
),
);
} else {
// 请求错误
return SizedBox();
}
} else {
// 骨架屏
return SizedBox();
}
},
)
],
),
),
),
),
],
),
);
}
}

View File

@ -0,0 +1,28 @@
import 'package:get/get.dart';
import 'package:pilipala/http/msg.dart';
import 'package:pilipala/models/msg/session.dart';
class WhisperDetailController extends GetxController {
late int talkerId;
late String name;
late String face;
late String mid;
RxList<MessageItem> messageList = <MessageItem>[].obs;
@override
void onInit() {
super.onInit();
talkerId = int.parse(Get.parameters['talkerId']!);
name = Get.parameters['name']!;
face = Get.parameters['face']!;
mid = Get.parameters['mid']!;
}
Future querySessionMsg() async {
var res = await MsgHttp.sessionMsg(talkerId: talkerId);
if (res['status']) {
messageList.value = res['data'].messages;
}
return res;
}
}

View File

@ -0,0 +1,4 @@
library whisper_detail;
export './controller.dart';
export './view.dart';

View File

@ -0,0 +1,193 @@
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/pages/whisperDetail/controller.dart';
import 'package:pilipala/utils/feed_back.dart';
import 'widget/chat_item.dart';
class WhisperDetailPage extends StatefulWidget {
const WhisperDetailPage({super.key});
@override
State<WhisperDetailPage> createState() => _WhisperDetailPageState();
}
class _WhisperDetailPageState extends State<WhisperDetailPage> {
final WhisperDetailController _whisperDetailController =
Get.put(WhisperDetailController());
late Future _futureBuilderFuture;
@override
void initState() {
super.initState();
_futureBuilderFuture = _whisperDetailController.querySessionMsg();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
automaticallyImplyLeading: false,
title: SizedBox(
width: double.infinity,
height: 50,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
SizedBox(
width: 34,
height: 34,
child: IconButton(
style: ButtonStyle(
padding: MaterialStateProperty.all(EdgeInsets.zero),
backgroundColor:
MaterialStateProperty.resolveWith((states) {
return Theme.of(context)
.colorScheme
.primaryContainer
.withOpacity(0.6);
}),
),
onPressed: () => Get.back(),
icon: Icon(
Icons.arrow_back_outlined,
size: 18,
color: Theme.of(context).colorScheme.onPrimaryContainer,
),
),
),
GestureDetector(
onTap: () {
feedBack();
Get.toNamed(
'/member?mid=${_whisperDetailController.mid}',
arguments: {
'face': _whisperDetailController.face,
'heroTag': null
},
);
},
child: Row(
children: [
NetworkImgLayer(
width: 34,
height: 34,
type: 'avatar',
src: _whisperDetailController.face,
),
const SizedBox(width: 6),
Text(
_whisperDetailController.name,
style: Theme.of(context).textTheme.titleMedium,
),
],
),
),
const SizedBox(width: 36, height: 36),
],
),
),
),
body: FutureBuilder(
future: _futureBuilderFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.done) {
Map data = snapshot.data as Map;
if (data['status']) {
List messageList = _whisperDetailController.messageList;
return Obx(
() => messageList.isEmpty
? const SizedBox()
: ListView.builder(
itemCount: messageList.length,
shrinkWrap: true,
reverse: true,
itemBuilder: (_, int i) {
if (i == 0) {
return Column(
children: [
ChatItem(item: messageList[i]),
const SizedBox(height: 12),
],
);
} else {
return ChatItem(item: messageList[i]);
}
},
),
);
} else {
// 请求错误
return const SizedBox();
}
} else {
// 骨架屏
return const SizedBox();
}
},
),
// resizeToAvoidBottomInset: true,
bottomNavigationBar: Container(
width: double.infinity,
height: MediaQuery.of(context).padding.bottom + 70,
padding: EdgeInsets.only(
left: 8,
right: 12,
top: 12,
bottom: MediaQuery.of(context).padding.bottom,
),
decoration: BoxDecoration(
color: Colors.white,
border: Border(
top: BorderSide(
width: 4,
color: Theme.of(context).colorScheme.primary.withOpacity(0.1),
),
),
),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// IconButton(
// onPressed: () {},
// icon: Icon(
// Icons.add_circle_outline,
// color: Theme.of(context).colorScheme.outline,
// ),
// ),
IconButton(
onPressed: () {},
icon: Icon(
Icons.emoji_emotions_outlined,
color: Theme.of(context).colorScheme.outline,
),
),
Expanded(
child: Container(
height: 45,
decoration: BoxDecoration(
color:
Theme.of(context).colorScheme.primary.withOpacity(0.08),
borderRadius: BorderRadius.circular(40.0),
),
child: TextField(
readOnly: true,
style: Theme.of(context).textTheme.titleMedium,
decoration: const InputDecoration(
border: InputBorder.none, // 移除默认边框
hintText: '开发中 ...', // 提示文本
contentPadding: EdgeInsets.symmetric(
horizontal: 16.0, vertical: 12.0), // 内边距
),
),
),
),
const SizedBox(width: 16),
],
),
),
);
}
}

View File

@ -0,0 +1,190 @@
// ignore_for_file: must_be_immutable
import 'package:flutter/material.dart';
import 'package:pilipala/common/widgets/network_img_layer.dart';
import 'package:pilipala/utils/utils.dart';
class ChatItem extends StatelessWidget {
dynamic item;
ChatItem({
super.key,
this.item,
});
@override
Widget build(BuildContext context) {
bool isOwner = item.senderUid == 17340771;
bool isPic = item.msgType == 2;
bool isText = item.msgType == 1;
bool isAchive = item.msgType == 11;
bool isArticle = item.msgType == 12;
bool isSystem =
item.msgType == 18 || item.msgType == 10 || item.msgType == 13;
int msgType = item.msgType;
Map content = item.content ?? '';
return isSystem
? (msgType == 10
? SystemNotice(item: item)
: msgType == 13
? SystemNotice2(item: item)
: const SizedBox())
: Row(
children: [
if (!isOwner) const SizedBox(width: 12),
if (isOwner) const Spacer(),
Container(
constraints: const BoxConstraints(
maxWidth: 300.0, // 设置最大宽度为200.0
),
decoration: BoxDecoration(
color: isOwner
? Theme.of(context).colorScheme.primary
: Theme.of(context).colorScheme.secondaryContainer,
borderRadius: BorderRadius.only(
topLeft: const Radius.circular(16),
topRight: const Radius.circular(16),
bottomLeft: Radius.circular(isOwner ? 16 : 6),
bottomRight: Radius.circular(isOwner ? 6 : 16),
),
),
margin: const EdgeInsets.only(top: 12),
padding: EdgeInsets.only(
top: 8,
bottom: 6,
left: isPic ? 8 : 12,
right: isPic ? 8 : 12,
),
child: Column(
crossAxisAlignment: isOwner
? CrossAxisAlignment.end
: CrossAxisAlignment.start,
children: [
isText
? Text(
content['content'],
style: TextStyle(
color: isOwner
? Theme.of(context).colorScheme.onPrimary
: Theme.of(context)
.colorScheme
.onSecondaryContainer),
)
: isPic
? NetworkImgLayer(
width: 220,
height:
220 * content['height'] / content['width'],
src: content['url'],
)
: const SizedBox(),
SizedBox(height: isPic ? 7 : 2),
Text(
Utils.dateFormat(item.timestamp),
style: Theme.of(context).textTheme.labelSmall!.copyWith(
color: isOwner
? Theme.of(context)
.colorScheme
.onPrimary
.withOpacity(0.8)
: Theme.of(context)
.colorScheme
.onSecondaryContainer
.withOpacity(0.8)),
)
],
),
),
if (!isOwner) const Spacer(),
if (isOwner) const SizedBox(width: 12),
],
);
}
}
class SystemNotice extends StatelessWidget {
dynamic item;
SystemNotice({super.key, this.item});
@override
Widget build(BuildContext context) {
Map content = item.content ?? '';
return Row(
children: [
const SizedBox(width: 12),
Container(
constraints: const BoxConstraints(
maxWidth: 300.0, // 设置最大宽度为200.0
),
decoration: BoxDecoration(
color: Theme.of(context)
.colorScheme
.secondaryContainer
.withOpacity(0.4),
borderRadius: const BorderRadius.only(
topLeft: Radius.circular(16),
topRight: Radius.circular(16),
bottomLeft: Radius.circular(6),
bottomRight: Radius.circular(16),
),
),
margin: const EdgeInsets.only(top: 12),
padding: const EdgeInsets.all(12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(content['title'],
style: Theme.of(context)
.textTheme
.titleMedium!
.copyWith(fontWeight: FontWeight.bold)),
Text(
Utils.dateFormat(item.timestamp),
style: Theme.of(context)
.textTheme
.labelSmall!
.copyWith(color: Theme.of(context).colorScheme.outline),
),
Divider(
color: Theme.of(context).colorScheme.primary.withOpacity(0.05),
),
Text(
content['text'],
)
],
),
),
const Spacer(),
],
);
}
}
class SystemNotice2 extends StatelessWidget {
dynamic item;
SystemNotice2({super.key, this.item});
@override
Widget build(BuildContext context) {
Map content = item.content ?? '';
return Row(
children: [
const SizedBox(width: 12),
Container(
constraints: const BoxConstraints(
maxWidth: 300.0, // 设置最大宽度为200.0
),
margin: const EdgeInsets.only(top: 12),
padding: const EdgeInsets.only(bottom: 6),
child: NetworkImgLayer(
width: 320,
height: 150,
src: content['pic_url'],
),
),
const Spacer(),
],
);
}
}

View File

@ -6,7 +6,6 @@ import 'dart:typed_data';
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:flutter_smart_dialog/flutter_smart_dialog.dart';
import 'package:flutter_volume_controller/flutter_volume_controller.dart'; import 'package:flutter_volume_controller/flutter_volume_controller.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:hive/hive.dart'; import 'package:hive/hive.dart';
@ -221,6 +220,7 @@ class PlPlayerController {
late List speedsList; late List speedsList;
// 缓存 // 缓存
double? defaultDuration; double? defaultDuration;
late bool enableAutoLongPressSpeed = false;
// 播放顺序相关 // 播放顺序相关
PlayRepeat playRepeat = PlayRepeat.pause; PlayRepeat playRepeat = PlayRepeat.pause;
@ -250,8 +250,12 @@ class PlPlayerController {
); );
_playbackSpeed.value = _playbackSpeed.value =
videoStorage.get(VideoBoxKey.playSpeedDefault, defaultValue: 1.0); videoStorage.get(VideoBoxKey.playSpeedDefault, defaultValue: 1.0);
_longPressSpeed.value = enableAutoLongPressSpeed = setting
videoStorage.get(VideoBoxKey.longPressSpeedDefault, defaultValue: 2.0); .get(SettingBoxKey.enableAutoLongPressSpeed, defaultValue: false);
if (!enableAutoLongPressSpeed) {
_longPressSpeed.value = videoStorage
.get(VideoBoxKey.longPressSpeedDefault, defaultValue: 2.0);
}
List speedsListTemp = List speedsListTemp =
videoStorage.get(VideoBoxKey.customSpeedsList, defaultValue: []); videoStorage.get(VideoBoxKey.customSpeedsList, defaultValue: []);
speedsList = List.from(speedsListTemp); speedsList = List.from(speedsListTemp);
@ -859,7 +863,8 @@ class PlPlayerController {
} }
_doubleSpeedStatus.value = val; _doubleSpeedStatus.value = val;
if (val) { if (val) {
setPlaybackSpeed(longPressSpeed); setPlaybackSpeed(
enableAutoLongPressSpeed ? playbackSpeed * 2 : longPressSpeed);
} else { } else {
print(playbackSpeed); print(playbackSpeed);
setPlaybackSpeed(playbackSpeed); setPlaybackSpeed(playbackSpeed);

View File

@ -27,7 +27,6 @@ import 'package:pilipala/pages/member_dynamics/index.dart';
import 'package:pilipala/pages/member_like/index.dart'; import 'package:pilipala/pages/member_like/index.dart';
import 'package:pilipala/pages/member_search/index.dart'; import 'package:pilipala/pages/member_search/index.dart';
import 'package:pilipala/pages/member_seasons/index.dart'; import 'package:pilipala/pages/member_seasons/index.dart';
import 'package:pilipala/pages/preview/index.dart';
import 'package:pilipala/pages/search/index.dart'; import 'package:pilipala/pages/search/index.dart';
import 'package:pilipala/pages/searchResult/index.dart'; import 'package:pilipala/pages/searchResult/index.dart';
import 'package:pilipala/pages/setting/extra_setting.dart'; import 'package:pilipala/pages/setting/extra_setting.dart';
@ -43,6 +42,8 @@ import 'package:pilipala/pages/video/detail/replyReply/index.dart';
import 'package:pilipala/pages/webview/index.dart'; import 'package:pilipala/pages/webview/index.dart';
import 'package:pilipala/pages/setting/index.dart'; import 'package:pilipala/pages/setting/index.dart';
import 'package:pilipala/pages/media/index.dart'; import 'package:pilipala/pages/media/index.dart';
import 'package:pilipala/pages/whisper/index.dart';
import 'package:pilipala/pages/whisperDetail/index.dart';
import 'package:pilipala/utils/storage.dart'; import 'package:pilipala/utils/storage.dart';
Box setting = GStrorage.setting; Box setting = GStrorage.setting;
@ -128,6 +129,11 @@ class Routes {
CustomGetPage(name: '/playSpeedSet', page: () => const PlaySpeedPage()), CustomGetPage(name: '/playSpeedSet', page: () => const PlaySpeedPage()),
// 收藏搜索 // 收藏搜索
CustomGetPage(name: '/favSearch', page: () => const FavSearchPage()), CustomGetPage(name: '/favSearch', page: () => const FavSearchPage()),
// 消息页面
CustomGetPage(name: '/whisper', page: () => const WhisperPage()),
// 私信详情
CustomGetPage(
name: '/whisperDetail', page: () => const WhisperDetailPage()),
// 登录页面 // 登录页面
CustomGetPage(name: '/loginPage', page: () => const LoginPage()), CustomGetPage(name: '/loginPage', page: () => const LoginPage()),
// 用户动态 // 用户动态

View File

@ -107,6 +107,7 @@ class SettingBoxKey {
static const String p1080 = 'p1080'; static const String p1080 = 'p1080';
static const String enableCDN = 'enableCDN'; static const String enableCDN = 'enableCDN';
static const String autoPiP = 'autoPiP'; static const String autoPiP = 'autoPiP';
static const String enableAutoLongPressSpeed = 'enableAutoLongPressSpeed';
// youtube 双击快进快退 // youtube 双击快进快退
static const String enableQuickDouble = 'enableQuickDouble'; static const String enableQuickDouble = 'enableQuickDouble';