convokit_flutter_ui 0.7.0
convokit_flutter_ui: ^0.7.0 copied to clipboard
Extensible, plug-and-play Flutter UI components for ConvoKit conversations and messaging.
Changelog #
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.