mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-09 08:01:20 +02:00
WIP, basic MVP
This commit is contained in:
parent
fc0de2144d
commit
6d53869180
11 changed files with 627 additions and 3 deletions
|
|
@ -265,7 +265,8 @@ class MainActivity : AppCompatActivity() {
|
||||||
server = current.server,
|
server = current.server,
|
||||||
startDestination =
|
startDestination =
|
||||||
requestedDestination
|
requestedDestination
|
||||||
?: Destination.Home(),
|
// TODO Undo
|
||||||
|
?: Destination.HomeSettings,
|
||||||
navigationManager = navigationManager,
|
navigationManager = navigationManager,
|
||||||
preferences = preferences,
|
preferences = preferences,
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import kotlinx.coroutines.withContext
|
||||||
import kotlinx.serialization.Serializable
|
import kotlinx.serialization.Serializable
|
||||||
import org.jellyfin.sdk.Jellyfin
|
import org.jellyfin.sdk.Jellyfin
|
||||||
import org.jellyfin.sdk.api.client.ApiClient
|
import org.jellyfin.sdk.api.client.ApiClient
|
||||||
|
import org.jellyfin.sdk.api.client.extensions.displayPreferencesApi
|
||||||
import org.jellyfin.sdk.api.client.extensions.systemApi
|
import org.jellyfin.sdk.api.client.extensions.systemApi
|
||||||
import org.jellyfin.sdk.api.client.extensions.userApi
|
import org.jellyfin.sdk.api.client.extensions.userApi
|
||||||
import org.jellyfin.sdk.model.api.AuthenticationResult
|
import org.jellyfin.sdk.model.api.AuthenticationResult
|
||||||
|
|
@ -102,6 +103,27 @@ class ServerRepository
|
||||||
currentUserId = updatedUser.id.toServerString()
|
currentUserId = updatedUser.id.toServerString()
|
||||||
}.build()
|
}.build()
|
||||||
}
|
}
|
||||||
|
val settings =
|
||||||
|
apiClient.displayPreferencesApi
|
||||||
|
.getDisplayPreferences(
|
||||||
|
displayPreferencesId = "settings",
|
||||||
|
userId = user.id,
|
||||||
|
client = "wholphin",
|
||||||
|
).content
|
||||||
|
val newSettings =
|
||||||
|
settings.copy(
|
||||||
|
customPrefs =
|
||||||
|
settings.customPrefs.toMutableMap().apply {
|
||||||
|
put("test_key", "test_value")
|
||||||
|
},
|
||||||
|
)
|
||||||
|
apiClient.displayPreferencesApi.updateDisplayPreferences(
|
||||||
|
displayPreferencesId = "settings",
|
||||||
|
userId = user.id,
|
||||||
|
client = "wholphin",
|
||||||
|
data = newSettings,
|
||||||
|
)
|
||||||
|
Timber.v("settings=$settings")
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
_current.value = CurrentUser(updatedServer, updatedUser)
|
_current.value = CurrentUser(updatedServer, updatedUser)
|
||||||
_currentUserDto.value = userDto
|
_currentUserDto.value = userDto
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,58 @@
|
||||||
|
@file:UseSerializers(UUIDSerializer::class)
|
||||||
|
|
||||||
|
package com.github.damontecres.wholphin.data.model
|
||||||
|
|
||||||
|
import com.github.damontecres.wholphin.preferences.PrefContentScale
|
||||||
|
import com.github.damontecres.wholphin.ui.AspectRatio
|
||||||
|
import com.github.damontecres.wholphin.ui.Cards
|
||||||
|
import com.github.damontecres.wholphin.ui.components.ViewOptionImageType
|
||||||
|
import kotlinx.serialization.Serializable
|
||||||
|
import kotlinx.serialization.UseSerializers
|
||||||
|
import org.jellyfin.sdk.model.serializer.UUIDSerializer
|
||||||
|
import java.util.UUID
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
sealed class HomeRowConfig {
|
||||||
|
abstract val viewOptions: HomeRowViewOptions
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class ContinueWatching(
|
||||||
|
val combined: Boolean,
|
||||||
|
override val viewOptions: HomeRowViewOptions,
|
||||||
|
) : HomeRowConfig()
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class RecentlyAdded(
|
||||||
|
val parentId: UUID,
|
||||||
|
override val viewOptions: HomeRowViewOptions,
|
||||||
|
) : HomeRowConfig()
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class RecentlyReleased(
|
||||||
|
val parentId: UUID,
|
||||||
|
override val viewOptions: HomeRowViewOptions,
|
||||||
|
) : HomeRowConfig()
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class Genres(
|
||||||
|
val genreId: UUID,
|
||||||
|
val parentId: UUID,
|
||||||
|
override val viewOptions: HomeRowViewOptions,
|
||||||
|
) : HomeRowConfig()
|
||||||
|
}
|
||||||
|
|
||||||
|
data class HomeRowConfigDisplay(
|
||||||
|
val title: String,
|
||||||
|
val config: HomeRowConfig,
|
||||||
|
)
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data class HomeRowViewOptions(
|
||||||
|
val heightDp: Int = Cards.HEIGHT_2X3_DP,
|
||||||
|
val spacing: Int = 16,
|
||||||
|
val contentScale: PrefContentScale = PrefContentScale.FIT,
|
||||||
|
val aspectRatio: AspectRatio = AspectRatio.TALL,
|
||||||
|
val imageType: ViewOptionImageType = ViewOptionImageType.PRIMARY,
|
||||||
|
val showTitles: Boolean = true,
|
||||||
|
val useSeries: Boolean = true,
|
||||||
|
)
|
||||||
|
|
@ -73,7 +73,8 @@ val SlimItemFields =
|
||||||
)
|
)
|
||||||
|
|
||||||
object Cards {
|
object Cards {
|
||||||
val height2x3 = 172.dp
|
const val HEIGHT_2X3_DP = 172
|
||||||
|
val height2x3 = HEIGHT_2X3_DP.dp
|
||||||
val heightEpisode = height2x3 * .75f
|
val heightEpisode = height2x3 * .75f
|
||||||
val playedPercentHeight = 6.dp
|
val playedPercentHeight = 6.dp
|
||||||
val serverUserCircle = height2x3 * .75f
|
val serverUserCircle = height2x3 * .75f
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.size
|
import androidx.compose.foundation.layout.size
|
||||||
import androidx.compose.foundation.layout.width
|
import androidx.compose.foundation.layout.width
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.LazyListState
|
||||||
import androidx.compose.foundation.lazy.itemsIndexed
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
|
@ -192,6 +193,7 @@ fun HomePageContent(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
onFocusPosition: ((RowColumn) -> Unit)? = null,
|
onFocusPosition: ((RowColumn) -> Unit)? = null,
|
||||||
loadingState: LoadingState? = null,
|
loadingState: LoadingState? = null,
|
||||||
|
listState: LazyListState = rememberLazyListState(),
|
||||||
) {
|
) {
|
||||||
var position by rememberPosition()
|
var position by rememberPosition()
|
||||||
val focusedItem =
|
val focusedItem =
|
||||||
|
|
@ -199,7 +201,6 @@ fun HomePageContent(
|
||||||
(homeRows.getOrNull(it.row) as? HomeRowLoadingState.Success)?.items?.getOrNull(it.column)
|
(homeRows.getOrNull(it.row) as? HomeRowLoadingState.Success)?.items?.getOrNull(it.column)
|
||||||
}
|
}
|
||||||
|
|
||||||
val listState = rememberLazyListState()
|
|
||||||
val rowFocusRequesters = remember(homeRows) { List(homeRows.size) { FocusRequester() } }
|
val rowFocusRequesters = remember(homeRows) { List(homeRows.size) { FocusRequester() } }
|
||||||
var firstFocused by remember { mutableStateOf(false) }
|
var firstFocused by remember { mutableStateOf(false) }
|
||||||
LaunchedEffect(homeRows) {
|
LaunchedEffect(homeRows) {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,155 @@
|
||||||
|
package com.github.damontecres.wholphin.ui.main.settings
|
||||||
|
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.heightIn
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.foundation.layout.wrapContentWidth
|
||||||
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
|
import androidx.compose.foundation.lazy.itemsIndexed
|
||||||
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Delete
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
import androidx.compose.ui.focus.focusRequester
|
||||||
|
import androidx.compose.ui.focus.focusRestorer
|
||||||
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.style.TextOverflow
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.tv.material3.Icon
|
||||||
|
import androidx.tv.material3.MaterialTheme
|
||||||
|
import androidx.tv.material3.Text
|
||||||
|
import androidx.tv.material3.surfaceColorAtElevation
|
||||||
|
import com.github.damontecres.wholphin.R
|
||||||
|
import com.github.damontecres.wholphin.data.model.HomeRowConfigDisplay
|
||||||
|
import com.github.damontecres.wholphin.ui.FontAwesome
|
||||||
|
import com.github.damontecres.wholphin.ui.components.Button
|
||||||
|
import com.github.damontecres.wholphin.ui.ifElse
|
||||||
|
|
||||||
|
enum class MoveDirection {
|
||||||
|
UP,
|
||||||
|
DOWN,
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun HomePageRowList(
|
||||||
|
state: HomePageSettingsState,
|
||||||
|
onClickAdd: () -> Unit,
|
||||||
|
onClickMove: (MoveDirection, Int) -> Unit,
|
||||||
|
onClickDelete: (Int) -> Unit,
|
||||||
|
modifier: Modifier,
|
||||||
|
firstFocus: FocusRequester = remember { FocusRequester() },
|
||||||
|
) {
|
||||||
|
Column(modifier = modifier) {
|
||||||
|
LazyColumn(
|
||||||
|
contentPadding = PaddingValues(8.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
modifier =
|
||||||
|
modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.focusRestorer(firstFocus),
|
||||||
|
) {
|
||||||
|
itemsIndexed(state.rows) { index, row ->
|
||||||
|
HomeRowConfigContent(
|
||||||
|
config = row,
|
||||||
|
moveUpAllowed = index > 0,
|
||||||
|
moveDownAllowed = index != state.rows.lastIndex,
|
||||||
|
onClickMove = { onClickMove.invoke(it, index) },
|
||||||
|
onClickDelete = { onClickDelete.invoke(index) },
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.ifElse(index == 0, Modifier.focusRequester(firstFocus)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
item {
|
||||||
|
Button(
|
||||||
|
onClick = onClickAdd,
|
||||||
|
modifier = Modifier.ifElse(state.rows.isEmpty(), Modifier.focusRequester(firstFocus)),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.add_row),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun HomeRowConfigContent(
|
||||||
|
config: HomeRowConfigDisplay,
|
||||||
|
moveUpAllowed: Boolean,
|
||||||
|
moveDownAllowed: Boolean,
|
||||||
|
onClickMove: (MoveDirection) -> Unit,
|
||||||
|
onClickDelete: () -> Unit,
|
||||||
|
modifier: Modifier,
|
||||||
|
) {
|
||||||
|
Box(
|
||||||
|
modifier = modifier,
|
||||||
|
) {
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
verticalAlignment = Alignment.CenterVertically,
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.heightIn(min = 40.dp, max = 88.dp)
|
||||||
|
.background(
|
||||||
|
color = MaterialTheme.colorScheme.surfaceColorAtElevation(1.dp),
|
||||||
|
shape = RoundedCornerShape(8.dp),
|
||||||
|
).padding(8.dp),
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = config.title,
|
||||||
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
|
style = MaterialTheme.typography.titleMedium,
|
||||||
|
overflow = TextOverflow.Ellipsis,
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
|
)
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(4.dp),
|
||||||
|
modifier = Modifier.wrapContentWidth(),
|
||||||
|
) {
|
||||||
|
Button(
|
||||||
|
onClick = { onClickMove.invoke(MoveDirection.UP) },
|
||||||
|
enabled = moveUpAllowed,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.fa_caret_up),
|
||||||
|
fontFamily = FontAwesome,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Button(
|
||||||
|
onClick = { onClickMove.invoke(MoveDirection.DOWN) },
|
||||||
|
enabled = moveDownAllowed,
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = stringResource(R.string.fa_caret_down),
|
||||||
|
fontFamily = FontAwesome,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
Button(
|
||||||
|
onClick = onClickDelete,
|
||||||
|
enabled = true,
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Delete,
|
||||||
|
contentDescription = "delete",
|
||||||
|
modifier = Modifier,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,359 @@
|
||||||
|
package com.github.damontecres.wholphin.ui.main.settings
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import androidx.compose.foundation.background
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.width
|
||||||
|
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.collectAsState
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import androidx.tv.material3.MaterialTheme
|
||||||
|
import com.github.damontecres.wholphin.R
|
||||||
|
import com.github.damontecres.wholphin.data.NavDrawerItemRepository
|
||||||
|
import com.github.damontecres.wholphin.data.ServerRepository
|
||||||
|
import com.github.damontecres.wholphin.data.model.BaseItem
|
||||||
|
import com.github.damontecres.wholphin.data.model.HomeRowConfig
|
||||||
|
import com.github.damontecres.wholphin.data.model.HomeRowConfigDisplay
|
||||||
|
import com.github.damontecres.wholphin.data.model.HomeRowViewOptions
|
||||||
|
import com.github.damontecres.wholphin.services.BackdropService
|
||||||
|
import com.github.damontecres.wholphin.services.LatestNextUpService
|
||||||
|
import com.github.damontecres.wholphin.services.UserPreferencesService
|
||||||
|
import com.github.damontecres.wholphin.ui.SlimItemFields
|
||||||
|
import com.github.damontecres.wholphin.ui.launchIO
|
||||||
|
import com.github.damontecres.wholphin.ui.main.HomePageContent
|
||||||
|
import com.github.damontecres.wholphin.ui.main.LatestData
|
||||||
|
import com.github.damontecres.wholphin.ui.nav.ServerNavDrawerItem
|
||||||
|
import com.github.damontecres.wholphin.util.GetGenresRequestHandler
|
||||||
|
import com.github.damontecres.wholphin.util.HomeRowLoadingState
|
||||||
|
import com.github.damontecres.wholphin.util.LoadingState
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import org.jellyfin.sdk.api.client.ApiClient
|
||||||
|
import org.jellyfin.sdk.model.api.CollectionType
|
||||||
|
import org.jellyfin.sdk.model.api.request.GetGenresRequest
|
||||||
|
import org.jellyfin.sdk.model.api.request.GetLatestMediaRequest
|
||||||
|
import java.util.UUID
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
class HomePageSettingsViewModel
|
||||||
|
@Inject
|
||||||
|
constructor(
|
||||||
|
@param:ApplicationContext private val context: Context,
|
||||||
|
private val api: ApiClient,
|
||||||
|
private val serverRepository: ServerRepository,
|
||||||
|
private val userPreferencesService: UserPreferencesService,
|
||||||
|
private val navDrawerItemRepository: NavDrawerItemRepository,
|
||||||
|
private val backdropService: BackdropService,
|
||||||
|
private val latestNextUpService: LatestNextUpService,
|
||||||
|
) : ViewModel() {
|
||||||
|
private val _state = MutableStateFlow(HomePageSettingsState.EMPTY)
|
||||||
|
val state: StateFlow<HomePageSettingsState> = _state
|
||||||
|
|
||||||
|
init {
|
||||||
|
viewModelScope.launchIO {
|
||||||
|
val navDrawerItems =
|
||||||
|
navDrawerItemRepository
|
||||||
|
.getNavDrawerItems()
|
||||||
|
val libraries =
|
||||||
|
navDrawerItems
|
||||||
|
.filter { it is ServerNavDrawerItem }
|
||||||
|
.map {
|
||||||
|
it as ServerNavDrawerItem
|
||||||
|
Library(it.itemId, it.name, it.type)
|
||||||
|
}
|
||||||
|
_state.update { it.copy(libraries = libraries) }
|
||||||
|
|
||||||
|
// TODO get config
|
||||||
|
|
||||||
|
// Or create default
|
||||||
|
val prefs = userPreferencesService.getCurrent().appPreferences.homePagePreferences
|
||||||
|
val includedIds =
|
||||||
|
navDrawerItemRepository
|
||||||
|
.getFilteredNavDrawerItems(navDrawerItems)
|
||||||
|
.filter { it is ServerNavDrawerItem }
|
||||||
|
.map {
|
||||||
|
val id = (it as ServerNavDrawerItem).itemId
|
||||||
|
val name = libraries.firstOrNull { it.itemId == id }?.name
|
||||||
|
val title =
|
||||||
|
name?.let { context.getString(R.string.recently_added_in, it) }
|
||||||
|
?: context.getString(R.string.recently_added)
|
||||||
|
HomeRowConfigDisplay(
|
||||||
|
title,
|
||||||
|
HomeRowConfig.RecentlyAdded(id, HomeRowViewOptions()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
val rowConfig =
|
||||||
|
listOf(
|
||||||
|
HomeRowConfigDisplay(
|
||||||
|
context.getString(R.string.continue_watching),
|
||||||
|
HomeRowConfig.ContinueWatching(
|
||||||
|
prefs.combineContinueNext,
|
||||||
|
HomeRowViewOptions(),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
) + includedIds
|
||||||
|
_state.update {
|
||||||
|
it.copy(rows = rowConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
fetchRowData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateBackdrop(item: BaseItem) {
|
||||||
|
viewModelScope.launchIO {
|
||||||
|
backdropService.submit(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun fetchRowData() {
|
||||||
|
val limit = 6
|
||||||
|
val rows =
|
||||||
|
serverRepository.currentUserDto.value?.let { userDto ->
|
||||||
|
val prefs = userPreferencesService.getCurrent().appPreferences.homePagePreferences
|
||||||
|
state.value.rows
|
||||||
|
.map { it.config }
|
||||||
|
.map { row ->
|
||||||
|
// TODO parallelize
|
||||||
|
when (row) {
|
||||||
|
is HomeRowConfig.ContinueWatching -> {
|
||||||
|
val resume = latestNextUpService.getResume(userDto.id, limit, true)
|
||||||
|
val nextUp =
|
||||||
|
latestNextUpService.getNextUp(
|
||||||
|
userDto.id,
|
||||||
|
limit,
|
||||||
|
prefs.enableRewatchingNextUp,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
val watching =
|
||||||
|
buildList {
|
||||||
|
if (row.combined) {
|
||||||
|
val items =
|
||||||
|
latestNextUpService.buildCombined(resume, nextUp)
|
||||||
|
add(
|
||||||
|
HomeRowLoadingState.Success(
|
||||||
|
title = context.getString(R.string.continue_watching),
|
||||||
|
items = items,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
if (resume.isNotEmpty()) {
|
||||||
|
add(
|
||||||
|
HomeRowLoadingState.Success(
|
||||||
|
title = context.getString(R.string.continue_watching),
|
||||||
|
items = resume,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (nextUp.isNotEmpty()) {
|
||||||
|
add(
|
||||||
|
HomeRowLoadingState.Success(
|
||||||
|
title = context.getString(R.string.next_up),
|
||||||
|
items = nextUp,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
watching
|
||||||
|
}
|
||||||
|
|
||||||
|
is HomeRowConfig.Genres -> {
|
||||||
|
val request =
|
||||||
|
GetGenresRequest(
|
||||||
|
parentId = row.parentId,
|
||||||
|
userId = userDto.id,
|
||||||
|
limit = limit,
|
||||||
|
)
|
||||||
|
val items =
|
||||||
|
GetGenresRequestHandler
|
||||||
|
.execute(api, request)
|
||||||
|
.content.items
|
||||||
|
.map { BaseItem(it, false) }
|
||||||
|
val name =
|
||||||
|
_state.value.libraries
|
||||||
|
.firstOrNull { it.itemId == row.parentId }
|
||||||
|
?.name
|
||||||
|
val title =
|
||||||
|
name?.let { context.getString(R.string.genres_in, it) }
|
||||||
|
?: context.getString(R.string.genres)
|
||||||
|
listOf(HomeRowLoadingState.Success(title, items))
|
||||||
|
}
|
||||||
|
|
||||||
|
is HomeRowConfig.RecentlyAdded -> {
|
||||||
|
val name =
|
||||||
|
_state.value.libraries
|
||||||
|
.firstOrNull { it.itemId == row.parentId }
|
||||||
|
?.name
|
||||||
|
val title =
|
||||||
|
name?.let { context.getString(R.string.recently_added_in, it) }
|
||||||
|
?: context.getString(R.string.recently_added)
|
||||||
|
val request =
|
||||||
|
GetLatestMediaRequest(
|
||||||
|
fields = SlimItemFields,
|
||||||
|
imageTypeLimit = 1,
|
||||||
|
parentId = row.parentId,
|
||||||
|
groupItems = true,
|
||||||
|
limit = limit,
|
||||||
|
isPlayed = null, // Server will handle user's preference
|
||||||
|
)
|
||||||
|
latestNextUpService.loadLatest(listOf(LatestData(title, request)))
|
||||||
|
}
|
||||||
|
|
||||||
|
is HomeRowConfig.RecentlyReleased -> {
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}.flatten()
|
||||||
|
}
|
||||||
|
rows?.let { rows ->
|
||||||
|
_state.update {
|
||||||
|
it.copy(loading = LoadingState.Success, rowData = rows)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <T> List<T>.move(
|
||||||
|
direction: MoveDirection,
|
||||||
|
index: Int,
|
||||||
|
): List<T> =
|
||||||
|
toMutableList().apply {
|
||||||
|
if (direction == MoveDirection.DOWN) {
|
||||||
|
val down = this[index]
|
||||||
|
val up = this[index + 1]
|
||||||
|
set(index, up)
|
||||||
|
set(index + 1, down)
|
||||||
|
} else {
|
||||||
|
val up = this[index]
|
||||||
|
val down = this[index - 1]
|
||||||
|
set(index - 1, up)
|
||||||
|
set(index, down)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun moveRow(
|
||||||
|
direction: MoveDirection,
|
||||||
|
index: Int,
|
||||||
|
) {
|
||||||
|
_state.update {
|
||||||
|
val rows = it.rows.move(direction, index)
|
||||||
|
// TODO would be more efficient to move rowData, but uncombined continue watching is two rows
|
||||||
|
// val rowData = it.rowData.move(direction, index)
|
||||||
|
it.copy(
|
||||||
|
loading = LoadingState.Loading,
|
||||||
|
rows = rows,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
viewModelScope.launchIO { fetchRowData() }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteRow(index: Int) {
|
||||||
|
_state.update {
|
||||||
|
val rows = it.rows.toMutableList().apply { removeAt(index) }
|
||||||
|
val rowData = it.rowData.toMutableList().apply { removeAt(index) }
|
||||||
|
it.copy(
|
||||||
|
rows = rows,
|
||||||
|
rowData = rowData,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class HomePageSettingsState(
|
||||||
|
val loading: LoadingState,
|
||||||
|
val rows: List<HomeRowConfigDisplay>,
|
||||||
|
val rowData: List<HomeRowLoadingState>,
|
||||||
|
val libraries: List<Library>,
|
||||||
|
) {
|
||||||
|
companion object {
|
||||||
|
val EMPTY =
|
||||||
|
HomePageSettingsState(
|
||||||
|
LoadingState.Pending,
|
||||||
|
listOf(),
|
||||||
|
listOf(),
|
||||||
|
listOf(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Library(
|
||||||
|
val itemId: UUID,
|
||||||
|
val name: String,
|
||||||
|
val collectionType: CollectionType,
|
||||||
|
)
|
||||||
|
|
||||||
|
val settingsWidth = 300.dp
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun HomePageSettings(
|
||||||
|
modifier: Modifier,
|
||||||
|
viewModel: HomePageSettingsViewModel = hiltViewModel(),
|
||||||
|
) {
|
||||||
|
val state by viewModel.state.collectAsState()
|
||||||
|
val listState = rememberLazyListState()
|
||||||
|
var destination by remember { mutableStateOf<HomePageSettingsDestination>(HomePageSettingsDestination.RowList) }
|
||||||
|
Row(
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(8.dp),
|
||||||
|
modifier = modifier,
|
||||||
|
) {
|
||||||
|
HomePageContent(
|
||||||
|
loadingState = state.loading,
|
||||||
|
homeRows = state.rowData,
|
||||||
|
onClickItem = { _, _ -> },
|
||||||
|
onLongClickItem = { _, _ -> },
|
||||||
|
onClickPlay = { _, _ -> },
|
||||||
|
showClock = false,
|
||||||
|
onUpdateBackdrop = viewModel::updateBackdrop,
|
||||||
|
listState = listState,
|
||||||
|
modifier = Modifier.fillMaxHeight().weight(1f),
|
||||||
|
)
|
||||||
|
Box(
|
||||||
|
modifier =
|
||||||
|
Modifier
|
||||||
|
.width(settingsWidth)
|
||||||
|
.fillMaxHeight()
|
||||||
|
.background(color = MaterialTheme.colorScheme.surface),
|
||||||
|
) {
|
||||||
|
when (destination) {
|
||||||
|
HomePageSettingsDestination.RowList -> {
|
||||||
|
HomePageRowList(
|
||||||
|
state = state,
|
||||||
|
onClickAdd = { destination = HomePageSettingsDestination.ChooseLibrary },
|
||||||
|
onClickMove = viewModel::moveRow,
|
||||||
|
onClickDelete = viewModel::deleteRow,
|
||||||
|
modifier = Modifier.fillMaxSize(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is HomePageSettingsDestination.ChooseLibrary -> {
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
|
|
||||||
|
is HomePageSettingsDestination.ChooseRow -> {
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
|
|
||||||
|
is HomePageSettingsDestination.RowSettings -> {
|
||||||
|
TODO()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package com.github.damontecres.wholphin.ui.main.settings
|
||||||
|
|
||||||
|
import com.github.damontecres.wholphin.data.model.HomeRowConfigDisplay
|
||||||
|
|
||||||
|
sealed interface HomePageSettingsDestination {
|
||||||
|
data object RowList : HomePageSettingsDestination
|
||||||
|
|
||||||
|
data object ChooseLibrary : HomePageSettingsDestination
|
||||||
|
|
||||||
|
data class ChooseRow(
|
||||||
|
val library: Library,
|
||||||
|
) : HomePageSettingsDestination
|
||||||
|
|
||||||
|
data class RowSettings(
|
||||||
|
val row: HomeRowConfigDisplay,
|
||||||
|
) : HomePageSettingsDestination
|
||||||
|
}
|
||||||
|
|
@ -33,6 +33,9 @@ sealed class Destination(
|
||||||
val id: Long = 0L,
|
val id: Long = 0L,
|
||||||
) : Destination()
|
) : Destination()
|
||||||
|
|
||||||
|
@Serializable
|
||||||
|
data object HomeSettings : Destination(true)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Settings(
|
data class Settings(
|
||||||
val screen: PreferenceScreenOption,
|
val screen: PreferenceScreenOption,
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import com.github.damontecres.wholphin.ui.detail.series.SeriesOverview
|
||||||
import com.github.damontecres.wholphin.ui.discover.DiscoverPage
|
import com.github.damontecres.wholphin.ui.discover.DiscoverPage
|
||||||
import com.github.damontecres.wholphin.ui.main.HomePage
|
import com.github.damontecres.wholphin.ui.main.HomePage
|
||||||
import com.github.damontecres.wholphin.ui.main.SearchPage
|
import com.github.damontecres.wholphin.ui.main.SearchPage
|
||||||
|
import com.github.damontecres.wholphin.ui.main.settings.HomePageSettings
|
||||||
import com.github.damontecres.wholphin.ui.playback.PlaybackPage
|
import com.github.damontecres.wholphin.ui.playback.PlaybackPage
|
||||||
import com.github.damontecres.wholphin.ui.preferences.PreferencesPage
|
import com.github.damontecres.wholphin.ui.preferences.PreferencesPage
|
||||||
import com.github.damontecres.wholphin.ui.setup.InstallUpdatePage
|
import com.github.damontecres.wholphin.ui.setup.InstallUpdatePage
|
||||||
|
|
@ -60,6 +61,10 @@ fun DestinationContent(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
is Destination.HomeSettings -> {
|
||||||
|
HomePageSettings(modifier)
|
||||||
|
}
|
||||||
|
|
||||||
is Destination.PlaybackList,
|
is Destination.PlaybackList,
|
||||||
is Destination.Playback,
|
is Destination.Playback,
|
||||||
-> {
|
-> {
|
||||||
|
|
|
||||||
|
|
@ -462,6 +462,8 @@
|
||||||
<string name="request_4k">Request in 4K</string>
|
<string name="request_4k">Request in 4K</string>
|
||||||
<string name="software_decoding_av1">AV1 software decoding</string>
|
<string name="software_decoding_av1">AV1 software decoding</string>
|
||||||
<string name="upgrade_mpv_toast">MPV is now the default player except for HDR.\nYou can change this in settings.</string>
|
<string name="upgrade_mpv_toast">MPV is now the default player except for HDR.\nYou can change this in settings.</string>
|
||||||
|
<string name="add_row">Add row</string>
|
||||||
|
<string name="genres_in">Genres in %1$s</string>
|
||||||
|
|
||||||
<string-array name="theme_song_volume">
|
<string-array name="theme_song_volume">
|
||||||
<item>Disabled</item>
|
<item>Disabled</item>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue