mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 23:51:21 +02:00
Small optimizations to home page & nav drawer (#743)
## Description This PR has several micro optimizations to the home page & nav drawer. Basically it computes text off of the main thread and removes redundant calculations.
This commit is contained in:
parent
0639a7a1da
commit
fc0de2144d
7 changed files with 191 additions and 243 deletions
|
|
@ -6,6 +6,7 @@ import androidx.compose.runtime.Stable
|
|||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.buildAnnotatedString
|
||||
import com.github.damontecres.wholphin.ui.DateFormatter
|
||||
import com.github.damontecres.wholphin.ui.abbreviateNumber
|
||||
import com.github.damontecres.wholphin.ui.detail.CardGridItem
|
||||
import com.github.damontecres.wholphin.ui.detail.series.SeasonEpisodeIds
|
||||
import com.github.damontecres.wholphin.ui.dot
|
||||
|
|
@ -88,6 +89,15 @@ data class BaseItem(
|
|||
@Transient
|
||||
val ui =
|
||||
BaseItemUi(
|
||||
episodeCornerText =
|
||||
data.indexNumber?.let { "E$it" }
|
||||
?: data.premiereDate?.let(::formatDateTime),
|
||||
episdodeUnplayedCornerText =
|
||||
data.indexNumber?.let { "E$it" }
|
||||
?: data.userData
|
||||
?.unplayedItemCount
|
||||
?.takeIf { it > 0 }
|
||||
?.let { abbreviateNumber(it) },
|
||||
quickDetails =
|
||||
buildAnnotatedString {
|
||||
val details =
|
||||
|
|
@ -191,5 +201,7 @@ val BaseItemDto.aspectRatioFloat: Float? get() = width?.let { w -> height?.let {
|
|||
|
||||
@Immutable
|
||||
data class BaseItemUi(
|
||||
val episodeCornerText: String?,
|
||||
val episdodeUnplayedCornerText: String?,
|
||||
val quickDetails: AnnotatedString,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -27,6 +27,7 @@ import androidx.compose.ui.res.stringResource
|
|||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.isSpecified
|
||||
import androidx.compose.ui.unit.sp
|
||||
import androidx.tv.material3.Card
|
||||
import androidx.tv.material3.CardDefaults
|
||||
|
|
@ -40,7 +41,6 @@ import com.github.damontecres.wholphin.ui.AspectRatios
|
|||
import com.github.damontecres.wholphin.ui.Cards
|
||||
import com.github.damontecres.wholphin.ui.FontAwesome
|
||||
import com.github.damontecres.wholphin.ui.LocalImageUrlService
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import org.jellyfin.sdk.model.api.ImageType
|
||||
|
||||
/**
|
||||
|
|
@ -63,17 +63,19 @@ fun BannerCard(
|
|||
) {
|
||||
val imageUrlService = LocalImageUrlService.current
|
||||
val density = LocalDensity.current
|
||||
val imageUrl =
|
||||
remember(item, cardHeight) {
|
||||
if (item != null) {
|
||||
val fillHeight =
|
||||
if (cardHeight != Dp.Unspecified) {
|
||||
remember(cardHeight) {
|
||||
if (cardHeight.isSpecified) {
|
||||
with(density) {
|
||||
cardHeight.roundToPx()
|
||||
}
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
val imageUrl =
|
||||
remember(item, fillHeight) {
|
||||
if (item != null) {
|
||||
imageUrlService.getItemImageUrl(
|
||||
item,
|
||||
ImageType.PRIMARY,
|
||||
|
|
@ -101,7 +103,7 @@ fun BannerCard(
|
|||
.fillMaxSize(),
|
||||
// .background(MaterialTheme.colorScheme.surfaceVariant),
|
||||
) {
|
||||
if (!imageError && imageUrl.isNotNullOrBlank()) {
|
||||
if (!imageError && imageUrl != null) {
|
||||
AsyncImage(
|
||||
model = imageUrl,
|
||||
contentDescription = null,
|
||||
|
|
@ -121,7 +123,7 @@ fun BannerCard(
|
|||
.align(Alignment.Center),
|
||||
)
|
||||
}
|
||||
if (played || cornerText.isNotNullOrBlank()) {
|
||||
if (played || cornerText != null) {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
|
|
@ -133,7 +135,7 @@ fun BannerCard(
|
|||
if (played && (playPercent <= 0 || playPercent >= 100)) {
|
||||
WatchedIcon(Modifier.size(24.dp))
|
||||
}
|
||||
if (cornerText.isNotNullOrBlank()) {
|
||||
if (cornerText != null) {
|
||||
Box(
|
||||
modifier =
|
||||
Modifier
|
||||
|
|
|
|||
|
|
@ -55,7 +55,6 @@ import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
|||
import com.github.damontecres.wholphin.ui.components.LoadingPage
|
||||
import com.github.damontecres.wholphin.ui.components.SeriesName
|
||||
import com.github.damontecres.wholphin.ui.components.TabRow
|
||||
import com.github.damontecres.wholphin.ui.formatDateTime
|
||||
import com.github.damontecres.wholphin.ui.ifElse
|
||||
import com.github.damontecres.wholphin.ui.logTab
|
||||
import com.github.damontecres.wholphin.ui.playback.isPlayKeyUp
|
||||
|
|
@ -215,9 +214,6 @@ fun SeriesOverviewContent(
|
|||
if (interactionSource.collectIsFocusedAsState().value) {
|
||||
onFocusEpisode.invoke(episodeIndex)
|
||||
}
|
||||
val cornerText =
|
||||
episode?.data?.indexNumber?.let { "E$it" }
|
||||
?: episode?.data?.premiereDate?.let(::formatDateTime)
|
||||
BannerCard(
|
||||
name = episode?.name,
|
||||
item = episode,
|
||||
|
|
@ -226,7 +222,7 @@ fun SeriesOverviewContent(
|
|||
?.aspectRatio
|
||||
?.coerceAtLeast(AspectRatios.FOUR_THREE)
|
||||
?: (AspectRatios.WIDE),
|
||||
cornerText = cornerText,
|
||||
cornerText = episode?.ui?.episodeCornerText,
|
||||
played = episode?.data?.userData?.played ?: false,
|
||||
playPercent =
|
||||
episode?.data?.userData?.playedPercentage
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package com.github.damontecres.wholphin.ui.main
|
||||
|
||||
import android.widget.Toast
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.focusGroup
|
||||
import androidx.compose.foundation.focusable
|
||||
|
|
@ -25,7 +24,6 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
|
@ -49,7 +47,6 @@ import com.github.damontecres.wholphin.data.model.BaseItem
|
|||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.ui.AspectRatios
|
||||
import com.github.damontecres.wholphin.ui.Cards
|
||||
import com.github.damontecres.wholphin.ui.abbreviateNumber
|
||||
import com.github.damontecres.wholphin.ui.cards.BannerCard
|
||||
import com.github.damontecres.wholphin.ui.cards.ItemRow
|
||||
import com.github.damontecres.wholphin.ui.components.CircularProgress
|
||||
|
|
@ -88,28 +85,15 @@ fun HomePage(
|
|||
playlistViewModel: AddPlaylistViewModel = hiltViewModel(),
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
var firstLoad by rememberSaveable { mutableStateOf(true) }
|
||||
LaunchedEffect(Unit) {
|
||||
viewModel.init(preferences).join()
|
||||
firstLoad = false
|
||||
viewModel.init()
|
||||
}
|
||||
val loading by viewModel.loadingState.observeAsState(LoadingState.Loading)
|
||||
val refreshing by viewModel.refreshState.observeAsState(LoadingState.Loading)
|
||||
val watchingRows by viewModel.watchingRows.observeAsState(listOf())
|
||||
val latestRows by viewModel.latestRows.observeAsState(listOf())
|
||||
LaunchedEffect(loading) {
|
||||
val state = loading
|
||||
if (!firstLoad && state is LoadingState.Error) {
|
||||
// After the first load, refreshes occur in the background and an ErrorMessage won't show
|
||||
// So send a Toast on errors instead
|
||||
Toast
|
||||
.makeText(
|
||||
context,
|
||||
"Home refresh error: ${state.localizedMessage}",
|
||||
Toast.LENGTH_LONG,
|
||||
).show()
|
||||
}
|
||||
}
|
||||
|
||||
val homeRows = remember(watchingRows, latestRows) { watchingRows + latestRows }
|
||||
|
||||
when (val state = loading) {
|
||||
is LoadingState.Error -> {
|
||||
|
|
@ -127,7 +111,7 @@ fun HomePage(
|
|||
var showPlaylistDialog by remember { mutableStateOf<UUID?>(null) }
|
||||
val playlistState by playlistViewModel.playlistState.observeAsState(PlaylistLoadingState.Pending)
|
||||
HomePageContent(
|
||||
watchingRows + latestRows,
|
||||
homeRows = homeRows,
|
||||
onClickItem = { position, item ->
|
||||
viewModel.navigationManager.navigateTo(item.destination())
|
||||
},
|
||||
|
|
@ -339,21 +323,11 @@ fun HomePageContent(
|
|||
.focusRequester(rowFocusRequesters[rowIndex])
|
||||
.animateItem(),
|
||||
cardContent = { index, item, cardModifier, onClick, onLongClick ->
|
||||
val cornerText =
|
||||
remember(item) {
|
||||
item?.data?.indexNumber?.let { "E$it" }
|
||||
?: item
|
||||
?.data
|
||||
?.userData
|
||||
?.unplayedItemCount
|
||||
?.takeIf { it > 0 }
|
||||
?.let { abbreviateNumber(it) }
|
||||
}
|
||||
BannerCard(
|
||||
name = item?.data?.seriesName ?: item?.name,
|
||||
item = item,
|
||||
aspectRatio = AspectRatios.TALL,
|
||||
cornerText = cornerText,
|
||||
cornerText = item?.ui?.episdodeUnplayedCornerText,
|
||||
played = item?.data?.userData?.played ?: false,
|
||||
favorite = item?.favorite ?: false,
|
||||
playPercent =
|
||||
|
|
|
|||
|
|
@ -14,9 +14,11 @@ import com.github.damontecres.wholphin.services.DatePlayedService
|
|||
import com.github.damontecres.wholphin.services.FavoriteWatchManager
|
||||
import com.github.damontecres.wholphin.services.LatestNextUpService
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.services.UserPreferencesService
|
||||
import com.github.damontecres.wholphin.ui.launchIO
|
||||
import com.github.damontecres.wholphin.ui.nav.ServerNavDrawerItem
|
||||
import com.github.damontecres.wholphin.ui.setValueOnMain
|
||||
import com.github.damontecres.wholphin.ui.showToast
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.HomeRowLoadingState
|
||||
import com.github.damontecres.wholphin.util.LoadingExceptionHandler
|
||||
|
|
@ -24,7 +26,6 @@ import com.github.damontecres.wholphin.util.LoadingState
|
|||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
|
|
@ -47,6 +48,7 @@ class HomeViewModel
|
|||
private val datePlayedService: DatePlayedService,
|
||||
private val latestNextUpService: LatestNextUpService,
|
||||
private val backdropService: BackdropService,
|
||||
private val userPreferencesService: UserPreferencesService,
|
||||
) : ViewModel() {
|
||||
val loadingState = MutableLiveData<LoadingState>(LoadingState.Pending)
|
||||
val refreshState = MutableLiveData<LoadingState>(LoadingState.Pending)
|
||||
|
|
@ -57,18 +59,11 @@ class HomeViewModel
|
|||
|
||||
init {
|
||||
datePlayedService.invalidateAll()
|
||||
init()
|
||||
}
|
||||
|
||||
fun init(preferences: UserPreferences): Job {
|
||||
val reload = loadingState.value != LoadingState.Success
|
||||
if (reload) {
|
||||
loadingState.value = LoadingState.Loading
|
||||
}
|
||||
refreshState.value = LoadingState.Loading
|
||||
this.preferences = preferences
|
||||
val prefs = preferences.appPreferences.homePagePreferences
|
||||
val limit = prefs.maxItemsPerRow
|
||||
return viewModelScope.launch(
|
||||
fun init() {
|
||||
viewModelScope.launch(
|
||||
Dispatchers.IO +
|
||||
LoadingExceptionHandler(
|
||||
loadingState,
|
||||
|
|
@ -76,10 +71,18 @@ class HomeViewModel
|
|||
),
|
||||
) {
|
||||
Timber.d("init HomeViewModel")
|
||||
val reload = loadingState.value != LoadingState.Success
|
||||
if (reload) {
|
||||
loadingState.setValueOnMain(LoadingState.Loading)
|
||||
}
|
||||
refreshState.setValueOnMain(LoadingState.Loading)
|
||||
this@HomeViewModel.preferences = userPreferencesService.getCurrent()
|
||||
val prefs = preferences.appPreferences.homePagePreferences
|
||||
val limit = prefs.maxItemsPerRow
|
||||
if (reload) {
|
||||
backdropService.clearBackdrop()
|
||||
}
|
||||
|
||||
try {
|
||||
serverRepository.currentUserDto.value?.let { userDto ->
|
||||
val includedIds =
|
||||
navDrawerItemRepository
|
||||
|
|
@ -138,6 +141,14 @@ class HomeViewModel
|
|||
val loadedLatest = latestNextUpService.loadLatest(latest)
|
||||
this@HomeViewModel.latestRows.setValueOnMain(loadedLatest)
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
Timber.e(ex)
|
||||
if (!reload) {
|
||||
loadingState.setValueOnMain(LoadingState.Error(ex))
|
||||
} else {
|
||||
showToast(context, "Error refreshing home: ${ex.localizedMessage}")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -147,7 +158,7 @@ class HomeViewModel
|
|||
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
|
||||
favoriteWatchManager.setWatched(itemId, played)
|
||||
withContext(Dispatchers.Main) {
|
||||
init(preferences)
|
||||
init()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -157,7 +168,7 @@ class HomeViewModel
|
|||
) = viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
|
||||
favoriteWatchManager.setFavorite(itemId, favorite)
|
||||
withContext(Dispatchers.Main) {
|
||||
init(preferences)
|
||||
init()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,7 +3,6 @@ package com.github.damontecres.wholphin.ui.nav
|
|||
import android.content.Context
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.animation.animateColorAsState
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.foundation.focusGroup
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.interaction.collectIsFocusedAsState
|
||||
|
|
@ -14,7 +13,6 @@ import androidx.compose.foundation.layout.PaddingValues
|
|||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.itemsIndexed
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
|
|
@ -26,13 +24,10 @@ import androidx.compose.material.icons.filled.Search
|
|||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.mutableIntStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawBehind
|
||||
|
|
@ -94,7 +89,6 @@ import com.github.damontecres.wholphin.ui.tryRequestFocus
|
|||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -117,9 +111,9 @@ class NavDrawerViewModel
|
|||
val backdropService: BackdropService,
|
||||
private val seerrServerRepository: SeerrServerRepository,
|
||||
) : ViewModel() {
|
||||
// private var all: List<NavDrawerItem>? = null
|
||||
val moreLibraries = MutableLiveData<List<NavDrawerItem>>(null)
|
||||
val libraries = MutableLiveData<List<NavDrawerItem>>(listOf())
|
||||
|
||||
val selectedIndex = MutableLiveData(-1)
|
||||
val showMore = MutableLiveData(false)
|
||||
|
||||
|
|
@ -133,7 +127,6 @@ class NavDrawerViewModel
|
|||
fun init() {
|
||||
viewModelScope.launchIO {
|
||||
val all = navDrawerItemRepository.getNavDrawerItems()
|
||||
// this@NavDrawerViewModel.all = all
|
||||
val libraries = navDrawerItemRepository.getFilteredNavDrawerItems(all)
|
||||
val moreLibraries = all.toMutableList().apply { removeAll(libraries) }
|
||||
|
||||
|
|
@ -187,6 +180,37 @@ class NavDrawerViewModel
|
|||
}
|
||||
}
|
||||
|
||||
fun onClickDrawerItem(
|
||||
index: Int,
|
||||
item: NavDrawerItem,
|
||||
) {
|
||||
if (item !is NavDrawerItem.More) setShowMore(false)
|
||||
when (item) {
|
||||
NavDrawerItem.Favorites -> {
|
||||
setIndex(index)
|
||||
navigationManager.navigateToFromDrawer(
|
||||
Destination.Favorites,
|
||||
)
|
||||
}
|
||||
|
||||
NavDrawerItem.More -> {
|
||||
setShowMore(!showMore.value!!)
|
||||
}
|
||||
|
||||
NavDrawerItem.Discover -> {
|
||||
setIndex(index)
|
||||
navigationManager.navigateToFromDrawer(
|
||||
Destination.Discover,
|
||||
)
|
||||
}
|
||||
|
||||
is ServerNavDrawerItem -> {
|
||||
setIndex(index)
|
||||
navigationManager.navigateToFromDrawer(item.destination)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun setIndex(index: Int) {
|
||||
selectedIndex.value = index
|
||||
}
|
||||
|
|
@ -250,7 +274,7 @@ fun NavDrawer(
|
|||
viewModel: NavDrawerViewModel =
|
||||
hiltViewModel(
|
||||
LocalView.current.findViewTreeViewModelStoreOwner()!!,
|
||||
key = "${server?.id}_${user?.id}", // Keyed to the server & user to ensure its reset when switching either
|
||||
key = "${server.id}_${user.id}", // Keyed to the server & user to ensure its reset when switching either
|
||||
),
|
||||
) {
|
||||
val drawerState = rememberDrawerState(DrawerValue.Closed)
|
||||
|
|
@ -271,76 +295,14 @@ fun NavDrawer(
|
|||
LaunchedEffect(Unit) { viewModel.init() }
|
||||
|
||||
val showMore by viewModel.showMore.observeAsState(false)
|
||||
// val libraries = if (showPinnedOnly) pinnedLibraries else allLibraries
|
||||
// A negative index is a built in page, >=0 is a library
|
||||
val selectedIndex by viewModel.selectedIndex.observeAsState(-1)
|
||||
var focusedIndex by remember { mutableIntStateOf(Int.MIN_VALUE) }
|
||||
val derivedFocusedIndex by remember { derivedStateOf { focusedIndex } }
|
||||
|
||||
fun setShowMore(value: Boolean) {
|
||||
viewModel.setShowMore(value)
|
||||
}
|
||||
|
||||
BackHandler(enabled = showMore && drawerState.currentValue == DrawerValue.Open) {
|
||||
setShowMore(false)
|
||||
viewModel.setShowMore(false)
|
||||
}
|
||||
|
||||
val onClick = { index: Int, item: NavDrawerItem ->
|
||||
when (item) {
|
||||
NavDrawerItem.Favorites -> {
|
||||
viewModel.setIndex(index)
|
||||
viewModel.navigationManager.navigateToFromDrawer(
|
||||
Destination.Favorites,
|
||||
)
|
||||
}
|
||||
|
||||
NavDrawerItem.More -> {
|
||||
setShowMore(!showMore)
|
||||
}
|
||||
|
||||
NavDrawerItem.Discover -> {
|
||||
viewModel.setIndex(index)
|
||||
viewModel.navigationManager.navigateToFromDrawer(
|
||||
Destination.Discover,
|
||||
)
|
||||
}
|
||||
|
||||
is ServerNavDrawerItem -> {
|
||||
viewModel.setIndex(index)
|
||||
viewModel.navigationManager.navigateToFromDrawer(item.destination)
|
||||
}
|
||||
}
|
||||
}
|
||||
// Temporarily disabled, see https://github.com/damontecres/Wholphin/pull/127#issuecomment-3478058418
|
||||
if (false && preferences.appPreferences.interfacePreferences.navDrawerSwitchOnFocus) {
|
||||
LaunchedEffect(derivedFocusedIndex) {
|
||||
val index = derivedFocusedIndex
|
||||
delay(600)
|
||||
if (index != selectedIndex) {
|
||||
if (index == -1) {
|
||||
viewModel.setIndex(-1)
|
||||
viewModel.navigationManager.goToHome()
|
||||
} else if (index in libraries.indices) {
|
||||
if (moreLibraries.isEmpty() || index != libraries.lastIndex) {
|
||||
libraries.getOrNull(index)?.let {
|
||||
onClick.invoke(index, it)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
val newIndex = libraries.size - index + 1
|
||||
if (newIndex in moreLibraries.indices) {
|
||||
moreLibraries.getOrNull(newIndex)?.let {
|
||||
onClick.invoke(index, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val closedDrawerWidth = 40.dp
|
||||
val drawerWidth by animateDpAsState(if (drawerState.isOpen) 260.dp else closedDrawerWidth)
|
||||
val drawerPadding by animateDpAsState(if (drawerState.isOpen) 0.dp else 8.dp)
|
||||
val closedDrawerWidth = NavigationDrawerItemDefaults.CollapsedDrawerItemWidth
|
||||
val drawerBackground by animateColorAsState(
|
||||
if (drawerState.isOpen) {
|
||||
MaterialTheme.colorScheme.surface
|
||||
|
|
@ -382,15 +344,12 @@ fun NavDrawer(
|
|||
modifier =
|
||||
Modifier
|
||||
.fillMaxHeight()
|
||||
.width(drawerWidth)
|
||||
.drawBehind {
|
||||
drawRect(drawerBackground)
|
||||
},
|
||||
) {
|
||||
// Even though some must be clicked, focusing on it should clear other focused items
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val focused by interactionSource.collectIsFocusedAsState()
|
||||
LaunchedEffect(focused) { if (focused) focusedIndex = Int.MIN_VALUE }
|
||||
val userImageUrl = remember(user) { viewModel.getUserImage(user) }
|
||||
ProfileIcon(
|
||||
user = user,
|
||||
|
|
@ -403,7 +362,7 @@ fun NavDrawer(
|
|||
SetupDestination.UserList(server),
|
||||
)
|
||||
},
|
||||
modifier = Modifier.padding(start = drawerPadding),
|
||||
modifier = Modifier,
|
||||
)
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
|
|
@ -426,13 +385,10 @@ fun NavDrawer(
|
|||
scrollToSelected()
|
||||
}
|
||||
}
|
||||
}.fillMaxHeight()
|
||||
.padding(start = drawerPadding),
|
||||
}.fillMaxHeight(),
|
||||
) {
|
||||
item {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val focused by interactionSource.collectIsFocusedAsState()
|
||||
LaunchedEffect(focused) { if (focused) focusedIndex = -2 }
|
||||
IconNavItem(
|
||||
text = stringResource(R.string.search),
|
||||
icon = Icons.Default.Search,
|
||||
|
|
@ -449,13 +405,11 @@ fun NavDrawer(
|
|||
.ifElse(
|
||||
selectedIndex == -2,
|
||||
Modifier.focusRequester(focusRequester),
|
||||
).animateItem(),
|
||||
),
|
||||
)
|
||||
}
|
||||
item {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val focused by interactionSource.collectIsFocusedAsState()
|
||||
LaunchedEffect(focused) { if (focused) focusedIndex = -1 }
|
||||
IconNavItem(
|
||||
text = stringResource(R.string.home),
|
||||
icon = Icons.Default.Home,
|
||||
|
|
@ -475,13 +429,11 @@ fun NavDrawer(
|
|||
.ifElse(
|
||||
selectedIndex == -1,
|
||||
Modifier.focusRequester(focusRequester),
|
||||
).animateItem(),
|
||||
),
|
||||
)
|
||||
}
|
||||
itemsIndexed(libraries) { index, it ->
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val focused by interactionSource.collectIsFocusedAsState()
|
||||
LaunchedEffect(focused) { if (focused) focusedIndex = index }
|
||||
NavItem(
|
||||
library = it,
|
||||
selected = selectedIndex == index,
|
||||
|
|
@ -489,31 +441,26 @@ fun NavDrawer(
|
|||
drawerOpen = drawerState.isOpen,
|
||||
interactionSource = interactionSource,
|
||||
onClick = {
|
||||
onClick.invoke(index, it)
|
||||
if (it !is NavDrawerItem.More) setShowMore(false)
|
||||
viewModel.onClickDrawerItem(index, it)
|
||||
},
|
||||
modifier =
|
||||
Modifier
|
||||
.ifElse(
|
||||
selectedIndex == index,
|
||||
Modifier.focusRequester(focusRequester),
|
||||
).animateItem(),
|
||||
),
|
||||
)
|
||||
}
|
||||
if (showMore) {
|
||||
itemsIndexed(moreLibraries) { index, it ->
|
||||
val adjustedIndex = (index + libraries.size + 1)
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val focused by interactionSource.collectIsFocusedAsState()
|
||||
LaunchedEffect(focused) {
|
||||
if (focused) focusedIndex = adjustedIndex
|
||||
}
|
||||
NavItem(
|
||||
library = it,
|
||||
selected = selectedIndex == adjustedIndex,
|
||||
moreExpanded = showMore,
|
||||
drawerOpen = drawerState.isOpen,
|
||||
onClick = { onClick.invoke(adjustedIndex, it) },
|
||||
onClick = { viewModel.onClickDrawerItem(adjustedIndex, it) },
|
||||
containerColor =
|
||||
if (drawerState.isOpen) {
|
||||
MaterialTheme.colorScheme.surfaceColorAtElevation(3.dp)
|
||||
|
|
@ -526,14 +473,12 @@ fun NavDrawer(
|
|||
.ifElse(
|
||||
selectedIndex == adjustedIndex,
|
||||
Modifier.focusRequester(focusRequester),
|
||||
).animateItem(),
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
item {
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
val focused by interactionSource.collectIsFocusedAsState()
|
||||
LaunchedEffect(focused) { if (focused) focusedIndex = Int.MIN_VALUE }
|
||||
IconNavItem(
|
||||
text = stringResource(R.string.settings),
|
||||
icon = Icons.Default.Settings,
|
||||
|
|
@ -547,7 +492,7 @@ fun NavDrawer(
|
|||
),
|
||||
)
|
||||
},
|
||||
modifier = Modifier.animateItem(),
|
||||
modifier = Modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -587,7 +532,6 @@ fun NavigationDrawerScope.ProfileIcon(
|
|||
modifier: Modifier = Modifier,
|
||||
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
|
||||
) {
|
||||
val focused by interactionSource.collectIsFocusedAsState()
|
||||
NavigationDrawerItem(
|
||||
modifier = modifier,
|
||||
selected = false,
|
||||
|
|
@ -639,7 +583,7 @@ fun NavigationDrawerScope.IconNavItem(
|
|||
icon,
|
||||
contentDescription = null,
|
||||
tint = color,
|
||||
modifier = Modifier.padding(0.dp),
|
||||
modifier = Modifier,
|
||||
)
|
||||
},
|
||||
supportingContent =
|
||||
|
|
@ -675,6 +619,7 @@ fun NavigationDrawerScope.NavItem(
|
|||
val context = LocalContext.current
|
||||
val useFont = library !is ServerNavDrawerItem || library.type != CollectionType.LIVETV
|
||||
val icon =
|
||||
remember(library) {
|
||||
when (library) {
|
||||
NavDrawerItem.Favorites -> {
|
||||
R.string.fa_heart
|
||||
|
|
@ -701,6 +646,7 @@ fun NavigationDrawerScope.NavItem(
|
|||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val focused by interactionSource.collectIsFocusedAsState()
|
||||
NavigationDrawerItem(
|
||||
modifier = modifier,
|
||||
|
|
@ -732,13 +678,16 @@ fun NavigationDrawerScope.NavItem(
|
|||
}
|
||||
}
|
||||
},
|
||||
trailingContent = {
|
||||
trailingContent =
|
||||
if (library is NavDrawerItem.More) {
|
||||
{
|
||||
Icon(
|
||||
imageVector = if (moreExpanded) Icons.Default.ArrowDropDown else Icons.Default.KeyboardArrowLeft,
|
||||
contentDescription = null,
|
||||
)
|
||||
}
|
||||
} else {
|
||||
null
|
||||
},
|
||||
interactionSource = interactionSource,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -8,8 +8,10 @@ import androidx.compose.runtime.compositionLocalOf
|
|||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import com.github.damontecres.wholphin.ui.TimeFormatter
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.time.LocalDateTime
|
||||
|
||||
val LocalClock = compositionLocalOf<Clock> { Clock() }
|
||||
|
|
@ -32,6 +34,7 @@ data class Clock(
|
|||
fun ProvideLocalClock(content: @Composable () -> Unit) {
|
||||
val clock = remember { Clock() }
|
||||
LaunchedEffect(Unit) {
|
||||
withContext(Dispatchers.IO) {
|
||||
while (isActive) {
|
||||
val now = LocalDateTime.now()
|
||||
val time = TimeFormatter.format(now)
|
||||
|
|
@ -40,5 +43,6 @@ fun ProvideLocalClock(content: @Composable () -> Unit) {
|
|||
delay(2_000)
|
||||
}
|
||||
}
|
||||
}
|
||||
CompositionLocalProvider(LocalClock provides clock, content)
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue