Add button to navigate to random item (#1916)
## Description Adds a random button on library tab/grid pages which navigates to a random item in the list. This goes to the item's detail page. The random item is chosen with the filters applied. So you could filter by unplayed Action movies with 7+ stars and the random button will select from those movies. Pressing back restores focus on the random button so it's easy to quickly retry. ### Related issues Closes #838 ### Testing Emulator ## Screenshots <img width="935" height="105" alt="image" src="https://github.com/user-attachments/assets/170b495c-b47d-41ce-93b5-99831f2064c7" /> <img width="936" height="114" alt="image" src="https://github.com/user-attachments/assets/3efa6a11-ebe8-42d7-b023-354b1e6eda37" /> ## AI or LLM usage None
This commit is contained in:
parent
5363db0b80
commit
cedca9b2b1
5 changed files with 134 additions and 39 deletions
|
|
@ -9,6 +9,7 @@ import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
|
|
@ -22,7 +23,6 @@ import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
import androidx.compose.ui.focus.focusRequester
|
||||||
import androidx.compose.ui.focus.focusRestorer
|
|
||||||
import androidx.compose.ui.layout.ContentScale
|
import androidx.compose.ui.layout.ContentScale
|
||||||
import androidx.compose.ui.platform.LocalDensity
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
|
@ -42,6 +42,7 @@ import com.github.damontecres.wholphin.ui.cards.GridCard
|
||||||
import com.github.damontecres.wholphin.ui.data.SortAndDirection
|
import com.github.damontecres.wholphin.ui.data.SortAndDirection
|
||||||
import com.github.damontecres.wholphin.ui.detail.CardGrid
|
import com.github.damontecres.wholphin.ui.detail.CardGrid
|
||||||
import com.github.damontecres.wholphin.ui.detail.GridItemDetails
|
import com.github.damontecres.wholphin.ui.detail.GridItemDetails
|
||||||
|
import com.github.damontecres.wholphin.ui.ifElse
|
||||||
import com.github.damontecres.wholphin.ui.main.HomePageHeader
|
import com.github.damontecres.wholphin.ui.main.HomePageHeader
|
||||||
import com.github.damontecres.wholphin.ui.playback.scale
|
import com.github.damontecres.wholphin.ui.playback.scale
|
||||||
import com.github.damontecres.wholphin.ui.util.ScrollToTopBringIntoViewSpec
|
import com.github.damontecres.wholphin.ui.util.ScrollToTopBringIntoViewSpec
|
||||||
|
|
@ -288,6 +289,7 @@ data class GridClickActions(
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun CollectionFolderHeader(
|
fun CollectionFolderHeader(
|
||||||
|
listIsNotEmpty: Boolean,
|
||||||
showHeader: Boolean,
|
showHeader: Boolean,
|
||||||
showTitle: Boolean,
|
showTitle: Boolean,
|
||||||
playEnabled: Boolean,
|
playEnabled: Boolean,
|
||||||
|
|
@ -298,12 +300,14 @@ fun CollectionFolderHeader(
|
||||||
getPossibleFilterValues: suspend (ItemFilterBy<*>) -> List<FilterValueOption>,
|
getPossibleFilterValues: suspend (ItemFilterBy<*>) -> List<FilterValueOption>,
|
||||||
onClickShowViewOptions: () -> Unit,
|
onClickShowViewOptions: () -> Unit,
|
||||||
onClickPlayAll: (Boolean) -> Unit,
|
onClickPlayAll: (Boolean) -> Unit,
|
||||||
|
onClickRandom: () -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
currentFilter: GetItemsFilter = GetItemsFilter(),
|
currentFilter: GetItemsFilter = GetItemsFilter(),
|
||||||
filterOptions: List<ItemFilterBy<*>> = listOf(),
|
filterOptions: List<ItemFilterBy<*>> = listOf(),
|
||||||
onFilterChange: (GetItemsFilter) -> Unit = {},
|
onFilterChange: (GetItemsFilter) -> Unit = {},
|
||||||
onShowFilterDropdown: ((Boolean) -> Unit)? = null,
|
onShowFilterDropdown: ((Boolean) -> Unit)? = null,
|
||||||
filterButtonFocusRequester: FocusRequester = remember { FocusRequester() },
|
filterButtonFocusRequester: FocusRequester = remember { FocusRequester() },
|
||||||
|
randomButtonFocusRequester: FocusRequester = remember { FocusRequester() },
|
||||||
) {
|
) {
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
showHeader,
|
showHeader,
|
||||||
|
|
@ -311,6 +315,7 @@ fun CollectionFolderHeader(
|
||||||
exit = shrinkVertically(),
|
exit = shrinkVertically(),
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
) {
|
) {
|
||||||
|
val focusRequester = remember { FocusRequester() }
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
|
@ -320,63 +325,84 @@ fun CollectionFolderHeader(
|
||||||
}
|
}
|
||||||
val endPadding =
|
val endPadding =
|
||||||
16.dp + if (sortAndDirection.sort == ItemSortBy.SORT_NAME) 24.dp else 0.dp
|
16.dp + if (sortAndDirection.sort == ItemSortBy.SORT_NAME) 24.dp else 0.dp
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
horizontalArrangement = Arrangement.SpaceBetween,
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
modifier =
|
modifier =
|
||||||
Modifier
|
Modifier
|
||||||
.padding(start = 16.dp, end = endPadding)
|
.padding(start = 16.dp, end = endPadding)
|
||||||
.focusRestorer()
|
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
) {
|
) {
|
||||||
if (sortOptions.isNotEmpty() || filterOptions.isNotEmpty()) {
|
Row(
|
||||||
Row(
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
modifier = Modifier,
|
||||||
modifier = Modifier.focusRestorer(),
|
) {
|
||||||
) {
|
if (sortOptions.isNotEmpty()) {
|
||||||
if (sortOptions.isNotEmpty()) {
|
SortByButton(
|
||||||
SortByButton(
|
sortOptions = sortOptions,
|
||||||
sortOptions = sortOptions,
|
current = sortAndDirection,
|
||||||
current = sortAndDirection,
|
onSortChange = onSortChange,
|
||||||
onSortChange = onSortChange,
|
modifier = Modifier.focusRequester(focusRequester),
|
||||||
modifier = Modifier,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (filterOptions.isNotEmpty()) {
|
|
||||||
FilterByButton(
|
|
||||||
filterOptions = filterOptions,
|
|
||||||
current = currentFilter,
|
|
||||||
onFilterChange = onFilterChange,
|
|
||||||
getPossibleValues = getPossibleFilterValues,
|
|
||||||
modifier = Modifier.focusRequester(filterButtonFocusRequester),
|
|
||||||
onShow = onShowFilterDropdown,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
ExpandableFaButton(
|
|
||||||
title = R.string.view_options,
|
|
||||||
iconStringRes = R.string.fa_sliders,
|
|
||||||
onClick = onClickShowViewOptions,
|
|
||||||
modifier = Modifier,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (filterOptions.isNotEmpty()) {
|
||||||
|
FilterByButton(
|
||||||
|
filterOptions = filterOptions,
|
||||||
|
current = currentFilter,
|
||||||
|
onFilterChange = onFilterChange,
|
||||||
|
getPossibleValues = getPossibleFilterValues,
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.focusRequester(filterButtonFocusRequester)
|
||||||
|
.ifElse(
|
||||||
|
sortOptions.isEmpty(),
|
||||||
|
Modifier.focusRequester(focusRequester),
|
||||||
|
),
|
||||||
|
onShow = onShowFilterDropdown,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ExpandableFaButton(
|
||||||
|
title = R.string.view_options,
|
||||||
|
iconStringRes = R.string.fa_sliders,
|
||||||
|
onClick = onClickShowViewOptions,
|
||||||
|
modifier =
|
||||||
|
Modifier.ifElse(
|
||||||
|
sortOptions.isEmpty() && filterOptions.isEmpty(),
|
||||||
|
Modifier.focusRequester(focusRequester),
|
||||||
|
),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
if (playEnabled) {
|
|
||||||
Row(
|
Spacer(Modifier.weight(1f))
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
|
||||||
verticalAlignment = Alignment.CenterVertically,
|
Row(
|
||||||
modifier = Modifier.focusRestorer(),
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
) {
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier = Modifier,
|
||||||
|
) {
|
||||||
|
ExpandableFaButton(
|
||||||
|
title = R.string.sort_by_random,
|
||||||
|
iconStringRes = R.string.fa_dice,
|
||||||
|
onClick = onClickRandom,
|
||||||
|
modifier = Modifier.focusRequester(randomButtonFocusRequester),
|
||||||
|
enabled = listIsNotEmpty,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (playEnabled) {
|
||||||
ExpandablePlayButton(
|
ExpandablePlayButton(
|
||||||
title = R.string.play,
|
title = R.string.play,
|
||||||
resume = Duration.ZERO,
|
resume = Duration.ZERO,
|
||||||
icon = Icons.Default.PlayArrow,
|
icon = Icons.Default.PlayArrow,
|
||||||
onClick = { onClickPlayAll.invoke(false) },
|
onClick = { onClickPlayAll.invoke(false) },
|
||||||
|
enabled = listIsNotEmpty,
|
||||||
)
|
)
|
||||||
ExpandableFaButton(
|
ExpandableFaButton(
|
||||||
title = R.string.shuffle,
|
title = R.string.shuffle,
|
||||||
iconStringRes = R.string.fa_shuffle,
|
iconStringRes = R.string.fa_shuffle,
|
||||||
onClick = { onClickPlayAll.invoke(true) },
|
onClick = { onClickPlayAll.invoke(true) },
|
||||||
|
enabled = listIsNotEmpty,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -65,6 +65,7 @@ import com.github.damontecres.wholphin.ui.showToast
|
||||||
import com.github.damontecres.wholphin.ui.toServerString
|
import com.github.damontecres.wholphin.ui.toServerString
|
||||||
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
import com.github.damontecres.wholphin.ui.tryRequestFocus
|
||||||
import com.github.damontecres.wholphin.util.ApiRequestPager
|
import com.github.damontecres.wholphin.util.ApiRequestPager
|
||||||
|
import com.github.damontecres.wholphin.util.BlockingList
|
||||||
import com.github.damontecres.wholphin.util.DataLoadingState
|
import com.github.damontecres.wholphin.util.DataLoadingState
|
||||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||||
import com.github.damontecres.wholphin.util.GetArtistsHandler
|
import com.github.damontecres.wholphin.util.GetArtistsHandler
|
||||||
|
|
@ -78,6 +79,7 @@ import dagger.assisted.AssistedFactory
|
||||||
import dagger.assisted.AssistedInject
|
import dagger.assisted.AssistedInject
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.catch
|
import kotlinx.coroutines.flow.catch
|
||||||
|
|
@ -264,6 +266,24 @@ class CollectionFolderViewModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onClickRandom() {
|
||||||
|
viewModelScope.launchIO {
|
||||||
|
try {
|
||||||
|
val random =
|
||||||
|
(state.value.items.successValue as? BlockingList<BaseItem?>)?.randomBlocking()
|
||||||
|
Timber.d("Got random item: %s", random?.id)
|
||||||
|
random?.destination()?.let {
|
||||||
|
navigateTo(random.destination())
|
||||||
|
}
|
||||||
|
} catch (ex: CancellationException) {
|
||||||
|
throw ex
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
Timber.e(ex, "Error getting random")
|
||||||
|
showToast(context, "Error: ${ex.localizedMessage}}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onFilterChange(
|
override fun onFilterChange(
|
||||||
newFilter: GetItemsFilter,
|
newFilter: GetItemsFilter,
|
||||||
recursive: Boolean,
|
recursive: Boolean,
|
||||||
|
|
@ -767,6 +787,11 @@ interface CollectionFolderViewActions {
|
||||||
suspend fun positionOfLetter(letter: Char): Int?
|
suspend fun positionOfLetter(letter: Char): Int?
|
||||||
|
|
||||||
fun saveViewOptions(viewOptions: ViewOptions)
|
fun saveViewOptions(viewOptions: ViewOptions)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Navigate to a random item
|
||||||
|
*/
|
||||||
|
fun onClickRandom()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -791,11 +816,15 @@ fun CollectionFolderViewContent(
|
||||||
) {
|
) {
|
||||||
var position by rememberInt(savedPosition)
|
var position by rememberInt(savedPosition)
|
||||||
|
|
||||||
|
// Track if leaving page due to clicking the random button so focus can be restored there
|
||||||
|
var clickedRandom by rememberSaveable { mutableStateOf(false) }
|
||||||
|
|
||||||
val contextMenu = rememberContextMenu(preferences, provider)
|
val contextMenu = rememberContextMenu(preferences, provider)
|
||||||
var showViewOptions by rememberSaveable { mutableStateOf(false) }
|
var showViewOptions by rememberSaveable { mutableStateOf(false) }
|
||||||
var filterDropdownShowing by remember { mutableStateOf(false) }
|
var filterDropdownShowing by remember { mutableStateOf(false) }
|
||||||
val headerRowFocusRequester = remember { FocusRequester() }
|
val headerRowFocusRequester = remember { FocusRequester() }
|
||||||
val filterButtonFocusRequester = remember { FocusRequester() }
|
val filterButtonFocusRequester = remember { FocusRequester() }
|
||||||
|
val randomButtonFocusRequester = remember { FocusRequester() }
|
||||||
|
|
||||||
val gridActions =
|
val gridActions =
|
||||||
remember(actions) {
|
remember(actions) {
|
||||||
|
|
@ -889,9 +918,10 @@ fun CollectionFolderViewContent(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CollectionFolderHeader(
|
CollectionFolderHeader(
|
||||||
|
listIsNotEmpty = state.items.successValue?.isNotEmpty() == true,
|
||||||
showHeader = showHeader || state.items !is DataLoadingState.Success,
|
showHeader = showHeader || state.items !is DataLoadingState.Success,
|
||||||
showTitle = showTitle,
|
showTitle = showTitle,
|
||||||
playEnabled = playEnabled && state.items.successValue?.isNotEmpty() == true,
|
playEnabled = playEnabled,
|
||||||
title = title,
|
title = title,
|
||||||
sortAndDirection = state.sortAndDirection,
|
sortAndDirection = state.sortAndDirection,
|
||||||
onSortChange = {
|
onSortChange = {
|
||||||
|
|
@ -908,9 +938,14 @@ fun CollectionFolderViewContent(
|
||||||
filterOptions = filterOptions,
|
filterOptions = filterOptions,
|
||||||
onClickPlayAll = gridActions.onClickPlayAll!!,
|
onClickPlayAll = gridActions.onClickPlayAll!!,
|
||||||
onClickShowViewOptions = { showViewOptions = true },
|
onClickShowViewOptions = { showViewOptions = true },
|
||||||
|
onClickRandom = {
|
||||||
|
clickedRandom = true
|
||||||
|
viewActions.onClickRandom()
|
||||||
|
},
|
||||||
modifier = Modifier.focusRequester(headerRowFocusRequester),
|
modifier = Modifier.focusRequester(headerRowFocusRequester),
|
||||||
onShowFilterDropdown = { filterDropdownShowing = it },
|
onShowFilterDropdown = { filterDropdownShowing = it },
|
||||||
filterButtonFocusRequester = filterButtonFocusRequester,
|
filterButtonFocusRequester = filterButtonFocusRequester,
|
||||||
|
randomButtonFocusRequester = randomButtonFocusRequester,
|
||||||
)
|
)
|
||||||
|
|
||||||
when (val pager = state.items) {
|
when (val pager = state.items) {
|
||||||
|
|
@ -933,10 +968,12 @@ fun CollectionFolderViewContent(
|
||||||
when {
|
when {
|
||||||
contextMenu.isShowing -> null
|
contextMenu.isShowing -> null
|
||||||
filterDropdownShowing -> filterButtonFocusRequester
|
filterDropdownShowing -> filterButtonFocusRequester
|
||||||
|
clickedRandom -> randomButtonFocusRequester
|
||||||
pager.data.isNotEmpty() -> gridFocusRequester
|
pager.data.isNotEmpty() -> gridFocusRequester
|
||||||
else -> focusRequesterOnEmpty ?: headerRowFocusRequester
|
else -> focusRequesterOnEmpty ?: headerRowFocusRequester
|
||||||
}
|
}
|
||||||
focusRequester?.tryRequestFocus()
|
focusRequester?.tryRequestFocus()
|
||||||
|
clickedRandom = false
|
||||||
}
|
}
|
||||||
Box(Modifier.fillMaxSize()) {
|
Box(Modifier.fillMaxSize()) {
|
||||||
if (state.viewOptions.type == ViewOptionsType.GRID) {
|
if (state.viewOptions.type == ViewOptionsType.GRID) {
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ import com.github.damontecres.wholphin.ui.nav.Destination
|
||||||
import com.github.damontecres.wholphin.ui.nav.NavDrawerItem
|
import com.github.damontecres.wholphin.ui.nav.NavDrawerItem
|
||||||
import com.github.damontecres.wholphin.ui.showToast
|
import com.github.damontecres.wholphin.ui.showToast
|
||||||
import com.github.damontecres.wholphin.util.ApiRequestPager
|
import com.github.damontecres.wholphin.util.ApiRequestPager
|
||||||
|
import com.github.damontecres.wholphin.util.BlockingList
|
||||||
import com.github.damontecres.wholphin.util.DataLoadingState
|
import com.github.damontecres.wholphin.util.DataLoadingState
|
||||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||||
import com.github.damontecres.wholphin.util.GetArtistsHandler
|
import com.github.damontecres.wholphin.util.GetArtistsHandler
|
||||||
|
|
@ -51,6 +52,7 @@ import com.github.damontecres.wholphin.util.GetItemsRequestHandler
|
||||||
import com.github.damontecres.wholphin.util.GetPersonsHandler
|
import com.github.damontecres.wholphin.util.GetPersonsHandler
|
||||||
import com.github.damontecres.wholphin.util.LoadingState
|
import com.github.damontecres.wholphin.util.LoadingState
|
||||||
import com.github.damontecres.wholphin.util.WholphinDispatchers
|
import com.github.damontecres.wholphin.util.WholphinDispatchers
|
||||||
|
import com.github.damontecres.wholphin.util.successValue
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
|
|
@ -693,6 +695,26 @@ class FavoritesViewModel
|
||||||
override fun saveViewOptions(viewOptions: ViewOptions) {
|
override fun saveViewOptions(viewOptions: ViewOptions) {
|
||||||
saveViewOptions(type, viewOptions)
|
saveViewOptions(type, viewOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onClickRandom() {
|
||||||
|
viewModelScope.launchIO {
|
||||||
|
try {
|
||||||
|
collectionStateFor(type)?.let { collectionState ->
|
||||||
|
val random =
|
||||||
|
(collectionState.items.successValue as? BlockingList<BaseItem?>)?.randomBlocking()
|
||||||
|
Timber.d("Got random item: %s", random?.id)
|
||||||
|
random?.destination()?.let {
|
||||||
|
navigateTo(random.destination())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (ex: CancellationException) {
|
||||||
|
throw ex
|
||||||
|
} catch (ex: Exception) {
|
||||||
|
Timber.e(ex, "Error getting random")
|
||||||
|
showToast(context, "Error: ${ex.localizedMessage}}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package com.github.damontecres.wholphin.util
|
||||||
|
|
||||||
import java.util.function.IntFunction
|
import java.util.function.IntFunction
|
||||||
import java.util.function.Predicate
|
import java.util.function.Predicate
|
||||||
|
import kotlin.random.Random
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A [List] which has function that will wait for a result
|
* A [List] which has function that will wait for a result
|
||||||
|
|
@ -17,6 +18,11 @@ interface BlockingList<T> : List<T> {
|
||||||
*/
|
*/
|
||||||
suspend fun indexOfBlocking(predicate: Predicate<T>): Int
|
suspend fun indexOfBlocking(predicate: Predicate<T>): Int
|
||||||
|
|
||||||
|
suspend fun randomBlocking(): T {
|
||||||
|
val index = Random.nextInt(size)
|
||||||
|
return getBlocking(index)
|
||||||
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
/**
|
/**
|
||||||
* Create a [BlockingList] over a regular [List]
|
* Create a [BlockingList] over a regular [List]
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,8 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<resources>
|
<resources>
|
||||||
|
<!-- Names should be prefixed with 'fa_' and use the icon name from FontAwesome -->
|
||||||
|
<!-- Uses v6 of FontAwesome: https://fontawesome.com/v6/icons/classic/solid/<name> -->
|
||||||
|
<!-- Value is '&#x' then the unicode value, then semicolon -->
|
||||||
<string name="fa_tag" translatable="false"></string>
|
<string name="fa_tag" translatable="false"></string>
|
||||||
<string name="fa_user" translatable="false"></string>
|
<string name="fa_user" translatable="false"></string>
|
||||||
<string name="fa_location_dot" translatable="false"></string>
|
<string name="fa_location_dot" translatable="false"></string>
|
||||||
|
|
@ -57,4 +60,5 @@
|
||||||
<string name="fa_repeat" translatable="false"></string>
|
<string name="fa_repeat" translatable="false"></string>
|
||||||
<string name="fa_compact_disc" translatable="false"></string>
|
<string name="fa_compact_disc" translatable="false"></string>
|
||||||
<string name="fa_xmark" translatable="false"></string>
|
<string name="fa_xmark" translatable="false"></string>
|
||||||
|
<string name="fa_dice" translatable="false"></string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue