feat(chat): archive chats and filter the chat list by All, Unread and Groups - #1669
Merged
Merged
Conversation
The sources test module has no Robolectric, so the test covers only the no-database paths. The read/write paths are exercised over a real database by the archive store's test.
…mentioned or replied to An archived chat's message that neither @mentions the viewer nor replies to one of their messages is posted quietly (no sound, vibration or heads-up) while the app is in the background, and not posted while it is in the foreground, as iOS delivers it passive. Muted chats still post nothing.
…rypted DMs first The archive read and the mention/reply classification ran before authentication, when a cold start from a push has not opened the per-user database or loaded the viewer's profile, so an archived chat's push posted loudly. The planner now exposes planNotification, which the post path calls after authentication to re-decide. The classifier read inlined DM content as received, so a mention or reply in an encrypted DM never counted. It now opens the message through the new ChatCoordinator.openPushedChatMessage first.
…M throws The classifier now treats a throw from openPushedChatMessage as an unopened message, so the push is classified as not addressed to the viewer and posts silently instead of being dropped by execute's outer catch. Also moves the postNotification silent KDoc back onto postNotification.
7dp vertical padding and the count at full opacity, as iOS draws them.
ChatsViewModel combines the main and archived feeds and runs them through projectChatList, so the chips, the Archived row and their counts come from the one tested function. The selected filter and whether the chips have been revealed live in the ViewModel, so both survive opening a chat and coming back. Row mapping moves to ConversationMapping so the archived feed maps the same way.
… list The chips are the list's first item and the list opens on item 1, so they sit under the bar until a pull from the top brings them on. ChipRevealConnection settles the release in onPreFling with one spring that starts at the finger's velocity, and stops any scroll toward the top that did not start there at item 1. The settle decision is chipSettle, a pure function. A row's swipe now offers Archive inside and Mute outside; archiving shows a Chat archived snackbar with Undo. The Archived row shows under All when anything is archived; its tap is a no-op until the Archived chats screen lands.
… are on Parked, the chip row lies at zero alpha under the title bar, so a tap on the bar's lower edge reached an invisible chip and slid the row in. Touches now stop at the row while it is hidden; its semantics stay, so a screen reader can still reveal it. A reveal was recorded only once the settle spring came to rest, 300-500ms after the chips were fully on. A touch in that time cancelled the spring before the reveal was recorded, and the next scroll toward the top parked the chips again. The reveal is now recorded when the chips reach the top, or when a cancelled settle leaves more than half the row on screen. A list restored on the chips with a fresh ViewModel now counts them as revealed, and the Archived row's icon sits in the avatar column instead of one inset further in.
With only a few chats, everything after the chip row was shorter than the viewport, so the list could not hold item 1 at the top. It fell back to index 0 on the first frame, so the chips showed and the list could not scroll. iOS parks regardless by setting the scroll distance to the chips' height. A last item now makes up the difference: the viewport less the height of every item after the chips. Each of those items reports its height as it is measured. The list measures forward from item 1, so in a short list the filler's height is already right in the first pass. It takes nothing once the list is longer than the screen, and nothing while any row has not been measured. So the list never scrolls further past its last row than the chip row's height.
ChatsViewModel combined chatListFeed() and archivedChatListFeed(), two separate maps of the chat state with no dedupe. Archiving the last main chat could emit the new empty main list before the archived list caught up, flashing the no-chats prompt ahead of the Archived row, and every unrelated state change emitted twice. FeedOperations.feedWithArchived() builds both lists from one ChatState read and drops repeats; the Chats screen reads it through chatListFeeds(), first frame included.
If applying archive to a push threw, the whole notification was dropped. Fall back to posting it unchanged and trace the failure. PushMessageClassifier's runCatching around the suspending openPushedChatMessage also swallowed CancellationException; rethrow it.
Comments cited rules 1, 2, 3 and 5, which nothing in the repo defines. Also correct ChatListEntry.id (the app passes the base58 chat id) and note that tabBadge is read only by the shared fixture tests on Android.
Archiving a chat fades its row out and slides the rows below into the gap; Undo fades it back in. Filter changes animate the same way, and the empty states fade so their message doesn't land on rows still fading out. The Archived row fades inside its always-present slot. The Archived chats screen animates an unarchived row the same way and keeps its empty state in the list, so the last row fades out rather than the list being swapped for the message.
A solid white selected chip was the brightest thing on the dark chat list. Selected is now white at 16% with the label in the main text colour; unselected has no fill and uses the secondary text colour. Both colours animate on change.
CodeSnackbar was a full-width Material snackbar while iOS floated a capsule. It is now the pill both platforms share: surfaceVariant fill with a 0.5dp white 10% border, an optional 18dp icon, the message, and the action in its own white 12% pill. CodeSnackbarHost replaces Material's SnackbarHost, whose fade and scale cannot be turned off. It slides the toast up and fades it in, leaves the same way, and dismisses on a swipe down. A short toast stays 4 seconds, extended by the accessibility timeout. showSnackbar(SnackData) now replaces the toast on screen instead of queueing. The chat list passes the archive icon to its undo toast.
…ge in regular weight
Snackbars now draw through it: the bar's glass instead of the surfaceVariant pill and hairline, an 18dp leading icon in the main text colour, and the bar's horizontal inset instead of 12dp. FloatingToastHost adds a host that can rise out of the bar, hug its message, and pass taps through.
The toast is hosted at the app root, outside the haze source and under the bar in z, so its glass blurs the list and it grows up from behind the bar. Bar visibility moves into AppNavigationBarVisibility so the bar and the toast read the same route, modal, bill and card-expansion gate. With no bar on screen the toast slides up from the bottom.
The countdown, unlocked and already-unlocked messages leave the system Toast for FloatingToast. The toast hugs the message, swaps it in place on each tap, and takes no pointer input, so the version taps beneath it keep counting.
…pt is up The root toast host draws above the nav content, so a toast shown before a sheet, the bill or a BottomBarManager prompt opened stayed on top of it and took its taps (archive a chat, then open Mute on another within 4s). Covering the host now dismisses the toast on screen, and a toast shown while covered returns Dismissed without appearing, so the Menu countdown's next tick cannot bring it back over a sheet.
CodeSnackbar, CodeSnackbarHost and SnackData.showSnackbar had no callers after the move to FloatingToast; CodeScaffold's default snackbar host is now empty.
It sat above AppNavigationBarVisibility's own KDoc; AppNavigationBar already carries a fuller copy.
The theme's Medium weight resolves to the Demi file, and the message had been forced to Normal, which drew the Regular cut and read as a different face from the rest of the app.
Replaces the grow-out-of-the-bar entrance. One progress value drives both the offset and the alpha, so leaving plays the entrance backwards.
* origin/code/cash: build: update release manifest fix(chat): settle the chats list in one update after launch (#1665) # Conflicts: # apps/flipcash/features/tipping/src/main/kotlin/com/flipcash/app/tipping/internal/ChatsViewModel.kt # apps/flipcash/shared/chat/src/main/kotlin/com/flipcash/shared/chat/internal/delegates/FeedSyncDelegate.kt
Toasts go through the app's root FloatingToastHost, and nothing passed a host to this slot any more.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Adds chat archiving and All / Unread / Groups filter chips to the chat list, matching iOS in code-payments/code-ios-app#948 and the restyle in code-payments/code-ios-app#949.
Archiving
A swipe on a chat row now shows Archive as the inner action, with Mute staying the outer full-swipe action. Archiving shows a "Chat archived" toast with Undo. Archived chats move to an Archived chats screen, reached from an "Archived" row at the top of the list; a swipe there unarchives. Archived chats stay archived when new messages arrive. The Archive rows on the DM and group profile screens from the original plan are left out, as on iOS.
Archive state is local. It lives in a new
chat_archivetable (Room 40 → 41, auto-migration,41.jsoncommitted), read throughChatArchiveStore.FeedSyncDelegatesplits the feed into main and archived lists in one read (feedWithArchived), so archiving the last visible chat can't flash the empty state between two emissions. Leaving a chat or erasing the account removes its record. The group invite picker no longer offers archived chats, as on iOS.Filter chips
The chips are the list's first item and start scrolled just out of view. A pull from the top brings them in with the finger, and on release they settle on the chips or just past them in one fling from the nested-scroll pre-fling, rather than a second scroll after the overscroll. A fling from mid-list stops at the first chat. Once revealed they stay, including after opening a chat, and they always show while Unread or Groups is selected. A trailing filler keeps short lists able to park the chips out of view.
The "Archived" row shows only under All. Filter counts and the row's count come from
projectChatList, whichchat_archive.jsontests against the same cases iOS uses.Notifications
setSilent, no sound, vibration or heads-up) in the background, and not posted in the foreground. iOS delivers these as passive notifications.The archive check runs after
authenticateIfNeeded, so a push that cold-starts the app reads the signed-in user's database. Muted chats that aren't archived behave as before: Android still drops them, where iOS posts them passively.Toast
CodeSnackbaris replaced byFloatingToast, hosted once at the app root and reached throughLocalFloatingToastHost. It draws on the nav bar's glass, now shared throughModifier.floatingGlass, and is capped at the bar's width. A new toast replaces the current one. It is dismissed while a sheet, the bill or a bottom-bar prompt is up, since those draw inside each nav entry and the root toast would otherwise sit on top of them. The developer-mode countdown on the menu tab uses it too, with taps passing through to the version label.CodeScaffoldno longer has asnackbarHostslot.The shared design is in code-payments/flipcash-client-orchestrator#33.