convokit_flutter_ui 0.10.2 copy "convokit_flutter_ui: ^0.10.2" to clipboard
convokit_flutter_ui: ^0.10.2 copied to clipboard

Extensible, plug-and-play Flutter UI components for ConvoKit conversations and messaging.

Changelog #

0.10.2 #

  • Keep reaction picker action labels within the menu at larger text scales and narrow widths.

0.10.1 #

  • Use a compact reaction chip and message-anchored quick picker in default and custom rows. Add a bottom sheet with more choices and an input for a single custom emoji. Include a runnable reaction preview.

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 ConvoKitReactionBar for custom rows.

0.9.0 #

  • Quoted replies. ConvoKitConversationController gains void startReply(String messageId), void cancelReply() and a sendMessage that stamps the quoted id on the pending row and sends it as replyToMessageId, clearing the target on success; ConvoKitConversationState gains Message? replyTarget and bool canReplyToMessages plus bool 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, from Conversation.membership.role or the caller's participant row when known, is not READ. Replying and editing are mutually exclusive, and a removed row clears the target. The reference is write-once: saveEdit and 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 present null is 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 one getReplyPreviews call 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 with textTruncated set) 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 through state.error and 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 by dispose() 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 through getMessageContext and replaces the loaded window with it, leaving ConvoKitConversationState.windowMode == ConvoKitMessageWindowMode.jumped. Future<void> loadNewerMessages(), Future<void> returnToLatest() and void clearHighlight() are new, as are String? highlightedMessageId, bool hasNewerMessages, bool isLoadingNewer, windowMode, bool canJumpToMessages and bool canResolveReplyPreviews on 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 code MESSAGE_NOT_FOUND is 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 getMessageContext request instead of the tail-anchored page walk, and the owed reconcile is still owed and runs on the return to live; sendMessage rejoins the live tail first, keeping the reply target, and a failed return sends nothing and keeps the draft; jumpToMessage is 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. hasNewerMessages stays 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 sets live before 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 runs returnToLatest() 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.
  • ConvoKitMessageListView and ConvoKitConversationView gain the reply/jump surface: messageContextBuilder, onReplyToMessage, replyPreviewByMessageId, onJumpToMessage, highlightedMessageId, onHighlightHandled, hasNewerMessages, isLoadingNewer and onLoadNewer on both, plus replyTarget, onCancelReply and onReturnToLatest on 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. ConvoKitConversation wires 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 replyToMessageId is set renders a quoted block above its text with the three preview states; the block is activatable and calls onJumpToMessage. The composer shows a cancellable "Replying to
  • New ConvoKitMessageItemScope and ConvoKitMessageItemContextBuilder, exposed as the named messageContextBuilder beside messageBuilder. ConvoKitMessageItemBuilder keeps its six positional parameters unchanged; the scope carries all of them plus canReply, reply, replyPreview, replyTargetUnavailable, isReply and jumpToReplyTarget, and grows with later releases. When both builders are supplied the scope builder wins.
  • ConvoKitUiThemeData gains highlightColor (and effectiveHighlightColor), the flash behind a jumped-to row; null uses primaryColor at low opacity, mirroring badgeColor.
  • Breaking for hand-written adapters, as editMessage/deleteMessage were in 0.8.0: ConvoKitUiClient gains required getMessageContext and getReplyPreviews, and its sendMessage gains an optional String? replyToMessageId. DefaultConvoKitUiClient forwards all three to the SDK. Dart implements adapters 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, and state.canJumpToMessages / state.canResolveReplyPreviews go false for the life of the loaded session so the affordances disappear instead of failing repeatedly. A coded 404 MESSAGE_NOT_FOUND never 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 adds Message.replyToMessageId, Message.copyWith, ReplyPreview, MessageContextPage, ConvoKit.getReplyPreviews and ConvoKit.getMessageContext. Every row rebuild in this package now goes through copyWith, 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 text and media, so it DROPS an unknown replyToMessageId and 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. ConvoKitConversationController gains void startEditing(String messageId), void cancelEditing(), Future<bool> saveEdit(String text) and Future<bool> deleteMessage(String messageId); ConvoKitConversationState gains Message? editingMessage (the snapshot captured by startEditing), canEditMessages and canDeleteMessages (a session is loaded and the caller's role, from Conversation.membership.role or the caller's participant row when known, is not READ; every Dart adapter implements the members, so these never reflect adapter support). startEditing is a no-op unless the row is rendered, sent by the connected user, confirmed, not deleted and the role allows it; deleteMessage requires an own confirmed row. Neither sends a typing update.
  • saveEdit sends the trimmed text (empty as null, which clears the caption of a message with attachments; a text-only message cannot be saved empty and sends nothing) with the snapshot's revision, never the live row's, through the adapter's editMessage. Success merges the response under the row precedence below, ends edit mode and returns true. A 409 REVISION_CONFLICT reloads the row once through getMessage: the row shows the server's content, editingMessage is replaced by it so the next save carries the fresh revision, state.error carries 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 code MESSAGE_NOT_FOUND on 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) sets state.error, keeps the row and keeps edit mode so views keep the draft. None of these evict the loaded history.
  • deleteMessage keeps the row until the adapter answers: success or a 404 with code MESSAGE_NOT_FOUND tombstones 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 own message_deleted afterwards is a no-op; every other failure sets state.error and keeps the row. A deletion from another device (onMessageDeleted), a MESSAGE_NOT_FOUND answer 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 the updatedAt ?? createdAt rule with its complete-wins-on-ties behaviour. Provisional rows built from raw row images now carry revision.
  • ConvoKitConversationView gains Message? 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)) and ConvoKitDeleteConfirmation? confirmDelete (FutureOr<bool> Function(Message)); ConvoKitMessageListView gains onEditMessage, onDeleteMessage and confirmDelete; ConvoKitConversation gains confirmDelete and wires the rest to its controller (the row actions only while state.canEditMessages / state.canDeleteMessages). Edit mode is owned by the host or the controller; the view is a function of editingMessage. The one submit callback handed to the default composer and to composerBuilder's send saves through onSaveEdit while editingMessage is set and sends otherwise, so custom composers need no branching (they read controller.state.editingMessage and call cancelEditing for 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 report onTypingChanged(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"); confirmDelete replaces that dialog. Rows that are not eligible (foreign, pending, READ, or views without the callbacks) render exactly as in 0.7.0. Every row whose Message.isEdited is true shows "Edited" beside its time, for every sender. ConvoKitMessageItemBuilder, ConvoKitComposerBuilder and every other builder typedef are unchanged; custom rows read message.isEdited and call the controller.
  • 0.8.0 adapter change: ConvoKitUiClient gains Future<Message> editMessage(String messageId, {required String? text, required int revision}) and Future<void> deleteMessage(String messageId). Custom implements adapters must add both: send both keys, a null text as JSON null, answer a stale revision with a ConvoKitException whose statusCode is 409 and code is REVISION_CONFLICT, and a missing, deleted or inaccessible message with a 404 whose code is MESSAGE_NOT_FOUND (a 404 without that code is treated as an error that keeps the row). The default adapter forwards to ConvoKit.editMessage and ConvoKit.deleteMessage (PATCH / DELETE /api/v1/messages/:id/own).
  • Requires convokit_flutter 0.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 as state.error with the row and edit mode kept.

0.7.0 #

  • Private "mark unread". ConvoKitConversationListController gains Future<void> markUnread(String conversationId) and Future<bool> clearUnread(String conversationId, {int? ifVersion}), which call the adapter's markConversationUnread / clearConversationUnread (POST / DELETE /api/v1/conversations/:id/unread). clearUnread returns the response's cleared ("this request removed the marker", false for a no-op, which is not an error). On any 200 (a mark, or a clear with cleared true or false) the response's unreadMarkedAt and privateStateVersion replace the current summary's as one unit and isUnread is 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 in state.error without evicting rows. Other devices learn of a mark or clear through the existing inboxActivity refresh. In legacy mode (custom pageLoader, 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.summaries carries the core SDK's new InboxSummary.isUnread, unreadMarkedAt and privateStateVersion, through initial pages, loadMore merges and refreshes ("later wins").
  • Default rows: the unread predicate (bold title) is isUnread || unreadCount > 0 || unreadCountCapped, so consumer-built summaries that omit isUnread keep their badges. A count or a capped count still renders ConvoKitUnreadBadge (99+ when capped, even with a marker); a marker with a count of 0 renders the new numberless ConvoKitUnreadDot (an 8-point circle on effectiveBadgeColor, announced as Unread, never as 0 unread). ConvoKitUnreadBadge is unchanged; rows without a summary and rowBuilder / itemBuilder signatures are unchanged.
  • Room controllers capture Conversation.membership.privateStateVersion (and whether unreadMarkedAt was 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 applies markReadOnLoad), never from a later reconcile, and reset it with the loaded data. Every targeted acknowledgement of that open sends markConversationRead(id, throughMessageId:, privateStateVersion:), so the server clears the connected user's own marker only while that version is current; a DTO without membership (a 0.6 backend) sends no version. An empty room opened with the marker set has nothing to acknowledge, so the controller calls clearConversationUnread(id, ifVersion: captured) once per open, under the markReadOnLoad / markRead() triggers and the same visibility gating (deferred while hidden, issued on setVisible(true)); never once a row is rendered; cleared: false is 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.
  • inboxActivity now 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.markConversationRead gains the named parameter int? privateStateVersion (markConversationRead(String conversationId, {String? throughMessageId, int? privateStateVersion})), and the interface gains Future<ConversationPrivateState> markConversationUnread(String conversationId) and Future<ClearUnreadResult> clearConversationUnread(String conversationId, {int? ifVersion}). Custom implements adapters 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 with cleared: false rather than an error. Return the caller's own membership as Conversation.membership from getConversation when the backend provides it. The default adapter forwards to ConvoKit.markConversationRead, ConvoKit.markConversationUnread and ConvoKit.clearConversationUnread.
  • Requires convokit_flutter 0.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 ignore isUnread.

0.6.0 #

  • Inbox previews and accurate unread counts. SDK-backed ConvoKitConversationListControllers page GET /api/v1/inbox by opaque cursor in activity order (activityAt desc, id desc) instead of getConversations by offset. state.summaries maps every loaded conversation id to the core SDK's InboxSummary (latest message preview, unreadCount, unreadCountCapped, the caller's readPosition/lastReadAt and activityAt); state.currentUserId carries 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 and onConversationSelected are 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 or Photo / file name / File / Location / Contact for 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 } and ConvoKitInboxItemBuilder, exposed as rowBuilder on ConvoKitConversationListView and ConvoKitConversationList; it wins over itemBuilder, whose signature is unchanged. Controlled lists accept summaries and currentUserId. convoKitInboxPreview, convoKitInboxTimeLabel and ConvoKitUnreadBadge are public so custom rows can reuse the default rules.
  • New theme token ConvoKitUiThemeData.badgeColor (optional; defaults to primaryColor through effectiveBadgeColor), in copyWith and lerp.
  • Live updates: the list subscribes to the core SDK's inbox_activity signal (message insert or edit, read-position advance) and refreshes at most once per activityRefreshWindowMs (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) where target = 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 and hasMore atomically. Pages are validated by shape (entries.length <= limit, non-blank unique ids) and by cursor advance (nextCursor must 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 offset getConversations path for the rest of its session without clearing rows (summaries become empty), warns once through debugPrint, 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 example INVALID_CURSOR) keeps rows and sets state.error.
  • Ordering: server order is authoritative within a page and the local (activityAt desc, id desc) order is applied whenever pages are merged. Setting ConvoKitConversationFilter.comparator replaces it. Custom pageLoaders keep offset requests, creation order and no summaries.
  • Inline error retry requests the next page when hasMore is true and onLoadMore is bound (bypassing the view's duplicate-request guard), otherwise calls onRefresh; the default button renders only when one of those callbacks exists.
  • 0.6.0 adapter change: ConvoKitUiClient gains Future<InboxPage> listInbox({required int limit, String? cursor, required bool archived}) and Stream<void> get inboxActivity. Custom implements adapters must add both: forward listInbox to the core SDK (or serve the same page shape) and reject an absent endpoint with a ConvoKitException whose statusCode is 404 so the controller falls back to getConversations; emit empty activity signals (never synthesised on joins) or return const Stream.empty(). The default adapter forwards to ConvoKit.listInbox and ConvoKit.realtime.onInboxActivity(ConvoKit.clientId).
  • Requires convokit_flutter 0.6.x and the coordinated backend release.

0.5.0 #

  • Compute read receipts from precise read positions. state.readPositionByUserId holds 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.
  • ConvoKitConversationView and ConvoKitMessageListView accept readPositionByUserId beside readAtByUserId; readReceiptBuilder, messageBuilder and readersResolver signatures are unchanged.
  • Acknowledge through a concrete message. Every automatic read (markReadOnLoad, markReadOnReceive) and markRead() 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. ConvoKitConversation maps AppLifecycleState.resumed to visible and paused, inactive, hidden and detached to hidden through a WidgetsBindingObserver. With both markReadOnLoad and markReadOnReceive false 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.markConversationRead is now markConversationRead(String conversationId, {String? throughMessageId}). Custom implements adapters must add the parameter, forward it to the core SDK (or their backend), and reject a missing target with a ConvoKitException carrying code: 'MESSAGE_NOT_FOUND'. The default adapter forwards it.
  • Requires convokit_flutter 0.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) or Read (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 clientMessageId from 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 send clientMessageId, 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's Message.updatedAt field. 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 the getMessages(before:) value cursor. Replace MessageChangeType.delete with MessageDeletedEvent on 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.
0
likes
150
points
919
downloads

Documentation

API reference

Publisher

verified publisherconvokit.app

Weekly Downloads

Extensible, plug-and-play Flutter UI components for ConvoKit conversations and messaging.

Topics

#chat #messaging #realtime #ui

License

Apache-2.0 (license)

Dependencies

convokit_flutter, flutter

More

Packages that depend on convokit_flutter_ui