Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,10 @@ class _MessageBubble extends ConsumerWidget {
currentPubkey?.toLowerCase() == pk ||
(profile?.ownerPubkey != null &&
profile?.ownerPubkey == currentPubkey?.toLowerCase());
final hasImageGallery = hasTrailingImageGallery(
message.content,
message.tags,
);

// Build mention names map from event p-tags.
final userCache = ref.watch(userCacheProvider);
Expand All @@ -55,10 +59,10 @@ class _MessageBubble extends ConsumerWidget {
return Material(
color: Colors.transparent,
borderRadius: BorderRadius.circular(Radii.md),
// The media carousel intentionally continues through the list's trailing
// gutter. InkWell still clips its ink to [borderRadius], while leaving
// overflowing message content visible.
clipBehavior: Clip.none,
// Only galleries paint through the trailing gutter. Keep normal rows
// clipped so recycled list children cannot leave stale text over the
// following message or thread summary.
clipBehavior: hasImageGallery ? Clip.none : Clip.antiAlias,
child: InkWell(
key: ValueKey('message-row-${message.id}'),
borderRadius: BorderRadius.circular(Radii.md),
Expand Down
6 changes: 6 additions & 0 deletions mobile/lib/features/channels/message_content.dart
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,12 @@ final openDownloadedFileProvider = Provider<OpenDownloadedFile>((ref) {
};
});

/// Whether [content] ends with the multi-image gallery rendered by
/// [MessageContent].
bool hasTrailingImageGallery(String content, List<List<String>> tags) {
return _extractTrailingImageGallery(content, parseImetaTags(tags)) != null;
}

String _safeDownloadedFilename(String filename) {
final safe = filename
.split(RegExp(r'[/\\]'))
Expand Down
12 changes: 8 additions & 4 deletions mobile/lib/features/channels/thread_detail_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,10 @@ class _ThreadMessage extends ConsumerWidget {
currentPubkey?.toLowerCase() == pk ||
(profile?.ownerPubkey != null &&
profile?.ownerPubkey == currentPubkey?.toLowerCase());
final hasImageGallery = hasTrailingImageGallery(
message.content,
message.tags,
);

final userCache = ref.watch(userCacheProvider);
final knownAgentPubkeys = ref.watch(mentionAgentPubkeysProvider(channelId));
Expand Down Expand Up @@ -504,10 +508,10 @@ class _ThreadMessage extends ConsumerWidget {
child: Material(
color: Colors.transparent,
borderRadius: BorderRadius.circular(Radii.md),
// The media carousel intentionally continues through the list's
// trailing gutter. InkWell still clips its ink to [borderRadius],
// while leaving overflowing message content visible.
clipBehavior: Clip.none,
// Only galleries paint through the trailing gutter. Keep normal rows
// clipped so recycled list children cannot leave stale text over the
// following message or thread summary.
clipBehavior: hasImageGallery ? Clip.none : Clip.antiAlias,
child: InkWell(
key: ValueKey('thread-message-row-${message.id}'),
borderRadius: BorderRadius.circular(Radii.md),
Expand Down
10 changes: 10 additions & 0 deletions mobile/test/features/channels/channel_detail_page_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -696,6 +696,16 @@ void main() {
expect(find.text('Alice'), findsOneWidget);
expect(find.text('alice@example.com'), findsOneWidget);
expect(find.text('Bob'), findsOneWidget);
final textMessageMaterial = find
.ancestor(
of: find.byKey(const ValueKey('message-row-msg1')),
matching: find.byType(Material),
)
.first;
expect(
tester.widget<Material>(textMessageMaterial).clipBehavior,
Clip.antiAlias,
);
final messageAvatars = find.byType(CircleAvatar);
expect(messageAvatars, findsNWidgets(2));
for (final avatar in messageAvatars.evaluate()) {
Expand Down