You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
115 lines
3.7 KiB
115 lines
3.7 KiB
import 'package:flutter/material.dart';
|
|
|
|
import '../helpers/link_handler.dart';
|
|
|
|
class TranslatedMessageContent extends StatelessWidget {
|
|
final String displayText;
|
|
final String? originalText;
|
|
final TextStyle style;
|
|
final TextStyle? originalStyle;
|
|
final bool showOriginalFirst;
|
|
|
|
const TranslatedMessageContent({
|
|
super.key,
|
|
required this.displayText,
|
|
required this.style,
|
|
this.originalText,
|
|
this.originalStyle,
|
|
this.showOriginalFirst = true,
|
|
});
|
|
|
|
// An `@[Name]` mention anywhere in the text (leading, inline, or repeated).
|
|
// Rendered as a chip; surrounding text stays plain. (#233)
|
|
static final RegExp _mention = RegExp(r'@\[([^\]]+)\]');
|
|
|
|
// A leading `@[Name] ` reply prefix.
|
|
static final RegExp _replyPrefix = RegExp(r'^@\[([^\]]+)\]\s+');
|
|
|
|
/// The name of a leading `@[Name]` reply mention, or null. Lets callers show
|
|
/// a reply chip above content that isn't rendered as text (e.g. a reply-gif,
|
|
/// #232).
|
|
static String? leadingReplyName(String text) =>
|
|
_replyPrefix.firstMatch(text.trim())?.group(1);
|
|
|
|
/// A styled `@Name` mention chip, shared by inline text rendering and reply
|
|
/// headers above non-text content.
|
|
static Widget mentionChip(
|
|
BuildContext context,
|
|
String name,
|
|
TextStyle style,
|
|
) {
|
|
final scheme = Theme.of(context).colorScheme;
|
|
return Container(
|
|
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 1),
|
|
decoration: BoxDecoration(
|
|
color: scheme.onSurface.withValues(alpha: 0.14),
|
|
borderRadius: BorderRadius.circular(6),
|
|
),
|
|
child: Text('@$name', style: style.copyWith(fontWeight: FontWeight.w500)),
|
|
);
|
|
}
|
|
|
|
Widget _buildText(BuildContext context, String text, TextStyle textStyle) {
|
|
if (!_mention.hasMatch(text)) {
|
|
return LinkHandler.buildLinkifyText(
|
|
context: context,
|
|
text: text,
|
|
style: textStyle,
|
|
);
|
|
}
|
|
// Mentions can't be interleaved with the Linkify widget, so a message that
|
|
// contains a mention renders as rich text with chip spans (links inside a
|
|
// mention message are not tappable — same as the prior leading-mention
|
|
// path). Messages without a mention keep full link support above.
|
|
final spans = <InlineSpan>[];
|
|
var last = 0;
|
|
for (final m in _mention.allMatches(text)) {
|
|
if (m.start > last) {
|
|
spans.add(TextSpan(text: text.substring(last, m.start)));
|
|
}
|
|
spans.add(
|
|
WidgetSpan(
|
|
alignment: PlaceholderAlignment.middle,
|
|
child: mentionChip(context, m.group(1)!, textStyle),
|
|
),
|
|
);
|
|
last = m.end;
|
|
}
|
|
if (last < text.length) {
|
|
spans.add(TextSpan(text: text.substring(last)));
|
|
}
|
|
return Text.rich(TextSpan(children: spans), style: textStyle);
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final trimmedDisplay = displayText.trim();
|
|
final trimmedOriginal = originalText?.trim();
|
|
final shouldShowOriginal =
|
|
trimmedOriginal != null &&
|
|
trimmedOriginal.isNotEmpty &&
|
|
trimmedOriginal != trimmedDisplay;
|
|
final effectiveOriginalStyle =
|
|
originalStyle ??
|
|
style.copyWith(fontStyle: FontStyle.italic, fontSize: style.fontSize);
|
|
final originalWidget = shouldShowOriginal
|
|
? _buildText(context, trimmedOriginal, effectiveOriginalStyle)
|
|
: null;
|
|
final translatedWidget = _buildText(context, trimmedDisplay, style);
|
|
|
|
if (!shouldShowOriginal) {
|
|
return translatedWidget;
|
|
}
|
|
|
|
final children = showOriginalFirst
|
|
? [originalWidget!, const SizedBox(height: 6), translatedWidget]
|
|
: [translatedWidget, const SizedBox(height: 6), originalWidget!];
|
|
|
|
return Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: children,
|
|
);
|
|
}
|
|
}
|