Message translation

This commit is contained in:
LittleSheep 2025-03-16 23:10:59 +08:00
parent aecd04e0b9
commit d2f4e7a969

View File

@ -10,6 +10,7 @@ import 'package:provider/provider.dart';
import 'package:styled_widget/styled_widget.dart'; import 'package:styled_widget/styled_widget.dart';
import 'package:surface/providers/config.dart'; import 'package:surface/providers/config.dart';
import 'package:surface/providers/keypair.dart'; import 'package:surface/providers/keypair.dart';
import 'package:surface/providers/translation.dart';
import 'package:surface/providers/user_directory.dart'; import 'package:surface/providers/user_directory.dart';
import 'package:surface/providers/userinfo.dart'; import 'package:surface/providers/userinfo.dart';
import 'package:surface/types/chat.dart'; import 'package:surface/types/chat.dart';
@ -18,6 +19,7 @@ import 'package:surface/widgets/account/account_popover.dart';
import 'package:surface/widgets/account/badge.dart'; import 'package:surface/widgets/account/badge.dart';
import 'package:surface/widgets/attachment/attachment_list.dart'; import 'package:surface/widgets/attachment/attachment_list.dart';
import 'package:surface/widgets/context_menu.dart'; import 'package:surface/widgets/context_menu.dart';
import 'package:surface/widgets/dialog.dart';
import 'package:surface/widgets/link_preview.dart'; import 'package:surface/widgets/link_preview.dart';
import 'package:surface/widgets/markdown_content.dart'; import 'package:surface/widgets/markdown_content.dart';
import 'package:flutter_animate/flutter_animate.dart'; import 'package:flutter_animate/flutter_animate.dart';
@ -228,7 +230,7 @@ class ChatMessage extends StatelessWidget {
} }
} }
class _ChatMessageText extends StatelessWidget { class _ChatMessageText extends StatefulWidget {
final SnChatMessage data; final SnChatMessage data;
final Function(SnChatMessage)? onReply; final Function(SnChatMessage)? onReply;
final Function(SnChatMessage)? onEdit; final Function(SnChatMessage)? onEdit;
@ -237,13 +239,38 @@ class _ChatMessageText extends StatelessWidget {
const _ChatMessageText( const _ChatMessageText(
{required this.data, this.onReply, this.onEdit, this.onDelete}); {required this.data, this.onReply, this.onEdit, this.onDelete});
@override
State<_ChatMessageText> createState() => _ChatMessageTextState();
}
class _ChatMessageTextState extends State<_ChatMessageText> {
late String _displayText = widget.data.body['text'] ?? '';
bool _isTranslated = false;
Future<void> _translateText() async {
final ta = context.read<SnTranslator>();
try {
final to = EasyLocalization.of(context)!.locale.languageCode;
_displayText = await ta.translate(
widget.data.body['text'],
to: to,
);
_isTranslated = true;
if (mounted) setState(() {});
} catch (err) {
if (mounted) context.showErrorDialog(err);
}
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final ua = context.read<UserProvider>(); final ua = context.read<UserProvider>();
final isOwner = ua.isAuthorized && data.sender.accountId == ua.user?.id; final isOwner =
ua.isAuthorized && widget.data.sender.accountId == ua.user?.id;
if (data.body['text'] != null && data.body['text'].isNotEmpty) { if (widget.data.body['text'] != null &&
widget.data.body['text'].isNotEmpty) {
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -252,38 +279,50 @@ class _ChatMessageText extends StatelessWidget {
final List<ContextMenuButtonItem> items = final List<ContextMenuButtonItem> items =
editableTextState.contextMenuButtonItems; editableTextState.contextMenuButtonItems;
if (onReply != null) { if (widget.onReply != null) {
items.insert( items.insert(
0, 0,
ContextMenuButtonItem( ContextMenuButtonItem(
label: 'reply'.tr(), label: 'reply'.tr(),
onPressed: () { onPressed: () {
ContextMenuController.removeAny(); ContextMenuController.removeAny();
onReply?.call(data); widget.onReply?.call(widget.data);
}, },
), ),
); );
} }
if (isOwner && onEdit != null) { if (isOwner && widget.onEdit != null) {
items.insert( items.insert(
1, 1,
ContextMenuButtonItem( ContextMenuButtonItem(
label: 'edit'.tr(), label: 'edit'.tr(),
onPressed: () { onPressed: () {
ContextMenuController.removeAny(); ContextMenuController.removeAny();
onEdit?.call(data); widget.onEdit?.call(widget.data);
}, },
), ),
); );
} }
if (isOwner && onDelete != null) { if (isOwner && widget.onDelete != null) {
items.insert( items.insert(
2, 2,
ContextMenuButtonItem( ContextMenuButtonItem(
label: 'delete'.tr(), label: 'delete'.tr(),
onPressed: () { onPressed: () {
ContextMenuController.removeAny(); ContextMenuController.removeAny();
onDelete?.call(data); widget.onDelete?.call(widget.data);
},
),
);
}
if (widget.data.body['algorithm'] == 'plain') {
items.insert(
3,
ContextMenuButtonItem(
label: 'translate'.tr(),
onPressed: () {
ContextMenuController.removeAny();
_translateText();
}, },
), ),
); );
@ -294,26 +333,37 @@ class _ChatMessageText extends StatelessWidget {
buttonItems: items, buttonItems: items,
); );
}, },
child: switch (data.body['algorithm']) { child: switch (widget.data.body['algorithm']) {
'rsa' => _ChatDecryptMessage(message: data), 'rsa' => _ChatDecryptMessage(message: widget.data),
_ => MarkdownTextContent( _ => MarkdownTextContent(
content: data.body['text'], content: _displayText,
isAutoWarp: true, isAutoWarp: true,
isEnlargeSticker: isEnlargeSticker: RegExp(r"^:([-\w]+):$")
RegExp(r"^:([-\w]+):$").hasMatch(data.body['text'] ?? ''), .hasMatch(widget.data.body['text'] ?? ''),
), ),
}, },
), ),
if (data.updatedAt != data.createdAt) if (widget.data.updatedAt != widget.data.createdAt)
Text('messageEditedHint'.tr()).fontSize(13).opacity(0.75), Text('messageEditedHint'.tr()).fontSize(13).opacity(0.75),
if (_isTranslated)
InkWell(
child: Text('translated').tr().opacity(0.75),
onTap: () {
setState(() {
_displayText = widget.data.body['text'] ?? '';
_isTranslated = false;
});
},
),
], ],
); );
} else if (data.body['attachments']?.isNotEmpty) { } else if (widget.data.body['attachments']?.isNotEmpty) {
return Row( return Row(
children: [ children: [
const Icon(Symbols.file_present, size: 20), const Icon(Symbols.file_present, size: 20),
const Gap(4), const Gap(4),
Text('messageFileHint'.plural(data.body['attachments']!.length)), Text('messageFileHint'
.plural(widget.data.body['attachments']!.length)),
], ],
).opacity(0.8); ).opacity(0.8);
} }