Add/Remove Profile from Lists#1202
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces the ability to manage profile memberships within lists, adding database queries, repository methods, and a bottom sheet for selection. The reviewer identified a logic bug in the view model where state initialization could be incorrectly skipped and highlighted a data isolation issue where memberships should be scoped to the signed-in user. Additional suggestions include refactoring redundant flow logic for better idiomatic Kotlin and moving hardcoded strings to resources for localization.
| companion object { | ||
| @Composable | ||
| fun rememberListMemberPickerSheetState( | ||
| paneTransitionScope: PaneTransitionScope, |
There was a problem hiding this comment.
Compose modal bottom sheets do not share the same window with the app, they are dialogs backed by a separate window and therefor a separate window token. Due to this dialogs cannot have shared elements with the rest of the app, making the paneTransitionScope have no effect.
| } | ||
|
|
||
| @Composable | ||
| fun ListMemberPickerItem( |
There was a problem hiding this comment.
List members are profile oriented, so the ProfileWithViewerState Composable in the com.tunjid.heron.timeline.ui.profile package is more appropriate. If you go to the ListsScreen, the people tab has a layout for displaying list members. I think we should consolidate around this template.
There was a problem hiding this comment.
Oh I see, this is for displaying lists that we may want edit memberships of.
| override fun membershipsByProfile( | ||
| profileId: ProfileId, | ||
| ): Flow<List<ListMember>> = | ||
| savedStateDataSource.singleAuthorizedSessionFlow { signedInProfileId -> |
There was a problem hiding this comment.
This just fetches from the db with no network refresh. If you open the bluesky website and inspect network traffic, when adding a profile to the lists of the signed in user, they make a call to the API to the signed in profiles list members records to fetch all the list members the signed in user has created.
This is the only way they are able to tell if a profile already exists in a list or not. Its inefficient, but what we have to do to.
There should also be a createdListMembers method, and should be exposed on RecordRepository via BlueskyRecordOperations. It should list the listRecords XRPC to paginate list members on the app.bsky.graph.listitem collection. Only then can we verify that a profile is a member of a list or not.
There was a problem hiding this comment.
Okay I hear you and your approach makes sense, I have made changes regarding this.
b41ca46 to
ae9ee04
Compare
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request implements the functionality to manage profile memberships within user-created lists. Key changes include the addition of listMembersByProfile to the repository and DAO layers, the introduction of a ListMemberPickerSheetState for a new bottom sheet UI, and the integration of "Add to List" actions within the profile screen. Review feedback identifies a critical logic error in OfflineFirstRecordRepository where the signed-in user's ID is incorrectly used instead of the viewed profile's ID for membership queries. Additionally, it is noted that the current synchronization logic only fetches the first page of list items, which may lead to incomplete membership data, and a suggestion was made to use existing constants for collection names to improve maintainability.
| createdListMembers( | ||
| query = CreatedListMembersQuery( | ||
| profileId = signedInProfileId, | ||
| data = CursorQuery.Data( | ||
| page = 0, | ||
| cursorAnchor = Clock.System.now(), | ||
| limit = 15, | ||
| ), | ||
| ), | ||
| cursor = Cursor.Initial, | ||
| ).first() |
There was a problem hiding this comment.
Calling .first() on the flow returned by createdListMembers only syncs the first page of list items (currently limited to 15). If the viewed profile is a member of a list but that membership record isn't in the first 15 records of the user's app.bsky.graph.listitem collection, it won't be detected. Consider increasing the limit or ensuring all pages are synced to provide accurate membership status.
| savedStateDataSource.singleAuthorizedSessionFlow { signedInProfileId -> | ||
| combine( | ||
| listDao.listMembersByProfile( | ||
| profileId = query.profileId.id, |
There was a problem hiding this comment.
The pagination here will likely break as this db query does not match the network result. The DB query should fetch list members created by the signed in profile, it shouldn't need a profile Id.
| listItem: Listitem, | ||
| ) { | ||
| add( | ||
| ListMemberEntity( |
There was a problem hiding this comment.
Trying to save a list member without saving the profiles of the list members will cause errors because of failed foreign key constraints.
tunjid
left a comment
There was a problem hiding this comment.
Think a little big picture about this. You want to add or remove a profile from the signed in profiles lists. To do this reliably, you want to present to the signed in user if a profile is in their lists.
- What are the things you need to know to present this?
- Do you have a reliable way of getting this information?
…adProfileMutations
…ign-in and account switching
…ByProfile for filtered read
…ry with network result
913e3bd to
55efd07
Compare
What
Allows a signed-in user to add or remove any profile from their own lists directly from the profile screen, via a bottom sheet picker.
Changes
UI
ProfileActionsMenuto trigger the list membership editorListMemberPickerBottomSheetshows the signed-in user's lists with aFilterChipper item indicating current membership status (Add/Remove)ListMemberPickerItemusesRecordLayoutwithListMembershipChipdriven by a nullableListMember?presence means member, null means notData
membershipsByProfile(profileId)DAO query onlistMemberstable scoped to a subject profilemembershipsByProfilein the repository usingsingleAuthorizedSessionFlow, returningFlow<List<ListMember>>for the viewed profileViewModel / State
profileListMemberships: List<ListMember>andsignedInProfileListsHoldertoStateloadProfileMutationsnow usesflatMapLatestto resolveprofile.didbefore fetching memberships avoids unsafeProfileHandleOrIdcastAction.AddListMemberwith a correspondingenqueueMutationwritingFeedList.AddMemberto theWriteQueueAction.DeleteRecordsinceListMemberUriis aRecordUriWhy
The lists data was already loaded per profile tab via
ProfileScreenStateHolders.Records.Liststhe signed-in user's lists are sourced from a dedicatedsignedInProfileListsHolderso the correct lists always appear regardless of which profile is being viewed.Demo
XRecorder_20260412_01.mp4