Redesign bottom nav bar and home screen scroll/hero UX

Bottom bar now overlays content edge-to-edge with a scrim background
and top hairline instead of reserving fixed space, gains labels,
FluxaIcons, selection animation, and a calendar badge for new
releases. Home screen's filter/notification/avatar bar is pinned and
fades in as the hero scrolls past instead of scrolling away with it.
Hero auto-swipe now pauses during user interaction, gained a Play
button, capped height, and windowed dot indicators. Row "view all"
now navigates to the category grid instead of silently loading more
into the row.
This commit is contained in:
KhooLy 2026-07-17 06:17:18 +03:00
parent e773f3438b
commit 8f459a0f76
2 changed files with 292 additions and 122 deletions

View file

@ -1,6 +1,9 @@
package com.fluxa.app.shared
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
@ -17,6 +20,7 @@ import androidx.compose.foundation.layout.WindowInsetsSides
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.only
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.PaddingValues
@ -29,21 +33,13 @@ import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowForward
import androidx.compose.material.icons.filled.DateRange
import androidx.compose.material.icons.filled.Explore
import androidx.compose.material.icons.filled.Home
import androidx.compose.material.icons.filled.Notifications
import androidx.compose.material.icons.filled.VideoLibrary
import androidx.compose.material.icons.outlined.DateRange
import androidx.compose.material.icons.outlined.Explore
import androidx.compose.material.icons.outlined.Home
import androidx.compose.material.icons.outlined.VideoLibrary
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.darkColorScheme
import androidx.compose.material3.CircularProgressIndicator
@ -51,24 +47,37 @@ import androidx.compose.material3.Icon
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
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.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.layout.onGloballyPositioned
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import kotlinx.coroutines.delay
import kotlinx.datetime.LocalDate
import kotlinx.datetime.TimeZone
import kotlinx.datetime.toLocalDateTime
import kotlin.time.Clock
import kotlin.time.ExperimentalTime
import com.fluxa.app.common.AppStrings
import com.fluxa.app.shared.feature.catalog.CatalogAction
import com.fluxa.app.shared.feature.catalog.CatalogHomeUiState
import com.fluxa.app.shared.feature.catalog.CatalogItemUiModel
import com.fluxa.app.shared.feature.catalog.CategoryResultsScreen
import com.fluxa.app.shared.image.FluxaRemoteImage
import com.fluxa.app.ui.catalog.FluxaIcons
import com.fluxa.app.shared.feature.addonstore.AddonStoreAction
import com.fluxa.app.shared.feature.addonstore.AddonStoreScreen
import com.fluxa.app.shared.feature.addonstore.AddonStoreUiState
@ -162,6 +171,7 @@ fun FluxaApp(
onSourceSelectionBackRequested: () -> Unit = {},
onCategoryBackRequested: () -> Unit = {},
onCategoryItemSelected: (CatalogItemUiModel) -> Unit = {},
onCategorySelected: (id: String, title: String) -> Unit = { _, _ -> },
searchState: SearchUiState? = null,
onSearchAction: (SearchAction) -> Unit = {},
discoverState: DiscoverUiState? = null,
@ -205,7 +215,7 @@ fun FluxaApp(
modifier: Modifier = Modifier
) {
MaterialTheme(colorScheme = FluxaColorScheme) {
Column(
Box(
modifier = modifier
.fillMaxSize()
.background(FluxaColors.background)
@ -220,6 +230,11 @@ fun FluxaApp(
state.showNotifications -> "notifications"
else -> "dest:${state.destination}"
}
val isHomeActive = screenKey == "dest:${FluxaDestination.Home}" &&
deviceType != com.fluxa.app.ui.catalog.DeviceType.TV
var navBarHeightPx by remember { mutableIntStateOf(0) }
val density = LocalDensity.current
val navBarHeightDp = with(density) { navBarHeightPx.toDp() }
AnimatedContent(
targetState = screenKey,
transitionSpec = {
@ -227,7 +242,11 @@ fun FluxaApp(
.togetherWith(fadeOut(tween(120)))
},
label = "fluxa-screen-transition",
modifier = Modifier.weight(1f)
modifier = if (isHomeActive || !showNavigationBar) {
Modifier.fillMaxSize()
} else {
Modifier.fillMaxSize().padding(bottom = navBarHeightDp)
}
) { _ ->
when {
playerState?.content != null -> PlayerControlsSurface(
@ -411,6 +430,7 @@ fun FluxaApp(
onCatalogAction = onCatalogAction,
onNotificationsRequested = onNotificationsRequested,
onAvatarClick = { onDestinationSelected(FluxaDestination.Settings) },
onCategorySelected = onCategorySelected,
profileAvatarUrl = profileState?.activeProfile?.avatarUrl,
modifier = Modifier.fillMaxSize()
)
@ -425,7 +445,12 @@ fun FluxaApp(
FluxaNavigationBar(
destination = state.destination,
accentColorArgb = profileState?.activeProfile?.accentColorArgb,
onDestinationSelected = onDestinationSelected
language = state.language,
calendarState = calendarState,
onDestinationSelected = onDestinationSelected,
modifier = Modifier
.align(Alignment.BottomCenter)
.onGloballyPositioned { navBarHeightPx = it.size.height }
)
}
}
@ -435,50 +460,112 @@ fun FluxaApp(
private data class FluxaBottomNavItem(
val destination: FluxaDestination,
val selectedIcon: ImageVector,
val icon: ImageVector
val icon: ImageVector,
val showsCalendarBadge: Boolean = false
)
private val FluxaBottomNavItems = listOf(
FluxaBottomNavItem(FluxaDestination.Home, Icons.Filled.Home, Icons.Outlined.Home),
FluxaBottomNavItem(FluxaDestination.Discover, Icons.Filled.Explore, Icons.Outlined.Explore),
FluxaBottomNavItem(FluxaDestination.Calendar, Icons.Filled.DateRange, Icons.Outlined.DateRange),
FluxaBottomNavItem(FluxaDestination.Library, Icons.Filled.VideoLibrary, Icons.Outlined.VideoLibrary)
FluxaBottomNavItem(FluxaDestination.Home, FluxaIcons.BottomHome, FluxaIcons.BottomHomeOutline),
FluxaBottomNavItem(FluxaDestination.Discover, FluxaIcons.BottomDiscover, FluxaIcons.BottomDiscoverOutline),
FluxaBottomNavItem(FluxaDestination.Calendar, FluxaIcons.BottomCalendar, FluxaIcons.BottomCalendarOutline, showsCalendarBadge = true),
FluxaBottomNavItem(FluxaDestination.Library, FluxaIcons.BottomLibrary, FluxaIcons.BottomLibraryOutline)
)
@OptIn(ExperimentalTime::class)
private fun CalendarUiState.hasUnseenReleases(): Boolean {
val today = Clock.System.now().toLocalDateTime(TimeZone.currentSystemDefault()).date
return items.any { release ->
val date = runCatching { LocalDate.parse(release.dateIso) }.getOrNull()
date != null && date <= today && date.toEpochDays() >= today.toEpochDays() - 3
}
}
@Composable
private fun FluxaNavigationBar(
destination: FluxaDestination,
accentColorArgb: Long?,
onDestinationSelected: (FluxaDestination) -> Unit
language: String?,
calendarState: CalendarUiState?,
onDestinationSelected: (FluxaDestination) -> Unit,
modifier: Modifier = Modifier
) {
val selectedColor = accentColorArgb?.let { Color(it) } ?: Color.White
val inactiveColor = Color(0xFFA0A5AD)
Row(
modifier = Modifier
val hasCalendarBadge = calendarState?.hasUnseenReleases() == true
Column(
modifier = modifier
.fillMaxWidth()
.background(Color.Black)
.windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Bottom))
.padding(horizontal = 12.dp, vertical = 16.dp),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically
) {
FluxaBottomNavItems.forEach { item ->
val isSelected = item.destination == destination
Column(
modifier = Modifier
.weight(1f)
.clip(RoundedCornerShape(14.dp))
.clickable { onDestinationSelected(item.destination) }
.padding(vertical = 4.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center
) {
Icon(
if (isSelected) item.selectedIcon else item.icon,
contentDescription = null,
tint = if (isSelected) selectedColor else inactiveColor,
modifier = Modifier.size(28.dp)
.background(
Brush.verticalGradient(
colorStops = arrayOf(
0f to FluxaColors.background.copy(alpha = 0f),
0.4f to FluxaColors.background
)
)
)
) {
Box(
modifier = Modifier
.fillMaxWidth()
.height(1.dp)
.background(Color.White.copy(alpha = 0.08f))
)
Row(
modifier = Modifier
.fillMaxWidth()
.windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Bottom))
.padding(horizontal = 12.dp)
.padding(top = 10.dp, bottom = 10.dp),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically
) {
FluxaBottomNavItems.forEach { item ->
val isSelected = item.destination == destination
val tint by animateColorAsState(
targetValue = if (isSelected) selectedColor else inactiveColor,
label = "nav-item-tint"
)
val scale by animateFloatAsState(
targetValue = if (isSelected) 1.1f else 1f,
label = "nav-item-scale"
)
Column(
modifier = Modifier
.weight(1f)
.clip(RoundedCornerShape(14.dp))
.clickable { onDestinationSelected(item.destination) }
.padding(vertical = 4.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(2.dp)
) {
Box {
Icon(
if (isSelected) item.selectedIcon else item.icon,
contentDescription = null,
tint = tint,
modifier = Modifier
.size(24.dp)
.graphicsLayer(scaleX = scale, scaleY = scale)
)
if (item.showsCalendarBadge && hasCalendarBadge) {
Box(
modifier = Modifier
.align(Alignment.TopEnd)
.offset(x = 3.dp, y = (-1).dp)
.size(6.dp)
.clip(CircleShape)
.background(selectedColor)
)
}
}
Text(
text = AppStrings.t(language, item.destination.titleKey),
color = tint,
fontSize = 11.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis
)
}
}
}
}
@ -495,18 +582,22 @@ private fun FluxaHomeOverlayBar(
activeFilter: String,
language: String?,
profileAvatarUrl: String?,
scrimAlpha: Float,
onFilterSelected: (String) -> Unit,
onNotificationsRequested: () -> Unit,
onAvatarClick: () -> Unit
onAvatarClick: () -> Unit,
modifier: Modifier = Modifier
) {
val backgroundColor = androidx.compose.ui.graphics.lerp(
Color.Black.copy(alpha = 0.4f),
FluxaColors.background,
scrimAlpha
)
Row(
modifier = Modifier
modifier = modifier
.fillMaxWidth()
.background(
Brush.verticalGradient(
colorStops = arrayOf(0f to Color.Black.copy(alpha = 0.55f), 1f to Color.Transparent)
)
)
.background(backgroundColor)
.windowInsetsPadding(WindowInsets.safeDrawing.only(WindowInsetsSides.Top))
.padding(horizontal = 20.dp, vertical = 12.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
@ -514,6 +605,7 @@ private fun FluxaHomeOverlayBar(
Row(horizontalArrangement = Arrangement.spacedBy(20.dp)) {
FluxaHomeFilter.entries.forEach { filter ->
val selected = filter.value == activeFilter || (filter == FluxaHomeFilter.All && activeFilter.isBlank())
val underlineWidth by animateDpAsState(if (selected) 20.dp else 0.dp, label = "filter-underline")
Column(modifier = Modifier.clickable { onFilterSelected(filter.value) }) {
Text(
text = AppStrings.t(language, filter.titleKey),
@ -525,15 +617,15 @@ private fun FluxaHomeOverlayBar(
modifier = Modifier
.padding(top = 8.dp)
.height(2.dp)
.width(if (selected) 20.dp else 0.dp)
.background(if (selected) Color.White else Color.Transparent)
.width(underlineWidth)
.background(Color.White)
)
}
}
}
Row(verticalAlignment = Alignment.CenterVertically) {
Icon(
imageVector = Icons.Filled.Notifications,
imageVector = FluxaIcons.Notifications,
contentDescription = AppStrings.t(language, "auto.notifications"),
tint = Color.White,
modifier = Modifier.size(24.dp).clickable(onClick = onNotificationsRequested)
@ -566,6 +658,7 @@ private fun FluxaHomeContent(
onCatalogAction: (CatalogAction) -> Unit,
onNotificationsRequested: () -> Unit,
onAvatarClick: () -> Unit,
onCategorySelected: (id: String, title: String) -> Unit,
profileAvatarUrl: String?,
modifier: Modifier
) {
@ -584,77 +677,101 @@ private fun FluxaHomeContent(
}
val heroItems = state.catalogHome.heroItems
val showHero = state.catalogHome.showHeroSection && heroItems.isNotEmpty()
val listState = rememberLazyListState()
var heroHeightPx by remember { mutableIntStateOf(0) }
val scrimAlpha by remember {
androidx.compose.runtime.derivedStateOf {
when {
!showHero -> 1f
listState.firstVisibleItemIndex > 0 -> 1f
heroHeightPx <= 0 -> 0f
else -> (listState.firstVisibleItemScrollOffset.toFloat() / heroHeightPx).coerceIn(0f, 1f)
}
}
}
LazyColumn(
modifier = modifier,
contentPadding = PaddingValues(bottom = 120.dp),
verticalArrangement = Arrangement.spacedBy(24.dp)
) {
if (state.catalogHome.showHeroSection && heroItems.isNotEmpty()) {
item(key = "hero") {
Box {
Box(modifier = modifier) {
LazyColumn(
state = listState,
modifier = Modifier.fillMaxSize(),
contentPadding = PaddingValues(bottom = 24.dp),
verticalArrangement = Arrangement.spacedBy(24.dp)
) {
if (showHero) {
item(key = "hero") {
FluxaHomeHero(
items = heroItems,
language = state.language,
onCatalogAction = onCatalogAction
)
FluxaHomeOverlayBar(
activeFilter = state.catalogHome.activeFilter,
language = state.language,
profileAvatarUrl = profileAvatarUrl,
onFilterSelected = { filter -> onCatalogAction(CatalogAction.FilterChanged(filter)) },
onNotificationsRequested = onNotificationsRequested,
onAvatarClick = onAvatarClick
onCatalogAction = onCatalogAction,
modifier = Modifier.onGloballyPositioned { heroHeightPx = it.size.height }
)
}
}
items(
state.catalogHome.rows.filterNot { it.categoryType == "collection_folder" },
key = { it.id }
) { row ->
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
Row(
modifier = Modifier
.fillMaxWidth()
.then(
if (row.canLoadMore) {
Modifier.clickable { onCategorySelected(row.id, row.title) }
} else {
Modifier
}
)
.padding(horizontal = 20.dp, vertical = 4.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = row.title,
color = Color.White,
fontWeight = FontWeight.SemiBold,
fontSize = 16.sp,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f)
)
if (row.canLoadMore) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowForward,
contentDescription = AppStrings.t(state.language, "common.view_all"),
tint = Color.White.copy(alpha = 0.7f),
modifier = Modifier
.padding(start = 12.dp)
.size(20.dp)
)
}
}
LazyRow(
contentPadding = androidx.compose.foundation.layout.PaddingValues(horizontal = 20.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
items(row.items, key = { it.id }) { item ->
CatalogCard(
model = item.card,
onClick = { onCatalogAction(CatalogAction.ItemSelected(item)) }
)
}
}
}
}
}
items(
state.catalogHome.rows.filterNot { it.categoryType == "collection_folder" },
key = { it.id }
) { row ->
Column(verticalArrangement = Arrangement.spacedBy(12.dp)) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 20.dp),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically
) {
Text(
text = row.title,
color = Color.White,
fontWeight = FontWeight.Bold,
maxLines = 1,
overflow = TextOverflow.Ellipsis,
modifier = Modifier.weight(1f)
)
if (row.canLoadMore) {
Icon(
imageVector = Icons.AutoMirrored.Filled.ArrowForward,
contentDescription = AppStrings.t(state.language, "common.view_all"),
tint = Color.White.copy(alpha = 0.7f),
modifier = Modifier
.padding(start = 12.dp)
.size(20.dp)
.clickable {
onCatalogAction(CatalogAction.LoadMore(row.id))
}
)
}
}
LazyRow(
contentPadding = androidx.compose.foundation.layout.PaddingValues(horizontal = 20.dp),
horizontalArrangement = Arrangement.spacedBy(12.dp)
) {
items(row.items, key = { it.id }) { item ->
CatalogCard(
model = item.card,
onClick = { onCatalogAction(CatalogAction.ItemSelected(item)) }
)
}
}
}
if (showHero) {
FluxaHomeOverlayBar(
activeFilter = state.catalogHome.activeFilter,
language = state.language,
profileAvatarUrl = profileAvatarUrl,
scrimAlpha = scrimAlpha,
onFilterSelected = { filter -> onCatalogAction(CatalogAction.FilterChanged(filter)) },
onNotificationsRequested = onNotificationsRequested,
onAvatarClick = onAvatarClick,
modifier = Modifier.align(Alignment.TopCenter)
)
}
}
}
@ -701,12 +818,14 @@ private fun FluxaHomeSkeleton(modifier: Modifier = Modifier) {
}
private const val HERO_AUTO_SWIPE_DELAY_MS = 5000L
private const val HERO_MAX_DOTS = 5
@Composable
private fun FluxaHomeHero(
items: List<CatalogItemUiModel>,
language: String?,
onCatalogAction: (CatalogAction) -> Unit
onCatalogAction: (CatalogAction) -> Unit,
modifier: Modifier = Modifier
) {
val loop = items.size > 1
val virtualPageCount = if (loop) Int.MAX_VALUE else items.size
@ -714,33 +833,46 @@ private fun FluxaHomeHero(
val pagerState = rememberPagerState(initialPage = startPage, pageCount = { virtualPageCount })
if (loop) {
LaunchedEffect(pagerState) {
while (true) {
LaunchedEffect(pagerState.currentPage, pagerState.isScrollInProgress) {
if (!pagerState.isScrollInProgress) {
delay(HERO_AUTO_SWIPE_DELAY_MS)
pagerState.animateScrollToPage(pagerState.currentPage + 1)
if (!pagerState.isScrollInProgress) {
pagerState.animateScrollToPage(pagerState.currentPage + 1)
}
}
}
}
Box(modifier = Modifier.fillMaxWidth()) {
Box(modifier = modifier.fillMaxWidth()) {
HorizontalPager(
state = pagerState,
modifier = Modifier
.fillMaxWidth()
.aspectRatio(3f / 4f)
.heightIn(max = 560.dp)
) { page ->
val item = items[page % items.size]
FluxaHomeHeroSlide(item = item, language = language, onCatalogAction = onCatalogAction)
}
if (items.size > 1) {
val current = pagerState.currentPage % items.size
val windowRange = remember(items.size, current) {
if (items.size <= HERO_MAX_DOTS) {
items.indices
} else {
val half = HERO_MAX_DOTS / 2
val start = (current - half).coerceIn(0, items.size - HERO_MAX_DOTS)
start until (start + HERO_MAX_DOTS)
}
}
Row(
modifier = Modifier
.align(Alignment.BottomCenter)
.padding(bottom = 12.dp),
horizontalArrangement = Arrangement.spacedBy(6.dp)
) {
items.indices.forEach { index ->
val selected = index == pagerState.currentPage % items.size
windowRange.forEach { index ->
val selected = index == current
Box(
modifier = Modifier
.size(if (selected) 8.dp else 6.dp)
@ -837,6 +969,30 @@ private fun FluxaHomeHeroSlide(
textAlign = androidx.compose.ui.text.style.TextAlign.Center
)
}
Row(
modifier = Modifier
.padding(top = 4.dp)
.clip(RoundedCornerShape(8.dp))
.background(Color.White)
.clickable { onCatalogAction(CatalogAction.PlayRequested(item)) }
.padding(horizontal = 20.dp, vertical = 10.dp),
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically
) {
Icon(
imageVector = FluxaIcons.PlayArrow,
contentDescription = null,
tint = Color.Black,
modifier = Modifier.size(18.dp)
)
Text(
text = AppStrings.t(language, "common.play"),
color = Color.Black,
fontWeight = FontWeight.Bold,
fontSize = 14.sp,
modifier = Modifier.padding(start = 6.dp)
)
}
}
}
}

View file

@ -249,6 +249,15 @@ fun FluxaAppHost(
}
}
val detailState = detailStore?.state?.collectAsState()?.value
var pendingAutoPlayId by remember { mutableStateOf<String?>(null) }
LaunchedEffect(detailState?.content?.id, pendingAutoPlayId) {
val contentId = detailState?.content?.id
if (pendingAutoPlayId != null && contentId == pendingAutoPlayId) {
detailStore?.dispatch(DetailAction.Play())
pendingAutoPlayId = null
}
}
LaunchedEffect(catalogHome) {
appState.updateCatalogHome(catalogHome)
@ -339,11 +348,16 @@ fun FluxaAppHost(
appState.selectDetail(action.item)
}
}
if (action is CatalogAction.PlayRequested && action.item.type != "catalog_folder") {
pendingAutoPlayId = action.item.id
appState.selectDetail(action.item)
}
scope.launch {
catalogHomeStore.dispatch(action)
}
onCatalogAction(action)
},
onCategorySelected = { id, title -> appState.selectCategory(id, title) },
detailState = detailState,
onDetailAction = { action ->
if (action is DetailAction.RelatedItemSelected) {