Merge Wholphin v1.1.0 while preserving Wolfskin features #27

Merged
justinzeus merged 72 commits from fm/wolfskin-upstream-1-1-0 into main 2026-11-05 01:49:35 +01:00
4 changed files with 397 additions and 725 deletions
Showing only changes of commit 52c26bc0fc - Show all commits

View file

@ -44,7 +44,6 @@ import com.github.damontecres.wholphin.ui.detail.CardGrid
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.playback.scale
import com.github.damontecres.wholphin.ui.util.ScrollToTopBringIntoViewSpec
import org.jellyfin.sdk.model.api.BaseItemKind
import org.jellyfin.sdk.model.api.CollectionType
@ -53,19 +52,18 @@ import kotlin.time.Duration
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun CollectionFolderGrid(
fun CollectionFolderViewScope.CollectionFolderGrid(
preferences: UserPreferences,
collectionType: CollectionType?,
focusedItem: BaseItem?,
items: List<BaseItem?>,
sortAndDirection: SortAndDirection,
onClickItem: (Int, BaseItem) -> Unit,
onLongClickItem: (Int, BaseItem) -> Unit,
letterPosition: suspend (Char) -> Int,
viewOptions: ViewOptions,
onClickPlay: (Int, BaseItem) -> Unit,
initialPosition: Int,
gridFocusRequester: FocusRequester,
cardContent: @Composable CollectionFolderViewScope.(GridItemDetails<BaseItem>) -> Unit,
modifier: Modifier = Modifier,
positionCallback: ((columns: Int, position: Int) -> Unit)? = null,
) {
@ -99,18 +97,8 @@ fun CollectionFolderGrid(
modifier = Modifier.fillMaxSize(),
initialPosition = initialPosition,
positionCallback = positionCallback,
cardContent = { (item, index, onClick, onLongClick, widthPx, mod) ->
GridCard(
item = item,
onClick = onClick,
onLongClick = onLongClick,
imageContentScale = viewOptions.contentScale.scale,
imageAspectRatio = viewOptions.aspectRatio.ratio,
imageType = viewOptions.imageType,
showTitle = viewOptions.showTitles,
fillWidth = widthPx,
modifier = mod,
)
cardContent = { details ->
cardContent.invoke(this@CollectionFolderGrid, details)
},
columns = viewOptions.columns,
spacing = viewOptions.spacing.dp,

View file

@ -28,6 +28,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
@ -59,6 +60,7 @@ import com.github.damontecres.wholphin.ui.cards.FavoriteIndicator
import com.github.damontecres.wholphin.ui.cards.WatchedIcon
import com.github.damontecres.wholphin.ui.data.SortAndDirection
import com.github.damontecres.wholphin.ui.detail.AlphabetButtons
import com.github.damontecres.wholphin.ui.detail.GridItemDetails
import com.github.damontecres.wholphin.ui.enableMarquee
import com.github.damontecres.wholphin.ui.formatDuration
import com.github.damontecres.wholphin.ui.ifElse
@ -75,7 +77,7 @@ import org.jellyfin.sdk.model.api.ItemSortBy
import org.jellyfin.sdk.model.extensions.ticks
@Composable
fun CollectionFolderList(
fun CollectionFolderViewScope.CollectionFolderList(
preferences: UserPreferences,
collectionType: CollectionType?,
focusedItem: BaseItem?,
@ -84,10 +86,10 @@ fun CollectionFolderList(
onClickItem: (Int, BaseItem) -> Unit,
onLongClickItem: (Int, BaseItem) -> Unit,
letterPosition: suspend (Char) -> Int,
viewOptions: ViewOptions,
onClickPlay: (Int, BaseItem) -> Unit,
initialPosition: Int,
gridFocusRequester: FocusRequester,
itemContent: @Composable CollectionFolderViewScope.(GridItemDetails<BaseItem>) -> Unit,
modifier: Modifier = Modifier,
positionCallback: ((columns: Int, position: Int) -> Unit)? = null,
) {
@ -153,10 +155,8 @@ fun CollectionFolderList(
).padding(8.dp),
)
}
CollectionFolderContent(
this@CollectionFolderList.CollectionFolderListContent(
items = items,
viewOptions = viewOptions,
collectionType = collectionType,
listState = listState,
position = position,
positionFocusRequester = positionFocusRequester,
@ -167,6 +167,7 @@ fun CollectionFolderList(
},
onClickItem = onClickItem,
onLongClickItem = onLongClickItem,
itemContent = itemContent,
modifier =
Modifier
.weight(1f)
@ -266,44 +267,43 @@ fun CollectionFolderListDetails(
}
@Composable
fun CollectionFolderContent(
fun CollectionFolderViewScope.CollectionFolderListContent(
items: List<BaseItem?>,
viewOptions: ViewOptions,
collectionType: CollectionType?,
listState: LazyListState,
position: Int,
positionFocusRequester: FocusRequester,
onClickItem: (Int, BaseItem) -> Unit,
onLongClickItem: (Int, BaseItem) -> Unit,
onPositionChange: (Int) -> Unit,
itemContent: @Composable CollectionFolderViewScope.(GridItemDetails<BaseItem>) -> Unit,
modifier: Modifier = Modifier,
) {
val currentPosition by rememberUpdatedState(position)
LazyColumn(
state = listState,
verticalArrangement = Arrangement.spacedBy(viewOptions.spacing.dp),
modifier = modifier.focusRestorer(positionFocusRequester),
) {
itemsIndexed(items) { index, item ->
val onClick: () -> Unit =
remember(index, item) {
{ item?.let { onClickItem.invoke(index, item) } }
val details =
remember(item, index, onClickItem, onLongClickItem, onPositionChange) {
GridItemDetails(
item = item,
index = index,
onClick = { item?.let { onClickItem.invoke(index, item) } },
onLongClick = { item?.let { onLongClickItem.invoke(index, item) } },
widthPx = -1,
mod =
Modifier
.onFocusChanged {
if (it.isFocused) onPositionChange.invoke(index)
}.ifElse(
index == currentPosition,
Modifier.focusRequester(positionFocusRequester),
),
)
}
val onLongClick: () -> Unit =
remember(index, item) {
{ item?.let { onLongClickItem.invoke(index, item) } }
}
CollectionFolderListItem(
item = item,
dense = viewOptions.type == ViewOptionsType.DENSE_LIST,
collectionType = collectionType,
onClick = onClick,
onLongClick = onLongClick,
modifier =
Modifier
.onFocusChanged {
if (it.isFocused) onPositionChange.invoke(index)
}.ifElse(index == position, Modifier.focusRequester(positionFocusRequester)),
)
itemContent.invoke(this@CollectionFolderListContent, details)
}
}
}

View file

@ -54,12 +54,15 @@ import com.github.damontecres.wholphin.services.ThemeSongPlayer
import com.github.damontecres.wholphin.services.UserPreferencesService
import com.github.damontecres.wholphin.services.deleteItem
import com.github.damontecres.wholphin.ui.SlimItemFields
import com.github.damontecres.wholphin.ui.cards.GridCard
import com.github.damontecres.wholphin.ui.data.SortAndDirection
import com.github.damontecres.wholphin.ui.detail.GridItemDetails
import com.github.damontecres.wholphin.ui.detail.music.addToQueue
import com.github.damontecres.wholphin.ui.equalsNotNull
import com.github.damontecres.wholphin.ui.launchDefault
import com.github.damontecres.wholphin.ui.launchIO
import com.github.damontecres.wholphin.ui.nav.Destination
import com.github.damontecres.wholphin.ui.playback.scale
import com.github.damontecres.wholphin.ui.rememberInt
import com.github.damontecres.wholphin.ui.showToast
import com.github.damontecres.wholphin.ui.toServerString
@ -843,6 +846,33 @@ fun CollectionFolderViewContent(
positionCallback: ((columns: Int, position: Int) -> Unit)? = null,
filterOptions: List<ItemFilterBy<*>> = DefaultFilterOptions,
focusRequesterOnEmpty: FocusRequester? = null,
gridCardContent: @Composable CollectionFolderViewScope.(
GridItemDetails<BaseItem>,
) -> Unit = { (item, index, onClick, onLongClick, widthPx, mod) ->
GridCard(
item = item,
onClick = onClick,
onLongClick = onLongClick,
imageContentScale = viewOptions.contentScale.scale,
imageAspectRatio = viewOptions.aspectRatio.ratio,
imageType = viewOptions.imageType,
showTitle = viewOptions.showTitles,
fillWidth = widthPx,
modifier = mod,
)
},
listItemContent: @Composable CollectionFolderViewScope.(
GridItemDetails<BaseItem>,
) -> Unit = { (item, index, onClick, onLongClick, _, mod) ->
CollectionFolderListItem(
item = item,
dense = viewOptions.type == ViewOptionsType.DENSE_LIST,
collectionType = collectionType,
onClick = onClick,
onLongClick = onLongClick,
modifier = mod,
)
},
) {
var position by rememberInt(savedPosition)
@ -1005,11 +1035,22 @@ fun CollectionFolderViewContent(
focusRequester?.tryRequestFocus()
clickedRandom = false
}
val scope =
object : CollectionFolderViewScope {
override val viewOptions: ViewOptions
get() = state.viewOptions
override val collectionType: CollectionType?
get() = item?.data?.collectionType
override val sortAndDirection: SortAndDirection
get() = state.sortAndDirection
override val collectionSize: Int
get() = pager.data.size
}
Box(Modifier.fillMaxSize()) {
if (state.viewOptions.type == ViewOptionsType.GRID) {
CollectionFolderGrid(
scope.CollectionFolderGrid(
preferences = preferences,
collectionType = item?.data?.collectionType,
initialPosition = savedPosition,
items = pager.data,
sortAndDirection = state.sortAndDirection,
@ -1023,12 +1064,12 @@ fun CollectionFolderViewContent(
positionCallback?.invoke(columns, pos)
},
letterPosition = { viewActions.positionOfLetter(it) ?: -1 },
viewOptions = state.viewOptions,
onClickPlay = gridActions.onClickPlayRemoteButton!!,
focusedItem = focusedItem,
cardContent = gridCardContent,
)
} else {
CollectionFolderList(
scope.CollectionFolderList(
preferences = preferences,
collectionType = item?.data?.collectionType,
initialPosition = savedPosition,
@ -1044,9 +1085,9 @@ fun CollectionFolderViewContent(
positionCallback?.invoke(columns, pos)
},
letterPosition = { viewActions.positionOfLetter(it) ?: -1 },
viewOptions = state.viewOptions,
onClickPlay = gridActions.onClickPlayRemoteButton!!,
focusedItem = focusedItem,
itemContent = listItemContent,
)
}
androidx.compose.animation.AnimatedVisibility(
@ -1085,3 +1126,13 @@ fun CollectionFolderViewContent(
)
}
}
/**
* Scope with global data for [CollectionFolderView], [CollectionFolderGrid], [CollectionFolderList], and individual items
*/
interface CollectionFolderViewScope {
val viewOptions: ViewOptions
val collectionType: CollectionType?
val sortAndDirection: SortAndDirection
val collectionSize: Int
}