Chat view
The assembled surface — thread above composer on a centred rail, with a zero state and jump-to-latest.
FlowChatView is the chat surface assembled: a bounded thread above a
composer, centred on a readable rail (760 by default), with a zero state
for the conversation that hasn’t started and a jump-to-latest button once
you’ve scrolled back through history.
FlowChatView( empty: messages.isEmpty, greeting: FlowGreeting(icon: Icons.wb_twilight, text: 'Good afternoon'), suggestions: FlowSuggestionGroup( layout: FlowSuggestionLayout.column, suggestions: starters, ), thread: FlowThread(messages: messages, controller: controller), composer: FlowComposer(onSend: send), threadController: controller,)It takes finished widgets
Section titled “It takes finished widgets”The view accepts a built FlowThread and FlowComposer rather than
their data, so it stays correct as those components grow. What it adds is
the one thing every host would otherwise have to know: the bounded height
a thread needs, the width caps, and where the zero state’s pieces go —
the composer lifts to the vertical centre on wide layouts and stays
docked on compact ones.
Body-only
Section titled “Body-only”It builds no Scaffold and no app bar. Drop it in a scaffold body and the
host keeps the chrome, the background, and the keyboard inset. Pass
composer: null for a read-only surface — an archived thread, a shared
transcript.
Taps that land on the surface itself — dead space, the thread, a settled message — dismiss the keyboard, the chat convention; the composer, links and buttons keep their taps.
Jump to latest
Section titled “Jump to latest”Pass the same ScrollController to the thread and to
threadController: — taking finished widgets means the view can’t
reach in and attach its own. Null leaves the button out entirely.
Drag and drop
Section titled “Drag and drop”onAttachmentsDropped turns the whole surface into a drop target. While
a file is dragged over it, a full-bleed tint covers the page with a
centred card inviting the drop (dropLabel, host-localized); let go and
the callback fires with the files read and decoded into
FlowAttachments, ready to hand to the composer.
FlowChatView( onAttachmentsDropped: (dropped) => setState(() => pending.addAll(dropped)), onAttachmentRejected: showRejection, attachmentOptions: const FlowAttachmentOptions( maxFileSize: 10 * 1024 * 1024, ), dropLabel: 'Drop images to attach', thread: thread, composer: FlowComposer( onSend: send, attachments: pending, onRemoveAttachment: removePending, ),)attachmentOptions and onAttachmentRejected work exactly as they do on
the composer — and matter more
here, because a dropped file never passed through a dialog’s filter, so
the accept list is enforced on arrival rather than merely suggested. Pass
the same options object to both and a file gets the same answer whichever
way it arrives.
DropRegion( // any drop package onDropEnter: (_) => setState(() => hovering = true), onDropLeave: (_) => setState(() => hovering = false), onPerformDrop: (files) => addAttachments(files), child: FlowChatView( dropActive: hovering, dropLabel: 'Drop files to attach', thread: thread, composer: composer, ),)dropActive is an override, not a switch: the treatment is up while it
is true or a real drag is over the surface, so the two coexist and
hosts that were driving the flag before keep working untouched.
The treatment is a vertical wash over a 12px blur of the page —
surfaceBright at 40% down to surface at 80% — with the glyph and the
invitation straight on it: no card, so the whole surface reads as the
target. It is pure
feedback: it never intercepts a pointer, it announces dropLabel to
assistive tech when it appears, and it leaves the widget tree entirely
while idle, so a conversation nobody is dragging over pays nothing for
it.
All of it is yours to change. dropIcon, dropIconSize and dropLabel
are widget parameters, and FlowChatViewStyle carries the wash, the
glyph’s colour and the label’s text style:
FlowChatView( onAttachmentsDropped: addAttachments, dropLabel: 'Drop files to add to chat', dropIcon: PhosphorIconsRegular.uploadSimple, style: FlowChatViewStyle( dropGradient: LinearGradient( begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [Color(0xCCFFFFFF), Color(0xF2FFFFFF)], ), dropIconColor: Color(0xFF1A1A1A), dropLabelStyle: TextStyle(fontSize: 28), ),)Install a FlowChatViewStyle on FlowTheme.chatViewStyle to restyle
every surface at once; a widget’s own style wins field by field.
Or scope it to the composer
Section titled “Or scope it to the composer”FlowComposer.onAttachmentsDropped
is the same detection scoped to the input card, which lights up instead
of the page. Use it where the rest of the surface has its own meaning for
a dropped file; use this one — the wider, more forgiving target — for an
ordinary chat. Wiring both is fine: the innermost target under the
pointer wins, so the card takes drops aimed at it and the surface takes
the rest.
For a surface that is neither, FlowDropTarget is the detection on its
own: wrap any subtree, take onDropped and onHoverChanged, and draw
whatever treatment you like. Its enabled is the same switch as
attachmentsEnabled: off keeps the zone registered and swallows drops
rather than letting the browser navigate to the file.
Key API
Section titled “Key API”thread— usually aFlowThread, given the bounded height it needs. Null renders an empty thread: a conversation nobody has spoken in yet is a real state of a chat, so the surface stands up on its own.composer— required, but nullable: an explicitcomposer: nullrenders no input, a read-only surface.onAttachmentsDropped— the surface’s own drag-and-drop (web only), handing back decoded attachments.attachmentOptions+onAttachmentRejected— what a dropped file must be, and what wasn’t.attachmentsEnabled— turns the surface’s drop off, handler left wired; pair it with the composer’s.dropLabel— the invitation card’s line.dropActive— raises the same treatment by hand, for desktop hosts with their own detection.empty+greeting+suggestions— the zero state; the host flipsempty(typicallymessages.isEmpty).header— optional full-bleed bar above the thread;aboveComposerpins a strip between the thread and the input.threadController,jumpToLatestTooltip— the jump-to-latest button and its host-localized label.maxContentWidth(760),emptyComposerWidth(640),emptySuggestionsWidth(480),padding— the rail and the zero state’s width caps.