Files
buzz/mobile/lib/features/channels/compose_bar/dock.dart
klopez4212 69107dc3bf Polish mobile message threads and composer (#5645)
## Summary

- refine mobile message metadata, search spacing, and Activity filter
semantics
- add channel-parity Latest navigation and stable tail following to
threads
- synchronize Android composer/keyboard geometry and keep Latest spacing
stable across IME transitions

## Validation

- `bin/just mobile-check`
- `bin/just mobile-test` (1,276 tests)
- Pixel 10 install/launch and channel/thread keyboard, Latest, tail, and
back-navigation review
- signed iPhone install/launch workflow

## Snapshots

See the review snapshots below.

---------

Signed-off-by: kenny lopez <klopez4212@gmail.com>
Signed-off-by: Kenny Lopez <klopez4212@gmail.com>
Signed-off-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
Co-authored-by: Fast Fizz <2df81cb51f05a9d5387ef24d7b9ecb8fcdfcd1c70ffabc67061c9596e1b5b1c4@buzz.block.builderlab.xyz>
Co-authored-by: Princess Donut <b238ea756dee4d98afa5883fc7f1de61eeabe65bf700e3a5a5a80db5e42e2c2b@buzz.block.builderlab.xyz>
2026-08-15 09:14:14 +01:00

151 lines
5.0 KiB
Dart

part of '../compose_bar.dart';
class _ComposerDockFrame extends StatelessWidget {
final Animation<double> expansionAnimation;
final Widget child;
const _ComposerDockFrame({
required this.expansionAnimation,
required this.child,
});
@override
Widget build(BuildContext context) {
final backdropHeight = mobileTabFooterBackdropHeight(context);
return Stack(
clipBehavior: Clip.none,
children: [
Positioned(
key: const ValueKey('composer-footer-gradient'),
left: 0,
right: 0,
bottom: 0,
height: backdropHeight,
child: IgnorePointer(
child: MobileTabFooterBackdrop(height: backdropHeight),
),
),
Padding(
padding: EdgeInsets.only(
left: Grid.twelve,
right: Grid.twelve,
bottom: MediaQuery.viewPaddingOf(context).bottom + Grid.xxs,
),
child: Align(
alignment: Alignment.bottomCenter,
child: AnimatedBuilder(
animation: expansionAnimation,
child: child,
builder: (context, child) {
final progress = expansionAnimation.value
.clamp(0.0, 1.0)
.toDouble();
return FractionallySizedBox(
key: const ValueKey('composer-width-transition'),
widthFactor: 0.85 + 0.15 * progress,
child: child,
);
},
),
),
),
],
);
}
}
class _ComposerOverlayPortal extends StatelessWidget {
final OverlayPortalController controller;
final ValueListenable<_AttachmentSurface> attachmentSurface;
final bool reducedMotion;
final Widget Function(_AttachmentSurface surface) buildOverlayPanel;
final VoidCallback onDismissAttachmentSurface;
final Widget child;
const _ComposerOverlayPortal({
required this.controller,
required this.attachmentSurface,
required this.reducedMotion,
required this.buildOverlayPanel,
required this.onDismissAttachmentSurface,
required this.child,
});
@override
Widget build(BuildContext context) {
return OverlayPortal.overlayChildLayoutBuilder(
controller: controller,
overlayChildBuilder: (context, layoutInfo) {
final composerOrigin = MatrixUtils.transformPoint(
layoutInfo.childPaintTransform,
Offset.zero,
);
return ValueListenableBuilder<_AttachmentSurface>(
valueListenable: attachmentSurface,
builder: (context, surface, _) {
final surfaceDuration = reducedMotion
? Duration.zero
: Duration(
milliseconds:
surface == _AttachmentSurface.camera ||
surface == _AttachmentSurface.photos
? 320
: 250,
);
final expandedSurfaceCoversComposer =
surface == _AttachmentSurface.camera ||
surface == _AttachmentSurface.photos;
final surfaceLeft = expandedSurfaceCoversComposer
? Grid.twelve
: composerOrigin.dx;
final surfaceWidth = expandedSurfaceCoversComposer
? layoutInfo.overlaySize.width - (Grid.twelve * 2)
: layoutInfo.childSize.width;
final overlayAnchorY =
composerOrigin.dy +
(expandedSurfaceCoversComposer
? layoutInfo.childSize.height + Grid.twelve
: 0);
return Stack(
children: [
if (surface != _AttachmentSurface.closed)
Positioned(
left: 0,
top: 0,
right: 0,
height: composerOrigin.dy,
child: ExcludeSemantics(
child: GestureDetector(
key: const ValueKey('attachment-dismiss-barrier'),
behavior: HitTestBehavior.opaque,
onTap: onDismissAttachmentSurface,
),
),
),
AnimatedPositioned(
duration: surfaceDuration,
curve:
surface == _AttachmentSurface.camera ||
surface == _AttachmentSurface.photos
? const Cubic(0.34, 1.25, 0.64, 1)
: const Cubic(0.22, 1, 0.36, 1),
left: surfaceLeft,
bottom: layoutInfo.overlaySize.height - overlayAnchorY,
width: surfaceWidth,
child: ClipRect(
child: Padding(
padding: const EdgeInsets.only(bottom: Grid.xxs),
child: buildOverlayPanel(surface),
),
),
),
],
);
},
);
},
child: child,
);
}
}