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:
Ray 2026-01-23 09:13:27 -05:00 committed by GitHub
parent 0639a7a1da
commit fc0de2144d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 191 additions and 243 deletions

View file

@ -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,
)

View file

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

View file

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

View file

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

View file

@ -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()
}
}

View file

@ -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,
) {

View file

@ -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)
}