Mitra chat input bar: port the exact pattern from client_app

The missing piece was `constraints: BoxConstraints()` on the
InputDecoration. The app-wide InputDecorationTheme in halo_theme
sets a 48dp min-height for form fields, which the chat input pill
doesn't want. Without explicitly nulling that constraint, the
TextField refuses to collapse below 48dp, so the line-box can't
sit on the parent 44dp container's midline — textAlignVertical
becomes a no-op and the text anchors top.

Switched to the same Material + StadiumBorder + Center wrapper
client_app already uses (chat_screen.dart::_InputBar). Verified
on emulator-5556 driving typed "halo" — text body now sits
visually centered on the pill midline.

Reverts the empirical TextAlignVertical(y: 0.4) shim from 75343f9.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-21 22:31:06 +08:00
parent 75343f97b6
commit 387f0f65de

View File

@@ -740,58 +740,57 @@ class _MitraChatBodyContentState extends ConsumerState<_MitraChatBodyContent> {
crossAxisAlignment: CrossAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center,
children: [ children: [
Expanded( Expanded(
child: Container( // Pattern lifted from client_app/chat_screen.dart::_InputBar
// — same shape, same vertical-centering trick. Key bits:
// - Material + StadiumBorder = pill outline + proper focus
// clipping
// - Center wrapper centers the (isCollapsed) TextField
// - `constraints: BoxConstraints()` overrides the app-wide
// InputDecorationTheme min-height (set in halo_theme for
// form fields). Without it, the field claims ~48dp and
// refuses to collapse — textAlignVertical becomes a
// no-op against the parent's 44dp height.
child: SizedBox(
height: 44, height: 44,
// alignment.center positions the TextField on the vertical child: Material(
// midline of the 44dp container. Without this, isDense's
// small intrinsic height docks to the top of the parent
// (no implicit vertical centering for Container children).
alignment: Alignment.center,
padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: const BoxDecoration(
// White bg so the pill stands out against the cream page
// (HaloTokens.bg) — previously the pill bg matched the
// page exactly, leaving only the very-light border to
// define the shape (looked like a soft shadow). Border
// color is unchanged — the visible outline keeps the
// same tone.
color: HaloTokens.surface, color: HaloTokens.surface,
borderRadius: HaloRadius.pill, shape: const StadiumBorder(
border: Border.fromBorderSide( side: BorderSide(color: HaloTokens.border),
BorderSide(color: HaloTokens.border),
), ),
), clipBehavior: Clip.antiAlias,
// Vertical centering recipe: Container.alignment.center child: Center(
// positions the TextField on the midline; textAlignVertical child: TextField(
// with a positive y (~0.4) nudges the baseline down so the controller: widget.messageController,
// optical center of Latin lowercase glyphs lines up with onChanged: widget.onTextChanged,
// the pill midline, not just the baseline. With y=0 (true textInputAction: TextInputAction.send,
// center), the field's line-box centers but Latin text onSubmitted: (_) => widget.onSend(),
// bodies sit in the upper half because the baseline is at maxLines: 1,
// ~75% of line height — leaving descender space empty textAlignVertical: TextAlignVertical.center,
// below. y=0.4 shifts down to match the visual midline. style: const TextStyle(
child: TextField( fontFamily: HaloTokens.fontBody,
controller: widget.messageController, fontSize: 13.5,
onChanged: widget.onTextChanged, color: HaloTokens.ink,
textInputAction: TextInputAction.send, ),
onSubmitted: (_) => widget.onSend(), decoration: const InputDecoration(
maxLines: 1, filled: false,
textAlignVertical: const TextAlignVertical(y: 0.4), fillColor: Colors.transparent,
style: const TextStyle( border: InputBorder.none,
fontSize: 13.5, enabledBorder: InputBorder.none,
color: HaloTokens.ink, focusedBorder: InputBorder.none,
), errorBorder: InputBorder.none,
decoration: const InputDecoration( focusedErrorBorder: InputBorder.none,
hintText: 'ketik balasan...', disabledBorder: InputBorder.none,
hintStyle: TextStyle(color: HaloTokens.inkMuted, fontSize: 13.5), isCollapsed: true,
isDense: true, contentPadding: EdgeInsets.symmetric(horizontal: 16),
contentPadding: EdgeInsets.zero, constraints: BoxConstraints(),
border: InputBorder.none, hintText: 'ketik balasan...',
enabledBorder: InputBorder.none, hintStyle: TextStyle(
focusedBorder: InputBorder.none, fontFamily: HaloTokens.fontBody,
disabledBorder: InputBorder.none, fontSize: 13.5,
errorBorder: InputBorder.none, color: HaloTokens.inkMuted,
focusedErrorBorder: InputBorder.none, ),
),
),
), ),
), ),
), ),