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:
@@ -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,
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in New Issue
Block a user