diff --git a/lib/grpc/bilibili/main/community/reply/v1.pb.dart b/lib/grpc/bilibili/main/community/reply/v1.pb.dart index 713498cf6..e313115dd 100644 --- a/lib/grpc/bilibili/main/community/reply/v1.pb.dart +++ b/lib/grpc/bilibili/main/community/reply/v1.pb.dart @@ -8159,6 +8159,8 @@ class ReplyControl extends $pb.GeneratedMessage { ReplyControl_EasterEggLabel? easterEggLabel, $core.String? contextFeature, ReplyControl_InsertEffect? insertEffect, + $core.int? translationSwitch, + $core.bool? showTranslation, }) { final result = create(); if (action != null) result.action = action; @@ -8198,6 +8200,8 @@ class ReplyControl extends $pb.GeneratedMessage { if (easterEggLabel != null) result.easterEggLabel = easterEggLabel; if (contextFeature != null) result.contextFeature = contextFeature; if (insertEffect != null) result.insertEffect = insertEffect; + if (translationSwitch != null) result.translationSwitch = translationSwitch; + if (showTranslation != null) result.showTranslation = showTranslation; return result; } @@ -8257,6 +8261,8 @@ class ReplyControl extends $pb.GeneratedMessage { ..aOS(35, _omitFieldNames ? '' : 'contextFeature') ..aOM(36, _omitFieldNames ? '' : 'insertEffect', subBuilder: ReplyControl_InsertEffect.create) + ..aI(37, _omitFieldNames ? '' : 'translationSwitch') + ..aOB(100, _omitFieldNames ? '' : 'showTranslation') ..hasRequiredFields = false; @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') @@ -8604,6 +8610,25 @@ class ReplyControl extends $pb.GeneratedMessage { void clearInsertEffect() => $_clearField(36); @$pb.TagNumber(36) ReplyControl_InsertEffect ensureInsertEffect() => $_ensure(35); + + @$pb.TagNumber(37) + $core.int get translationSwitch => $_getIZ(36); + @$pb.TagNumber(37) + set translationSwitch($core.int value) => $_setSignedInt32(36, value); + @$pb.TagNumber(37) + $core.bool hasTranslationSwitch() => $_has(36); + @$pb.TagNumber(37) + void clearTranslationSwitch() => $_clearField(37); + + /// extra field + @$pb.TagNumber(100) + $core.bool get showTranslation => $_getBF(37); + @$pb.TagNumber(100) + set showTranslation($core.bool value) => $_setBool(37, value); + @$pb.TagNumber(100) + $core.bool hasShowTranslation() => $_has(37); + @$pb.TagNumber(100) + void clearShowTranslation() => $_clearField(100); } class ReplyExtra extends $pb.GeneratedMessage { @@ -9494,6 +9519,7 @@ class ReplyInfo extends $pb.GeneratedMessage { ReplyControl? replyControl, MemberV2? memberV2, $core.String? trackInfo, + Content? translatedContent, }) { final result = create(); if (replies != null) result.replies.addAll(replies); @@ -9512,6 +9538,7 @@ class ReplyInfo extends $pb.GeneratedMessage { if (replyControl != null) result.replyControl = replyControl; if (memberV2 != null) result.memberV2 = memberV2; if (trackInfo != null) result.trackInfo = trackInfo; + if (translatedContent != null) result.translatedContent = translatedContent; return result; } @@ -9550,6 +9577,8 @@ class ReplyInfo extends $pb.GeneratedMessage { ..aOM(15, _omitFieldNames ? '' : 'memberV2', subBuilder: MemberV2.create) ..aOS(16, _omitFieldNames ? '' : 'trackInfo') + ..aOM(17, _omitFieldNames ? '' : 'translatedContent', + subBuilder: Content.create) ..hasRequiredFields = false; @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') @@ -9715,6 +9744,17 @@ class ReplyInfo extends $pb.GeneratedMessage { $core.bool hasTrackInfo() => $_has(15); @$pb.TagNumber(16) void clearTrackInfo() => $_clearField(16); + + @$pb.TagNumber(17) + Content get translatedContent => $_getN(16); + @$pb.TagNumber(17) + set translatedContent(Content value) => $_setField(17, value); + @$pb.TagNumber(17) + $core.bool hasTranslatedContent() => $_has(16); + @$pb.TagNumber(17) + void clearTranslatedContent() => $_clearField(17); + @$pb.TagNumber(17) + Content ensureTranslatedContent() => $_ensure(16); } class ReplyInfoReply extends $pb.GeneratedMessage { @@ -13976,6 +14016,135 @@ class WordSearchParam extends $pb.GeneratedMessage { void clearShownCount() => $_clearField(1); } +class TranslateReplyReq extends $pb.GeneratedMessage { + factory TranslateReplyReq({ + $fixnum.Int64? type, + $fixnum.Int64? oid, + $core.Iterable<$fixnum.Int64>? rpid, + }) { + final result = create(); + if (type != null) result.type = type; + if (oid != null) result.oid = oid; + if (rpid != null) result.rpid.addAll(rpid); + return result; + } + + TranslateReplyReq._(); + + factory TranslateReplyReq.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory TranslateReplyReq.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'TranslateReplyReq', + package: const $pb.PackageName( + _omitMessageNames ? '' : 'bilibili.main.community.reply.v1'), + createEmptyInstance: create) + ..aInt64(1, _omitFieldNames ? '' : 'type') + ..aInt64(2, _omitFieldNames ? '' : 'oid') + ..p<$fixnum.Int64>(3, _omitFieldNames ? '' : 'rpid', $pb.PbFieldType.K6) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + TranslateReplyReq clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + TranslateReplyReq copyWith(void Function(TranslateReplyReq) updates) => + super.copyWith((message) => updates(message as TranslateReplyReq)) + as TranslateReplyReq; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static TranslateReplyReq create() => TranslateReplyReq._(); + @$core.override + TranslateReplyReq createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static TranslateReplyReq getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static TranslateReplyReq? _defaultInstance; + + @$pb.TagNumber(1) + $fixnum.Int64 get type => $_getI64(0); + @$pb.TagNumber(1) + set type($fixnum.Int64 value) => $_setInt64(0, value); + @$pb.TagNumber(1) + $core.bool hasType() => $_has(0); + @$pb.TagNumber(1) + void clearType() => $_clearField(1); + + @$pb.TagNumber(2) + $fixnum.Int64 get oid => $_getI64(1); + @$pb.TagNumber(2) + set oid($fixnum.Int64 value) => $_setInt64(1, value); + @$pb.TagNumber(2) + $core.bool hasOid() => $_has(1); + @$pb.TagNumber(2) + void clearOid() => $_clearField(2); + + @$pb.TagNumber(3) + $pb.PbList<$fixnum.Int64> get rpid => $_getList(2); +} + +class TranslateReplyResp extends $pb.GeneratedMessage { + factory TranslateReplyResp({ + $core.Iterable<$core.MapEntry<$fixnum.Int64, ReplyInfo>>? translatedReply, + }) { + final result = create(); + if (translatedReply != null) + result.translatedReply.addEntries(translatedReply); + return result; + } + + TranslateReplyResp._(); + + factory TranslateReplyResp.fromBuffer($core.List<$core.int> data, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromBuffer(data, registry); + factory TranslateReplyResp.fromJson($core.String json, + [$pb.ExtensionRegistry registry = $pb.ExtensionRegistry.EMPTY]) => + create()..mergeFromJson(json, registry); + + static final $pb.BuilderInfo _i = $pb.BuilderInfo( + _omitMessageNames ? '' : 'TranslateReplyResp', + package: const $pb.PackageName( + _omitMessageNames ? '' : 'bilibili.main.community.reply.v1'), + createEmptyInstance: create) + ..m<$fixnum.Int64, ReplyInfo>(1, _omitFieldNames ? '' : 'translatedReply', + entryClassName: 'TranslateReplyResp.TranslatedReplyEntry', + keyFieldType: $pb.PbFieldType.O6, + valueFieldType: $pb.PbFieldType.OM, + valueCreator: ReplyInfo.create, + valueDefaultOrMaker: ReplyInfo.getDefault, + packageName: const $pb.PackageName('bilibili.main.community.reply.v1')) + ..hasRequiredFields = false; + + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + TranslateReplyResp clone() => deepCopy(); + @$core.Deprecated('See https://github.com/google/protobuf.dart/issues/998.') + TranslateReplyResp copyWith(void Function(TranslateReplyResp) updates) => + super.copyWith((message) => updates(message as TranslateReplyResp)) + as TranslateReplyResp; + + @$core.override + $pb.BuilderInfo get info_ => _i; + + @$core.pragma('dart2js:noInline') + static TranslateReplyResp create() => TranslateReplyResp._(); + @$core.override + TranslateReplyResp createEmptyInstance() => create(); + @$core.pragma('dart2js:noInline') + static TranslateReplyResp getDefault() => _defaultInstance ??= + $pb.GeneratedMessage.$_defaultFor(create); + static TranslateReplyResp? _defaultInstance; + + @$pb.TagNumber(1) + $pb.PbMap<$fixnum.Int64, ReplyInfo> get translatedReply => $_getMap(0); +} + const $core.bool _omitFieldNames = $core.bool.fromEnvironment('protobuf.omit_field_names'); const $core.bool _omitMessageNames = diff --git a/lib/grpc/bilibili/main/community/reply/v1.pbjson.dart b/lib/grpc/bilibili/main/community/reply/v1.pbjson.dart index 2391b32bf..ee6b2dd07 100644 --- a/lib/grpc/bilibili/main/community/reply/v1.pbjson.dart +++ b/lib/grpc/bilibili/main/community/reply/v1.pbjson.dart @@ -2857,6 +2857,20 @@ const ReplyControl$json = { '6': '.bilibili.main.community.reply.v1.ReplyControl.InsertEffect', '10': 'insertEffect' }, + { + '1': 'translation_switch', + '3': 37, + '4': 1, + '5': 5, + '10': 'translationSwitch' + }, + { + '1': 'show_translation', + '3': 100, + '4': 1, + '5': 8, + '10': 'showTranslation' + }, ], '3': [ ReplyControl_EasterEggLabel$json, @@ -2977,18 +2991,20 @@ final $typed_data.Uint8List replyControlDescriptor = $convert.base64Decode( 'aWxpLm1haW4uY29tbXVuaXR5LnJlcGx5LnYxLlJlcGx5Q29udHJvbC5FYXN0ZXJFZ2dMYWJlbF' 'IOZWFzdGVyRWdnTGFiZWwSJwoPY29udGV4dF9mZWF0dXJlGCMgASgJUg5jb250ZXh0RmVhdHVy' 'ZRJgCg1pbnNlcnRfZWZmZWN0GCQgASgLMjsuYmlsaWJpbGkubWFpbi5jb21tdW5pdHkucmVwbH' - 'kudjEuUmVwbHlDb250cm9sLkluc2VydEVmZmVjdFIMaW5zZXJ0RWZmZWN0GkEKDkVhc3RlckVn' - 'Z0xhYmVsEhQKBWltYWdlGAEgASgJUgVpbWFnZRIZCghqdW1wX3VybBgCIAEoCVIHanVtcFVybB' - 'rXAQoLR3JhZGVSZWNvcmQSFAoFc2NvcmUYASABKAVSBXNjb3JlElUKBXRleHRzGAIgAygLMj8u' - 'YmlsaWJpbGkubWFpbi5jb21tdW5pdHkucmVwbHkudjEuUmVwbHlDb250cm9sLkdyYWRlUmVjb3' - 'JkLlRleHRSBXRleHRzGlsKBFRleHQSEAoDcmF3GAEgASgJUgNyYXcSQQoFc3R5bGUYAiABKAsy' - 'Ky5iaWxpYmlsaS5tYWluLmNvbW11bml0eS5yZXBseS52MS5UZXh0U3R5bGVSBXN0eWxlGjwKDE' - 'luc2VydEVmZmVjdBIYCgdjb250ZW50GAEgASgJUgdjb250ZW50EhIKBGljb24YAiABKAlSBGlj' - 'b24a8QEKClZvdGVPcHRpb24SYgoKbGFiZWxfa2luZBgBIAEoDjJDLmJpbGliaWxpLm1haW4uY2' - '9tbXVuaXR5LnJlcGx5LnYxLlJlcGx5Q29udHJvbC5Wb3RlT3B0aW9uLkxhYmVsS2luZFIJbGFi' - 'ZWxLaW5kEhIKBGRlc2MYAiABKAlSBGRlc2MSEAoDaWR4GAMgASgDUgNpZHgSFwoHdm90ZV9pZB' - 'gEIAEoA1IGdm90ZUlkIkAKCUxhYmVsS2luZBIVChFERUZBVUxUX0xhYmVsS2luZBAAEgcKA1JF' - 'RBABEggKBEJMVUUQAhIJCgVQTEFJThAD'); + 'kudjEuUmVwbHlDb250cm9sLkluc2VydEVmZmVjdFIMaW5zZXJ0RWZmZWN0Ei0KEnRyYW5zbGF0' + 'aW9uX3N3aXRjaBglIAEoBVIRdHJhbnNsYXRpb25Td2l0Y2gSKQoQc2hvd190cmFuc2xhdGlvbh' + 'hkIAEoCFIPc2hvd1RyYW5zbGF0aW9uGkEKDkVhc3RlckVnZ0xhYmVsEhQKBWltYWdlGAEgASgJ' + 'UgVpbWFnZRIZCghqdW1wX3VybBgCIAEoCVIHanVtcFVybBrXAQoLR3JhZGVSZWNvcmQSFAoFc2' + 'NvcmUYASABKAVSBXNjb3JlElUKBXRleHRzGAIgAygLMj8uYmlsaWJpbGkubWFpbi5jb21tdW5p' + 'dHkucmVwbHkudjEuUmVwbHlDb250cm9sLkdyYWRlUmVjb3JkLlRleHRSBXRleHRzGlsKBFRleH' + 'QSEAoDcmF3GAEgASgJUgNyYXcSQQoFc3R5bGUYAiABKAsyKy5iaWxpYmlsaS5tYWluLmNvbW11' + 'bml0eS5yZXBseS52MS5UZXh0U3R5bGVSBXN0eWxlGjwKDEluc2VydEVmZmVjdBIYCgdjb250ZW' + '50GAEgASgJUgdjb250ZW50EhIKBGljb24YAiABKAlSBGljb24a8QEKClZvdGVPcHRpb24SYgoK' + 'bGFiZWxfa2luZBgBIAEoDjJDLmJpbGliaWxpLm1haW4uY29tbXVuaXR5LnJlcGx5LnYxLlJlcG' + 'x5Q29udHJvbC5Wb3RlT3B0aW9uLkxhYmVsS2luZFIJbGFiZWxLaW5kEhIKBGRlc2MYAiABKAlS' + 'BGRlc2MSEAoDaWR4GAMgASgDUgNpZHgSFwoHdm90ZV9pZBgEIAEoA1IGdm90ZUlkIkAKCUxhYm' + 'VsS2luZBIVChFERUZBVUxUX0xhYmVsS2luZBAAEgcKA1JFRBABEggKBEJMVUUQAhIJCgVQTEFJ' + 'ThAD'); @$core.Deprecated('Use replyExtraDescriptor instead') const ReplyExtra$json = { @@ -3311,6 +3327,14 @@ const ReplyInfo$json = { '10': 'memberV2' }, {'1': 'track_info', '3': 16, '4': 1, '5': 9, '10': 'trackInfo'}, + { + '1': 'translated_content', + '3': 17, + '4': 1, + '5': 11, + '6': '.bilibili.main.community.reply.v1.Content', + '10': 'translatedContent' + }, ], }; @@ -3326,8 +3350,9 @@ final $typed_data.Uint8List replyInfoDescriptor = $convert.base64Decode( 'NvbW11bml0eS5yZXBseS52MS5NZW1iZXJSBm1lbWJlchJTCg1yZXBseV9jb250cm9sGA4gASgL' 'Mi4uYmlsaWJpbGkubWFpbi5jb21tdW5pdHkucmVwbHkudjEuUmVwbHlDb250cm9sUgxyZXBseU' 'NvbnRyb2wSRwoJbWVtYmVyX3YyGA8gASgLMiouYmlsaWJpbGkubWFpbi5jb21tdW5pdHkucmVw' - 'bHkudjEuTWVtYmVyVjJSCG1lbWJlclYyEh0KCnRyYWNrX2luZm8YECABKAlSCXRyYWNrSW5mbw' - '=='); + 'bHkudjEuTWVtYmVyVjJSCG1lbWJlclYyEh0KCnRyYWNrX2luZm8YECABKAlSCXRyYWNrSW5mbx' + 'JYChJ0cmFuc2xhdGVkX2NvbnRlbnQYESABKAsyKS5iaWxpYmlsaS5tYWluLmNvbW11bml0eS5y' + 'ZXBseS52MS5Db250ZW50UhF0cmFuc2xhdGVkQ29udGVudA=='); @$core.Deprecated('Use replyInfoReplyDescriptor instead') const ReplyInfoReply$json = { @@ -4561,3 +4586,60 @@ const WordSearchParam$json = { /// Descriptor for `WordSearchParam`. Decode as a `google.protobuf.DescriptorProto`. final $typed_data.Uint8List wordSearchParamDescriptor = $convert.base64Decode( 'Cg9Xb3JkU2VhcmNoUGFyYW0SHwoLc2hvd25fY291bnQYASABKANSCnNob3duQ291bnQ='); + +@$core.Deprecated('Use translateReplyReqDescriptor instead') +const TranslateReplyReq$json = { + '1': 'TranslateReplyReq', + '2': [ + {'1': 'type', '3': 1, '4': 1, '5': 3, '10': 'type'}, + {'1': 'oid', '3': 2, '4': 1, '5': 3, '10': 'oid'}, + {'1': 'rpid', '3': 3, '4': 3, '5': 3, '10': 'rpid'}, + ], +}; + +/// Descriptor for `TranslateReplyReq`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List translateReplyReqDescriptor = $convert.base64Decode( + 'ChFUcmFuc2xhdGVSZXBseVJlcRISCgR0eXBlGAEgASgDUgR0eXBlEhAKA29pZBgCIAEoA1IDb2' + 'lkEhIKBHJwaWQYAyADKANSBHJwaWQ='); + +@$core.Deprecated('Use translateReplyRespDescriptor instead') +const TranslateReplyResp$json = { + '1': 'TranslateReplyResp', + '2': [ + { + '1': 'translated_reply', + '3': 1, + '4': 3, + '5': 11, + '6': + '.bilibili.main.community.reply.v1.TranslateReplyResp.TranslatedReplyEntry', + '10': 'translatedReply' + }, + ], + '3': [TranslateReplyResp_TranslatedReplyEntry$json], +}; + +@$core.Deprecated('Use translateReplyRespDescriptor instead') +const TranslateReplyResp_TranslatedReplyEntry$json = { + '1': 'TranslatedReplyEntry', + '2': [ + {'1': 'key', '3': 1, '4': 1, '5': 3, '10': 'key'}, + { + '1': 'value', + '3': 2, + '4': 1, + '5': 11, + '6': '.bilibili.main.community.reply.v1.ReplyInfo', + '10': 'value' + }, + ], + '7': {'7': true}, +}; + +/// Descriptor for `TranslateReplyResp`. Decode as a `google.protobuf.DescriptorProto`. +final $typed_data.Uint8List translateReplyRespDescriptor = $convert.base64Decode( + 'ChJUcmFuc2xhdGVSZXBseVJlc3ASdAoQdHJhbnNsYXRlZF9yZXBseRgBIAMoCzJJLmJpbGliaW' + 'xpLm1haW4uY29tbXVuaXR5LnJlcGx5LnYxLlRyYW5zbGF0ZVJlcGx5UmVzcC5UcmFuc2xhdGVk' + 'UmVwbHlFbnRyeVIPdHJhbnNsYXRlZFJlcGx5Gm8KFFRyYW5zbGF0ZWRSZXBseUVudHJ5EhAKA2' + 'tleRgBIAEoA1IDa2V5EkEKBXZhbHVlGAIgASgLMisuYmlsaWJpbGkubWFpbi5jb21tdW5pdHku' + 'cmVwbHkudjEuUmVwbHlJbmZvUgV2YWx1ZToCOAE='); diff --git a/lib/grpc/reply.dart b/lib/grpc/reply.dart index de680c6ef..1d0c7f4d3 100644 --- a/lib/grpc/reply.dart +++ b/lib/grpc/reply.dart @@ -148,4 +148,20 @@ abstract final class ReplyGrpc { SearchItemReply.fromBuffer, ); } + + static Future> translateReply({ + required Int64 type, + required Int64 oid, + required Int64 rpid, + }) { + return GrpcReq.request( + GrpcUrl.translateReply, + TranslateReplyReq( + type: type, + oid: oid, + rpid: [rpid], + ), + TranslateReplyResp.fromBuffer, + ); + } } diff --git a/lib/grpc/url.dart b/lib/grpc/url.dart index 0bbdc6b3b..0769a5f2b 100644 --- a/lib/grpc/url.dart +++ b/lib/grpc/url.dart @@ -22,6 +22,7 @@ abstract final class GrpcUrl { static const dialogList = '$reply/DialogList'; // static const replyInfo = '$reply/ReplyInfo'; static const searchItem = '$reply/SearchItem'; + static const translateReply = '$reply/TranslateReply'; // im static const im = '/bilibili.im.interface.v1.ImInterface'; diff --git a/lib/pages/video/reply/widgets/reply_item_grpc.dart b/lib/pages/video/reply/widgets/reply_item_grpc.dart index da4ec25a4..dd6369f0d 100644 --- a/lib/pages/video/reply/widgets/reply_item_grpc.dart +++ b/lib/pages/video/reply/widgets/reply_item_grpc.dart @@ -13,6 +13,8 @@ import 'package:PiliPlus/common/widgets/image_grid/image_grid_view.dart'; import 'package:PiliPlus/common/widgets/pendant_avatar.dart'; import 'package:PiliPlus/grpc/bilibili/main/community/reply/v1.pb.dart' show ReplyInfo, ReplyControl, Content, Url; +import 'package:PiliPlus/grpc/reply.dart'; +import 'package:PiliPlus/http/loading_state.dart'; import 'package:PiliPlus/http/reply.dart'; import 'package:PiliPlus/http/video.dart'; import 'package:PiliPlus/models/common/badge_type.dart'; @@ -290,6 +292,7 @@ class ReplyItemGrpc extends StatelessWidget { } Widget _buildContent(BuildContext context, ThemeData theme) { + final replyControl = replyItem.replyControl; final padding = EdgeInsets.only(left: replyLevel == 0 ? 6 : 45, right: 6); return Column( mainAxisSize: .min, @@ -308,7 +311,7 @@ class ReplyItemGrpc extends StatelessWidget { maxLines: replyLevel == 1 ? replyLengthLimit : null, TextSpan( children: [ - if (replyItem.replyControl.isUpTop) ...[ + if (replyControl.isUpTop) ...[ const WidgetSpan( alignment: PlaceholderAlignment.middle, child: PBadge( @@ -322,7 +325,14 @@ class ReplyItemGrpc extends StatelessWidget { ), const TextSpan(text: ' '), ], - _buildMessage(context, theme, replyItem), + _buildMessage( + context, + theme, + replyControl.showTranslation + ? replyItem.translatedContent + : replyItem.content, + replyControl, + ), ], ), ), @@ -347,7 +357,7 @@ class ReplyItemGrpc extends StatelessWidget { ], if (replyLevel != 0) ...[ const SizedBox(height: 4), - buttonAction(context, theme, replyItem.replyControl), + buttonAction(context, theme, replyControl), ], if (replyLevel == 1 && replyItem.count > Int64.ZERO) ...[ Padding( @@ -359,20 +369,87 @@ class ReplyItemGrpc extends StatelessWidget { ); } + Widget _buildTranslateBtn( + BuildContext context, + ThemeData theme, + ReplyControl replyControl, + TextStyle textStyle, + ButtonStyle buttonStyle, + ) { + late bool isProcessing = false; + final color = replyControl.showTranslation + ? theme.colorScheme.primary + : theme.colorScheme.outline.withValues(alpha: 0.8); + return SizedBox( + height: 32, + child: TextButton( + style: buttonStyle, + onPressed: () async { + if (replyControl.showTranslation) { + replyControl.showTranslation = false; + (context as Element).markNeedsBuild(); + } else { + if (isProcessing) { + return; + } + if (replyItem.hasTranslatedContent()) { + replyControl.showTranslation = true; + (context as Element).markNeedsBuild(); + return; + } + isProcessing = true; + final res = await ReplyGrpc.translateReply( + type: replyItem.type, + oid: replyItem.oid, + rpid: replyItem.id, + ); + if (res case Success(:final response)) { + final item = response.translatedReply[replyItem.id]; + if (item != null && item.hasTranslatedContent()) { + replyControl.showTranslation = true; + replyItem.translatedContent = item.translatedContent; + if (context.mounted) { + (context as Element).markNeedsBuild(); + } + } else { + SmartDialog.showToast('翻译结果为空'); + } + } else if (res case Error(:final errMsg)) { + SmartDialog.showToast('翻译失败: $errMsg'); + } + isProcessing = false; + } + }, + child: Row( + spacing: 3, + mainAxisSize: .min, + children: [ + Icon(Icons.translate, size: 16, color: color), + Text( + replyControl.showTranslation ? '原文' : '翻译', + style: textStyle.copyWith(color: color), + ), + ], + ), + ), + ); + } + Widget buttonAction( BuildContext context, ThemeData theme, ReplyControl replyControl, ) { final textStyle = TextStyle( - fontSize: theme.textTheme.labelMedium!.fontSize, + height: 1, + fontWeight: .normal, color: theme.colorScheme.outline, - fontWeight: FontWeight.normal, + fontSize: theme.textTheme.labelMedium!.fontSize, ); - final buttonStyle = TextButton.styleFrom( - padding: EdgeInsets.zero, - tapTargetSize: MaterialTapTargetSize.shrinkWrap, - visualDensity: VisualDensity.compact, + const buttonStyle = ButtonStyle( + visualDensity: .compact, + tapTargetSize: .shrinkWrap, + padding: WidgetStatePropertyAll(.zero), ); return Row( children: [ @@ -386,20 +463,30 @@ class ReplyItemGrpc extends StatelessWidget { onReply?.call(replyItem); }, child: Row( + spacing: 3, + mainAxisSize: .min, children: [ Icon( Icons.reply, size: 18, color: theme.colorScheme.outline.withValues(alpha: 0.8), ), - const SizedBox(width: 3), Text('回复', style: textStyle), ], ), ), ), const SizedBox(width: 2), - if (replyItem.replyControl.cardLabels.isNotEmpty) ...[ + if (replyControl.translationSwitch == 2) ...[ + _buildTranslateBtn( + context, + theme, + replyControl, + textStyle, + buttonStyle, + ), + const SizedBox(width: 2), + ] else if (replyItem.replyControl.cardLabels.isNotEmpty) ...[ Text( replyItem.replyControl.cardLabels .map((e) => e.textContent) @@ -536,7 +623,12 @@ class ReplyItemGrpc extends StatelessWidget { ? '' : ' ', ), - _buildMessage(context, theme, childReply), + _buildMessage( + context, + theme, + childReply.content, + childReply.replyControl, + ), ], ), ), @@ -585,9 +677,9 @@ class ReplyItemGrpc extends StatelessWidget { InlineSpan _buildMessage( BuildContext context, ThemeData theme, - ReplyInfo replyItem, + Content content, + ReplyControl replyControl, ) { - final Content content = replyItem.content; final List spanChildren = []; bool hasNote = false; @@ -826,9 +918,7 @@ class ReplyItemGrpc extends StatelessWidget { } } - if (!hasNote && - replyItem.replyControl.isNote && - replyItem.replyControl.isNoteV2) { + if (!hasNote && replyControl.isNote && replyControl.isNoteV2) { final Color color; NoDeadlineTapGestureRecognizer? recognizer;