feat(MediaImageCard): add dropdown menu for marking media as watched/unwatched

This commit is contained in:
2026-05-03 22:09:46 +02:00
parent f2c46af410
commit 31c1b0dd92
9 changed files with 111 additions and 32 deletions

View File

@@ -1,10 +1,12 @@
package hu.bbara.purefin.ui.common.card
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.BoxScope
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
@@ -12,11 +14,22 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.MoreVert
import androidx.compose.material3.Card
import androidx.compose.material3.CardDefaults
import androidx.compose.material3.DropdownMenu
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.layout.ContentScale
@@ -26,6 +39,7 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
import hu.bbara.purefin.ui.common.image.PurefinAsyncImage
import hu.bbara.purefin.ui.model.MediaAction
@Composable
fun MediaImageCard(
@@ -34,6 +48,7 @@ fun MediaImageCard(
onClick: () -> Unit,
modifier: Modifier = Modifier,
subtitle: String? = null,
popupActions: List<MediaAction> = emptyList(),
imageModifier: Modifier = Modifier,
shapeSize: Dp = 12.dp,
imageAspectRatio: Float = 16f / 10f,
@@ -45,6 +60,8 @@ fun MediaImageCard(
val scheme = MaterialTheme.colorScheme
val shape = RoundedCornerShape(shapeSize)
var expanded by remember { mutableStateOf(false) }
Card(
onClick = onClick,
shape = shape,
@@ -67,26 +84,55 @@ fun MediaImageCard(
)
imageOverlay()
}
Column(modifier = Modifier.padding(textPadding)) {
Text(
text = title,
style = titleStyle,
fontWeight = FontWeight.SemiBold,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
subtitle
?.takeIf { it.isNotBlank() }
?.let { text ->
Spacer(modifier = Modifier.height(4.dp))
Text(
text = text,
style = subtitleStyle,
color = scheme.onSurfaceVariant,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
Row(
modifier = Modifier.fillMaxWidth(),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.SpaceBetween
) {
Column(modifier = Modifier.padding(textPadding)) {
Text(
text = title,
style = titleStyle,
fontWeight = FontWeight.SemiBold,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
subtitle
?.takeIf { it.isNotBlank() }
?.let { text ->
Spacer(modifier = Modifier.height(4.dp))
Text(
text = text,
style = subtitleStyle,
color = scheme.onSurfaceVariant,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
if (popupActions.isNotEmpty()) {
Box() {
IconButton(
onClick = { expanded = !expanded },
) {
Icon(
imageVector = Icons.Outlined.MoreVert,
contentDescription = "More actions"
)
}
DropdownMenu(
expanded = expanded,
onDismissRequest = { expanded = false },
) {
popupActions.forEach { action ->
DropdownMenuItem(
text = { Text(text = action.name) },
onClick = action.onClick
)
}
}
}
}
}
}
}

View File

@@ -0,0 +1,6 @@
package hu.bbara.purefin.ui.model
data class MediaAction(
val name: String,
val onClick: () -> Unit
)

View File

@@ -91,6 +91,7 @@ fun AppScreen(
library.name
)
},
onMarkWatched = viewModel::markAsWatched,
onProfileClick = {},
onCheckForUpdates = { updateViewModel.checkForUpdates() },
isCheckingForUpdates = isCheckingForUpdates,

View File

@@ -26,6 +26,7 @@ fun HomeScreen(
onRefresh: () -> Unit,
onMediaSelected: (MediaUiModel) -> Unit,
onLibrarySelected: (LibraryUiModel) -> Unit,
onMarkWatched: (MediaUiModel, Boolean) -> Unit,
onProfileClick: () -> Unit,
onCheckForUpdates: () -> Unit,
isCheckingForUpdates: Boolean,
@@ -71,6 +72,7 @@ fun HomeScreen(
onMediaSelected = onMediaSelected,
onLibrarySelected = onLibrarySelected,
onBrowseLibrariesClick = { onTabSelected(1) },
onMarkAsWatched = onMarkWatched,
modifier = Modifier.padding(innerPadding)
)
}

View File

@@ -42,6 +42,7 @@ fun HomeContent(
onMediaSelected: (MediaUiModel) -> Unit,
onLibrarySelected: (LibraryUiModel) -> Unit,
onBrowseLibrariesClick: () -> Unit,
onMarkAsWatched: (MediaUiModel, Boolean) -> Unit,
modifier: Modifier = Modifier
) {
val scheme = MaterialTheme.colorScheme
@@ -105,6 +106,7 @@ fun HomeContent(
item(key = "continue-watching") {
ContinueWatchingSection(
items = continueWatching,
onMarkAsWatched = onMarkAsWatched,
onMediaSelected = onMediaSelected
)
}

View File

@@ -15,6 +15,7 @@ import hu.bbara.purefin.ui.common.bar.MediaProgressBar
import hu.bbara.purefin.ui.common.card.MediaImageCard
import hu.bbara.purefin.ui.common.media.homeMediaSharedBoundsSource
import hu.bbara.purefin.ui.common.media.rememberHomeMediaSharedBoundsClick
import hu.bbara.purefin.ui.model.MediaAction
import hu.bbara.purefin.ui.model.MediaUiModel
@Composable
@@ -22,6 +23,7 @@ internal fun ContinueWatchingCard(
item: MediaUiModel,
sharedBoundsKey: String,
onMediaSelected: (MediaUiModel) -> Unit,
onMarkAsWatched: (MediaUiModel, Boolean) -> Unit,
modifier: Modifier = Modifier
) {
val scheme = MaterialTheme.colorScheme
@@ -34,6 +36,16 @@ internal fun ContinueWatchingCard(
title = item.primaryText,
subtitle = item.secondaryText,
onClick = onClick,
popupActions = listOf(
MediaAction(
name = "Mark as watched",
onClick = { onMarkAsWatched(item, true) }
),
MediaAction(
name = "Mark as unwatched",
onClick = { onMarkAsWatched(item, false) }
)
),
imageModifier = Modifier.homeMediaSharedBoundsSource(sharedBoundsKey),
shapeSize = 26.dp,
imageAspectRatio = 16f / 9f,

View File

@@ -21,6 +21,7 @@ import hu.bbara.purefin.ui.model.MediaUiModel
fun ContinueWatchingSection(
items: List<MediaUiModel>,
onMediaSelected: (MediaUiModel) -> Unit,
onMarkAsWatched: (MediaUiModel, Boolean) -> Unit,
modifier: Modifier = Modifier
) {
if (items.isEmpty()) return
@@ -47,7 +48,8 @@ fun ContinueWatchingSection(
ContinueWatchingCard(
item = item,
sharedBoundsKey = homeMediaSharedBoundsKey("continue-$index", item.id),
onMediaSelected = onMediaSelected
onMediaSelected = onMediaSelected,
onMarkAsWatched = onMarkAsWatched
)
}
}