From b272d25157436e65d3c03489b84b97dfcef7596c Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 15 Oct 2023 16:12:09 +0800 Subject: [PATCH 1/8] =?UTF-8?q?feat:=20=E7=A7=81=E4=BF=A1=E6=9F=A5?= =?UTF-8?q?=E7=9C=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/api.dart | 34 +++ lib/http/msg.dart | 80 +++++++ lib/models/msg/account.dart | 80 +++++++ lib/models/msg/session.dart | 226 ++++++++++++++++++ lib/pages/home/view.dart | 6 +- lib/pages/whisper/controller.dart | 50 ++++ lib/pages/whisper/index.dart | 4 + lib/pages/whisper/view.dart | 178 ++++++++++++++ lib/pages/whisperDetail/controller.dart | 24 ++ lib/pages/whisperDetail/index.dart | 4 + lib/pages/whisperDetail/view.dart | 167 +++++++++++++ lib/pages/whisperDetail/widget/chat_item.dart | 189 +++++++++++++++ lib/router/app_pages.dart | 7 + 13 files changed, 1048 insertions(+), 1 deletion(-) create mode 100644 lib/http/msg.dart create mode 100644 lib/models/msg/account.dart create mode 100644 lib/models/msg/session.dart create mode 100644 lib/pages/whisper/controller.dart create mode 100644 lib/pages/whisper/index.dart create mode 100644 lib/pages/whisper/view.dart create mode 100644 lib/pages/whisperDetail/controller.dart create mode 100644 lib/pages/whisperDetail/index.dart create mode 100644 lib/pages/whisperDetail/view.dart create mode 100644 lib/pages/whisperDetail/widget/chat_item.dart diff --git a/lib/http/api.dart b/lib/http/api.dart index 50106dae..c10ced10 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -321,4 +321,38 @@ class Api { // 获取指定分组下的up static const String followUpGroup = '/x/relation/tag'; + + /// 私聊 + /// 'https://api.vc.bilibili.com/session_svr/v1/session_svr/get_sessions? + /// session_type=1& + /// group_fold=1& + /// unfollow_fold=0& + /// sort_rule=2& + /// build=0& + /// mobi_app=web& + /// w_rid=8641d157fb9a9255eb2159f316ee39e2& + /// wts=1697305010 + + static const String sessionList = + 'https://api.vc.bilibili.com/session_svr/v1/session_svr/get_sessions'; + + /// 私聊用户信息 + /// uids + /// build=0&mobi_app=web + static const String sessionAccountList = + 'https://api.vc.bilibili.com/account/v1/user/cards'; + + /// https://api.vc.bilibili.com/svr_sync/v1/svr_sync/fetch_session_msgs? + /// talker_id=400787461& + /// session_type=1& + /// size=20& + /// sender_device_id=1& + /// build=0& + /// mobi_app=web& + /// web_location=333.1296& + /// w_rid=cfe3bf58c9fe181bbf4dd6c75175e6b0& + /// wts=1697350697 + + static const String sessionMsg = + 'https://api.vc.bilibili.com/svr_sync/v1/svr_sync/fetch_session_msgs'; } diff --git a/lib/http/msg.dart b/lib/http/msg.dart new file mode 100644 index 00000000..41cdefe8 --- /dev/null +++ b/lib/http/msg.dart @@ -0,0 +1,80 @@ +import 'package:pilipala/http/api.dart'; +import 'package:pilipala/http/init.dart'; +import 'package:pilipala/models/msg/account.dart'; +import 'package:pilipala/models/msg/session.dart'; +import 'package:pilipala/utils/wbi_sign.dart'; + +class MsgHttp { + // 会话列表 + static Future sessionList() async { + Map params = await WbiSign().makSign({ + 'session_type': 1, + 'group_fold': 1, + 'unfollow_fold': 0, + 'sort_rule': 2, + 'build': 0, + 'mobi_app': 'web', + }); + var res = await Request().get(Api.sessionList, data: params); + if (res.data['code'] == 0) { + return { + 'status': true, + 'data': SessionDataModel.fromJson(res.data['data']), + }; + } else { + return { + 'status': false, + 'date': [], + 'msg': res.data['message'], + }; + } + } + + static Future accountList(uids) async { + var res = await Request().get(Api.sessionAccountList, data: { + 'uids': uids, + 'build': 0, + 'mobi_app': 'web', + }); + if (res.data['code'] == 0) { + return { + 'status': true, + 'data': res.data['data'] + .map((e) => AccountListModel.fromJson(e)) + .toList(), + }; + } else { + return { + 'status': false, + 'date': [], + 'msg': res.data['message'], + }; + } + } + + static Future sessionMsg({ + int? talkerId, + }) async { + Map params = await WbiSign().makSign({ + 'talker_id': talkerId, + 'session_type': 1, + 'size': 20, + 'sender_device_id': 1, + 'build': 0, + 'mobi_app': 'web', + }); + var res = await Request().get(Api.sessionMsg, data: params); + if (res.data['code'] == 0) { + return { + 'status': true, + 'data': SessionMsgDataModel.fromJson(res.data['data']), + }; + } else { + return { + 'status': false, + 'date': [], + 'msg': res.data['message'], + }; + } + } +} diff --git a/lib/models/msg/account.dart b/lib/models/msg/account.dart new file mode 100644 index 00000000..bedd49b5 --- /dev/null +++ b/lib/models/msg/account.dart @@ -0,0 +1,80 @@ +class AccountListModel { + AccountListModel({ + this.mid, + this.name, + this.sex, + this.face, + this.sign, + this.rank, + this.level, + this.silence, + this.vip, + this.pendant, + this.nameplate, + this.official, + this.birthday, + this.isFakeAccount, + this.isDeleted, + this.inRegAudit, + this.faceNft, + this.faceNftNew, + this.isSeniorMember, + this.digitalId, + this.digitalType, + this.attestation, + this.expertInfo, + this.honours, + }); + + int? mid; + String? name; + String? sex; + String? face; + String? sign; + int? rank; + int? level; + int? silence; + Map? vip; + Map? pendant; + Map? nameplate; + Map? official; + int? birthday; + int? isFakeAccount; + int? isDeleted; + int? inRegAudit; + int? faceNft; + int? faceNftNew; + int? isSeniorMember; + String? digitalId; + int? digitalType; + Map? attestation; + Map? expertInfo; + Map? honours; + + AccountListModel.fromJson(Map json) { + mid = json['mid']; + name = json['name'] ?? ''; + sex = json['sex']; + face = json['face']; + sign = json['sign']; + rank = json['rank']; + level = json['level']; + silence = json['silence']; + vip = json['vip']; + pendant = json['pendant']; + nameplate = json['nameplate']; + official = json['official']; + birthday = json['birthday']; + isFakeAccount = json['is_fake_account']; + isDeleted = json['is_deleted']; + inRegAudit = json['in_reg_audit']; + faceNft = json['face_nft']; + faceNftNew = json['face_nft_new']; + isSeniorMember = json['is_senior_member']; + digitalId = json['digital_id']; + digitalType = json['digital_type']; + attestation = json['attestation']; + expertInfo = json['expert_info']; + honours = json['honours']; + } +} diff --git a/lib/models/msg/session.dart b/lib/models/msg/session.dart new file mode 100644 index 00000000..683dd94a --- /dev/null +++ b/lib/models/msg/session.dart @@ -0,0 +1,226 @@ +import 'dart:convert'; + +import 'package:pilipala/models/msg/account.dart'; + +class SessionDataModel { + SessionDataModel({ + this.sessionList, + this.hasMore, + }); + + List? sessionList; + int? hasMore; + + SessionDataModel.fromJson(Map json) { + sessionList = json['session_list'] + .map((e) => SessionList.fromJson(e)) + .toList(); + hasMore = json['has_more']; + } +} + +class SessionList { + SessionList({ + this.talkerId, + this.sessionType, + this.atSeqno, + this.topTs, + this.groupName, + this.groupCover, + this.isFollow, + this.isDnd, + this.ackSeqno, + this.ackTs, + this.sessionTs, + this.unreadCount, + this.lastMsg, + this.groupType, + this.canFold, + this.status, + this.maxSeqno, + this.newPushMsg, + this.setting, + this.isGuardian, + this.isIntercept, + this.isTrust, + this.systemMsgType, + this.liveStatus, + this.bizMsgUnreadCount, + // this.userLabel, + }); + + int? talkerId; + int? sessionType; + int? atSeqno; + int? topTs; + String? groupName; + String? groupCover; + int? isFollow; + int? isDnd; + int? ackSeqno; + int? ackTs; + int? sessionTs; + int? unreadCount; + LastMsg? lastMsg; + int? groupType; + int? canFold; + int? status; + int? maxSeqno; + int? newPushMsg; + int? setting; + int? isGuardian; + int? isIntercept; + int? isTrust; + int? systemMsgType; + int? liveStatus; + int? bizMsgUnreadCount; + // int? userLabel; + AccountListModel? accountInfo; + + SessionList.fromJson(Map json) { + talkerId = json["talker_id"]; + sessionType = json["session_type"]; + atSeqno = json["at_seqno"]; + topTs = json["top_ts"]; + groupName = json["group_name"]; + groupCover = json["group_cover"]; + isFollow = json["is_follow"]; + isDnd = json["is_dnd"]; + ackSeqno = json["ack_seqno"]; + ackTs = json["ack_ts"]; + sessionTs = json["session_ts"]; + unreadCount = json["unread_count"]; + lastMsg = + json["last_msg"] != null ? LastMsg.fromJson(json["last_msg"]) : null; + groupType = json["group_type"]; + canFold = json["can_fold"]; + status = json["status"]; + maxSeqno = json["max_seqno"]; + newPushMsg = json["new_push_msg"]; + setting = json["setting"]; + isGuardian = json["is_guardian"]; + isIntercept = json["is_intercept"]; + isTrust = json["is_trust"]; + systemMsgType = json["system_msg_type"]; + liveStatus = json["live_status"]; + bizMsgUnreadCount = json["biz_msg_unread_count"]; + // userLabel = json["user_label"]; + } +} + +class LastMsg { + LastMsg({ + this.senderIid, + this.receiverType, + this.receiverId, + this.msgType, + this.content, + this.msgSeqno, + this.timestamp, + this.atUids, + this.msgKey, + this.msgStatus, + this.notifyCode, + this.newFaceVersion, + }); + + int? senderIid; + int? receiverType; + int? receiverId; + int? msgType; + Map? content; + int? msgSeqno; + int? timestamp; + String? atUids; + int? msgKey; + int? msgStatus; + String? notifyCode; + int? newFaceVersion; + + LastMsg.fromJson(Map json) { + senderIid = json['sender_uid']; + receiverType = json['receiver_type']; + receiverId = json['receiver_id']; + msgType = json['msg_type']; + content = jsonDecode(json['content']); + msgSeqno = json['msg_seqno']; + timestamp = json['timestamp']; + atUids = json['at_uids']; + msgKey = json['msg_key']; + msgStatus = json['msg_status']; + notifyCode = json['notify_code']; + newFaceVersion = json['new_face_version']; + } +} + +class SessionMsgDataModel { + SessionMsgDataModel({ + this.messages, + this.hasMore, + this.minSeqno, + this.maxSeqno, + this.eInfos, + }); + + List? messages; + int? hasMore; + int? minSeqno; + int? maxSeqno; + List? eInfos; + + SessionMsgDataModel.fromJson(Map json) { + messages = json['messages'] + .map((e) => MessageItem.fromJson(e)) + .toList(); + hasMore = json['has_more']; + minSeqno = json['min_seqno']; + maxSeqno = json['max_seqno']; + eInfos = json['e_infos']; + } +} + +class MessageItem { + MessageItem({ + this.senderUid, + this.receiverType, + this.receiverId, + this.msgType, + this.content, + this.msgSeqno, + this.timestamp, + this.atUids, + this.msgKey, + this.msgStatus, + this.notifyCode, + this.newFaceVersion, + }); + + int? senderUid; + int? receiverType; + int? receiverId; + int? msgType; + Map? content; + int? msgSeqno; + int? timestamp; + List? atUids; + int? msgKey; + int? msgStatus; + String? notifyCode; + int? newFaceVersion; + + MessageItem.fromJson(Map json) { + senderUid = json['sender_uid']; + receiverType = json['receiver_type']; + receiverId = json['receiver_id']; + // 1 文本 2 图片 18 系统提示 10 系统通知 + msgType = json['msg_type']; + content = jsonDecode(json['content']); + msgSeqno = json['msg_seqno']; + timestamp = json['timestamp']; + atUids = json['at_uids']; + msgKey = json['msg_key']; + msgStatus = json['msg_status']; + notifyCode = json['notify_code']; + newFaceVersion = json['new_face_version']; + } +} diff --git a/lib/pages/home/view.dart b/lib/pages/home/view.dart index 3cdf81c7..dc55b4f5 100644 --- a/lib/pages/home/view.dart +++ b/lib/pages/home/view.dart @@ -129,7 +129,11 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { child: Row( children: [ const Expanded(child: SearchPage()), - const SizedBox(width: 10), + const SizedBox(width: 6), + IconButton( + onPressed: () => Get.toNamed('/whisper'), + icon: const Icon(Icons.notifications_none)), + const SizedBox(width: 6), Obx( () => ctr!.userLogin.value ? Stack( diff --git a/lib/pages/whisper/controller.dart b/lib/pages/whisper/controller.dart new file mode 100644 index 00000000..3e0ae2bc --- /dev/null +++ b/lib/pages/whisper/controller.dart @@ -0,0 +1,50 @@ +import 'package:get/get.dart'; +import 'package:pilipala/http/msg.dart'; +import 'package:pilipala/models/msg/account.dart'; +import 'package:pilipala/models/msg/session.dart'; + +class WhisperController extends GetxController { + RxList sessionList = [].obs; + RxList accountList = [].obs; + + Future querySessionList() async { + var res = await MsgHttp.sessionList(); + if (res['data'].sessionList.isNotEmpty) { + await queryAccountList(res['data'].sessionList); + // 将 accountList 转换为 Map 结构 + Map accountMap = {}; + for (var j in accountList) { + accountMap[j.mid!] = j; + } + + // 遍历 sessionList,通过 mid 查找并赋值 accountInfo + for (var i in res['data'].sessionList) { + var accountInfo = accountMap[i.talkerId]; + if (accountInfo != null) { + i.accountInfo = accountInfo; + } + if (i.talkerId == 844424930131966) { + i.accountInfo = AccountListModel( + name: 'UP主小助手', + face: + 'https://message.biliimg.com/bfs/im/489a63efadfb202366c2f88853d2217b5ddc7a13.png', + ); + } + } + } + if (res['status']) { + sessionList.value = res['data'].sessionList; + } + + return res; + } + + Future queryAccountList(sessionList) async { + List midsList = sessionList.map((e) => e.talkerId!).toList(); + var res = await MsgHttp.accountList(midsList.join(',')); + if (res['status']) { + accountList.value = res['data']; + } + return res; + } +} diff --git a/lib/pages/whisper/index.dart b/lib/pages/whisper/index.dart new file mode 100644 index 00000000..6d2ce533 --- /dev/null +++ b/lib/pages/whisper/index.dart @@ -0,0 +1,4 @@ +library whisper; + +export './controller.dart'; +export './view.dart'; diff --git a/lib/pages/whisper/view.dart b/lib/pages/whisper/view.dart new file mode 100644 index 00000000..7de3fa75 --- /dev/null +++ b/lib/pages/whisper/view.dart @@ -0,0 +1,178 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:pilipala/common/widgets/network_img_layer.dart'; +import 'package:pilipala/utils/utils.dart'; + +import 'controller.dart'; + +class WhisperPage extends StatefulWidget { + const WhisperPage({super.key}); + + @override + State createState() => _WhisperPageState(); +} + +class _WhisperPageState extends State { + late final WhisperController _whisperController = + Get.put(WhisperController()); + late Future _futureBuilderFuture; + + @override + void initState() { + super.initState(); + _futureBuilderFuture = _whisperController.querySessionList(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + scrolledUnderElevation: 0, + ), + body: Column( + children: [ + // LayoutBuilder( + // builder: (BuildContext context, BoxConstraints constraints) { + // // 在这里根据父级容器的约束条件构建小部件树 + // return Padding( + // padding: const EdgeInsets.only(left: 20, right: 20), + // child: SizedBox( + // height: constraints.maxWidth / 5, + // child: GridView.count( + // primary: false, + // crossAxisCount: 4, + // padding: const EdgeInsets.all(0), + // childAspectRatio: 1.25, + // children: [ + // Column( + // crossAxisAlignment: CrossAxisAlignment.center, + // mainAxisAlignment: MainAxisAlignment.center, + // children: [ + // SizedBox( + // width: 36, + // height: 36, + // child: IconButton( + // style: ButtonStyle( + // padding: + // MaterialStateProperty.all(EdgeInsets.zero), + // backgroundColor: + // MaterialStateProperty.resolveWith((states) { + // return Theme.of(context) + // .colorScheme + // .primary + // .withOpacity(0.1); + // }), + // ), + // onPressed: () {}, + // icon: Icon( + // Icons.message_outlined, + // size: 18, + // color: Theme.of(context).colorScheme.primary, + // ), + // ), + // ), + // const SizedBox(height: 6), + // const Text('回复我的', style: TextStyle(fontSize: 13)) + // ], + // ), + // ], + // ), + // ), + // ); + // }, + // ), + Expanded( + child: FutureBuilder( + future: _futureBuilderFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + Map data = snapshot.data as Map; + if (data['status']) { + List sessionList = _whisperController.sessionList; + return Obx( + () => sessionList.isEmpty + ? const SizedBox() + : ListView.builder( + itemCount: sessionList.length, + shrinkWrap: true, + itemBuilder: (_, int i) { + return ListTile( + onTap: () { + Get.toNamed( + '/whisperDetail?talkerId=${sessionList[i].talkerId}&name=${sessionList[i].accountInfo.name}'); + }, + leading: NetworkImgLayer( + width: 45, + height: 45, + type: 'avatar', + src: sessionList[i].accountInfo.face, + ), + title: Text( + sessionList[i].accountInfo.name, + style: + Theme.of(context).textTheme.titleSmall, + ), + subtitle: Text( + sessionList[i].lastMsg.content['text'] ?? + sessionList[i] + .lastMsg + .content['content'] ?? + sessionList[i] + .lastMsg + .content['title'], + maxLines: 1, + overflow: TextOverflow.ellipsis, + style: Theme.of(context) + .textTheme + .labelMedium! + .copyWith( + color: Theme.of(context) + .colorScheme + .outline)), + trailing: Text( + Utils.dateFormat( + sessionList[i].lastMsg.timestamp), + style: Theme.of(context) + .textTheme + .labelSmall! + .copyWith( + color: Theme.of(context) + .colorScheme + .outline), + ), + ); + }, + ), + ); + } else { + // 请求错误 + return SizedBox(); + } + } else { + // 骨架屏 + return SizedBox(); + } + }, + ), + ), + + // ListTile( + // onTap: () {}, + // leading: CircleAvatar(), + // title: Text('钱瑞昌'), + // subtitle: Text('没事', style: Theme.of(context).textTheme.bodySmall), + // trailing: Text('昨天'), + // ), + // ListTile( + // onTap: () {}, + // leading: CircleAvatar(), + // title: Text('李天'), + // subtitle: + // Text('明天有空吗', style: Theme.of(context).textTheme.bodySmall), + // trailing: Text('现在'), + // ) + ], + ), + ); + } +} diff --git a/lib/pages/whisperDetail/controller.dart b/lib/pages/whisperDetail/controller.dart new file mode 100644 index 00000000..ef25cfd4 --- /dev/null +++ b/lib/pages/whisperDetail/controller.dart @@ -0,0 +1,24 @@ +import 'package:get/get.dart'; +import 'package:pilipala/http/msg.dart'; +import 'package:pilipala/models/msg/session.dart'; + +class WhisperDetailController extends GetxController { + late int talkerId; + RxString name = ''.obs; + RxList messageList = [].obs; + + @override + void onInit() { + super.onInit(); + talkerId = int.parse(Get.parameters['talkerId']!); + name.value = Get.parameters['name']!; + } + + Future querySessionMsg() async { + var res = await MsgHttp.sessionMsg(talkerId: talkerId); + if (res['status']) { + messageList.value = res['data'].messages; + } + return res; + } +} diff --git a/lib/pages/whisperDetail/index.dart b/lib/pages/whisperDetail/index.dart new file mode 100644 index 00000000..79b4ea19 --- /dev/null +++ b/lib/pages/whisperDetail/index.dart @@ -0,0 +1,4 @@ +library whisper_detail; + +export './controller.dart'; +export './view.dart'; diff --git a/lib/pages/whisperDetail/view.dart b/lib/pages/whisperDetail/view.dart new file mode 100644 index 00000000..b2aba5da --- /dev/null +++ b/lib/pages/whisperDetail/view.dart @@ -0,0 +1,167 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:pilipala/pages/whisperDetail/controller.dart'; + +import 'widget/chat_item.dart'; + +class WhisperDetailPage extends StatefulWidget { + const WhisperDetailPage({super.key}); + + @override + State createState() => _WhisperDetailPageState(); +} + +class _WhisperDetailPageState extends State { + final WhisperDetailController _whisperDetailController = + Get.put(WhisperDetailController()); + late Future _futureBuilderFuture; + + @override + void initState() { + super.initState(); + _futureBuilderFuture = _whisperDetailController.querySessionMsg(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + automaticallyImplyLeading: false, + scrolledUnderElevation: 0, + title: SizedBox( + width: double.infinity, + height: 50, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox( + width: 34, + height: 34, + child: IconButton( + style: ButtonStyle( + padding: MaterialStateProperty.all(EdgeInsets.zero), + backgroundColor: + MaterialStateProperty.resolveWith((states) { + return Theme.of(context) + .colorScheme + .primaryContainer + .withOpacity(0.6); + }), + ), + onPressed: () => Get.back(), + icon: Icon( + Icons.arrow_back_outlined, + size: 18, + color: Theme.of(context).colorScheme.primary, + ), + ), + ), + Obx( + () => Text( + _whisperDetailController.name.value, + style: Theme.of(context).textTheme.titleMedium, + ), + ), + const SizedBox(width: 36, height: 36), + ], + ), + ), + ), + body: FutureBuilder( + future: _futureBuilderFuture, + builder: (context, snapshot) { + if (snapshot.connectionState == ConnectionState.done) { + Map data = snapshot.data as Map; + if (data['status']) { + List messageList = _whisperDetailController.messageList; + return Obx( + () => messageList.isEmpty + ? const SizedBox() + : ListView.builder( + itemCount: messageList.length, + shrinkWrap: true, + reverse: true, + itemBuilder: (_, int i) { + if (i == 0) { + return Column( + children: [ + ChatItem(item: messageList[i]), + const SizedBox(height: 12), + ], + ); + } else { + return ChatItem(item: messageList[i]); + } + }, + ), + ); + } else { + // 请求错误 + return const SizedBox(); + } + } else { + // 骨架屏 + return const SizedBox(); + } + }, + ), + bottomNavigationBar: Container( + width: double.infinity, + height: MediaQuery.of(context).padding.bottom + 70, + padding: EdgeInsets.only( + left: 8, + right: 12, + bottom: MediaQuery.of(context).padding.bottom, + ), + decoration: BoxDecoration( + color: Colors.white, + border: Border( + top: BorderSide( + width: 4, + color: Theme.of(context).colorScheme.primary.withOpacity(0.1), + ), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // IconButton( + // onPressed: () {}, + // icon: Icon( + // Icons.add_circle_outline, + // color: Theme.of(context).colorScheme.outline, + // ), + // ), + IconButton( + onPressed: () {}, + icon: Icon( + Icons.emoji_emotions_outlined, + color: Theme.of(context).colorScheme.outline, + ), + ), + // Expanded( + // child: Container( + // height: 42, + // decoration: BoxDecoration( + // color: Theme.of(context).colorScheme.primary.withOpacity(0.1), + // borderRadius: BorderRadius.circular(40.0), + // ), + // child: TextField( + // readOnly: true, + // style: Theme.of(context).textTheme.titleMedium, + // decoration: const InputDecoration( + // border: InputBorder.none, // 移除默认边框 + // hintText: '请输入内容', // 提示文本 + // contentPadding: EdgeInsets.symmetric( + // horizontal: 12.0, vertical: 12.0), // 内边距 + // ), + // ), + // ), + // ), + const SizedBox(width: 16), + ], + ), + ), + ); + } +} diff --git a/lib/pages/whisperDetail/widget/chat_item.dart b/lib/pages/whisperDetail/widget/chat_item.dart new file mode 100644 index 00000000..512300ce --- /dev/null +++ b/lib/pages/whisperDetail/widget/chat_item.dart @@ -0,0 +1,189 @@ +// ignore_for_file: must_be_immutable + +import 'package:flutter/material.dart'; +import 'package:pilipala/common/widgets/network_img_layer.dart'; +import 'package:pilipala/utils/utils.dart'; + +class ChatItem extends StatelessWidget { + dynamic item; + + ChatItem({ + super.key, + this.item, + }); + + @override + Widget build(BuildContext context) { + bool isOwner = item.senderUid == 17340771; + bool isPic = item.msgType == 2; + bool isText = item.msgType == 1; + bool isSystem = + item.msgType == 18 || item.msgType == 10 || item.msgType == 13; + int msgType = item.msgType; + Map content = item.content ?? ''; + return isSystem + ? (msgType == 10 + ? SystemNotice(item: item) + : msgType == 13 + ? SystemNotice2(item: item) + : const SizedBox()) + : Row( + children: [ + if (!isOwner) const SizedBox(width: 12), + if (isOwner) const Spacer(), + Container( + constraints: const BoxConstraints( + maxWidth: 300.0, // 设置最大宽度为200.0 + ), + decoration: BoxDecoration( + color: isOwner + ? Theme.of(context).colorScheme.primary + : Theme.of(context).colorScheme.secondaryContainer, + borderRadius: BorderRadius.only( + topLeft: const Radius.circular(16), + topRight: const Radius.circular(16), + bottomLeft: Radius.circular(isOwner ? 16 : 6), + bottomRight: Radius.circular(isOwner ? 6 : 16), + ), + ), + margin: const EdgeInsets.only(top: 12), + padding: EdgeInsets.only( + top: 8, + bottom: 6, + left: isPic ? 8 : 12, + right: isPic ? 8 : 12, + ), + child: Column( + crossAxisAlignment: isOwner + ? CrossAxisAlignment.end + : CrossAxisAlignment.start, + children: [ + isText + ? Text( + content['content'], + style: TextStyle( + color: isOwner + ? Theme.of(context).colorScheme.onPrimary + : Theme.of(context) + .colorScheme + .onSecondaryContainer), + ) + : isPic + ? NetworkImgLayer( + width: 220, + height: + 220 * content['height'] / content['width'], + src: content['url'], + ) + : const SizedBox(), + SizedBox(height: isPic ? 7 : 2), + Text( + Utils.dateFormat(item.timestamp), + style: Theme.of(context).textTheme.labelSmall!.copyWith( + color: isOwner + ? Theme.of(context) + .colorScheme + .onPrimary + .withOpacity(0.8) + : Theme.of(context) + .colorScheme + .onSecondaryContainer + .withOpacity(0.8)), + ) + ], + ), + ), + if (!isOwner) const Spacer(), + if (isOwner) const SizedBox(width: 12), + ], + ); + } +} + +class SystemNotice extends StatelessWidget { + dynamic item; + SystemNotice({super.key, this.item}); + + @override + Widget build(BuildContext context) { + Map content = item.content ?? ''; + return Row( + children: [ + const SizedBox(width: 12), + Container( + constraints: const BoxConstraints( + maxWidth: 300.0, // 设置最大宽度为200.0 + ), + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.secondaryContainer, + borderRadius: const BorderRadius.only( + topLeft: Radius.circular(16), + topRight: Radius.circular(16), + bottomLeft: Radius.circular(6), + bottomRight: Radius.circular(16), + ), + ), + margin: const EdgeInsets.only(top: 12), + padding: const EdgeInsets.all(12), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(content['title'], + style: Theme.of(context) + .textTheme + .titleMedium! + .copyWith(fontWeight: FontWeight.bold)), + Text( + Utils.dateFormat(item.timestamp), + style: Theme.of(context) + .textTheme + .labelSmall! + .copyWith(color: Theme.of(context).colorScheme.outline), + ) + ], + ), + Divider( + color: Theme.of(context).colorScheme.primary.withOpacity(0.1), + ), + Text( + content['text'], + ) + ], + ), + ), + const Spacer(), + ], + ); + } +} + +class SystemNotice2 extends StatelessWidget { + dynamic item; + SystemNotice2({super.key, this.item}); + + @override + Widget build(BuildContext context) { + Map content = item.content ?? ''; + return Row( + children: [ + const SizedBox(width: 12), + Container( + constraints: const BoxConstraints( + maxWidth: 300.0, // 设置最大宽度为200.0 + ), + margin: const EdgeInsets.only(top: 12), + padding: const EdgeInsets.only(bottom: 6), + child: NetworkImgLayer( + width: 320, + height: 150, + src: content['pic_url'], + ), + ), + const Spacer(), + ], + ); + } +} diff --git a/lib/router/app_pages.dart b/lib/router/app_pages.dart index 544217f9..afcfea72 100644 --- a/lib/router/app_pages.dart +++ b/lib/router/app_pages.dart @@ -37,6 +37,8 @@ import 'package:pilipala/pages/video/detail/replyReply/index.dart'; import 'package:pilipala/pages/webview/index.dart'; import 'package:pilipala/pages/setting/index.dart'; import 'package:pilipala/pages/media/index.dart'; +import 'package:pilipala/pages/whisper/index.dart'; +import 'package:pilipala/pages/whisperDetail/index.dart'; import 'package:pilipala/utils/storage.dart'; Box setting = GStrorage.setting; @@ -122,6 +124,11 @@ class Routes { CustomGetPage(name: '/playSpeedSet', page: () => const PlaySpeedPage()), // 收藏搜索 CustomGetPage(name: '/favSearch', page: () => const FavSearchPage()), + // 消息页面 + CustomGetPage(name: '/whisper', page: () => const WhisperPage()), + // 私信详情 + CustomGetPage( + name: '/whisperDetail', page: () => const WhisperDetailPage()), ]; } From c85f5abcdbb8c705541d70f06d7a3d5f4ae9103f Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 3 Dec 2023 00:58:41 +0800 Subject: [PATCH 2/8] =?UTF-8?q?mod:=20=E7=99=BB=E5=BD=95=E6=97=B6=E8=8E=B7?= =?UTF-8?q?=E5=8F=96accessKey?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/common/widgets/video_card_v.dart | 6 ++++-- lib/http/member.dart | 2 +- lib/http/user.dart | 2 +- lib/models/home/rcmd/result.dart | 2 +- lib/pages/webview/controller.dart | 3 ++- 5 files changed, 9 insertions(+), 6 deletions(-) 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/member.dart b/lib/http/member.dart index a2af8d82..6854dfc6 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 { 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/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( From bda56169b06f0054edb6a107301766671a1e8ab0 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Mon, 4 Dec 2023 00:01:07 +0800 Subject: [PATCH 3/8] mod --- lib/models/video/play/url.dart | 2 -- lib/pages/about/index.dart | 3 --- lib/pages/bangumi/introduction/view.dart | 1 - lib/pages/dynamics/widgets/rich_node_panel.dart | 4 ---- lib/pages/follow/widgets/follow_item.dart | 1 - lib/pages/follow/widgets/owner_follow_list.dart | 2 -- lib/pages/history/widgets/item.dart | 1 - lib/pages/home/view.dart | 2 +- lib/pages/html/view.dart | 1 - lib/pages/live/view.dart | 1 + lib/pages/liveRoom/view.dart | 1 - lib/pages/liveRoom/widgets/bottom_control.dart | 5 ----- lib/pages/rcmd/view.dart | 4 ++-- lib/pages/search/view.dart | 17 +++++++++-------- lib/pages/searchPanel/view.dart | 1 + lib/pages/searchResult/view.dart | 1 + lib/pages/video/detail/introduction/view.dart | 1 - lib/plugin/pl_player/controller.dart | 1 - lib/router/app_pages.dart | 1 - 19 files changed, 15 insertions(+), 35 deletions(-) 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 31808e1c..33b2ee3e 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 fa5a1cc9..a6f3b42f 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'; @@ -75,6 +74,7 @@ class _HomePageState extends State dividerColor: Colors.transparent, enableFeedback: true, splashBorderRadius: BorderRadius.circular(10), + tabAlignment: TabAlignment.center, onTap: (value) { feedBack(); if (_homeController.initialIndex == value) { 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/rcmd/view.dart b/lib/pages/rcmd/view.dart index a0f867f7..f9773271 100644 --- a/lib/pages/rcmd/view.dart +++ b/lib/pages/rcmd/view.dart @@ -196,8 +196,8 @@ class _RcmdPageState extends State } class LoadingMore extends StatelessWidget { - dynamic ctr; - LoadingMore({super.key, this.ctr}); + final dynamic ctr; + const LoadingMore({super.key, this.ctr}); @override Widget build(BuildContext context) { 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/searchResult/view.dart b/lib/pages/searchResult/view.dart index f2efb33a..ceb77190 100644 --- a/lib/pages/searchResult/view.dart +++ b/lib/pages/searchResult/view.dart @@ -82,6 +82,7 @@ class _SearchResultPageState extends State labelStyle: const TextStyle(fontSize: 13), dividerColor: Colors.transparent, unselectedLabelColor: Theme.of(context).colorScheme.outline, + tabAlignment: TabAlignment.start, onTap: (index) { if (index == _searchResultController!.tabIndex) { Get.find( 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/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index 8f09d0ca..569c2a1c 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'; diff --git a/lib/router/app_pages.dart b/lib/router/app_pages.dart index cec93c26..03e3d97f 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'; From 52ab78f332aaabbe5ddd6971f2cfc0203bf065b3 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 16 Dec 2023 22:35:01 +0800 Subject: [PATCH 4/8] =?UTF-8?q?feat:=20up=E4=B8=BB=E9=A1=B5=E6=98=BE?= =?UTF-8?q?=E7=A4=BA=E8=8E=B7=E8=B5=9E=E6=95=B0=20issues=20#160?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/api.dart | 1 + lib/http/member.dart | 14 ++++++++++++++ lib/pages/member/controller.dart | 12 +++++++++++- lib/pages/member/widgets/profile.dart | 10 ++++++++-- 4 files changed, 34 insertions(+), 3 deletions(-) diff --git a/lib/http/api.dart b/lib/http/api.dart index 04fe0a44..b531fd59 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -410,6 +410,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 6854dfc6..20826451 100644 --- a/lib/http/member.dart +++ b/lib/http/member.dart @@ -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/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( From e9a356c4839643ed42cf2bb677311fa4bc7b1680 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sat, 16 Dec 2023 23:11:24 +0800 Subject: [PATCH 5/8] =?UTF-8?q?feat:=20=E5=90=88=E9=9B=86=E5=88=97?= =?UTF-8?q?=E8=A1=A8=E8=87=AA=E5=8A=A8=E8=B7=B3=E8=BD=AC=E6=8C=87=E5=AE=9A?= =?UTF-8?q?index?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../detail/introduction/widgets/page.dart | 142 +++++++++++------- .../detail/introduction/widgets/season.dart | 122 +++++++++------ 2 files changed, 158 insertions(+), 106 deletions(-) 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), From a6ab72cadd4a560aaba44ba75eec2386b231894f Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 17 Dec 2023 14:55:52 +0800 Subject: [PATCH 6/8] =?UTF-8?q?feat:=20=E6=B6=88=E6=81=AF=E5=88=86?= =?UTF-8?q?=E9=A1=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/http/api.dart | 12 + lib/http/msg.dart | 13 +- lib/models/msg/session.dart | 2 +- lib/pages/whisper/controller.dart | 27 ++- lib/pages/whisper/view.dart | 228 +++++++++++------- lib/pages/whisperDetail/controller.dart | 8 +- lib/pages/whisperDetail/view.dart | 76 ++++-- lib/pages/whisperDetail/widget/chat_item.dart | 37 +-- 8 files changed, 259 insertions(+), 144 deletions(-) diff --git a/lib/http/api.dart b/lib/http/api.dart index 9584f108..3db3c617 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -359,6 +359,18 @@ class Api { 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 diff --git a/lib/http/msg.dart b/lib/http/msg.dart index 41cdefe8..3bf17aaf 100644 --- a/lib/http/msg.dart +++ b/lib/http/msg.dart @@ -6,16 +6,21 @@ import 'package:pilipala/utils/wbi_sign.dart'; class MsgHttp { // 会话列表 - static Future sessionList() async { - Map params = await WbiSign().makSign({ + 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', - }); - var res = await Request().get(Api.sessionList, data: params); + }; + 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, diff --git a/lib/models/msg/session.dart b/lib/models/msg/session.dart index 683dd94a..706f0ae8 100644 --- a/lib/models/msg/session.dart +++ b/lib/models/msg/session.dart @@ -13,7 +13,7 @@ class SessionDataModel { SessionDataModel.fromJson(Map json) { sessionList = json['session_list'] - .map((e) => SessionList.fromJson(e)) + ?.map((e) => SessionList.fromJson(e)) .toList(); hasMore = json['has_more']; } diff --git a/lib/pages/whisper/controller.dart b/lib/pages/whisper/controller.dart index 3e0ae2bc..c82cab5b 100644 --- a/lib/pages/whisper/controller.dart +++ b/lib/pages/whisper/controller.dart @@ -6,10 +6,13 @@ import 'package:pilipala/models/msg/session.dart'; class WhisperController extends GetxController { RxList sessionList = [].obs; RxList accountList = [].obs; + bool isLoading = false; - Future querySessionList() async { - var res = await MsgHttp.sessionList(); - if (res['data'].sessionList.isNotEmpty) { + 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 = {}; @@ -32,10 +35,14 @@ class WhisperController extends GetxController { } } } - if (res['status']) { - sessionList.value = res['data'].sessionList; + 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; } @@ -47,4 +54,12 @@ class WhisperController extends GetxController { } return res; } + + Future onLoad() async { + querySessionList('onLoad'); + } + + Future onRefresh() async { + querySessionList('onRefresh'); + } } diff --git a/lib/pages/whisper/view.dart b/lib/pages/whisper/view.dart index 7de3fa75..4de197ca 100644 --- a/lib/pages/whisper/view.dart +++ b/lib/pages/whisper/view.dart @@ -1,3 +1,4 @@ +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'; @@ -16,18 +17,31 @@ 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(); + _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( - scrolledUnderElevation: 0, + title: const Text('消息'), ), body: Column( children: [ @@ -82,95 +96,133 @@ class _WhisperPageState extends State { // }, // ), Expanded( - child: FutureBuilder( - future: _futureBuilderFuture, - builder: (context, snapshot) { - if (snapshot.connectionState == ConnectionState.done) { - Map data = snapshot.data as Map; - if (data['status']) { - List sessionList = _whisperController.sessionList; - return Obx( - () => sessionList.isEmpty - ? const SizedBox() - : ListView.builder( - itemCount: sessionList.length, - shrinkWrap: true, - itemBuilder: (_, int i) { - return ListTile( - onTap: () { - Get.toNamed( - '/whisperDetail?talkerId=${sessionList[i].talkerId}&name=${sessionList[i].accountInfo.name}'); - }, - leading: NetworkImgLayer( - width: 45, - height: 45, - type: 'avatar', - src: sessionList[i].accountInfo.face, - ), - title: Text( - sessionList[i].accountInfo.name, - style: - Theme.of(context).textTheme.titleSmall, - ), - subtitle: Text( - sessionList[i].lastMsg.content['text'] ?? - sessionList[i] - .lastMsg - .content['content'] ?? - sessionList[i] - .lastMsg - .content['title'], - maxLines: 1, - overflow: TextOverflow.ellipsis, - style: Theme.of(context) - .textTheme - .labelMedium! - .copyWith( - color: Theme.of(context) - .colorScheme - .outline)), - trailing: Text( - Utils.dateFormat( - sessionList[i].lastMsg.timestamp), - style: Theme.of(context) - .textTheme - .labelSmall! - .copyWith( - color: Theme.of(context) - .colorScheme - .outline), - ), - ); - }, - ), - ); - } else { - // 请求错误 - return SizedBox(); - } - } else { - // 骨架屏 - return SizedBox(); - } + 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(); + } + }, + ) + ], + ), + ), ), ), - - // ListTile( - // onTap: () {}, - // leading: CircleAvatar(), - // title: Text('钱瑞昌'), - // subtitle: Text('没事', style: Theme.of(context).textTheme.bodySmall), - // trailing: Text('昨天'), - // ), - // ListTile( - // onTap: () {}, - // leading: CircleAvatar(), - // title: Text('李天'), - // subtitle: - // Text('明天有空吗', style: Theme.of(context).textTheme.bodySmall), - // trailing: Text('现在'), - // ) ], ), ); diff --git a/lib/pages/whisperDetail/controller.dart b/lib/pages/whisperDetail/controller.dart index ef25cfd4..52a70c49 100644 --- a/lib/pages/whisperDetail/controller.dart +++ b/lib/pages/whisperDetail/controller.dart @@ -4,14 +4,18 @@ import 'package:pilipala/models/msg/session.dart'; class WhisperDetailController extends GetxController { late int talkerId; - RxString name = ''.obs; + late String name; + late String face; + late String mid; RxList messageList = [].obs; @override void onInit() { super.onInit(); talkerId = int.parse(Get.parameters['talkerId']!); - name.value = Get.parameters['name']!; + name = Get.parameters['name']!; + face = Get.parameters['face']!; + mid = Get.parameters['mid']!; } Future querySessionMsg() async { diff --git a/lib/pages/whisperDetail/view.dart b/lib/pages/whisperDetail/view.dart index b2aba5da..0029eaff 100644 --- a/lib/pages/whisperDetail/view.dart +++ b/lib/pages/whisperDetail/view.dart @@ -1,6 +1,8 @@ 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'; @@ -27,7 +29,6 @@ class _WhisperDetailPageState extends State { return Scaffold( appBar: AppBar( automaticallyImplyLeading: false, - scrolledUnderElevation: 0, title: SizedBox( width: double.infinity, height: 50, @@ -52,14 +53,35 @@ class _WhisperDetailPageState extends State { icon: Icon( Icons.arrow_back_outlined, size: 18, - color: Theme.of(context).colorScheme.primary, + color: Theme.of(context).colorScheme.onPrimaryContainer, ), ), ), - Obx( - () => Text( - _whisperDetailController.name.value, - style: Theme.of(context).textTheme.titleMedium, + 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), @@ -105,12 +127,14 @@ class _WhisperDetailPageState extends State { } }, ), + // 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( @@ -124,6 +148,7 @@ class _WhisperDetailPageState extends State { ), child: Row( mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.start, children: [ // IconButton( // onPressed: () {}, @@ -139,25 +164,26 @@ class _WhisperDetailPageState extends State { color: Theme.of(context).colorScheme.outline, ), ), - // Expanded( - // child: Container( - // height: 42, - // decoration: BoxDecoration( - // color: Theme.of(context).colorScheme.primary.withOpacity(0.1), - // borderRadius: BorderRadius.circular(40.0), - // ), - // child: TextField( - // readOnly: true, - // style: Theme.of(context).textTheme.titleMedium, - // decoration: const InputDecoration( - // border: InputBorder.none, // 移除默认边框 - // hintText: '请输入内容', // 提示文本 - // contentPadding: EdgeInsets.symmetric( - // horizontal: 12.0, vertical: 12.0), // 内边距 - // ), - // ), - // ), - // ), + 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 index 512300ce..7ac72aa5 100644 --- a/lib/pages/whisperDetail/widget/chat_item.dart +++ b/lib/pages/whisperDetail/widget/chat_item.dart @@ -17,6 +17,9 @@ class ChatItem extends StatelessWidget { 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; @@ -115,7 +118,10 @@ class SystemNotice extends StatelessWidget { maxWidth: 300.0, // 设置最大宽度为200.0 ), decoration: BoxDecoration( - color: Theme.of(context).colorScheme.secondaryContainer, + color: Theme.of(context) + .colorScheme + .secondaryContainer + .withOpacity(0.4), borderRadius: const BorderRadius.only( topLeft: Radius.circular(16), topRight: Radius.circular(16), @@ -128,25 +134,20 @@ class SystemNotice extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(content['title'], - style: Theme.of(context) - .textTheme - .titleMedium! - .copyWith(fontWeight: FontWeight.bold)), - Text( - Utils.dateFormat(item.timestamp), - style: Theme.of(context) - .textTheme - .labelSmall! - .copyWith(color: Theme.of(context).colorScheme.outline), - ) - ], + Text(content['title'], + style: Theme.of(context) + .textTheme + .titleMedium! + .copyWith(fontWeight: FontWeight.bold)), + Text( + Utils.dateFormat(item.timestamp), + style: Theme.of(context) + .textTheme + .labelSmall! + .copyWith(color: Theme.of(context).colorScheme.outline), ), Divider( - color: Theme.of(context).colorScheme.primary.withOpacity(0.1), + color: Theme.of(context).colorScheme.primary.withOpacity(0.05), ), Text( content['text'], From 8ef3c1d9bbe179311ccf23d31c04e37c83e7d977 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 17 Dec 2023 15:16:34 +0800 Subject: [PATCH 7/8] =?UTF-8?q?mod:=20=E6=9C=AA=E7=99=BB=E5=BD=95=E4=B8=8D?= =?UTF-8?q?=E6=98=BE=E7=A4=BA=E6=B6=88=E6=81=AF=E5=85=A5=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/home/view.dart | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/pages/home/view.dart b/lib/pages/home/view.dart index e119cbcb..bcde246e 100644 --- a/lib/pages/home/view.dart +++ b/lib/pages/home/view.dart @@ -139,10 +139,12 @@ class CustomAppBar extends StatelessWidget implements PreferredSizeWidget { child: Row( children: [ const Expanded(child: SearchPage()), - const SizedBox(width: 6), - IconButton( - onPressed: () => Get.toNamed('/whisper'), - icon: const Icon(Icons.notifications_none)), + 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 From 3d6d0b0c4433cffb6ff03bbed16b9e3b0eb89bf6 Mon Sep 17 00:00:00 2001 From: guozhigq Date: Sun, 17 Dec 2023 16:11:53 +0800 Subject: [PATCH 8/8] =?UTF-8?q?opt:=20=E9=95=BF=E6=8C=89=E5=80=8D=E9=80=9F?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E4=BC=98=E5=8C=96=20issues=20#240?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/pages/setting/pages/play_speed_set.dart | 68 +++++++++++++++------ lib/plugin/pl_player/controller.dart | 12 +++- lib/utils/storage.dart | 1 + 3 files changed, 61 insertions(+), 20 deletions(-) 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/plugin/pl_player/controller.dart b/lib/plugin/pl_player/controller.dart index 05280e75..5ba1ccfb 100644 --- a/lib/plugin/pl_player/controller.dart +++ b/lib/plugin/pl_player/controller.dart @@ -220,6 +220,7 @@ class PlPlayerController { late List speedsList; // 缓存 double? defaultDuration; + late bool enableAutoLongPressSpeed = false; // 播放顺序相关 PlayRepeat playRepeat = PlayRepeat.pause; @@ -249,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); @@ -858,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/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';