From 2df6c91a3dafaa0c6bbd6afcc458784a11ce27e7 Mon Sep 17 00:00:00 2001 From: bggRGjQaUbCoE Date: Sat, 9 Aug 2025 11:14:33 +0800 Subject: [PATCH] feat: like live room Closes #963 Signed-off-by: bggRGjQaUbCoE --- lib/http/api.dart | 3 + lib/http/live.dart | 25 ++++++ lib/pages/live_room/controller.dart | 41 ++++++++++ lib/pages/live_room/view.dart | 81 ++++++++++++++++++- .../live_room/widgets/bottom_control.dart | 2 +- .../accounts/account_manager/account_mgr.dart | 1 + 6 files changed, 151 insertions(+), 2 deletions(-) diff --git a/lib/http/api.dart b/lib/http/api.dart index ef772141..5aa3aeb6 100644 --- a/lib/http/api.dart +++ b/lib/http/api.dart @@ -945,4 +945,7 @@ class Api { static const String expLog = '/x/member/web/exp/log'; static const String moralLog = '/x/member/web/moral/log'; + + static const String liveLikeReport = + '${HttpString.liveBaseUrl}/xlive/app-ucenter/v1/like_info_v3/like/likeReportV3'; } diff --git a/lib/http/live.dart b/lib/http/live.dart index 169d8d60..38614307 100644 --- a/lib/http/live.dart +++ b/lib/http/live.dart @@ -619,4 +619,29 @@ class LiveHttp { return {'status': false, 'msg': res.data['message']}; } } + + static Future liveLikeReport({ + required int clickTime, + required dynamic roomId, + required dynamic uid, + required dynamic anchorId, + }) async { + var res = await Request().post( + Api.liveLikeReport, + data: await WbiSign.makSign({ + 'click_time': clickTime, + 'room_id': roomId, + 'uid': uid, + 'anchor_id': anchorId, + 'web_location': 444.8, + 'csrf': Accounts.heartbeat.csrf, + }), + options: Options(contentType: Headers.formUrlEncodedContentType), + ); + if (res.data['code'] == 0) { + return {'status': true}; + } else { + return {'status': false, 'msg': res.data['message']}; + } + } } diff --git a/lib/pages/live_room/controller.dart b/lib/pages/live_room/controller.dart index 4265870e..7b0e25aa 100644 --- a/lib/pages/live_room/controller.dart +++ b/lib/pages/live_room/controller.dart @@ -24,6 +24,7 @@ import 'package:canvas_danmaku/canvas_danmaku.dart'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; +import 'package:flutter_smart_dialog/flutter_smart_dialog.dart'; import 'package:get/get.dart'; class LiveRoomController extends GetxController { @@ -256,6 +257,7 @@ class LiveRoomController extends GetxController { @override void onClose() { + cancelLikeTimer(); cancelLiveTimer(); savedDanmaku?.clear(); savedDanmaku = null; @@ -330,4 +332,43 @@ class LiveRoomController extends GetxController { }) ..init(); } + + final RxInt likeClickTime = 0.obs; + Timer? likeClickTimer; + + void cancelLikeTimer() { + likeClickTimer?.cancel(); + likeClickTimer = null; + } + + void onLikeTapDown([_]) { + cancelLikeTimer(); + likeClickTime.value++; + } + + void onLikeTapUp([_]) { + likeClickTimer ??= Timer( + const Duration(milliseconds: 800), + onLike, + ); + } + + Future onLike() async { + if (!Accounts.heartbeat.isLogin) { + likeClickTime.value = 0; + return; + } + var res = await LiveHttp.liveLikeReport( + clickTime: likeClickTime.value, + roomId: roomId, + uid: accountService.mid, + anchorId: roomInfoH5.value?.roomInfo?.uid, + ); + if (res['status']) { + SmartDialog.showToast('点赞成功'); + } else { + SmartDialog.showToast(res['msg']); + } + likeClickTime.value = 0; + } } diff --git a/lib/pages/live_room/view.dart b/lib/pages/live_room/view.dart index 1b307521..20346b4c 100644 --- a/lib/pages/live_room/view.dart +++ b/lib/pages/live_room/view.dart @@ -578,6 +578,60 @@ class _LiveRoomPageState extends State style: TextStyle(color: _color), ), ), + Builder( + builder: (context) { + final theme = Theme.of(context).colorScheme; + return Material( + type: MaterialType.transparency, + child: Stack( + clipBehavior: Clip.none, + children: [ + InkWell( + overlayColor: overlayColor(theme), + customBorder: const CircleBorder(), + onTapDown: _liveRoomController.onLikeTapDown, + onTapUp: _liveRoomController.onLikeTapUp, + onTapCancel: _liveRoomController.onLikeTapUp, + child: Padding( + padding: const EdgeInsets.all(8.0), + child: Icon(Icons.thumb_up_off_alt, color: _color), + ), + ), + Positioned( + right: -12, + top: -12, + child: Obx(() { + final likeClickTime = + _liveRoomController.likeClickTime.value; + if (likeClickTime == 0) { + return const SizedBox.shrink(); + } + return AnimatedSwitcher( + duration: const Duration(milliseconds: 160), + transitionBuilder: (child, animation) { + return ScaleTransition( + scale: animation, + child: child, + ); + }, + child: Text( + key: ValueKey(likeClickTime), + 'x$likeClickTime', + style: TextStyle( + fontSize: 16, + color: theme.brightness.isDark + ? theme.primary + : theme.inversePrimary, + ), + ), + ); + }), + ), + ], + ), + ); + }, + ), IconButton( onPressed: () => onSendDanmaku(true), icon: Icon(Icons.emoji_emotions_outlined, color: _color), @@ -588,8 +642,33 @@ class _LiveRoomPageState extends State ), ); + WidgetStateProperty? overlayColor(ColorScheme theme) => + WidgetStateProperty.resolveWith((Set states) { + if (states.contains(WidgetState.selected)) { + if (states.contains(WidgetState.pressed)) { + return theme.primary.withValues(alpha: 0.1); + } + if (states.contains(WidgetState.hovered)) { + return theme.primary.withValues(alpha: 0.08); + } + if (states.contains(WidgetState.focused)) { + return theme.primary.withValues(alpha: 0.1); + } + } + if (states.contains(WidgetState.pressed)) { + return theme.onSurfaceVariant.withValues(alpha: 0.1); + } + if (states.contains(WidgetState.hovered)) { + return theme.onSurfaceVariant.withValues(alpha: 0.08); + } + if (states.contains(WidgetState.focused)) { + return theme.onSurfaceVariant.withValues(alpha: 0.1); + } + return Colors.transparent; + }); + void onSendDanmaku([bool fromEmote = false]) { - if (!_liveRoomController.accountService.isLogin.value) { + if (!_liveRoomController.isLogin) { SmartDialog.showToast('账号未登录'); return; } diff --git a/lib/pages/live_room/widgets/bottom_control.dart b/lib/pages/live_room/widgets/bottom_control.dart index db207fa4..803626e5 100644 --- a/lib/pages/live_room/widgets/bottom_control.dart +++ b/lib/pages/live_room/widgets/bottom_control.dart @@ -54,7 +54,7 @@ class BottomControl extends StatelessWidget { padding: WidgetStateProperty.all(EdgeInsets.zero), ), onPressed: () { - if (liveRoomCtr.accountService.isLogin.value) { + if (liveRoomCtr.isLogin) { Get.toNamed( '/liveDmBlockPage', parameters: { diff --git a/lib/utils/accounts/account_manager/account_mgr.dart b/lib/utils/accounts/account_manager/account_mgr.dart index e39e487b..75a38b16 100644 --- a/lib/utils/accounts/account_manager/account_mgr.dart +++ b/lib/utils/accounts/account_manager/account_mgr.dart @@ -29,6 +29,7 @@ class AccountManager extends Interceptor { Api.heartBeat, Api.historyReport, Api.roomEntryAction, + Api.liveLikeReport, // Api.historyList, // Api.pauseHistory, // Api.clearHistory,