From 6e1ceb1277ef20f981d840221ed00c5d769ef7c5 Mon Sep 17 00:00:00 2001 From: My-Responsitories <107370289+My-Responsitories@users.noreply.github.com> Date: Sun, 19 Oct 2025 13:45:29 +0800 Subject: [PATCH] feat: like count (#1640) --- lib/common/widgets/button/icon_button.dart | 2 +- .../bilibili/community/service/dm/v1.pb.dart | 82 ++++++++----------- .../community/service/dm/v1.pbjson.dart | 53 +++--------- lib/pages/danmaku/dnamaku_model.dart | 9 +- lib/pages/danmaku/view.dart | 11 ++- lib/pages/video/widgets/header_control.dart | 49 ++++++++--- lib/plugin/pl_player/view.dart | 57 +++++++++---- 7 files changed, 139 insertions(+), 124 deletions(-) diff --git a/lib/common/widgets/button/icon_button.dart b/lib/common/widgets/button/icon_button.dart index e3778a0c..5f2c421b 100644 --- a/lib/common/widgets/button/icon_button.dart +++ b/lib/common/widgets/button/icon_button.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; Widget iconButton({ BuildContext? context, String? tooltip, - required Icon icon, + required Widget icon, required VoidCallback? onPressed, double size = 36, double? iconSize, diff --git a/lib/grpc/bilibili/community/service/dm/v1.pb.dart b/lib/grpc/bilibili/community/service/dm/v1.pb.dart index c7f8d494..a2da055c 100644 --- a/lib/grpc/bilibili/community/service/dm/v1.pb.dart +++ b/lib/grpc/bilibili/community/service/dm/v1.pb.dart @@ -1240,6 +1240,7 @@ class DanmakuElem extends $pb.GeneratedMessage { $core.int? pool, $core.String? idStr, $core.int? attr, + $fixnum.Int64? like, $core.String? animation, $core.String? extra, DmColorfulType? colorful, @@ -1248,8 +1249,6 @@ class DanmakuElem extends $pb.GeneratedMessage { DmFromType? dmFrom, $core.int? count, $core.bool? isSelf, - $core.int? filterVer, - $core.bool? shouldRemove, }) { final result = create(); if (id != null) result.id = id; @@ -1265,6 +1264,7 @@ class DanmakuElem extends $pb.GeneratedMessage { if (pool != null) result.pool = pool; if (idStr != null) result.idStr = idStr; if (attr != null) result.attr = attr; + if (like != null) result.like = like; if (animation != null) result.animation = animation; if (extra != null) result.extra = extra; if (colorful != null) result.colorful = colorful; @@ -1273,8 +1273,6 @@ class DanmakuElem extends $pb.GeneratedMessage { if (dmFrom != null) result.dmFrom = dmFrom; if (count != null) result.count = count; if (isSelf != null) result.isSelf = isSelf; - if (filterVer != null) result.filterVer = filterVer; - if (shouldRemove != null) result.shouldRemove = shouldRemove; return result; } @@ -1305,6 +1303,7 @@ class DanmakuElem extends $pb.GeneratedMessage { ..aI(11, _omitFieldNames ? '' : 'pool') ..aOS(12, _omitFieldNames ? '' : 'idStr') ..aI(13, _omitFieldNames ? '' : 'attr') + ..aInt64(15, _omitFieldNames ? '' : 'like') ..aOS(22, _omitFieldNames ? '' : 'animation') ..aOS(23, _omitFieldNames ? '' : 'extra') ..aE(24, _omitFieldNames ? '' : 'colorful', @@ -1314,9 +1313,7 @@ class DanmakuElem extends $pb.GeneratedMessage { ..aE(27, _omitFieldNames ? '' : 'dmFrom', enumValues: DmFromType.values) ..aI(28, _omitFieldNames ? '' : 'count') - ..aOB(29, _omitFieldNames ? '' : 'isSelf', protoName: 'isSelf') - ..aI(30, _omitFieldNames ? '' : 'filterVer', protoName: 'filterVer') - ..aOB(31, _omitFieldNames ? '' : 'shouldRemove', protoName: 'shouldRemove') + ..aOB(29, _omitFieldNames ? '' : 'isSelf') ..hasRequiredFields = false; @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') @@ -1456,95 +1453,86 @@ class DanmakuElem extends $pb.GeneratedMessage { @$pb.TagNumber(13) void clearAttr() => $_clearField(13); + @$pb.TagNumber(15) + $fixnum.Int64 get like => $_getI64(13); + @$pb.TagNumber(15) + set like($fixnum.Int64 value) => $_setInt64(13, value); + @$pb.TagNumber(15) + $core.bool hasLike() => $_has(13); + @$pb.TagNumber(15) + void clearLike() => $_clearField(15); + @$pb.TagNumber(22) - $core.String get animation => $_getSZ(13); + $core.String get animation => $_getSZ(14); @$pb.TagNumber(22) - set animation($core.String value) => $_setString(13, value); + set animation($core.String value) => $_setString(14, value); @$pb.TagNumber(22) - $core.bool hasAnimation() => $_has(13); + $core.bool hasAnimation() => $_has(14); @$pb.TagNumber(22) void clearAnimation() => $_clearField(22); @$pb.TagNumber(23) - $core.String get extra => $_getSZ(14); + $core.String get extra => $_getSZ(15); @$pb.TagNumber(23) - set extra($core.String value) => $_setString(14, value); + set extra($core.String value) => $_setString(15, value); @$pb.TagNumber(23) - $core.bool hasExtra() => $_has(14); + $core.bool hasExtra() => $_has(15); @$pb.TagNumber(23) void clearExtra() => $_clearField(23); @$pb.TagNumber(24) - DmColorfulType get colorful => $_getN(15); + DmColorfulType get colorful => $_getN(16); @$pb.TagNumber(24) set colorful(DmColorfulType value) => $_setField(24, value); @$pb.TagNumber(24) - $core.bool hasColorful() => $_has(15); + $core.bool hasColorful() => $_has(16); @$pb.TagNumber(24) void clearColorful() => $_clearField(24); @$pb.TagNumber(25) - $core.int get type => $_getIZ(16); + $core.int get type => $_getIZ(17); @$pb.TagNumber(25) - set type($core.int value) => $_setSignedInt32(16, value); + set type($core.int value) => $_setSignedInt32(17, value); @$pb.TagNumber(25) - $core.bool hasType() => $_has(16); + $core.bool hasType() => $_has(17); @$pb.TagNumber(25) void clearType() => $_clearField(25); @$pb.TagNumber(26) - $fixnum.Int64 get oid => $_getI64(17); + $fixnum.Int64 get oid => $_getI64(18); @$pb.TagNumber(26) - set oid($fixnum.Int64 value) => $_setInt64(17, value); + set oid($fixnum.Int64 value) => $_setInt64(18, value); @$pb.TagNumber(26) - $core.bool hasOid() => $_has(17); + $core.bool hasOid() => $_has(18); @$pb.TagNumber(26) void clearOid() => $_clearField(26); @$pb.TagNumber(27) - DmFromType get dmFrom => $_getN(18); + DmFromType get dmFrom => $_getN(19); @$pb.TagNumber(27) set dmFrom(DmFromType value) => $_setField(27, value); @$pb.TagNumber(27) - $core.bool hasDmFrom() => $_has(18); + $core.bool hasDmFrom() => $_has(19); @$pb.TagNumber(27) void clearDmFrom() => $_clearField(27); @$pb.TagNumber(28) - $core.int get count => $_getIZ(19); + $core.int get count => $_getIZ(20); @$pb.TagNumber(28) - set count($core.int value) => $_setSignedInt32(19, value); + set count($core.int value) => $_setSignedInt32(20, value); @$pb.TagNumber(28) - $core.bool hasCount() => $_has(19); + $core.bool hasCount() => $_has(20); @$pb.TagNumber(28) void clearCount() => $_clearField(28); @$pb.TagNumber(29) - $core.bool get isSelf => $_getBF(20); + $core.bool get isSelf => $_getBF(21); @$pb.TagNumber(29) - set isSelf($core.bool value) => $_setBool(20, value); + set isSelf($core.bool value) => $_setBool(21, value); @$pb.TagNumber(29) - $core.bool hasIsSelf() => $_has(20); + $core.bool hasIsSelf() => $_has(21); @$pb.TagNumber(29) void clearIsSelf() => $_clearField(29); - - @$pb.TagNumber(30) - $core.int get filterVer => $_getIZ(21); - @$pb.TagNumber(30) - set filterVer($core.int value) => $_setSignedInt32(21, value); - @$pb.TagNumber(30) - $core.bool hasFilterVer() => $_has(21); - @$pb.TagNumber(30) - void clearFilterVer() => $_clearField(30); - - @$pb.TagNumber(31) - $core.bool get shouldRemove => $_getBF(22); - @$pb.TagNumber(31) - set shouldRemove($core.bool value) => $_setBool(22, value); - @$pb.TagNumber(31) - $core.bool hasShouldRemove() => $_has(22); - @$pb.TagNumber(31) - void clearShouldRemove() => $_clearField(31); } class DanmakuFlag extends $pb.GeneratedMessage { diff --git a/lib/grpc/bilibili/community/service/dm/v1.pbjson.dart b/lib/grpc/bilibili/community/service/dm/v1.pbjson.dart index 68dccf0f..20f3f29b 100644 --- a/lib/grpc/bilibili/community/service/dm/v1.pbjson.dart +++ b/lib/grpc/bilibili/community/service/dm/v1.pbjson.dart @@ -623,6 +623,7 @@ const DanmakuElem$json = { {'1': 'pool', '3': 11, '4': 1, '5': 5, '10': 'pool'}, {'1': 'id_str', '3': 12, '4': 1, '5': 9, '10': 'idStr'}, {'1': 'attr', '3': 13, '4': 1, '5': 5, '10': 'attr'}, + {'1': 'like', '3': 15, '4': 1, '5': 3, '10': 'like'}, {'1': 'animation', '3': 22, '4': 1, '5': 9, '10': 'animation'}, {'1': 'extra', '3': 23, '4': 1, '5': 9, '10': 'extra'}, { @@ -643,40 +644,8 @@ const DanmakuElem$json = { '6': '.bilibili.community.service.dm.v1.DmFromType', '10': 'dmFrom' }, - {'1': 'count', '3': 28, '4': 1, '5': 5, '9': 0, '10': 'count', '17': true}, - { - '1': 'isSelf', - '3': 29, - '4': 1, - '5': 8, - '9': 1, - '10': 'isSelf', - '17': true - }, - { - '1': 'filterVer', - '3': 30, - '4': 1, - '5': 5, - '9': 2, - '10': 'filterVer', - '17': true - }, - { - '1': 'shouldRemove', - '3': 31, - '4': 1, - '5': 8, - '9': 3, - '10': 'shouldRemove', - '17': true - }, - ], - '8': [ - {'1': '_count'}, - {'1': '_isSelf'}, - {'1': '_filterVer'}, - {'1': '_shouldRemove'}, + {'1': 'count', '3': 28, '4': 1, '5': 5, '10': 'count'}, + {'1': 'is_self', '3': 29, '4': 1, '5': 8, '10': 'isSelf'}, ], }; @@ -687,15 +656,13 @@ final $typed_data.Uint8List danmakuElemDescriptor = $convert.base64Decode( 'bG9yGAUgASgNUgVjb2xvchIZCghtaWRfaGFzaBgGIAEoCVIHbWlkSGFzaBIYCgdjb250ZW50GA' 'cgASgJUgdjb250ZW50EhQKBWN0aW1lGAggASgDUgVjdGltZRIWCgZ3ZWlnaHQYCSABKAVSBndl' 'aWdodBIWCgZhY3Rpb24YCiABKAlSBmFjdGlvbhISCgRwb29sGAsgASgFUgRwb29sEhUKBmlkX3' - 'N0chgMIAEoCVIFaWRTdHISEgoEYXR0chgNIAEoBVIEYXR0chIcCglhbmltYXRpb24YFiABKAlS' - 'CWFuaW1hdGlvbhIUCgVleHRyYRgXIAEoCVIFZXh0cmESTAoIY29sb3JmdWwYGCABKA4yMC5iaW' - 'xpYmlsaS5jb21tdW5pdHkuc2VydmljZS5kbS52MS5EbUNvbG9yZnVsVHlwZVIIY29sb3JmdWwS' - 'EgoEdHlwZRgZIAEoBVIEdHlwZRIQCgNvaWQYGiABKANSA29pZBJFCgdkbV9mcm9tGBsgASgOMi' - 'wuYmlsaWJpbGkuY29tbXVuaXR5LnNlcnZpY2UuZG0udjEuRG1Gcm9tVHlwZVIGZG1Gcm9tEhkK' - 'BWNvdW50GBwgASgFSABSBWNvdW50iAEBEhsKBmlzU2VsZhgdIAEoCEgBUgZpc1NlbGaIAQESIQ' - 'oJZmlsdGVyVmVyGB4gASgFSAJSCWZpbHRlclZlcogBARInCgxzaG91bGRSZW1vdmUYHyABKAhI' - 'A1IMc2hvdWxkUmVtb3ZliAEBQggKBl9jb3VudEIJCgdfaXNTZWxmQgwKCl9maWx0ZXJWZXJCDw' - 'oNX3Nob3VsZFJlbW92ZQ=='); + 'N0chgMIAEoCVIFaWRTdHISEgoEYXR0chgNIAEoBVIEYXR0chISCgRsaWtlGA8gASgDUgRsaWtl' + 'EhwKCWFuaW1hdGlvbhgWIAEoCVIJYW5pbWF0aW9uEhQKBWV4dHJhGBcgASgJUgVleHRyYRJMCg' + 'hjb2xvcmZ1bBgYIAEoDjIwLmJpbGliaWxpLmNvbW11bml0eS5zZXJ2aWNlLmRtLnYxLkRtQ29s' + 'b3JmdWxUeXBlUghjb2xvcmZ1bBISCgR0eXBlGBkgASgFUgR0eXBlEhAKA29pZBgaIAEoA1IDb2' + 'lkEkUKB2RtX2Zyb20YGyABKA4yLC5iaWxpYmlsaS5jb21tdW5pdHkuc2VydmljZS5kbS52MS5E' + 'bUZyb21UeXBlUgZkbUZyb20SFAoFY291bnQYHCABKAVSBWNvdW50EhcKB2lzX3NlbGYYHSABKA' + 'hSBmlzU2VsZg=='); @$core.Deprecated('Use danmakuFlagDescriptor instead') const DanmakuFlag$json = { diff --git a/lib/pages/danmaku/dnamaku_model.dart b/lib/pages/danmaku/dnamaku_model.dart index 80f3ec46..72009e23 100644 --- a/lib/pages/danmaku/dnamaku_model.dart +++ b/lib/pages/danmaku/dnamaku_model.dart @@ -11,9 +11,16 @@ class VideoDanmaku extends DanmakuExtra { @override final String mid; + int like; + bool isLike; - VideoDanmaku({required this.id, required this.mid, this.isLike = false}); + VideoDanmaku({ + required this.id, + required this.mid, + this.like = 0, + this.isLike = false, + }); } class LiveDanmaku extends DanmakuExtra { diff --git a/lib/pages/danmaku/view.dart b/lib/pages/danmaku/view.dart index 2d8b98ac..3e060112 100644 --- a/lib/pages/danmaku/view.dart +++ b/lib/pages/danmaku/view.dart @@ -115,6 +115,11 @@ class _PlDanmakuState extends State { DmUtils.decimalToColor(e.color), e.fontsize.toDouble(), jsonDecode(e.content.replaceAll('\n', '\\n')), + extra: VideoDanmaku( + id: e.id.toInt(), + mid: e.midHash, + like: e.like.toInt(), + ), ), ); } catch (_) {} @@ -131,7 +136,11 @@ class _PlDanmakuState extends State { e.colorful == DmColorfulType.VipGradualColor, count: e.hasCount() ? e.count : null, selfSend: e.isSelf, - extra: VideoDanmaku(id: e.id.toInt(), mid: e.midHash), + extra: VideoDanmaku( + id: e.id.toInt(), + mid: e.midHash, + like: e.like.toInt(), + ), ), ); } diff --git a/lib/pages/video/widgets/header_control.dart b/lib/pages/video/widgets/header_control.dart index 03d4572b..4bb985c5 100644 --- a/lib/pages/video/widgets/header_control.dart +++ b/lib/pages/video/widgets/header_control.dart @@ -83,6 +83,11 @@ class HeaderControl extends StatefulWidget { ); if (res.isSuccess) { extra.isLike = isLike; + if (isLike) { + extra.like++; + } else { + extra.like--; + } SmartDialog.showToast('${isLike ? '' : '取消'}点赞成功'); return true; } else { @@ -2059,19 +2064,37 @@ class HeaderControlState extends State { mainAxisSize: MainAxisSize.min, children: [ Builder( - builder: (context) => iconButton( - onPressed: () async { - if (await HeaderControl.likeDanmaku( - extra, - plPlayerController.cid!, - ) && - context.mounted) { - (context as Element).markNeedsBuild(); - } - }, - icon: extra.isLike - ? const Icon(Icons.thumb_up_off_alt_sharp) - : const Icon(Icons.thumb_up_off_alt_outlined), + builder: (context) => Stack( + clipBehavior: Clip.none, + children: [ + iconButton( + onPressed: () async { + if (await HeaderControl.likeDanmaku( + extra, + plPlayerController.cid!, + ) && + context.mounted) { + (context as Element).markNeedsBuild(); + } + }, + icon: extra.isLike + ? const Icon(Icons.thumb_up_off_alt_sharp) + : const Icon(Icons.thumb_up_off_alt_outlined), + ), + if (extra.like > 0) + Positioned( + left: 24.5, + top: 1.5, + child: Text( + extra.like.toString(), + style: const TextStyle( + fontSize: 10.5, + letterSpacing: 0, + // fontWeight: FontWeight.bold, + ), + ), + ), + ], ), ), if (item.content.selfSend) diff --git a/lib/plugin/pl_player/view.dart b/lib/plugin/pl_player/view.dart index 68599420..965da5e8 100644 --- a/lib/plugin/pl_player/view.dart +++ b/lib/plugin/pl_player/view.dart @@ -2210,12 +2210,15 @@ class _PLVideoPlayerState extends State _refreshDmCallback?.call(); } - Widget _dmActionItem(Widget child, {required VoidCallback onTap}) { + Widget _dmActionItem( + Widget child, { + required FutureOr Function() onTap, + }) { return GestureDetector( behavior: HitTestBehavior.opaque, - onTap: () { + onTap: () async { + await onTap(); _removeDmAction(); - onTap(); }, child: SizedBox( width: _actionItemWidth, @@ -2289,23 +2292,41 @@ class _PLVideoPlayerState extends State children: switch (extra) { null => throw UnimplementedError(), VideoDanmaku() => [ - _dmActionItem( - extra.isLike - ? const Icon( - size: 20, - CustomIcons.player_dm_tip_like_solid, - color: Colors.white, - ) - : const Icon( - size: 20, - CustomIcons.player_dm_tip_like, - color: Colors.white, + Stack( + clipBehavior: Clip.none, + children: [ + _dmActionItem( + extra.isLike + ? const Icon( + size: 20, + CustomIcons.player_dm_tip_like_solid, + color: Colors.white, + ) + : const Icon( + size: 20, + CustomIcons.player_dm_tip_like, + color: Colors.white, + ), + onTap: () => HeaderControl.likeDanmaku( + extra, + plPlayerController.cid!, + ), + ), + if (extra.like > 0) + Positioned( + left: _actionItemWidth - 10.5, + top: 0, + child: Text( + extra.like.toString(), + style: const TextStyle( + fontSize: 10.5, + color: Colors.white, + ), ), - onTap: () => HeaderControl.likeDanmaku( - extra, - plPlayerController.cid!, - ), + ), + ], ), + _dmActionItem( const Icon( size: 19,