Skip to content

Add/Remove Profile from Lists#1202

Open
joelmuraguri wants to merge 19 commits into
mainfrom
joel/allow-adding-profile-in-list-ui
Open

Add/Remove Profile from Lists#1202
joelmuraguri wants to merge 19 commits into
mainfrom
joel/allow-adding-profile-in-list-ui

Conversation

@joelmuraguri

@joelmuraguri joelmuraguri commented Apr 12, 2026

Copy link
Copy Markdown
Collaborator

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

  • Added a new entry in ProfileActionsMenu to trigger the list membership editor
  • Implemented ListMemberPickerBottomSheet shows the signed-in user's lists with a FilterChip per item indicating current membership status (Add/Remove)
  • ListMemberPickerItem uses RecordLayout with ListMembershipChip driven by a nullable ListMember? presence means member, null means not

Data

  • Added membershipsByProfile(profileId) DAO query on listMembers table scoped to a subject profile
  • Implemented membershipsByProfile in the repository using singleAuthorizedSessionFlow, returning Flow<List<ListMember>> for the viewed profile

ViewModel / State

  • Added profileListMemberships: List<ListMember> and signedInProfileListsHolder to State
  • loadProfileMutations now uses flatMapLatest to resolve profile.did before fetching memberships avoids unsafe ProfileHandleOrId cast
  • Added Action.AddListMember with a corresponding enqueueMutation writing FeedList.AddMember to the WriteQueue
  • Removal reuses the existing Action.DeleteRecord since ListMemberUri is a RecordUri

Why

The lists data was already loaded per profile tab via ProfileScreenStateHolders.Records.Lists the signed-in user's lists are sourced from a dedicated signedInProfileListsHolder so the correct lists always appear regardless of which profile is being viewed.

Demo

XRecorder_20260412_01.mp4

@joelmuraguri joelmuraguri requested a review from tunjid April 12, 2026 21:00

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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,

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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(

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 ->

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay I hear you and your approach makes sense, I have made changes regarding this.

@joelmuraguri joelmuraguri force-pushed the joel/allow-adding-profile-in-list-ui branch from b41ca46 to ae9ee04 Compare April 14, 2026 12:44
@joelmuraguri

Copy link
Copy Markdown
Collaborator Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +112 to +122
createdListMembers(
query = CreatedListMembersQuery(
profileId = signedInProfileId,
data = CursorQuery.Data(
page = 0,
cursorAnchor = Clock.System.now(),
limit = 15,
),
),
cursor = Cursor.Initial,
).first()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is valid.

savedStateDataSource.singleAuthorizedSessionFlow { signedInProfileId ->
combine(
listDao.listMembersByProfile(
profileId = query.profileId.id,

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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(

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trying to save a list member without saving the profiles of the list members will cause errors because of failed foreign key constraints.

@tunjid tunjid left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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?

@joelmuraguri joelmuraguri force-pushed the joel/allow-adding-profile-in-list-ui branch from 913e3bd to 55efd07 Compare April 16, 2026 13:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants