convokit_flutter_ui 0.10.0
convokit_flutter_ui: ^0.10.0 copied to clipboard
Extensible, plug-and-play Flutter UI components for ConvoKit conversations and messaging.
Changelog #
0.10.0 #
- Add default-row emoji picker, count chips, and paged reactor dialog. The controller coalesces summary refreshes on changes/reconnect and exposes reaction actions to builders. Export
ConvoKitReactionBarfor custom rows.
0.9.0 #
- Quoted replies.
ConvoKitConversationControllergainsvoid startReply(String messageId),void cancelReply()and asendMessagethat stamps the quoted id on the pending row and sends it asreplyToMessageId, clearing the target on success;ConvoKitConversationStategainsMessage? replyTargetandbool canReplyToMessagesplusbool canReply(Message). Unlike editing, replying is not limited to the caller's own rows — any member may quote any row — but the same rule applies otherwise: a no-op unless the row is rendered, confirmed (not pending), not deleted, and the caller's role, fromConversation.membership.roleor the caller's participant row when known, is notREAD. Replying and editing are mutually exclusive, and a removed row clears the target. The reference is write-once:saveEditand every other edit path leave it alone, so a reply keeps quoting its parent after the parent is edited or deleted. - Quoted-message previews, resolved out of band and batched.
ConvoKitConversationState.replyPreviewByMessageId(Map<String, ReplyPreview?>) has three states: a value is a resolved preview, a presentnullis the terminal "the quoted message is gone" answer, and an absent key is "not resolved yet" — render the reference with no quoted text, never the unavailable copy. The controller issues onegetReplyPreviewscall per rendered page, refresh/reconcile completion and live-insert burst, covering only the distinct ids it does not already have; a parent inside the loaded window is derived locally (text truncated at 500 characters withtextTruncatedset) and costs no request. Absence from a resolved result is the only deletion signal and is terminal. A call that rejects writes no entry for any id in it, surfaces once throughstate.errorand is re-tried on the next trigger, with no retry loop of its own. Entries are invalidated explicitly — a deletion of the quoted id marks it unavailable, an edit refreshes it from the rendered row or marks it stale when the parent is outside the window, and a rejoin marks every non-terminal entry stale — and entries no rendered row references are dropped on each reconcile. A burst followed bydispose()issues no call and emits no state. - Jump to message.
Future<bool> jumpToMessage(String id)highlights a row that is already loaded, and otherwise fetches its centred window throughgetMessageContextand replaces the loaded window with it, leavingConvoKitConversationState.windowMode == ConvoKitMessageWindowMode.jumped.Future<void> loadNewerMessages(),Future<void> returnToLatest()andvoid clearHighlight()are new, as areString? highlightedMessageId,bool hasNewerMessages,bool isLoadingNewer,windowMode,bool canJumpToMessagesandbool canResolveReplyPreviewson the state. A jump is a window operation, never a re-open: the loaded conversation, the private state captured at open, edit mode, tombstones, pending sends and the acknowledgement progress all survive, and it acknowledges nothing. A 404 with codeMESSAGE_NOT_FOUNDis not a generic failure but the guaranteed answer for a deleted parent, so it marks that preview unavailable instead of surfacing an error; every other failure leaves the window untouched. - Jumped windows. Older and newer paging use the window's context
cursors; realtime inserts are recorded but not rendered, and a deferred
insert acknowledges nothing until it is actually rendered, while edits,
revisions and deletions for rows inside the window still apply; no read is
acknowledged at all, because the newest rendered row is not the room's
newest row; a queued refresh re-reads the window with one bounded
getMessageContextrequest instead of the tail-anchored page walk, and the owed reconcile is still owed and runs on the return to live;sendMessagerejoins the live tail first, keeping the reply target, and a failed return sends nothing and keeps the draft;jumpToMessageis a no-op while a send or a page load is in flight, and a page that lands on a window that changed under it is dropped rather than merged.hasNewerMessagesstays true for the whole of a jumped window, so the "Jump to latest" affordance and the newer-edge trigger never disappear on a centred window that already reaches the room's newest row.returnToLatest()reloads the newest page through the normal live loader and setslivebefore issuing the request, so inserts arriving during the round trip are folded in by the ordinary row precedence and the deferred inserts are rendered and acknowledged when it lands; a failed reload stays jumped with the window intact. A newer page that reports it reached the tail runsreturnToLatest()rather than flipping in place. Context pages are validated by their own rule (room-scoped, distinct, confirmed, newest-first, at most the requested limit, and a centred page must contain its target), never by relaxing the strictly-older history page validator. ConvoKitMessageListViewandConvoKitConversationViewgain the reply/jump surface:messageContextBuilder,onReplyToMessage,replyPreviewByMessageId,onJumpToMessage,highlightedMessageId,onHighlightHandled,hasNewerMessages,isLoadingNewerandonLoadNeweron both, plusreplyTarget,onCancelReplyandonReturnToLateston the conversation view. Reply and jump state stays host-owned and the views are pure functions of those props; with the callbacks absent the markup is identical to 0.8.0, and a row that quotes nothing renders exactly as it did.ConvoKitConversationwires every prop to the controller.- Default rows offer "Reply" in the existing actions sheet on any confirmed
row the caller's role allows, and a row whose
replyToMessageIdis set renders a quoted block above its text with the three preview states; the block is activatable and callsonJumpToMessage. The composer shows a cancellable "Replying to - New
ConvoKitMessageItemScopeandConvoKitMessageItemContextBuilder, exposed as the namedmessageContextBuilderbesidemessageBuilder.ConvoKitMessageItemBuilderkeeps its six positional parameters unchanged; the scope carries all of them pluscanReply,reply,replyPreview,replyTargetUnavailable,isReplyandjumpToReplyTarget, and grows with later releases. When both builders are supplied the scope builder wins. ConvoKitUiThemeDatagainshighlightColor(andeffectiveHighlightColor), the flash behind a jumped-to row; null usesprimaryColorat low opacity, mirroringbadgeColor.- Breaking for hand-written adapters, as
editMessage/deleteMessagewere in 0.8.0:ConvoKitUiClientgains requiredgetMessageContextandgetReplyPreviews, and itssendMessagegains an optionalString? replyToMessageId.DefaultConvoKitUiClientforwards all three to the SDK. Dartimplementsadapters must add them; see the adapter notes in the README for the wire contract each one owes. - Mixed fleet: against a 0.8 backend both new routes answer with an
unmatched-route 404 that carries no
code. That is a missing backend, not a missing message — the ids stay unresolved rather than becoming "unavailable", the failure surfaces once, andstate.canJumpToMessages/state.canResolveReplyPreviewsgo false for the life of the loaded session so the affordances disappear instead of failing repeatedly. A coded404 MESSAGE_NOT_FOUNDnever trips that. A 0.8 client against a 0.9 backend ignores the new row key. - Requires
convokit_flutter>=0.9.0 <0.10.0, which addsMessage.replyToMessageId,Message.copyWith,ReplyPreview,MessageContextPage,ConvoKit.getReplyPreviewsandConvoKit.getMessageContext. Every row rebuild in this package now goes throughcopyWith, so no field is silently dropped when a pending send is confirmed or a raw row image is re-wrapped with the attachments it lacks. - Mixed fleet, sending: a pre-0.9 backend parses only
textandmedia, so it DROPS an unknownreplyToMessageIdand stores the message with no reference. The optimistic row shows the quote and the confirmed row replaces it without one, so the quote visibly disappears on send. The message itself is delivered; only the reference is lost. This is not detected — the send response is the only signal, and acting on it would mean a second request or rewriting a row that is already delivered.
0.8.0 #
- Edit and delete your own messages.
ConvoKitConversationControllergainsvoid startEditing(String messageId),void cancelEditing(),Future<bool> saveEdit(String text)andFuture<bool> deleteMessage(String messageId);ConvoKitConversationStategainsMessage? editingMessage(the snapshot captured bystartEditing),canEditMessagesandcanDeleteMessages(a session is loaded and the caller's role, fromConversation.membership.roleor the caller's participant row when known, is notREAD; every Dart adapter implements the members, so these never reflect adapter support).startEditingis a no-op unless the row is rendered, sent by the connected user, confirmed, not deleted and the role allows it;deleteMessagerequires an own confirmed row. Neither sends a typing update. saveEditsends the trimmed text (empty asnull, which clears the caption of a message with attachments; a text-only message cannot be saved empty and sends nothing) with the snapshot'srevision, never the live row's, through the adapter'seditMessage. Success merges the response under the row precedence below, ends edit mode and returns true. A 409REVISION_CONFLICTreloads the row once throughgetMessage: the row shows the server's content,editingMessageis replaced by it so the next save carries the fresh revision,state.errorcarries the conflict and edit mode stays on (false). The same conflict state is entered without a request when a row for the edited id with a higher revision reaches the controller while editing (a row image, a hydration, a reconcile or a refresh); while this controller's own save is in flight (its echo carries the next revision) the check is deferred until the request settles, so a newer row that landed during a failed save is a conflict as well. A 404 with codeMESSAGE_NOT_FOUNDon the save or on the reload removes the row and ends edit mode; every other failure (a 403, a 404 without that code such as an older backend's unmatched-route answer, a network or server error, a reload failure) setsstate.error, keeps the row and keeps edit mode so views keep the draft. None of these evict the loaded history.deleteMessagekeeps the row until the adapter answers: success or a 404 with codeMESSAGE_NOT_FOUNDtombstones and removes it, retargets the read acknowledgement when it was the target, and ends edit mode when it was the edited row, so a late edit response, row image or hydration for the id is dropped and the room's ownmessage_deletedafterwards is a no-op; every other failure setsstate.errorand keeps the row. A deletion from another device (onMessageDeleted), aMESSAGE_NOT_FOUNDanswer or a reconcile that no longer finds the edited row also ends edit mode. Deleting cannot be undone; files already received cannot be retracted.- Row precedence: when two rows for one id carry different
revisions the higher wins and a lower one never overwrites it (a stale edit response cannot rewind a newer live row); equal revisions, including pending rows and every row against a 0.7 backend, keep theupdatedAt ?? createdAtrule with its complete-wins-on-ties behaviour. Provisional rows built from raw row images now carryrevision. ConvoKitConversationViewgainsMessage? editingMessage,ValueChanged<Message>? onEditMessage,ConvoKitSaveEditCallback? onSaveEdit(FutureOr<bool> Function(Message, String), false keeps the edited text and edit mode like a failed send keeps its draft),VoidCallback? onCancelEdit,ConvoKitDeleteMessageCallback? onDeleteMessage(FutureOr<bool> Function(Message)) andConvoKitDeleteConfirmation? confirmDelete(FutureOr<bool> Function(Message));ConvoKitMessageListViewgainsonEditMessage,onDeleteMessageandconfirmDelete;ConvoKitConversationgainsconfirmDeleteand wires the rest to its controller (the row actions only whilestate.canEditMessages/state.canDeleteMessages). Edit mode is owned by the host or the controller; the view is a function ofeditingMessage. The one submit callback handed to the default composer and tocomposerBuilder'ssendsaves throughonSaveEditwhileeditingMessageis set and sends otherwise, so custom composers need no branching (they readcontroller.state.editingMessageand callcancelEditingfor their own banner). Entering edit mode stashes the unsent draft and prefills the field with the message text without reporting typing; Cancel and a successful save restore the stash and reportonTypingChanged(stash has text); an external clear (the row was removed) keeps text the user changed and restores the stash only when the field is empty or still equals the message text; a refreshed snapshot with the same id (a conflict) updates the banner and leaves the field alone; the field is never cleared while a save is in flight.- Default composer edit mode: an "Editing message" banner (a live region)
with the original text and a visible "Cancel" (tooltip "Cancel editing"),
and the primary action becomes a check icon with the tooltip "Save
message" ("Send message" when not editing), enabled when the trimmed field
is non-empty or the edited row has attachments (caption clearing). Default
rows: the connected user's own confirmed rows gain a long press and the
"Message actions" accessibility action opening a bottom sheet with "Edit
message" / "Delete message" (only the callbacks that are set), when the
view has the callbacks and the caller's role, when known, is not
READ. Delete asks "Delete this message?" with "Cancel" (tooltip "Cancel delete") and "Delete" (tooltip "Confirm delete");confirmDeletereplaces that dialog. Rows that are not eligible (foreign, pending,READ, or views without the callbacks) render exactly as in 0.7.0. Every row whoseMessage.isEditedis true shows "Edited" beside its time, for every sender.ConvoKitMessageItemBuilder,ConvoKitComposerBuilderand every other builder typedef are unchanged; custom rows readmessage.isEditedand call the controller. - 0.8.0 adapter change:
ConvoKitUiClientgainsFuture<Message> editMessage(String messageId, {required String? text, required int revision})andFuture<void> deleteMessage(String messageId). Customimplementsadapters must add both: send both keys, a null text as JSONnull, answer a stale revision with aConvoKitExceptionwhosestatusCodeis 409 andcodeisREVISION_CONFLICT, and a missing, deleted or inaccessible message with a 404 whosecodeisMESSAGE_NOT_FOUND(a 404 without that code is treated as an error that keeps the row). The default adapter forwards toConvoKit.editMessageandConvoKit.deleteMessage(PATCH/DELETE /api/v1/messages/:id/own). - Requires
convokit_flutter0.8.x (Message.revision,isEdited) and the coordinated backend release. Against a 0.7 backend every row is revision 0, no "Edited" caption appears, and the author routes answer an uncoded 404 that surfaces asstate.errorwith the row and edit mode kept.
0.7.0 #
- Private "mark unread".
ConvoKitConversationListControllergainsFuture<void> markUnread(String conversationId)andFuture<bool> clearUnread(String conversationId, {int? ifVersion}), which call the adapter'smarkConversationUnread/clearConversationUnread(POST/DELETE /api/v1/conversations/:id/unread).clearUnreadreturns the response'scleared("this request removed the marker", false for a no-op, which is not an error). On any 200 (a mark, or a clear withclearedtrue or false) the response'sunreadMarkedAtandprivateStateVersionreplace the current summary's as one unit andisUnreadis recomputed (unreadCount > 0 || unreadCountCapped || unreadMarkedAt != null), only when the response version is at least the stored one; a lower version (a delayed response behind a newer action) leaves the summary untouched. Failures are recorded instate.errorwithout evicting rows. Other devices learn of a mark or clear through the existinginboxActivityrefresh. In legacy mode (custompageLoader, or a backend without the inbox endpoint) the adapter is still called but there is no summary to patch. There is no built-in row gesture; wire the action through a custom row, menu or the controller. state.summariescarries the core SDK's newInboxSummary.isUnread,unreadMarkedAtandprivateStateVersion, through initial pages,loadMoremerges and refreshes ("later wins").- Default rows: the unread predicate (bold title) is
isUnread || unreadCount > 0 || unreadCountCapped, so consumer-built summaries that omitisUnreadkeep their badges. A count or a capped count still rendersConvoKitUnreadBadge(99+when capped, even with a marker); a marker with a count of 0 renders the new numberlessConvoKitUnreadDot(an 8-point circle oneffectiveBadgeColor, announced asUnread, never as0 unread).ConvoKitUnreadBadgeis unchanged; rows without a summary androwBuilder/itemBuildersignatures are unchanged. - Room controllers capture
Conversation.membership.privateStateVersion(and whetherunreadMarkedAtwas set) once per open, from the first conversation DTO of the loaded session (loadInitial, or the first successful reconcile after a transient first-load failure, which then also appliesmarkReadOnLoad), never from a later reconcile, and reset it with the loaded data. Every targeted acknowledgement of that open sendsmarkConversationRead(id, throughMessageId:, privateStateVersion:), so the server clears the connected user's own marker only while that version is current; a DTO withoutmembership(a 0.6 backend) sends no version. An empty room opened with the marker set has nothing to acknowledge, so the controller callsclearConversationUnread(id, ifVersion: captured)once per open, under themarkReadOnLoad/markRead()triggers and the same visibility gating (deferred while hidden, issued onsetVisible(true)); never once a row is rendered;cleared: falseis not an error and failures take the existing fail path. Room controllers still never send an untargeted acknowledgement and never fabricate read or marker state locally. inboxActivitynow also follows a change to the connected user's own marker (a mark, a clear, or an acknowledgement that clears it); the throttle and the room controllers' indifference to it are unchanged.- 0.7.0 adapter change:
ConvoKitUiClient.markConversationReadgains the named parameterint? privateStateVersion(markConversationRead(String conversationId, {String? throughMessageId, int? privateStateVersion})), and the interface gainsFuture<ConversationPrivateState> markConversationUnread(String conversationId)andFuture<ClearUnreadResult> clearConversationUnread(String conversationId, {int? ifVersion}). Customimplementsadapters must add all three: forward the version unchanged (dropping it means the room never clears the marker), return the private state from the mark and clear responses, and answer a no-op clear withcleared: falserather than an error. Return the caller's own membership asConversation.membershipfromgetConversationwhen the backend provides it. The default adapter forwards toConvoKit.markConversationRead,ConvoKit.markConversationUnreadandConvoKit.clearConversationUnread. - Requires
convokit_flutter0.7.x and the coordinated backend release. Against a 0.6 backend the room sends no version and the marker is never cleared by the room; 0.6 lists ignoreisUnread.
0.6.0 #
- Inbox previews and accurate unread counts. SDK-backed
ConvoKitConversationListControllers pageGET /api/v1/inboxby opaque cursor in activity order (activityAt desc, id desc) instead ofgetConversationsby offset.state.summariesmaps every loaded conversation id to the core SDK'sInboxSummary(latest message preview,unreadCount,unreadCountCapped, the caller'sreadPosition/lastReadAtandactivityAt);state.currentUserIdcarries the connected user id while the list is bound to a session in inbox mode (null without a session, in legacy mode and after disposal).state.conversations, filters, predicates, comparators andonConversationSelectedare unchanged. - Default rows show the preview (
You:for the connected user's own message,<name>:in rooms with more than two participants, the trimmed text orPhoto/ file name /File/Location/Contactfor a media-only message), the activity time in the device zone, a bold title while unread and an unread badge (99+above 99 or when the server capped the count) announced as<count> unread. Rows without a summary render as before. - New
ConvoKitInboxRow { conversation, summary, index, currentUserId }andConvoKitInboxItemBuilder, exposed asrowBuilderonConvoKitConversationListViewandConvoKitConversationList; it wins overitemBuilder, whose signature is unchanged. Controlled lists acceptsummariesandcurrentUserId.convoKitInboxPreview,convoKitInboxTimeLabelandConvoKitUnreadBadgeare public so custom rows can reuse the default rules. - New theme token
ConvoKitUiThemeData.badgeColor(optional; defaults toprimaryColorthrougheffectiveBadgeColor), incopyWithandlerp. - Live updates: the list subscribes to the core SDK's
inbox_activitysignal (message insert or edit, read-position advance) and refreshes at most once peractivityRefreshWindowMs(default 500; 0 refreshes immediately) using a max-wait timer that later signals do not extend.inboxChanges(structural changes, verified joins and rejoins) still refreshes immediately and drops any pending activity timer. Room controllers ignore activity signals. - Refresh walks the inbox from its head with
limit = min(100, target - consumed)wheretarget = max(pageSize, loadedCount), continues past fully hidden head pages, merges pages by conversation id (a later entry wins, then re-sorted by activity), and swaps rows, summaries, cursor andhasMoreatomically. Pages are validated by shape (entries.length <= limit, non-blank unique ids) and by cursor advance (nextCursormust differ from the requested cursor; an empty page with a cursor is invalid: "Inbox pagination did not advance"). A full page of already-loaded ids is legitimate in inbox mode; the offset-era "did not advance" rule now applies to the legacy path only. - A 404 from
listInbox(backend without the route) switches the controller to the offsetgetConversationspath for the rest of its session without clearing rows (summaries become empty), warns once throughdebugPrint, and re-runs the same operation;loadInitial()retries the inbox endpoint. 401/403 from either endpoint and 404 from the legacy endpoint still evict; 400 (for exampleINVALID_CURSOR) keeps rows and setsstate.error. - Ordering: server order is authoritative within a page and the local
(activityAt desc, id desc)order is applied whenever pages are merged. SettingConvoKitConversationFilter.comparatorreplaces it. CustompageLoaders keep offset requests, creation order and no summaries. - Inline error retry requests the next page when
hasMoreis true andonLoadMoreis bound (bypassing the view's duplicate-request guard), otherwise callsonRefresh; the default button renders only when one of those callbacks exists. - 0.6.0 adapter change:
ConvoKitUiClientgainsFuture<InboxPage> listInbox({required int limit, String? cursor, required bool archived})andStream<void> get inboxActivity. Customimplementsadapters must add both: forwardlistInboxto the core SDK (or serve the same page shape) and reject an absent endpoint with aConvoKitExceptionwhosestatusCodeis 404 so the controller falls back togetConversations; emit empty activity signals (never synthesised on joins) or returnconst Stream.empty(). The default adapter forwards toConvoKit.listInboxandConvoKit.realtime.onInboxActivity(ConvoKit.clientId). - Requires
convokit_flutter0.6.x and the coordinated backend release.
0.5.0 #
- Compute read receipts from precise read positions.
state.readPositionByUserIdholds the newest message each participant has confirmed reading as the server's(createdAt, id)cursor;readerIdsFor(message)and the default rows apply one rule: the position when present, otherwise the acknowledgement time (readAtByUserId, kept for custom "seen at" renderers and legacy memberships). Equal creation times fall back to id order. Positions only advance; the connected user's own read is never fabricated from the device clock. ConvoKitConversationViewandConvoKitMessageListViewacceptreadPositionByUserIdbesidereadAtByUserId;readReceiptBuilder,messageBuilderandreadersResolversignatures are unchanged.- Acknowledge through a concrete message. Every automatic read
(
markReadOnLoad,markReadOnReceive) andmarkRead()targets the newest non-pending row of the rendered list by(createdAt, id), never a raw realtime row: a media-only message is acknowledged once hydration renders it, a refresh that discovers new foreign rows acknowledges the newest, and a room with nothing rendered sends nothing. One request is in flight at a time; a follow-up resolves its target when sent and is skipped at or below the last acknowledged target. - Gate acknowledgements on visibility.
ConvoKitConversationController.setVisible(bool)defers reads while hidden and re-issues only a suppressed one on becoming visible; an unreported state counts as visible.ConvoKitConversationmapsAppLifecycleState.resumedto visible and paused, inactive, hidden and detached to hidden through aWidgetsBindingObserver. With bothmarkReadOnLoadandmarkReadOnReceivefalse no request is ever sent, including on visibility changes. - Retarget a lost acknowledgement target. A targeted read rejected with
ConvoKitException.code == 'MESSAGE_NOT_FOUND', or deletion (event or hydration 404) of the in-flight or last acknowledged target, marks that id unacknowledgeable and re-issues once for the next newest rendered row without surfacing an error. A 404 without that code (membership gone) still surfaces as an error and evicts the room like any other access denial. - 0.5.0 adapter change:
ConvoKitUiClient.markConversationReadis nowmarkConversationRead(String conversationId, {String? throughMessageId}). Customimplementsadapters must add the parameter, forward it to the core SDK (or their backend), and reject a missing target with aConvoKitExceptioncarryingcode: 'MESSAGE_NOT_FOUND'. The default adapter forwards it. - Requires
convokit_flutter0.5.x and the coordinated backend release. Mixed fleet: precise receipts need the sender and the reader on 0.5; 0.4 readers keep timestamp semantics and still parse the additive payloads.
0.4.1 #
- Announce the outgoing status icon as
Sent(single check) orRead(double check) to assistive technology. ConvoKit does not report recipient delivery; visible layout, pending rows and read indicators are unchanged.
0.4.0 #
- Refresh SDK-backed inboxes automatically on private app invalidations and verified joins; preserve filters, loaded pages and visible rows while fetching.
- Reconcile open rooms after membership, metadata and cascade changes; discard old-session responses and clear caches on authoritative access denial.
- Replace optimistic bubbles atomically using
clientMessageIdfrom live, history or HTTP confirmation. Never match by text or media similarity. - Preserve media-only previews, newer edits and deletions when HTTP finishes late; a lost acknowledgement cannot turn a confirmed send into a failed draft.
- Breaking custom-client additions:
inboxChanges, forwarding the optional sendclientMessageId, and preserving that field on returned messages. Requires core 0.4.0 and the coordinated backend release.
0.3.0 #
-
Renew typing during continued input without broadcasting every keystroke; stop on idle and ignore failures from superseded or disconnected requests.
-
Fetch complete related media after raw row events, retain images/files during edits, and discard stale, deleted or retired-session hydration results.
-
Bound and coalesce lookups across reloads; reconcile all viewed live messages and retain snapshot-discovered deletion markers against delayed send ACKs.
-
Reconcile pending sends only by the acknowledged ID, never matching content. Release send state without waiting for the typing-stop request.
-
Require
getMessage(id)on custom adapters and the coordinated core SDK'sMessage.updatedAtfield. Add real SDK/provider and decoded-image widget tests. -
Consume authorized ID-only private deletion events and remove affected rows. Retain deletion markers across reconciliation so delayed HTTP responses and older Realtime rows cannot resurrect messages; cancel listeners on session end.
-
Preserve a live edit that arrives before the original send acknowledgement.
-
Reconcile missed messages, loaded edits/deletions and persisted receipts on private room rejoin without clearing pending sends or the loaded view.
-
Page history by a timestamp/ID cursor; preserve live changes arriving during REST reads and prevent older receipts from rewinding the displayed read state.
-
Bind conversation and inbox controllers to a session identity, discard late work after user/session replacement, and evict room caches on access denial.
-
Expire remote typing indicators and clear them on interruption.
-
Breaking custom-client contract: implement
sessionIdentity,connectionEvents,onMessageDeleted, and thegetMessages(before:)value cursor. ReplaceMessageChangeType.deletewithMessageDeletedEventon the separate deletion stream. The default adapter requires the core 0.3.x connection/cursor/deletion APIs. Do not publish this source with the existing package versions/dependency floor; coordinate new SDK/UI versions and backend deployment first.
0.2.2 #
- Show
Sending…for optimistic messages instead of a device-clock timestamp that can jump after acknowledgement. - Keep pending messages after canonical history and exclude them from read receipts.
- Continue displaying acknowledged server timestamps in the viewer's local timezone.
0.2.1 #
- Show outgoing messages immediately while the request completes, then reconcile the server response and realtime echo without duplicates.
- Clear the composer immediately and restore its draft after a failed send when the user has not typed a replacement.
0.2.0 #
- Require ConvoKit Flutter SDK 0.2.x for private Realtime channels and publishable-key discovery.
- Preserve the existing Flutter component and configuration API.
0.1.4 #
- Document the managed ConvoKit API endpoint used by the core SDK.
- Remove endpoint configuration from the standard setup example.
- Support inserted, updated, and deleted message events from core SDK 0.0.4.
0.1.3 #
- Point the public package metadata to the accessible examples repository.
0.1.2 #
- Point the component gallery to the public examples-only repository.
- Add trusted GitHub Actions publishing for semantic-version tags.
0.1.1 #
- Add real screenshots rendered from the public component showcase.
- Document standard, branded support, and compact UI configurations.
- Add public repository and issue tracker metadata.
0.1.0 #
- Add SDK-backed and controlled conversation list components.
- Add filterable offset pagination and automatic infinite scrolling.
- Add SDK-backed conversation history, sending, realtime typing, and receipts.
- Add extensible message, media, header, composer, state, and theme builders.