mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-09 16:11:20 +02:00
Basic navigation
This commit is contained in:
parent
51ad92f539
commit
80070f4174
18 changed files with 496 additions and 22 deletions
|
|
@ -57,17 +57,18 @@ class ServerManagementViewModel
|
|||
|
||||
val accessToken = authenticationResult.accessToken
|
||||
if (accessToken != null) {
|
||||
val authedUser = authenticationResult.user
|
||||
val server =
|
||||
authenticationResult.serverId?.let {
|
||||
JellyfinServer(
|
||||
id = it,
|
||||
name = serverUrl,
|
||||
name = authedUser?.serverName,
|
||||
url = serverUrl,
|
||||
)
|
||||
}
|
||||
if (server != null) {
|
||||
val user =
|
||||
authenticationResult.user?.let {
|
||||
authedUser?.let {
|
||||
JellyfinUser(
|
||||
id = it.id.toString(),
|
||||
name = it.name,
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.github.damontecres.dolphin.ui.cards
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import com.github.damontecres.dolphin.data.model.DolphinModel
|
||||
import com.github.damontecres.dolphin.data.model.Library
|
||||
import com.github.damontecres.dolphin.data.model.Video
|
||||
|
||||
@Composable
|
||||
|
|
@ -18,6 +19,8 @@ fun DolphinCard(
|
|||
onClick = onClick,
|
||||
modifier = modifier,
|
||||
)
|
||||
|
||||
is Library -> TODO()
|
||||
null -> TODO()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,16 +14,18 @@ import androidx.compose.runtime.livedata.observeAsState
|
|||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import androidx.tv.material3.Text
|
||||
import com.github.damontecres.dolphin.data.model.DolphinModel
|
||||
import com.github.damontecres.dolphin.data.model.convertModel
|
||||
import com.github.damontecres.dolphin.isNotNullOrBlank
|
||||
import com.github.damontecres.dolphin.preferences.UserPreferences
|
||||
import com.github.damontecres.dolphin.ui.cards.DolphinCard
|
||||
import com.github.damontecres.dolphin.ui.nav.Destination
|
||||
import com.github.damontecres.dolphin.ui.nav.NavigationManager
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.launch
|
||||
|
|
@ -118,8 +120,9 @@ class MainViewModel
|
|||
@Composable
|
||||
fun MainPage(
|
||||
preferences: UserPreferences,
|
||||
navigationManager: NavigationManager,
|
||||
modifier: Modifier,
|
||||
viewModel: MainViewModel = viewModel(),
|
||||
viewModel: MainViewModel = hiltViewModel(),
|
||||
) {
|
||||
val homeRows by viewModel.homeRows.observeAsState(listOf())
|
||||
Column(modifier = modifier) {
|
||||
|
|
@ -129,6 +132,7 @@ fun MainPage(
|
|||
item {
|
||||
HomePageRow(
|
||||
row = row,
|
||||
onClickItem = { navigationManager.navigateTo(Destination.MediaItem(it.id)) },
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
)
|
||||
}
|
||||
|
|
@ -140,6 +144,7 @@ fun MainPage(
|
|||
@Composable
|
||||
fun HomePageRow(
|
||||
row: HomeRow,
|
||||
onClickItem: (DolphinModel) -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Column(
|
||||
|
|
@ -160,7 +165,7 @@ fun HomePageRow(
|
|||
items(row.items) { item ->
|
||||
DolphinCard(
|
||||
item = item,
|
||||
onClick = {},
|
||||
onClick = { onClickItem.invoke(item) },
|
||||
modifier = Modifier,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
package com.github.damontecres.dolphin.ui.nav
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator
|
||||
import androidx.navigation3.runtime.NavEntry
|
||||
import androidx.navigation3.runtime.rememberNavBackStack
|
||||
import androidx.navigation3.runtime.rememberSavedStateNavEntryDecorator
|
||||
import androidx.navigation3.scene.rememberSceneSetupNavEntryDecorator
|
||||
import androidx.navigation3.ui.NavDisplay
|
||||
import com.github.damontecres.dolphin.preferences.UserPreferences
|
||||
|
||||
@Composable
|
||||
fun ApplicationContent(
|
||||
preferences: UserPreferences,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
val backStack = rememberNavBackStack(Destination.Main)
|
||||
|
||||
val navigationManager =
|
||||
object : NavigationManager {
|
||||
override fun navigateTo(destination: Destination) {
|
||||
backStack.add(destination)
|
||||
}
|
||||
|
||||
override fun goBack() {
|
||||
backStack.removeLastOrNull()
|
||||
}
|
||||
|
||||
override fun goToHome() {
|
||||
backStack.clear()
|
||||
backStack.add(Destination.Main)
|
||||
}
|
||||
}
|
||||
|
||||
NavDisplay(
|
||||
backStack = backStack,
|
||||
onBack = { backStack.removeLastOrNull() },
|
||||
entryDecorators =
|
||||
listOf(
|
||||
rememberSceneSetupNavEntryDecorator(),
|
||||
rememberSavedStateNavEntryDecorator(),
|
||||
rememberViewModelStoreNavEntryDecorator(),
|
||||
),
|
||||
entryProvider = { key ->
|
||||
NavEntry(key) {
|
||||
key as Destination
|
||||
if (key.fullScreen) {
|
||||
DestinationContent(
|
||||
destination = key,
|
||||
preferences = preferences,
|
||||
navigationManager = navigationManager,
|
||||
modifier = modifier.fillMaxSize(),
|
||||
)
|
||||
} else {
|
||||
NavDrawer(
|
||||
destination = key,
|
||||
preferences = preferences,
|
||||
navigationManager = navigationManager,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
package com.github.damontecres.dolphin.ui.nav
|
||||
|
||||
import androidx.navigation3.runtime.NavKey
|
||||
import com.github.damontecres.dolphin.util.UuidSerializer
|
||||
import kotlinx.serialization.Serializable
|
||||
import java.util.UUID
|
||||
|
||||
@Serializable
|
||||
sealed class Destination(
|
||||
val fullScreen: Boolean = false,
|
||||
) : NavKey {
|
||||
@Serializable
|
||||
data object Setup : Destination(true)
|
||||
|
||||
@Serializable
|
||||
data object Main : Destination()
|
||||
|
||||
@Serializable
|
||||
data object Settings : Destination(true)
|
||||
|
||||
@Serializable
|
||||
data object Search : Destination()
|
||||
|
||||
@Serializable
|
||||
data class MediaItem(
|
||||
@Serializable(with = UuidSerializer::class) val itemId: UUID,
|
||||
) : Destination()
|
||||
|
||||
@Serializable
|
||||
data class Playback(
|
||||
val itemId: String,
|
||||
val positionMs: Long,
|
||||
) : Destination(true)
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package com.github.damontecres.dolphin.ui.nav
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.tv.material3.Text
|
||||
import com.github.damontecres.dolphin.preferences.UserPreferences
|
||||
import com.github.damontecres.dolphin.ui.main.MainPage
|
||||
|
||||
@Composable
|
||||
fun DestinationContent(
|
||||
destination: Destination,
|
||||
preferences: UserPreferences,
|
||||
navigationManager: NavigationManager,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
when (destination) {
|
||||
Destination.Main -> {
|
||||
MainPage(
|
||||
preferences = preferences,
|
||||
navigationManager = navigationManager,
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
is Destination.MediaItem -> {
|
||||
Text("MediaItem: ${destination.itemId}")
|
||||
}
|
||||
is Destination.Playback -> TODO()
|
||||
Destination.Search -> TODO()
|
||||
Destination.Settings -> TODO()
|
||||
Destination.Setup -> TODO()
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,219 @@
|
|||
package com.github.damontecres.dolphin.ui.nav
|
||||
|
||||
import androidx.activity.compose.BackHandler
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.focusGroup
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.PaddingValues
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.AccountCircle
|
||||
import androidx.compose.material.icons.filled.DateRange
|
||||
import androidx.compose.material.icons.filled.Email
|
||||
import androidx.compose.material.icons.filled.Home
|
||||
import androidx.compose.material.icons.filled.Info
|
||||
import androidx.compose.material.icons.filled.Settings
|
||||
import androidx.compose.material.icons.filled.ShoppingCart
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
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.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.findViewTreeViewModelStoreOwner
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import androidx.tv.material3.DrawerValue
|
||||
import androidx.tv.material3.Icon
|
||||
import androidx.tv.material3.MaterialTheme
|
||||
import androidx.tv.material3.NavigationDrawer
|
||||
import androidx.tv.material3.NavigationDrawerItem
|
||||
import androidx.tv.material3.NavigationDrawerScope
|
||||
import androidx.tv.material3.ProvideTextStyle
|
||||
import androidx.tv.material3.Text
|
||||
import androidx.tv.material3.rememberDrawerState
|
||||
import com.github.damontecres.dolphin.data.ServerRepository
|
||||
import com.github.damontecres.dolphin.data.model.Library
|
||||
import com.github.damontecres.dolphin.preferences.UserPreferences
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import org.jellyfin.sdk.api.client.extensions.userViewsApi
|
||||
import org.jellyfin.sdk.model.api.CollectionType
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
class NavDrawerViewModel
|
||||
@Inject
|
||||
constructor(
|
||||
val serverRepository: ServerRepository,
|
||||
val api: ApiClient,
|
||||
) : ViewModel() {
|
||||
val libraries = MutableLiveData<List<Library>>(listOf())
|
||||
|
||||
init {
|
||||
viewModelScope.launch {
|
||||
val userViews =
|
||||
api.userViewsApi
|
||||
.getUserViews()
|
||||
.content.items
|
||||
Timber.v("userViews: ${userViews.map { it.type }}")
|
||||
libraries.value = userViews.map { Library.fromDto(it, api) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NavDrawer(
|
||||
destination: Destination,
|
||||
preferences: UserPreferences,
|
||||
navigationManager: NavigationManager,
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: NavDrawerViewModel = hiltViewModel(LocalView.current.findViewTreeViewModelStoreOwner()!!),
|
||||
) {
|
||||
val drawerState = rememberDrawerState(DrawerValue.Closed)
|
||||
val listState = rememberLazyListState()
|
||||
val scope = rememberCoroutineScope()
|
||||
val context = LocalContext.current
|
||||
val drawerFocusRequester = remember { FocusRequester() }
|
||||
BackHandler(enabled = (drawerState.currentValue == DrawerValue.Closed && destination == Destination.Main)) {
|
||||
drawerState.setValue(DrawerValue.Open)
|
||||
drawerFocusRequester.requestFocus()
|
||||
}
|
||||
|
||||
val user = viewModel.serverRepository.currentUser
|
||||
val libraries by viewModel.libraries.observeAsState(listOf())
|
||||
|
||||
NavigationDrawer(
|
||||
modifier =
|
||||
modifier
|
||||
.focusRequester(drawerFocusRequester),
|
||||
drawerState = drawerState,
|
||||
drawerContent = {
|
||||
ProvideTextStyle(MaterialTheme.typography.labelMedium) {
|
||||
LazyColumn(
|
||||
state = listState,
|
||||
contentPadding = PaddingValues(0.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.SpaceBetween,
|
||||
modifier =
|
||||
Modifier
|
||||
.fillMaxHeight()
|
||||
.background(MaterialTheme.colorScheme.background)
|
||||
.focusGroup(),
|
||||
) {
|
||||
item {
|
||||
IconNavItem(
|
||||
text = user?.name ?: "",
|
||||
icon = Icons.Default.AccountCircle,
|
||||
onClick = {},
|
||||
)
|
||||
}
|
||||
item {
|
||||
IconNavItem(
|
||||
text = "Home",
|
||||
icon = Icons.Default.Home,
|
||||
onClick = {},
|
||||
)
|
||||
}
|
||||
items(libraries) {
|
||||
LibraryNavItem(
|
||||
library = it,
|
||||
onClick = { navigationManager.navigateTo(Destination.MediaItem(it.id)) },
|
||||
)
|
||||
}
|
||||
item {
|
||||
IconNavItem(
|
||||
text = "Settings",
|
||||
icon = Icons.Default.Settings,
|
||||
onClick = {},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
) {
|
||||
// Drawer content
|
||||
DestinationContent(
|
||||
destination = destination,
|
||||
preferences = preferences,
|
||||
navigationManager = navigationManager,
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NavigationDrawerScope.IconNavItem(
|
||||
text: String,
|
||||
icon: ImageVector,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
NavigationDrawerItem(
|
||||
modifier = modifier,
|
||||
selected = false,
|
||||
onClick = onClick,
|
||||
leadingContent = {
|
||||
Icon(
|
||||
icon,
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
interactionSource = null,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier,
|
||||
text = text,
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun NavigationDrawerScope.LibraryNavItem(
|
||||
library: Library,
|
||||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
// TODO
|
||||
val icon =
|
||||
when (library.type) {
|
||||
CollectionType.MOVIES -> Icons.Default.Email
|
||||
CollectionType.TVSHOWS -> Icons.Default.DateRange
|
||||
CollectionType.HOMEVIDEOS -> Icons.Default.ShoppingCart
|
||||
else -> Icons.Default.Info
|
||||
}
|
||||
NavigationDrawerItem(
|
||||
modifier = modifier,
|
||||
selected = false,
|
||||
onClick = onClick,
|
||||
leadingContent = {
|
||||
Icon(
|
||||
icon,
|
||||
contentDescription = null,
|
||||
)
|
||||
},
|
||||
interactionSource = null,
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier,
|
||||
text = library.name ?: library.id.toString(),
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package com.github.damontecres.dolphin.ui.nav
|
||||
|
||||
interface NavigationManager {
|
||||
fun navigateTo(destination: Destination)
|
||||
|
||||
fun goBack()
|
||||
|
||||
fun goToHome()
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue