mirror of
https://github.com/block/buzz.git
synced 2026-11-14 21:31:47 +00:00
## Summary - align message typography, avatars, metadata, and spacing across mobile surfaces - improve message follow behavior, touch feedback, and Activity popover motion - refine Search motion, gutters, and explicit recent-search history ## Snapshots ### Home  ### Activity  ### Search  ## Testing - `just mobile-check` - `just mobile-test` (749 passed, 1 skipped) --------- Signed-off-by: Taylor Ho <taylorkmho@gmail.com> Signed-off-by: kenny lopez <klopez4212@gmail.com> Signed-off-by: npub14vtk7pvazqrq9639qu7e560wnqtl0d53ca4gjuvq6jzf3k2el23qqlwa7f <ab176f059d100602ea25073d9a69ee9817f7b691c76a897180d48498d959faa2@buzz.block.builderlab.xyz> Signed-off-by: Wes <wesbillman@users.noreply.github.com> Signed-off-by: npub15w828kxsxu2684ynste0uah2jwkgatd99flt7ds4523hzm8ju6cshdr8hh <a38ea3d8d03715a3d49382f2fe76ea93ac8eada52a7ebf3615a2a3716cf2e6b1@buzz.block.builderlab.xyz> Co-authored-by: npub1223z34hd7vtwc6qj4s7flsxkj644nlre2nthu7lrrmkumhu3xddsrx9r6w <52a228d6edf316ec6812ac3c9fc0d696ab59fc7954d77e7be31eedcddf91335b@buzz.block.builderlab.xyz> Co-authored-by: Taylor Ho <taylorkmho@gmail.com> Co-authored-by: npub14vtk7pvazqrq9639qu7e560wnqtl0d53ca4gjuvq6jzf3k2el23qqlwa7f <ab176f059d100602ea25073d9a69ee9817f7b691c76a897180d48498d959faa2@buzz.block.builderlab.xyz> Co-authored-by: Wes <wesbillman@users.noreply.github.com> Co-authored-by: Carl <c7ebe626f000404285d3686e1dc74cc07cc60a9754a150041ba132e14bd3e2ec@buzz.block.builderlab.xyz> Co-authored-by: npub15w828kxsxu2684ynste0uah2jwkgatd99flt7ds4523hzm8ju6cshdr8hh <a38ea3d8d03715a3d49382f2fe76ea93ac8eada52a7ebf3615a2a3716cf2e6b1@buzz.block.builderlab.xyz>
248 lines
8.2 KiB
Dart
248 lines
8.2 KiB
Dart
part of '../activity_page.dart';
|
|
|
|
/// "New" boundary between unread and previously read rows.
|
|
class _NewBoundaryDivider extends StatelessWidget {
|
|
const _NewBoundaryDivider();
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
return Padding(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: Grid.gutter,
|
|
vertical: Grid.half,
|
|
),
|
|
child: Row(
|
|
children: [
|
|
Expanded(
|
|
child: Divider(color: context.colors.error.withValues(alpha: 0.5)),
|
|
),
|
|
Padding(
|
|
padding: const EdgeInsets.symmetric(horizontal: Grid.xxs),
|
|
child: Text(
|
|
'New',
|
|
style: context.textTheme.labelSmall?.copyWith(
|
|
color: context.colors.error,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
),
|
|
Expanded(
|
|
child: Divider(color: context.colors.error.withValues(alpha: 0.5)),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
/// One conversation row, matching desktop's inbox item hierarchy:
|
|
/// avatar | sender + unread dot + time | contextual label | preview.
|
|
class _InboxRow extends ConsumerWidget {
|
|
final InboxItem item;
|
|
final Channel? channel;
|
|
final String? currentPubkey;
|
|
final bool isDone;
|
|
final VoidCallback onTap;
|
|
final VoidCallback onMarkRead;
|
|
final VoidCallback onMarkUnread;
|
|
|
|
const _InboxRow({
|
|
required this.item,
|
|
required this.channel,
|
|
required this.currentPubkey,
|
|
required this.isDone,
|
|
required this.onTap,
|
|
required this.onMarkRead,
|
|
required this.onMarkUnread,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final userCache = ref.watch(userCacheProvider);
|
|
final profile = userCache[item.item.pubkey.toLowerCase()];
|
|
final senderLabel = profile?.displayName ?? shortPubkey(item.item.pubkey);
|
|
|
|
final isDm = channel?.isDm ?? false;
|
|
final channelName = channel != null && !isDm
|
|
? (channel!.name.isNotEmpty ? channel!.name : null)
|
|
: null;
|
|
final label = inboxTypeLabel(
|
|
item,
|
|
channelName: channelName,
|
|
isDm: isDm,
|
|
senderLabel: senderLabel,
|
|
);
|
|
|
|
final mutedColor = context.colors.onSurfaceVariant;
|
|
final labelColor = item.isActionRequired && !isDone
|
|
? context.colors.tertiary
|
|
: mutedColor;
|
|
|
|
return InkWell(
|
|
key: ValueKey('inbox-row-${item.id}'),
|
|
onTap: onTap,
|
|
onLongPress: () => _showRowActions(context),
|
|
child: Padding(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: Grid.gutter,
|
|
vertical: Grid.twelve,
|
|
),
|
|
child: Row(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
_RowAvatar(pubkey: item.item.pubkey, profile: profile),
|
|
const SizedBox(width: messageAvatarContentGap),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
// Sender + unread dot + timestamp.
|
|
Row(
|
|
children: [
|
|
Expanded(
|
|
child: MessageAuthorMeta(
|
|
displayName: senderLabel,
|
|
username: messageUsernameLabel(profile),
|
|
timestamp: _inboxTimestamp(item.latestActivityAt),
|
|
nameColor: context.colors.onSurface,
|
|
metadataColor: mutedColor,
|
|
nameStyle: activityUsernameTextStyle,
|
|
metadataStyle: activityTimestampTextStyle,
|
|
displayNameKey: ValueKey(
|
|
'activity-author-${item.id}',
|
|
),
|
|
usernameKey: ValueKey('activity-username-${item.id}'),
|
|
timestampKey: ValueKey(
|
|
'activity-timestamp-${item.id}',
|
|
),
|
|
),
|
|
),
|
|
if (!isDone) ...[
|
|
const SizedBox(width: Grid.xxs),
|
|
Container(
|
|
key: ValueKey('inbox-unread-dot-${item.id}'),
|
|
width: 6,
|
|
height: 6,
|
|
decoration: BoxDecoration(
|
|
shape: BoxShape.circle,
|
|
color: context.colors.primary,
|
|
),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
const SizedBox(height: Grid.quarter),
|
|
// Contextual label: "Mentioned in #channel" etc.
|
|
Row(
|
|
children: [
|
|
Flexible(
|
|
child: Text(
|
|
label.text,
|
|
style: activityContextTextStyle.copyWith(
|
|
color: labelColor,
|
|
),
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
if (label.channelLabel != null) ...[
|
|
const SizedBox(width: Grid.half),
|
|
Flexible(
|
|
child: Container(
|
|
padding: const EdgeInsets.symmetric(
|
|
horizontal: Grid.half + Grid.quarter,
|
|
vertical: Grid.quarter / 2,
|
|
),
|
|
decoration: BoxDecoration(
|
|
color: context.colors.surfaceContainerHighest,
|
|
borderRadius: BorderRadius.circular(Grid.half),
|
|
),
|
|
child: Text(
|
|
'#${label.channelLabel}',
|
|
style: activityContextTextStyle.copyWith(
|
|
color: mutedColor,
|
|
),
|
|
overflow: TextOverflow.ellipsis,
|
|
),
|
|
),
|
|
),
|
|
],
|
|
],
|
|
),
|
|
const SizedBox(height: Grid.half),
|
|
// Message preview.
|
|
MessageContent(
|
|
content: item.item.displayContent,
|
|
tags: item.item.tags,
|
|
maxLines: 2,
|
|
baseStyle: activityPreviewTextStyle.copyWith(
|
|
color: context.colors.onSurface,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
void _showRowActions(BuildContext context) {
|
|
showModalBottomSheet<void>(
|
|
context: context,
|
|
builder: (sheetContext) => SafeArea(
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
ListTile(
|
|
leading: Icon(
|
|
isDone ? LucideIcons.mail : LucideIcons.mailOpen,
|
|
size: 20,
|
|
),
|
|
title: Text(isDone ? 'Mark unread' : 'Mark as read'),
|
|
onTap: () {
|
|
Navigator.of(sheetContext).pop();
|
|
isDone ? onMarkUnread() : onMarkRead();
|
|
},
|
|
),
|
|
ListTile(
|
|
leading: const Icon(LucideIcons.externalLink, size: 20),
|
|
title: const Text('Open conversation'),
|
|
onTap: () {
|
|
Navigator.of(sheetContext).pop();
|
|
onTap();
|
|
},
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
class _RowAvatar extends StatelessWidget {
|
|
final String pubkey;
|
|
final UserProfile? profile;
|
|
|
|
const _RowAvatar({required this.pubkey, required this.profile});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
final initial =
|
|
profile?.initial ?? (pubkey.isNotEmpty ? pubkey[0].toUpperCase() : '?');
|
|
return AvatarImage(
|
|
imageUrl: profile?.avatarUrl,
|
|
radius: activityAvatarSize / 2,
|
|
backgroundColor: context.colors.primaryContainer,
|
|
fallback: Text(
|
|
initial,
|
|
style: TextStyle(
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.w600,
|
|
color: context.colors.onPrimaryContainer,
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|