fix(podcasts): bookmark button now reflects state + confirms with a toast

PodcastBookmarkButton read `note in bookmarks.public` — an identity-based
List<Note> containment that is unreliable for addressable shows/episodes
(the rendered note instance may differ from the one rebuilt from the
a-tag), so the icon never flipped and there was no way to tell a podcast
was already bookmarked.

Switch to the public bookmark id/address sets (the same reactive pattern
the working git-repository bookmark button uses): match note.address for
addressable notes and note.idHex otherwise. The icon now reliably shows
bookmarked vs not (filled/primary vs outline/onSurfaceVariant), and each
tap fires a confirming toast so the action is never silent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JGa1EM5KWyDo1o5Yr6sS18
This commit is contained in:
Claude
2026-07-01 13:22:31 +00:00
parent 496cda2f22
commit 056d54434e
2 changed files with 25 additions and 5 deletions
@@ -24,11 +24,13 @@ import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.vitorpamplona.amethyst.R
import com.vitorpamplona.amethyst.commons.icons.symbols.Icon
import com.vitorpamplona.amethyst.commons.icons.symbols.MaterialSymbols
import com.vitorpamplona.amethyst.model.AddressableNote
import com.vitorpamplona.amethyst.model.Note
import com.vitorpamplona.amethyst.ui.screen.loggedIn.AccountViewModel
import com.vitorpamplona.amethyst.ui.stringRes
@@ -39,6 +41,10 @@ import com.vitorpamplona.amethyst.ui.stringRes
* recommendation the dedicated favorites list (10054) was meant for, and the note then appears in
* the standard Bookmarks screen. Works for both regular events (e-tag) and addressable shows/
* episodes (a-tag) because [AccountViewModel.addPublicBookmark] branches on the note type.
*
* Bookmarked state is read from the public bookmark id/address **sets** (not `List<Note>`
* containment) so it reflects reliably for addressable notes and updates the moment the list
* changes; a toast confirms each add/remove so the action is never silent.
*/
@Composable
fun PodcastBookmarkButton(
@@ -46,27 +52,39 @@ fun PodcastBookmarkButton(
accountViewModel: AccountViewModel,
modifier: Modifier = Modifier,
) {
val bookmarks by accountViewModel.account.bookmarkState.bookmarks
.collectAsStateWithLifecycle()
val isBookmarked = note in bookmarks.public
val bookmarkState = accountViewModel.account.bookmarkState
val publicAddresses by bookmarkState.publicBookmarkAddressIdSet.collectAsStateWithLifecycle()
val publicEvents by bookmarkState.publicBookmarkEventIdSet.collectAsStateWithLifecycle()
val isBookmarked =
remember(note, publicAddresses, publicEvents) {
if (note is AddressableNote) {
note.address in publicAddresses
} else {
note.idHex in publicEvents
}
}
IconButton(
onClick = {
if (isBookmarked) {
accountViewModel.removePublicBookmark(note)
accountViewModel.toastManager.toast(R.string.bookmarks_title, R.string.podcast_bookmark_removed)
} else {
accountViewModel.addPublicBookmark(note)
accountViewModel.toastManager.toast(R.string.bookmarks_title, R.string.podcast_bookmark_added)
}
},
modifier = modifier,
) {
Icon(
symbol = if (isBookmarked) MaterialSymbols.Bookmark else MaterialSymbols.BookmarkBorder,
symbol = if (isBookmarked) MaterialSymbols.Bookmark else MaterialSymbols.BookmarkAdd,
contentDescription =
stringRes(
if (isBookmarked) R.string.remove_from_public_bookmarks else R.string.add_to_public_bookmarks,
),
tint = MaterialTheme.colorScheme.primary,
tint = if (isBookmarked) MaterialTheme.colorScheme.primary else MaterialTheme.colorScheme.onSurfaceVariant,
)
}
}
+2
View File
@@ -1011,6 +1011,8 @@
<string name="podcast_value_for_value">Value-for-Value</string>
<string name="podcast_value_split_percent">%1$d%%</string>
<string name="podcast_value_zap_split_hint">Zaps to this are split between:</string>
<string name="podcast_bookmark_added">Added to your bookmarks</string>
<string name="podcast_bookmark_removed">Removed from your bookmarks</string>
<string name="podcast_role_host">Host</string>
<string name="podcast_role_cohost">Co-host</string>
<string name="podcast_role_editor">Editor</string>