Files
buzz/mobile/lib/features/channels/message_gallery_frame.dart
klopez4212 42b395cdef Polish mobile bubbles, mentions, galleries, and huddle cards (#8359)
Builds on the merged themes work in #8358 and inherits its appearance
and chat-color settings.

Polishes mobile conversations with uniform rounded bubbles,
desktop-style text-only mentions and agent icon, aligned gallery counts,
fading gallery avatars, and standard iOS scrolling. Huddles use rounded
cards with attached reaction trays, without duplicate start/end notices;
other activity rows stay centered with smaller text.

Validation: mobile checks, the full mobile test suite, and the gallery
regression test passed. Release build installed on iPhone. Full `just
ci` could not finish: desktop compilation exhausted local disk space.

### Snapshots
Flutter-rendered light and dark previews using seeded sample messages.

| Light | Dark |
| --- | --- |
|
![light](https://raw.githubusercontent.com/block/buzz/62346699199dd707579f46d21608c2a49a8095ef/pr-8359--light.png)
|
![dark](https://raw.githubusercontent.com/block/buzz/62346699199dd707579f46d21608c2a49a8095ef/pr-8359--dark.png)
|

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
2026-10-15 12:39:21 -04:00

63 lines
1.7 KiB
Dart

import 'package:flutter/material.dart';
import '../../shared/theme/theme.dart';
import '../../shared/widgets/skeleton.dart';
/// Shared carousel viewport height for real media and loading placeholders.
const messageMediaCarouselHeight = 220.0;
/// Shared label, spacing, and viewport layout for an image gallery.
class MessageGalleryFrame extends StatelessWidget {
final int count;
final Widget child;
final bool loading;
const MessageGalleryFrame({
required this.count,
required this.child,
this.loading = false,
super.key,
});
@override
Widget build(BuildContext context) {
final label = Text(
'$count images',
key: ValueKey(
loading
? 'message-skeleton-carousel-count'
: 'message-media-carousel-count',
),
style: context.textTheme.labelMedium?.copyWith(
color: context.colors.onSurfaceVariant,
fontWeight: FontWeight.w400,
),
);
return Padding(
padding: const EdgeInsets.only(top: Grid.half),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Padding(
padding: const EdgeInsetsDirectional.only(start: Grid.xs),
child: loading
? ExcludeSemantics(
child: Stack(
alignment: Alignment.centerLeft,
children: [
Opacity(opacity: 0, child: label),
const SkeletonBar(width: 48, height: 10),
],
),
)
: label,
),
const SizedBox(height: Grid.half + Grid.quarter),
child,
],
),
);
}
}