Mitra chat input bar: drop maxLines:3 attempt + nudge alignment down

Follow-up to 92da8b2. With `textAlignVertical: center` + `isDense:true`,
the TextField was centering the line-box baseline on the parent
midline — but Latin lowercase glyphs sit at ~75% of line height,
leaving descender space empty below and the optical center of text
visibly above the pill midline.

Fix: `textAlignVertical: TextAlignVertical(y: 0.4)` shifts the
baseline down to align Latin x-height optical centers with the pill
midline. Also added explicit `alignment: Alignment.center` on the
Container so the field's small intrinsic line-box positions on the
midline rather than docking to the top.

Verified on emulator-5556 driving the typed "halo" through the chat
input — text body now sits on the visual midline of the 44dp pill.

The horizontal underline below typed text is Gboard's composing-
region indicator (Android IME behavior), not a TextField underline,
and will go away once the user commits the word with space/send.

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

View File

@@ -742,6 +742,11 @@ class _MitraChatBodyContentState extends ConsumerState<_MitraChatBodyContent> {
Expanded( Expanded(
child: Container( child: Container(
height: 44, height: 44,
// alignment.center positions the TextField on the vertical
// 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), padding: const EdgeInsets.symmetric(horizontal: 16),
decoration: const BoxDecoration( decoration: const BoxDecoration(
// White bg so the pill stands out against the cream page // White bg so the pill stands out against the cream page
@@ -756,29 +761,37 @@ class _MitraChatBodyContentState extends ConsumerState<_MitraChatBodyContent> {
BorderSide(color: HaloTokens.border), BorderSide(color: HaloTokens.border),
), ),
), ),
// Center wrapper makes the TextField sit on the vertical // Vertical centering recipe: Container.alignment.center
// midline of the 44dp pill. `textAlignVertical: center` // positions the TextField on the midline; textAlignVertical
// + `isCollapsed: true` alone don't center against the // with a positive y (~0.4) nudges the baseline down so the
// parent height — they only center within the field's // optical center of Latin lowercase glyphs lines up with
// own intrinsic line-box, which then docks to the top of // the pill midline, not just the baseline. With y=0 (true
// the parent. Wrapping in Center delegates the vertical // center), the field's line-box centers but Latin text
// alignment to the container's stack. // bodies sit in the upper half because the baseline is at
child: Center( // ~75% of line height — leaving descender space empty
child: TextField( // below. y=0.4 shifts down to match the visual midline.
controller: widget.messageController, child: TextField(
onChanged: widget.onTextChanged, controller: widget.messageController,
textInputAction: TextInputAction.send, onChanged: widget.onTextChanged,
onSubmitted: (_) => widget.onSend(), textInputAction: TextInputAction.send,
style: const TextStyle(fontSize: 13.5, color: HaloTokens.ink), onSubmitted: (_) => widget.onSend(),
decoration: const InputDecoration( maxLines: 1,
hintText: 'ketik balasan...', textAlignVertical: const TextAlignVertical(y: 0.4),
hintStyle: TextStyle(color: HaloTokens.inkMuted, fontSize: 13.5), style: const TextStyle(
isCollapsed: true, fontSize: 13.5,
border: InputBorder.none, color: HaloTokens.ink,
enabledBorder: InputBorder.none, ),
focusedBorder: InputBorder.none, decoration: const InputDecoration(
disabledBorder: InputBorder.none, hintText: 'ketik balasan...',
), hintStyle: TextStyle(color: HaloTokens.inkMuted, fontSize: 13.5),
isDense: true,
contentPadding: EdgeInsets.zero,
border: InputBorder.none,
enabledBorder: InputBorder.none,
focusedBorder: InputBorder.none,
disabledBorder: InputBorder.none,
errorBorder: InputBorder.none,
focusedErrorBorder: InputBorder.none,
), ),
), ),
), ),