diff --git a/lib/common/widgets/video_card_v.dart b/lib/common/widgets/video_card_v.dart index 39358fda..fa15a75c 100644 --- a/lib/common/widgets/video_card_v.dart +++ b/lib/common/widgets/video_card_v.dart @@ -333,8 +333,10 @@ class VideoStat extends StatelessWidget { color: Theme.of(context).colorScheme.outline, ), children: [ - TextSpan(text: '${videoItem.stat.view}观看'), - TextSpan(text: ' • ${videoItem.stat.danmu}弹幕'), + if (videoItem.stat.view != '-') + TextSpan(text: '${videoItem.stat.view}观看'), + if (videoItem.stat.danmu != '-') + TextSpan(text: ' • ${videoItem.stat.danmu}弹幕'), ], ), ); diff --git a/lib/http/api.dart b/lib/http/api.dart index 04fe0a44..3db3c617 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -326,6 +326,51 @@ class Api { // 获取指定分组下的up static const String followUpGroup = '/x/relation/tag'; + /// 私聊 + /// 'https://api.vc.bilibili.com/session_svr/v1/session_svr/get_sessions? + /// session_type=1& + /// group_fold=1& + /// unfollow_fold=0& + /// sort_rule=2& + /// build=0& + /// mobi_app=web& + /// w_rid=8641d157fb9a9255eb2159f316ee39e2& + /// wts=1697305010 + + static const String sessionList = + 'https://api.vc.bilibili.com/session_svr/v1/session_svr/get_sessions'; + + /// 私聊用户信息 + /// uids + /// build=0&mobi_app=web + static const String sessionAccountList = + 'https://api.vc.bilibili.com/account/v1/user/cards'; + + /// https://api.vc.bilibili.com/svr_sync/v1/svr_sync/fetch_session_msgs? + /// talker_id=400787461& + /// session_type=1& + /// size=20& + /// sender_device_id=1& + /// build=0& + /// mobi_app=web& + /// web_location=333.1296& + /// w_rid=cfe3bf58c9fe181bbf4dd6c75175e6b0& + /// wts=1697350697 + + static const String sessionMsg = + 'https://api.vc.bilibili.com/svr_sync/v1/svr_sync/fetch_session_msgs'; + + /// 标记已读 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 // id=849312409672744983 @@ -410,6 +455,7 @@ class Api { static const getMemberSeasonsApi = '/x/polymer/web-space/home/seasons_series'; /// 获赞数 播放数 + /// mid static const getMemberViewApi = '/x/space/upstat'; /// 查询某个专栏 diff --git a/lib/http/member.dart b/lib/http/member.dart index a2af8d82..20826451 100644 --- a/lib/http/member.dart +++ b/lib/http/member.dart @@ -403,7 +403,7 @@ class MemberHttp { 'csrf': await Request.getCsrf(), }); await Future.delayed(const Duration(milliseconds: 300)); - qrcodePoll(authCodeRes['data']); + await qrcodePoll(authCodeRes['data']); if (res.data['code'] == 0) { return {'status': true, 'data': [], 'msg': '操作成功'}; } 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'], + }; + } + } } diff --git a/lib/http/msg.dart b/lib/http/msg.dart new file mode 100644 index 00000000..3bf17aaf --- /dev/null +++ b/lib/http/msg.dart @@ -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 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((e) => AccountListModel.fromJson(e)) + .toList(), + }; + } else { + return { + 'status': false, + 'date': [], + 'msg': res.data['message'], + }; + } + } + + static Future sessionMsg({ + int? talkerId, + }) async { + Map params = await WbiSign().makSign({ + 'talker_id': talkerId, + 'session_type': 1, + 'size': 20, + 'sender_device_id': 1, + 'build': 0, + 'mobi_app': 'web', + }); + var res = await Request().get(Api.sessionMsg, data: params); + if (res.data['code'] == 0) { + return { + 'status': true, + 'data': SessionMsgDataModel.fromJson(res.data['data']), + }; + } else { + return { + 'status': false, + 'date': [], + 'msg': res.data['message'], + }; + } + } +} diff --git a/lib/http/user.dart b/lib/http/user.dart index 1ab465e0..f439b815 100644 --- a/lib/http/user.dart +++ b/lib/http/user.dart @@ -199,7 +199,7 @@ class UserHttp { } } - // 获取用户凭证 + // 获取用户凭证 失效 static Future thirdLogin() async { var res = await Request().get( 'https://passport.bilibili.com/login/app/third', diff --git a/lib/models/home/rcmd/result.dart b/lib/models/home/rcmd/result.dart index 94ed0727..a2a8006d 100644 --- a/lib/models/home/rcmd/result.dart +++ b/lib/models/home/rcmd/result.dart @@ -118,7 +118,7 @@ class RcmdStat { RcmdStat.fromJson(Map json) { view = json["cover_left_text_1"]; - danmu = json['cover_left_text_2']; + danmu = json['cover_left_text_2'] ?? '-'; } } diff --git a/lib/models/msg/account.dart b/lib/models/msg/account.dart new file mode 100644 index 00000000..bedd49b5 --- /dev/null +++ b/lib/models/msg/account.dart @@ -0,0 +1,80 @@ +class AccountListModel { + AccountListModel({ + this.mid, + this.name, + this.sex, + this.face, + this.sign, + this.rank, + this.level, + this.silence, + this.vip, + this.pendant, + this.nameplate, + this.official, + this.birthday, + this.isFakeAccount, + this.isDeleted, + this.inRegAudit, + this.faceNft, + this.faceNftNew, + this.isSeniorMember, + this.digitalId, + this.digitalType, + this.attestation, + this.expertInfo, + this.honours, + }); + + int? mid; + String? name; + String? sex; + String? face; + String? sign; + int? rank; + int? level; + int? silence; + Map? vip; + Map? pendant; + Map? nameplate; + Map? official; + int? birthday; + int? isFakeAccount; + int? isDeleted; + int? inRegAudit; + int? faceNft; + int? faceNftNew; + int? isSeniorMember; + String? digitalId; + int? digitalType; + Map? attestation; + Map? expertInfo; + Map? honours; + + AccountListModel.fromJson(Map json) { + mid = json['mid']; + name = json['name'] ?? ''; + sex = json['sex']; + face = json['face']; + sign = json['sign']; + rank = json['rank']; + level = json['level']; + silence = json['silence']; + vip = json['vip']; + pendant = json['pendant']; + nameplate = json['nameplate']; + official = json['official']; + birthday = json['birthday']; + isFakeAccount = json['is_fake_account']; + isDeleted = json['is_deleted']; + inRegAudit = json['in_reg_audit']; + faceNft = json['face_nft']; + faceNftNew = json['face_nft_new']; + isSeniorMember = json['is_senior_member']; + digitalId = json['digital_id']; + digitalType = json['digital_type']; + attestation = json['attestation']; + expertInfo = json['expert_info']; + honours = json['honours']; + } +} diff --git a/lib/models/msg/session.dart b/lib/models/msg/session.dart new file mode 100644 index 00000000..706f0ae8 --- /dev/null +++ b/lib/models/msg/session.dart @@ -0,0 +1,226 @@ +import 'dart:convert'; + +import 'package:pilipala/models/msg/account.dart'; + +class SessionDataModel { + SessionDataModel({ + this.sessionList, + this.hasMore, + }); + + List? sessionList; + int? hasMore; + + SessionDataModel.fromJson(Map json) { + sessionList = json['session_list'] + ?.map((e) => SessionList.fromJson(e)) + .toList(); + hasMore = json['has_more']; + } +} + +class SessionList { + SessionList({ + this.talkerId, + this.sessionType, + this.atSeqno, + this.topTs, + this.groupName, + this.groupCover, + this.isFollow, + this.isDnd, + this.ackSeqno, + this.ackTs, + this.sessionTs, + this.unreadCount, + this.lastMsg, + this.groupType, + this.canFold, + this.status, + this.maxSeqno, + this.newPushMsg, + this.setting, + this.isGuardian, + this.isIntercept, + this.isTrust, + this.systemMsgType, + this.liveStatus, + this.bizMsgUnreadCount, + // this.userLabel, + }); + + int? talkerId; + int? sessionType; + int? atSeqno; + int? topTs; + String? groupName; + String? groupCover; + int? isFollow; + int? isDnd; + int? ackSeqno; + int? ackTs; + int? sessionTs; + int? unreadCount; + LastMsg? lastMsg; + int? groupType; + int? canFold; + int? status; + int? maxSeqno; + int? newPushMsg; + int? setting; + int? isGuardian; + int? isIntercept; + int? isTrust; + int? systemMsgType; + int? liveStatus; + int? bizMsgUnreadCount; + // int? userLabel; + AccountListModel? accountInfo; + + SessionList.fromJson(Map json) { + talkerId = json["talker_id"]; + sessionType = json["session_type"]; + atSeqno = json["at_seqno"]; + topTs = json["top_ts"]; + groupName = json["group_name"]; + groupCover = json["group_cover"]; + isFollow = json["is_follow"]; + isDnd = json["is_dnd"]; + ackSeqno = json["ack_seqno"]; + ackTs = json["ack_ts"]; + sessionTs = json["session_ts"]; + unreadCount = json["unread_count"]; + lastMsg = + json["last_msg"] != null ? LastMsg.fromJson(json["last_msg"]) : null; + groupType = json["group_type"]; + canFold = json["can_fold"]; + status = json["status"]; + maxSeqno = json["max_seqno"]; + newPushMsg = json["new_push_msg"]; + setting = json["setting"]; + isGuardian = json["is_guardian"]; + isIntercept = json["is_intercept"]; + isTrust = json["is_trust"]; + systemMsgType = json["system_msg_type"]; + liveStatus = json["live_status"]; + bizMsgUnreadCount = json["biz_msg_unread_count"]; + // userLabel = json["user_label"]; + } +} + +class LastMsg { + LastMsg({ + this.senderIid, + this.receiverType, + this.receiverId, + this.msgType, + this.content, + this.msgSeqno, + this.timestamp, + this.atUids, + this.msgKey, + this.msgStatus, + this.notifyCode, + this.newFaceVersion, + }); + + int? senderIid; + int? receiverType; + int? receiverId; + int? msgType; + Map? content; + int? msgSeqno; + int? timestamp; + String? atUids; + int? msgKey; + int? msgStatus; + String? notifyCode; + int? newFaceVersion; + + LastMsg.fromJson(Map json) { + senderIid = json['sender_uid']; + receiverType = json['receiver_type']; + receiverId = json['receiver_id']; + msgType = json['msg_type']; + content = jsonDecode(json['content']); + msgSeqno = json['msg_seqno']; + timestamp = json['timestamp']; + atUids = json['at_uids']; + msgKey = json['msg_key']; + msgStatus = json['msg_status']; + notifyCode = json['notify_code']; + newFaceVersion = json['new_face_version']; + } +} + +class SessionMsgDataModel { + SessionMsgDataModel({ + this.messages, + this.hasMore, + this.minSeqno, + this.maxSeqno, + this.eInfos, + }); + + List? messages; + int? hasMore; + int? minSeqno; + int? maxSeqno; + List? eInfos; + + SessionMsgDataModel.fromJson(Map json) { + messages = json['messages'] + .map((e) => MessageItem.fromJson(e)) + .toList(); + hasMore = json['has_more']; + minSeqno = json['min_seqno']; + maxSeqno = json['max_seqno']; + eInfos = json['e_infos']; + } +} + +class MessageItem { + MessageItem({ + this.senderUid, + this.receiverType, + this.receiverId, + this.msgType, + this.content, + this.msgSeqno, + this.timestamp, + this.atUids, + this.msgKey, + this.msgStatus, + this.notifyCode, + this.newFaceVersion, + }); + + int? senderUid; + int? receiverType; + int? receiverId; + int? msgType; + Map? content; + int? msgSeqno; + int? timestamp; + List? atUids; + int? msgKey; + int? msgStatus; + String? notifyCode; + int? newFaceVersion; + + MessageItem.fromJson(Map json) { + senderUid = json['sender_uid']; + receiverType = json['receiver_type']; + receiverId = json['receiver_id']; + // 1 文本 2 图片 18 系统提示 10 系统通知 + msgType = json['msg_type']; + content = jsonDecode(json['content']); + msgSeqno = json['msg_seqno']; + timestamp = json['timestamp']; + atUids = json['at_uids']; + msgKey = json['msg_key']; + msgStatus = json['msg_status']; + notifyCode = json['notify_code']; + newFaceVersion = json['new_face_version']; + } +} diff --git a/lib/models/video/play/url.dart b/lib/models/video/play/url.dart index 71894406..4c43cb00 100644 --- a/lib/models/video/play/url.dart +++ b/lib/models/video/play/url.dart @@ -1,5 +1,3 @@ -import 'dart:developer'; - import 'package:pilipala/models/video/play/quality.dart'; class PlayUrlModel { diff --git a/lib/pages/about/index.dart b/lib/pages/about/index.dart index d50946d6..17eabee5 100644 --- a/lib/pages/about/index.dart +++ b/lib/pages/about/index.dart @@ -1,6 +1,3 @@ -import 'dart:io'; - -import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; diff --git a/lib/pages/bangumi/introduction/view.dart b/lib/pages/bangumi/introduction/view.dart index af47d7da..62842075 100644 --- a/lib/pages/bangumi/introduction/view.dart +++ b/lib/pages/bangumi/introduction/view.dart @@ -5,7 +5,6 @@ import 'package:get/get.dart'; import 'package:hive/hive.dart'; import 'package:pilipala/common/constants.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/stat/danmu.dart'; import 'package:pilipala/common/widgets/stat/view.dart'; diff --git a/lib/pages/dynamics/widgets/rich_node_panel.dart b/lib/pages/dynamics/widgets/rich_node_panel.dart index a66772a4..8b7dcd69 100644 --- a/lib/pages/dynamics/widgets/rich_node_panel.dart +++ b/lib/pages/dynamics/widgets/rich_node_panel.dart @@ -1,8 +1,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.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) { @@ -11,13 +9,11 @@ InlineSpan richNode(item, context) { TextStyle authorStyle = TextStyle(color: Theme.of(context).colorScheme.primary); List spanChilds = []; - String contentType = 'desc'; dynamic richTextNodes; if (item.modules.moduleDynamic.desc != null) { richTextNodes = item.modules.moduleDynamic.desc.richTextNodes; } else if (item.modules.moduleDynamic.major != null) { - contentType = 'major'; // 动态页面 richTextNodes 层级可能与主页动态层级不同 richTextNodes = item.modules.moduleDynamic.major.opus.summary.richTextNodes; diff --git a/lib/pages/follow/widgets/follow_item.dart b/lib/pages/follow/widgets/follow_item.dart index 3f9e4f3c..ac9cc01b 100644 --- a/lib/pages/follow/widgets/follow_item.dart +++ b/lib/pages/follow/widgets/follow_item.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:pilipala/common/widgets/network_img_layer.dart'; import 'package:pilipala/models/follow/result.dart'; diff --git a/lib/pages/follow/widgets/owner_follow_list.dart b/lib/pages/follow/widgets/owner_follow_list.dart index 0dcd785d..ccc11c44 100644 --- a/lib/pages/follow/widgets/owner_follow_list.dart +++ b/lib/pages/follow/widgets/owner_follow_list.dart @@ -1,5 +1,3 @@ -import 'dart:math'; - import 'package:easy_debounce/easy_throttle.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; diff --git a/lib/pages/history/widgets/item.dart b/lib/pages/history/widgets/item.dart index b80affc8..a83e118b 100644 --- a/lib/pages/history/widgets/item.dart +++ b/lib/pages/history/widgets/item.dart @@ -11,7 +11,6 @@ import 'package:pilipala/models/bangumi/info.dart'; import 'package:pilipala/models/common/business_type.dart'; import 'package:pilipala/models/common/search_type.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/utils/feed_back.dart'; import 'package:pilipala/utils/id_utils.dart'; diff --git a/lib/pages/home/view.dart b/lib/pages/home/view.dart index 4bc46e29..bcde246e 100644 --- a/lib/pages/home/view.dart +++ b/lib/pages/home/view.dart @@ -3,7 +3,6 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:get/get.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/search/index.dart'; import 'package:pilipala/utils/feed_back.dart'; @@ -140,7 +139,13 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { child: Row( children: [ 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( () => ctr!.userLogin.value ? Stack( diff --git a/lib/pages/html/view.dart b/lib/pages/html/view.dart index 64fabff8..478626af 100644 --- a/lib/pages/html/view.dart +++ b/lib/pages/html/view.dart @@ -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/network_img_layer.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/replyNew/index.dart'; import 'package:pilipala/pages/video/detail/replyReply/index.dart'; diff --git a/lib/pages/live/view.dart b/lib/pages/live/view.dart index c224dddf..302d226d 100644 --- a/lib/pages/live/view.dart +++ b/lib/pages/live/view.dart @@ -72,6 +72,7 @@ class _LivePageState extends State @override Widget build(BuildContext context) { + super.build(context); return Container( clipBehavior: Clip.hardEdge, margin: const EdgeInsets.only( diff --git a/lib/pages/liveRoom/view.dart b/lib/pages/liveRoom/view.dart index 36b1f979..125460b9 100644 --- a/lib/pages/liveRoom/view.dart +++ b/lib/pages/liveRoom/view.dart @@ -52,7 +52,6 @@ class _LiveRoomPageState extends State { @override Widget build(BuildContext context) { - final videoHeight = MediaQuery.of(context).size.width * 9 / 16; Widget childWhenDisabled = Scaffold( primary: true, appBar: AppBar( diff --git a/lib/pages/liveRoom/widgets/bottom_control.dart b/lib/pages/liveRoom/widgets/bottom_control.dart index 49343bb1..7347a8fc 100644 --- a/lib/pages/liveRoom/widgets/bottom_control.dart +++ b/lib/pages/liveRoom/widgets/bottom_control.dart @@ -3,7 +3,6 @@ import 'dart:io'; import 'package:floating/floating.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:get/get.dart'; import 'package:hive/hive.dart'; import 'package:pilipala/models/video/play/url.dart'; import 'package:pilipala/pages/liveRoom/index.dart'; @@ -43,10 +42,6 @@ class _BottomControlState extends State { @override Widget build(BuildContext context) { - const textStyle = TextStyle( - color: Colors.white, - fontSize: 12, - ); return AppBar( backgroundColor: Colors.transparent, foregroundColor: Colors.white, diff --git a/lib/pages/member/controller.dart b/lib/pages/member/controller.dart index 49a448f1..14ede4d3 100644 --- a/lib/pages/member/controller.dart +++ b/lib/pages/member/controller.dart @@ -14,7 +14,7 @@ import 'package:share_plus/share_plus.dart'; class MemberController extends GetxController { late int mid; Rx memberInfo = MemberInfoModel().obs; - Map? userStat; + late Map userStat; RxString face = ''.obs; String? heroTag; Box userInfoCache = GStrorage.userInfo; @@ -40,6 +40,7 @@ class MemberController extends GetxController { // 获取用户信息 Future> getInfo() async { await getMemberStat(); + await getMemberView(); var res = await MemberHttp.memberInfo(mid: mid); if (res['status']) { memberInfo.value = res['data']; @@ -57,6 +58,15 @@ class MemberController extends GetxController { return res; } + // 获取用户播放数 获赞数 + Future> getMemberView() async { + var res = await MemberHttp.memberView(mid: mid); + if (res['status']) { + userStat.addAll(res['data']); + } + return res; + } + // 关注/取关up Future actionRelationMod() async { if (userInfo == null) { diff --git a/lib/pages/member/widgets/profile.dart b/lib/pages/member/widgets/profile.dart index a8791631..68d0ff44 100644 --- a/lib/pages/member/widgets/profile.dart +++ b/lib/pages/member/widgets/profile.dart @@ -137,8 +137,14 @@ Widget profile(ctr, {loadingStatus = false}) { ), Column( children: [ - const Text('-', - style: TextStyle(fontWeight: FontWeight.bold)), + Text( + !loadingStatus + ? Utils.numFormat( + ctr.userStat!['likes'], + ) + : '-', + style: const TextStyle( + fontWeight: FontWeight.bold)), Text( '获赞', style: TextStyle( diff --git a/lib/pages/search/view.dart b/lib/pages/search/view.dart index 0ec910f1..55ee3b22 100644 --- a/lib/pages/search/view.dart +++ b/lib/pages/search/view.dart @@ -160,25 +160,25 @@ class _SearchPageState extends State with RouteAware { } Widget _searchSuggest() { - SSearchController _ssCtr = _searchController; + SSearchController ssCtr = _searchController; return Obx( - () => _ssCtr.searchSuggestList.isNotEmpty && - _ssCtr.searchSuggestList.first.term != null && - _ssCtr.controller.value.text != '' + () => ssCtr.searchSuggestList.isNotEmpty && + ssCtr.searchSuggestList.first.term != null && + ssCtr.controller.value.text != '' ? ListView.builder( physics: const NeverScrollableScrollPhysics(), shrinkWrap: true, - itemCount: _ssCtr.searchSuggestList.length, + itemCount: ssCtr.searchSuggestList.length, itemBuilder: (context, index) { return InkWell( customBorder: RoundedRectangleBorder( borderRadius: BorderRadius.circular(4), ), - onTap: () => _ssCtr - .onClickKeyword(_ssCtr.searchSuggestList[index].term!), + onTap: () => ssCtr + .onClickKeyword(ssCtr.searchSuggestList[index].term!), child: Padding( 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 with RouteAware { return Obx( () => HotKeyword( width: width, + // ignore: invalid_use_of_protected_member hotSearchList: _searchController.hotSearchList.value, onClick: (keyword) async { _searchController.searchFocusNode.unfocus(); diff --git a/lib/pages/searchPanel/view.dart b/lib/pages/searchPanel/view.dart index 9fd37b7e..10e52a54 100644 --- a/lib/pages/searchPanel/view.dart +++ b/lib/pages/searchPanel/view.dart @@ -83,6 +83,7 @@ class _SearchPanelState extends State case SearchType.video: return SearchVideoPanel( ctr: _searchPanelController, + // ignore: invalid_use_of_protected_member list: list.value, ); case SearchType.media_bangumi: diff --git a/lib/pages/setting/pages/play_speed_set.dart b/lib/pages/setting/pages/play_speed_set.dart index c5eb70e6..ceff07ed 100644 --- a/lib/pages/setting/pages/play_speed_set.dart +++ b/lib/pages/setting/pages/play_speed_set.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_smart_dialog/flutter_smart_dialog.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/utils/storage.dart'; @@ -13,9 +15,11 @@ class PlaySpeedPage extends StatefulWidget { class _PlaySpeedPageState extends State { Box videoStorage = GStrorage.video; + Box settingStorage = GStrorage.setting; late double playSpeedDefault; late double longPressSpeedDefault; late List customSpeedsList; + late bool enableAutoLongPressSpeed; List> sheetMenu = [ { 'id': 1, @@ -24,6 +28,7 @@ class _PlaySpeedPageState extends State { Icons.speed, size: 21, ), + 'show': true, }, { 'id': 2, @@ -32,6 +37,7 @@ class _PlaySpeedPageState extends State { Icons.speed_sharp, size: 21, ), + 'show': true, }, { 'id': -1, @@ -40,6 +46,7 @@ class _PlaySpeedPageState extends State { Icons.delete_outline, size: 21, ), + 'show': true, }, ]; @@ -55,6 +62,15 @@ class _PlaySpeedPageState extends State { // 自定义倍速 customSpeedsList = 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,19 +136,21 @@ class _PlaySpeedPageState extends State { //重要 itemCount: sheetMenu.length, itemBuilder: (BuildContext context, int index) { - return ListTile( - onTap: () { - Navigator.pop(context); - menuAction(type, i, sheetMenu[index]['id']); - }, - minLeadingWidth: 0, - iconColor: Theme.of(context).colorScheme.onSurface, - leading: sheetMenu[index]['leading'], - title: Text( - sheetMenu[index]['title'], - style: Theme.of(context).textTheme.titleSmall, - ), - ); + return sheetMenu[index]['show'] + ? ListTile( + onTap: () { + Navigator.pop(context); + menuAction(type, i, sheetMenu[index]['id']); + }, + minLeadingWidth: 0, + iconColor: Theme.of(context).colorScheme.onSurface, + leading: sheetMenu[index]['leading'], + title: Text( + sheetMenu[index]['title'], + style: Theme.of(context).textTheme.titleSmall, + ), + ) + : const SizedBox(); }, ), ); @@ -210,11 +228,27 @@ class _PlaySpeedPageState extends State { title: const Text('默认倍速'), subtitle: Text(playSpeedDefault.toString()), ), - ListTile( - dense: false, - title: const Text('默认长按倍速'), - subtitle: Text(longPressSpeedDefault.toString()), + 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, + title: const Text('默认长按倍速'), + subtitle: Text(longPressSpeedDefault.toString()), + ) + : const SizedBox(), Padding( padding: const EdgeInsets.only( left: 14, diff --git a/lib/pages/video/detail/introduction/view.dart b/lib/pages/video/detail/introduction/view.dart index c6bd7f97..98ce9ba8 100644 --- a/lib/pages/video/detail/introduction/view.dart +++ b/lib/pages/video/detail/introduction/view.dart @@ -12,7 +12,6 @@ import 'package:pilipala/common/widgets/stat/view.dart'; import 'package:pilipala/models/video_detail_res.dart'; import 'package:pilipala/pages/video/detail/introduction/controller.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/storage.dart'; import 'package:pilipala/utils/utils.dart'; diff --git a/lib/pages/video/detail/introduction/widgets/page.dart b/lib/pages/video/detail/introduction/widgets/page.dart index b3f1f7a0..e1fdaf09 100644 --- a/lib/pages/video/detail/introduction/widgets/page.dart +++ b/lib/pages/video/detail/introduction/widgets/page.dart @@ -27,6 +27,7 @@ class _PagesPanelState extends State { late int currentIndex; String heroTag = Get.arguments['heroTag']; late VideoDetailController _videoDetailController; + final ScrollController _scrollController = ScrollController(); @override void initState() { @@ -50,6 +51,12 @@ class _PagesPanelState extends State { setState(() {}); } + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { return Column( @@ -80,73 +87,92 @@ class _PagesPanelState extends State { onPressed: () { showBottomSheet( context: context, - builder: (_) => Container( - height: widget.sheetHeight, - color: Theme.of(context).colorScheme.background, - child: Column( - children: [ - Container( - height: 45, - padding: - const EdgeInsets.only(left: 14, right: 14), - child: Row( - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Text( - '合集(${episodes.length})', - style: - Theme.of(context).textTheme.titleMedium, + 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, + color: Theme.of(context).colorScheme.background, + child: Column( + children: [ + Container( + height: 45, + padding: const EdgeInsets.only( + left: 14, right: 14), + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + '合集(${episodes.length})', + style: Theme.of(context) + .textTheme + .titleMedium, + ), + IconButton( + icon: const Icon(Icons.close), + onPressed: () => Navigator.pop(context), + ), + ], ), - IconButton( - icon: const Icon(Icons.close), - onPressed: () => Navigator.pop(context), - ), - ], - ), - ), - Divider( - height: 1, - color: Theme.of(context) - .dividerColor - .withOpacity(0.1), - ), - Expanded( - child: Material( - child: ListView.builder( - itemCount: episodes.length, - itemBuilder: (context, index) { - return InkWell( - onTap: () { - changeFucCall(episodes[index], index); - Get.back(); - }, - child: Padding( - padding: const EdgeInsets.only( - top: 10, - bottom: 10, - left: 15, - right: 15), - child: Text( - episodes[index].pagePart!, - style: TextStyle( + ), + Divider( + height: 1, + color: Theme.of(context) + .dividerColor + .withOpacity(0.1), + ), + Expanded( + child: Material( + child: ListView.builder( + controller: _scrollController, + itemCount: episodes.length, + itemBuilder: (context, index) { + return ListTile( + onTap: () { + changeFucCall( + episodes[index], index); + Get.back(); + }, + dense: false, + leading: index == currentIndex + ? Image.asset( + 'assets/images/live.gif', + color: Theme.of(context) + .colorScheme + .primary, + height: 12, + ) + : null, + title: Text( + episodes[index].pagePart!, + style: TextStyle( + fontSize: 14, color: index == currentIndex ? Theme.of(context) .colorScheme .primary : Theme.of(context) .colorScheme - .onSurface), - ), - ), - ); - }, + .onSurface, + ), + ), + ); + }, + ), + ), ), - ), + ], ), - ], - ), - ), + ); + }); + }, ); }, child: Text( diff --git a/lib/pages/video/detail/introduction/widgets/season.dart b/lib/pages/video/detail/introduction/widgets/season.dart index 611b9d70..4b075c66 100644 --- a/lib/pages/video/detail/introduction/widgets/season.dart +++ b/lib/pages/video/detail/introduction/widgets/season.dart @@ -28,6 +28,7 @@ class _SeasonPanelState extends State { late int currentIndex; String heroTag = Get.arguments['heroTag']; late VideoDetailController _videoDetailController; + final ScrollController _scrollController = ScrollController(); @override void initState() { @@ -73,6 +74,12 @@ class _SeasonPanelState extends State { setState(() {}); } + @override + void dispose() { + _scrollController.dispose(); + super.dispose(); + } + @override Widget build(BuildContext context) { return Builder(builder: (context) { @@ -90,63 +97,82 @@ class _SeasonPanelState extends State { child: InkWell( onTap: () => showBottomSheet( context: context, - builder: (_) => Container( - height: widget.sheetHeight, - color: Theme.of(context).colorScheme.background, - child: Column( - children: [ - Container( - height: 45, - padding: const EdgeInsets.only(left: 14, right: 14), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - '合集(${episodes.length})', - style: Theme.of(context).textTheme.titleMedium, + 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, + color: Theme.of(context).colorScheme.background, + child: Column( + children: [ + Container( + height: 45, + padding: const EdgeInsets.only(left: 14, right: 14), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + '合集(${episodes.length})', + style: Theme.of(context).textTheme.titleMedium, + ), + IconButton( + icon: const Icon(Icons.close), + onPressed: () => Navigator.pop(context), + ), + ], ), - IconButton( - icon: const Icon(Icons.close), - onPressed: () => Navigator.pop(context), - ), - ], - ), - ), - Divider( - height: 1, - color: Theme.of(context).dividerColor.withOpacity(0.1), - ), - Expanded( - child: Material( - child: ListView.builder( - itemCount: episodes.length, - itemBuilder: (context, index) { - return InkWell( - onTap: () => - changeFucCall(episodes[index], index), - child: Padding( - padding: const EdgeInsets.only( - top: 10, bottom: 10, left: 15, right: 15), - child: Text( - episodes[index].title!, - style: TextStyle( + ), + Divider( + height: 1, + color: + Theme.of(context).dividerColor.withOpacity(0.1), + ), + Expanded( + child: Material( + child: ListView.builder( + controller: _scrollController, + itemCount: episodes.length, + itemBuilder: (context, index) { + return ListTile( + onTap: () => + changeFucCall(episodes[index], index), + dense: false, + leading: index == currentIndex + ? Image.asset( + 'assets/images/live.gif', + color: Theme.of(context) + .colorScheme + .primary, + height: 12, + ) + : null, + title: Text( + episodes[index].title!, + style: TextStyle( + fontSize: 14, color: index == currentIndex ? Theme.of(context) .colorScheme .primary : Theme.of(context) .colorScheme - .onSurface), - ), - ), - ); - }, + .onSurface, + ), + ), + ); + }, + ), + ), ), - ), + ], ), - ], - ), - ), + ); + }); + }, ), child: Padding( padding: const EdgeInsets.fromLTRB(8, 12, 8, 12), diff --git a/lib/pages/webview/controller.dart b/lib/pages/webview/controller.dart index fae5a5fc..2dfa9582 100644 --- a/lib/pages/webview/controller.dart +++ b/lib/pages/webview/controller.dart @@ -6,6 +6,7 @@ import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; import 'package:hive/hive.dart'; import 'package:pilipala/http/init.dart'; +import 'package:pilipala/http/member.dart'; import 'package:pilipala/http/user.dart'; import 'package:pilipala/pages/home/index.dart'; import 'package:pilipala/pages/media/index.dart'; @@ -102,7 +103,6 @@ class WebviewController extends GetxController { try { await SetCookie.onSet(); var result = await UserHttp.userInfo(); - UserHttp.thirdLogin(); if (result['status'] && result['data'].isLogin) { SmartDialog.showToast('登录成功'); try { @@ -115,6 +115,7 @@ class WebviewController extends GetxController { MediaController mediaCtr = Get.find(); mediaCtr.mid = result['data'].mid; await LoginUtils.refreshLoginStatus(true); + MemberHttp.cookieToKey(); } catch (err) { SmartDialog.show(builder: (context) { return AlertDialog( diff --git a/lib/pages/whisper/controller.dart b/lib/pages/whisper/controller.dart new file mode 100644 index 00000000..c82cab5b --- /dev/null +++ b/lib/pages/whisper/controller.dart @@ -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 = [].obs; + RxList accountList = [].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 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'); + } +} diff --git a/lib/pages/whisper/index.dart b/lib/pages/whisper/index.dart new file mode 100644 index 00000000..6d2ce533 --- /dev/null +++ b/lib/pages/whisper/index.dart @@ -0,0 +1,4 @@ +library whisper; + +export './controller.dart'; +export './view.dart'; diff --git a/lib/pages/whisper/view.dart b/lib/pages/whisper/view.dart new file mode 100644 index 00000000..4de197ca --- /dev/null +++ b/lib/pages/whisper/view.dart @@ -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 createState() => _WhisperPageState(); +} + +class _WhisperPageState extends State { + 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(); + } + }, + ) + ], + ), + ), + ), + ), + ], + ), + ); + } +} diff --git a/lib/pages/whisperDetail/controller.dart b/lib/pages/whisperDetail/controller.dart new file mode 100644 index 00000000..52a70c49 --- /dev/null +++ b/lib/pages/whisperDetail/controller.dart @@ -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 messageList = [].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; + } +} diff --git a/lib/pages/whisperDetail/index.dart b/lib/pages/whisperDetail/index.dart new file mode 100644 index 00000000..79b4ea19 --- /dev/null +++ b/lib/pages/whisperDetail/index.dart @@ -0,0 +1,4 @@ +library whisper_detail; + +export './controller.dart'; +export './view.dart'; diff --git a/lib/pages/whisperDetail/view.dart b/lib/pages/whisperDetail/view.dart new file mode 100644 index 00000000..0029eaff --- /dev/null +++ b/lib/pages/whisperDetail/view.dart @@ -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 createState() => _WhisperDetailPageState(); +} + +class _WhisperDetailPageState extends State { + final WhisperDetailController _whisperDetailController = + Get.put(WhisperDetailController()); + late Future _futureBuilderFuture; + + @override + void initState() { + super.initState(); + _futureBuilderFuture = _whisperDetailController.querySessionMsg(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + automaticallyImplyLeading: false, + 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), + ], + ), + ), + ); + } +} diff --git a/lib/pages/whisperDetail/widget/chat_item.dart b/lib/pages/whisperDetail/widget/chat_item.dart new file mode 100644 index 00000000..7ac72aa5 --- /dev/null +++ b/lib/pages/whisperDetail/widget/chat_item.dart @@ -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(), + ], + ); + } +} diff --git a/lib/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index 65f3fbca..5ba1ccfb 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -6,7 +6,6 @@ import 'dart:typed_data'; import 'package:easy_debounce/easy_throttle.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:get/get.dart'; import 'package:hive/hive.dart'; @@ -221,6 +220,7 @@ class PlPlayerController { late List speedsList; // 缓存 double? defaultDuration; + late bool enableAutoLongPressSpeed = false; // 播放顺序相关 PlayRepeat playRepeat = PlayRepeat.pause; @@ -250,8 +250,12 @@ class PlPlayerController { ); _playbackSpeed.value = videoStorage.get(VideoBoxKey.playSpeedDefault, defaultValue: 1.0); - _longPressSpeed.value = - videoStorage.get(VideoBoxKey.longPressSpeedDefault, defaultValue: 2.0); + enableAutoLongPressSpeed = setting + .get(SettingBoxKey.enableAutoLongPressSpeed, defaultValue: false); + if (!enableAutoLongPressSpeed) { + _longPressSpeed.value = videoStorage + .get(VideoBoxKey.longPressSpeedDefault, defaultValue: 2.0); + } List speedsListTemp = videoStorage.get(VideoBoxKey.customSpeedsList, defaultValue: []); speedsList = List.from(speedsListTemp); @@ -859,7 +863,8 @@ class PlPlayerController { } _doubleSpeedStatus.value = val; if (val) { - setPlaybackSpeed(longPressSpeed); + setPlaybackSpeed( + enableAutoLongPressSpeed ? playbackSpeed * 2 : longPressSpeed); } else { print(playbackSpeed); setPlaybackSpeed(playbackSpeed); diff --git a/lib/router/app_pages.dart b/lib/router/app_pages.dart index cec93c26..9fa926ee 100644 --- a/lib/router/app_pages.dart +++ b/lib/router/app_pages.dart @@ -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_search/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/searchResult/index.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/setting/index.dart'; import 'package:pilipala/pages/media/index.dart'; +import 'package:pilipala/pages/whisper/index.dart'; +import 'package:pilipala/pages/whisperDetail/index.dart'; import 'package:pilipala/utils/storage.dart'; Box setting = GStrorage.setting; @@ -128,6 +129,11 @@ class Routes { CustomGetPage(name: '/playSpeedSet', page: () => const PlaySpeedPage()), // 收藏搜索 CustomGetPage(name: '/favSearch', page: () => const FavSearchPage()), + // 消息页面 + CustomGetPage(name: '/whisper', page: () => const WhisperPage()), + // 私信详情 + CustomGetPage( + name: '/whisperDetail', page: () => const WhisperDetailPage()), // 登录页面 CustomGetPage(name: '/loginPage', page: () => const LoginPage()), // 用户动态 diff --git a/lib/utils/storage.dart b/lib/utils/storage.dart index 4e92c1ff..3f187638 100644 --- a/lib/utils/storage.dart +++ b/lib/utils/storage.dart @@ -107,6 +107,7 @@ class SettingBoxKey { static const String p1080 = 'p1080'; static const String enableCDN = 'enableCDN'; static const String autoPiP = 'autoPiP'; + static const String enableAutoLongPressSpeed = 'enableAutoLongPressSpeed'; // youtube 双击快进快退 static const String enableQuickDouble = 'enableQuickDouble';