Files
buzz/mobile/lib/shared/widgets/message_author_meta.dart

155 lines
4.8 KiB
Dart

import 'package:flutter/material.dart';
import '../theme/theme.dart';
/// A consistent inline author row for message-oriented surfaces.
class MessageAuthorMeta extends StatelessWidget {
/// Primary author name shown at the start of the row.
final String displayName;
/// Optional secondary username, hidden when blank or equal to [displayName].
final String? username;
/// Timestamp label shown after the author metadata.
final String timestamp;
/// Whether metadata includes the timestamp alongside the author.
final bool showTimestamp;
/// Whether the name, username, and verification mark are visible.
final bool showIdentity;
/// Space before the timestamp when the author is visible.
final double timestampGap;
/// Color applied to [displayName].
final Color nameColor;
/// Color applied to the username and [timestamp].
final Color metadataColor;
/// Optional callback invoked when [displayName] is tapped.
final VoidCallback? onAuthorTap;
/// Optional key assigned to the display-name text.
final Key? displayNameKey;
/// Optional key assigned to the username text.
final Key? usernameKey;
/// Optional key assigned to the timestamp text.
final Key? timestampKey;
/// Base text style for [displayName], with [nameColor] applied.
final TextStyle nameStyle;
/// Base text style for secondary metadata, with [metadataColor] applied.
final TextStyle metadataStyle;
/// Base text style for [timestamp], with [metadataColor] applied.
final TextStyle timestampStyle;
/// Optional verification mark drawn right after the name, outside its text.
final Widget? mark;
/// Mirrors metadata order without changing text reading direction.
final bool reversed;
/// Positions the metadata within the available message width.
final MainAxisAlignment alignment;
/// Creates an inline author row with optional username and tap handling.
const MessageAuthorMeta({
super.key,
required this.displayName,
required this.timestamp,
required this.nameColor,
required this.metadataColor,
this.username,
this.mark,
this.alignment = MainAxisAlignment.start,
this.reversed = false,
this.showTimestamp = true,
this.showIdentity = true,
this.timestampGap = Grid.xxs,
this.onAuthorTap,
this.displayNameKey,
this.usernameKey,
this.timestampKey,
this.nameStyle = messageUsernameTextStyle,
this.metadataStyle = messageMetadataTextStyle,
this.timestampStyle = messageTimestampTextStyle,
});
@override
Widget build(BuildContext context) {
final normalizedUsername = username?.trim();
final showUsername =
normalizedUsername != null &&
normalizedUsername.isNotEmpty &&
normalizedUsername != displayName.trim();
final resolvedNameStyle = nameStyle.copyWith(color: nameColor);
final resolvedMetadataStyle = metadataStyle.copyWith(color: metadataColor);
final resolvedTimestampStyle = timestampStyle.copyWith(
color: metadataColor,
);
Widget authorName = Text(
displayName,
key: displayNameKey,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: resolvedNameStyle,
);
if (onAuthorTap != null) {
authorName = GestureDetector(onTap: onAuthorTap, child: authorName);
}
return LayoutBuilder(
builder: (context, constraints) {
final metadataMaxWidth = constraints.hasBoundedWidth
? constraints.maxWidth / (showUsername ? 3 : 2)
: double.infinity;
final children = <Widget>[
if (showIdentity) ...[Flexible(child: authorName), ?mark],
if (showIdentity && showUsername) ...[
const SizedBox(width: Grid.half),
ConstrainedBox(
constraints: BoxConstraints(maxWidth: metadataMaxWidth),
child: Text(
normalizedUsername,
key: usernameKey,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: resolvedMetadataStyle,
),
),
],
if (showTimestamp) ...[
if (showIdentity) SizedBox(width: timestampGap),
ConstrainedBox(
constraints: BoxConstraints(maxWidth: metadataMaxWidth),
child: Text(
timestamp,
key: timestampKey,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: resolvedTimestampStyle,
),
),
],
];
return Semantics(
label: showIdentity ? null : displayName,
child: Row(
mainAxisAlignment: alignment,
crossAxisAlignment: CrossAxisAlignment.center,
children: reversed ? children.reversed.toList() : children,
),
);
},
);
}
}