diff --git a/app/build.gradle.kts b/app/build.gradle.kts index cc2863cc..82fc9620 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -8,6 +8,7 @@ plugins { alias(libs.plugins.hilt) alias(libs.plugins.room) alias(libs.plugins.protobuf) + alias(libs.plugins.kotlin.plugin.serialization) } android { @@ -112,6 +113,8 @@ dependencies { implementation(libs.androidx.room.common.jvm) implementation(libs.androidx.room.ktx) implementation(libs.androidx.compose.material3) + implementation(libs.lifecycle.viewmodel.navigation3) + implementation(libs.androidx.hilt.navigation.compose) ksp(libs.androidx.room.compiler) ksp(libs.hilt.android.compiler) diff --git a/app/src/main/java/com/github/damontecres/dolphin/MainActivity.kt b/app/src/main/java/com/github/damontecres/dolphin/MainActivity.kt index 187498ab..4450208c 100644 --- a/app/src/main/java/com/github/damontecres/dolphin/MainActivity.kt +++ b/app/src/main/java/com/github/damontecres/dolphin/MainActivity.kt @@ -3,22 +3,31 @@ package com.github.damontecres.dolphin import android.os.Bundle import androidx.activity.compose.setContent import androidx.appcompat.app.AppCompatActivity +import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.size +import androidx.compose.material3.CircularProgressIndicator +import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +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.graphics.RectangleShape +import androidx.compose.ui.unit.dp import androidx.datastore.core.DataStore import androidx.tv.material3.ExperimentalTvMaterial3Api +import androidx.tv.material3.MaterialTheme import androidx.tv.material3.Surface import com.github.damontecres.dolphin.data.ServerRepository import com.github.damontecres.dolphin.preferences.UserPreferences import com.github.damontecres.dolphin.ui.ServerLoginPage -import com.github.damontecres.dolphin.ui.main.MainPage +import com.github.damontecres.dolphin.ui.nav.ApplicationContent import com.github.damontecres.dolphin.ui.theme.DolphinTheme import dagger.hilt.android.AndroidEntryPoint -import kotlinx.coroutines.launch import okhttp3.OkHttpClient import javax.inject.Inject @@ -45,26 +54,37 @@ class MainActivity : AppCompatActivity() { ) { CoilConfig(serverRepository, okHttpClient, false) + var isRestoringSession by remember { mutableStateOf(true) } val preferences by userPreferencesDataStore.data.collectAsState(null) preferences?.let { preferences -> - if (preferences.currentServerId.isNotBlank() && preferences.currentUserId.isNotBlank()) { - scope.launch { + LaunchedEffect(Unit) { + if (preferences.currentServerId.isNotBlank() && preferences.currentUserId.isNotBlank()) { serverRepository.restoreSession( preferences.currentServerId, preferences.currentUserId, ) } - } else { - ServerLoginPage(modifier = Modifier.fillMaxSize()) + isRestoringSession = false } val server = serverRepository.currentServer val user = serverRepository.currentUser if (server != null && user != null) { - // TODO navigation - MainPage( + ApplicationContent( preferences = preferences, modifier = Modifier.fillMaxSize(), ) + } else if (isRestoringSession) { + Box( + modifier = Modifier.size(200.dp), + contentAlignment = Alignment.Center, + ) { + CircularProgressIndicator( + color = MaterialTheme.colorScheme.border, + modifier = Modifier.align(Alignment.Center), + ) + } + } else { + ServerLoginPage(modifier = Modifier.fillMaxSize()) } } } diff --git a/app/src/main/java/com/github/damontecres/dolphin/data/DolphinDatabase.kt b/app/src/main/java/com/github/damontecres/dolphin/data/DolphinDatabase.kt index aa1fd500..6e39d085 100644 --- a/app/src/main/java/com/github/damontecres/dolphin/data/DolphinDatabase.kt +++ b/app/src/main/java/com/github/damontecres/dolphin/data/DolphinDatabase.kt @@ -3,7 +3,11 @@ package com.github.damontecres.dolphin.data import androidx.room.Database import androidx.room.RoomDatabase -@Database(entities = [JellyfinServer::class, JellyfinUser::class], version = 1, exportSchema = false) +@Database( + entities = [JellyfinServer::class, JellyfinUser::class], + version = 2, + exportSchema = false, +) abstract class DolphinDatabase : RoomDatabase() { abstract fun serverDao(): JellyfinServerDao } diff --git a/app/src/main/java/com/github/damontecres/dolphin/data/JellyfinServer.kt b/app/src/main/java/com/github/damontecres/dolphin/data/JellyfinServer.kt index bfc76da8..f3fbe8d5 100644 --- a/app/src/main/java/com/github/damontecres/dolphin/data/JellyfinServer.kt +++ b/app/src/main/java/com/github/damontecres/dolphin/data/JellyfinServer.kt @@ -15,7 +15,7 @@ import androidx.room.Transaction @Entity(tableName = "servers") data class JellyfinServer( @PrimaryKey val id: String, - val name: String, + val name: String?, val url: String, ) @@ -37,7 +37,7 @@ data class JellyfinUser( val name: String?, @ColumnInfo(index = true) val serverId: String, - val accessToken: String, + val accessToken: String?, ) data class JellyfinServerUsers( diff --git a/app/src/main/java/com/github/damontecres/dolphin/data/ServerRepository.kt b/app/src/main/java/com/github/damontecres/dolphin/data/ServerRepository.kt index f9682525..11f1b39d 100644 --- a/app/src/main/java/com/github/damontecres/dolphin/data/ServerRepository.kt +++ b/app/src/main/java/com/github/damontecres/dolphin/data/ServerRepository.kt @@ -10,7 +10,9 @@ import org.jellyfin.sdk.api.client.exception.InvalidStatusException import org.jellyfin.sdk.api.client.extensions.userApi import timber.log.Timber import javax.inject.Inject +import javax.inject.Singleton +@Singleton class ServerRepository @Inject constructor( @@ -55,11 +57,22 @@ class ServerRepository Timber.v("Changing user to ${user.name} on ${server.url}") apiClient.update(baseUrl = server.url, accessToken = user.accessToken) try { - apiClient.userApi - .getCurrentUser() - .content.name - _currentServer = server - _currentUser = user + val userDto = + apiClient.userApi + .getCurrentUser() + .content + val updatedServer = server.copy(name = userDto.serverName) + val updatedUser = + user.copy( + id = userDto.id.toString(), + name = userDto.name, + ) + withContext(Dispatchers.IO) { + serverDao.addServer(updatedServer) + serverDao.addUser(updatedUser) + } + _currentServer = updatedServer + _currentUser = updatedUser } catch (e: InvalidStatusException) { // TODO Timber.e(e) diff --git a/app/src/main/java/com/github/damontecres/dolphin/data/model/DolphinModel.kt b/app/src/main/java/com/github/damontecres/dolphin/data/model/DolphinModel.kt index e4d814f4..dee37cd3 100644 --- a/app/src/main/java/com/github/damontecres/dolphin/data/model/DolphinModel.kt +++ b/app/src/main/java/com/github/damontecres/dolphin/data/model/DolphinModel.kt @@ -16,6 +16,13 @@ fun convertModel( api: ApiClient, ): DolphinModel = when (dto.type) { + BaseItemKind.COLLECTION_FOLDER -> Library.fromDto(dto, api) + + // TODO BaseItemKind.VIDEO -> Video.fromDto(dto, api) + BaseItemKind.SERIES -> Video.fromDto(dto, api) + BaseItemKind.MOVIE -> Video.fromDto(dto, api) + BaseItemKind.SEASON -> Video.fromDto(dto, api) + BaseItemKind.EPISODE -> Video.fromDto(dto, api) else -> throw IllegalArgumentException("Unsupported item type: ${dto.type}") } diff --git a/app/src/main/java/com/github/damontecres/dolphin/data/model/Library.kt b/app/src/main/java/com/github/damontecres/dolphin/data/model/Library.kt new file mode 100644 index 00000000..e2b834e6 --- /dev/null +++ b/app/src/main/java/com/github/damontecres/dolphin/data/model/Library.kt @@ -0,0 +1,28 @@ +package com.github.damontecres.dolphin.data.model + +import org.jellyfin.sdk.api.client.ApiClient +import org.jellyfin.sdk.api.client.extensions.imageApi +import org.jellyfin.sdk.model.api.BaseItemDto +import org.jellyfin.sdk.model.api.CollectionType +import org.jellyfin.sdk.model.api.ImageType +import java.util.UUID + +data class Library( + override val id: UUID, + override val name: String?, + override val imageUrl: String?, + val type: CollectionType, +) : DolphinModel { + companion object { + fun fromDto( + dto: BaseItemDto, + api: ApiClient, + ): Library = + Library( + id = dto.id, + name = dto.name, + imageUrl = api.imageApi.getItemImageUrl(dto.id, ImageType.PRIMARY), + type = dto.collectionType ?: CollectionType.UNKNOWN, + ) + } +} diff --git a/app/src/main/java/com/github/damontecres/dolphin/hilt/DatabaseModule.kt b/app/src/main/java/com/github/damontecres/dolphin/hilt/DatabaseModule.kt index c2d61b27..d29a95ca 100644 --- a/app/src/main/java/com/github/damontecres/dolphin/hilt/DatabaseModule.kt +++ b/app/src/main/java/com/github/damontecres/dolphin/hilt/DatabaseModule.kt @@ -30,7 +30,8 @@ object DatabaseModule { context, DolphinDatabase::class.java, "dolphin", - ).build() + ).fallbackToDestructiveMigration(false) + .build() @Provides @Singleton diff --git a/app/src/main/java/com/github/damontecres/dolphin/ui/ServerManagement.kt b/app/src/main/java/com/github/damontecres/dolphin/ui/ServerManagement.kt index 871d5ec0..b3aa35dd 100644 --- a/app/src/main/java/com/github/damontecres/dolphin/ui/ServerManagement.kt +++ b/app/src/main/java/com/github/damontecres/dolphin/ui/ServerManagement.kt @@ -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, diff --git a/app/src/main/java/com/github/damontecres/dolphin/ui/cards/DolphinCard.kt b/app/src/main/java/com/github/damontecres/dolphin/ui/cards/DolphinCard.kt index 308b14d6..48232be7 100644 --- a/app/src/main/java/com/github/damontecres/dolphin/ui/cards/DolphinCard.kt +++ b/app/src/main/java/com/github/damontecres/dolphin/ui/cards/DolphinCard.kt @@ -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() } } diff --git a/app/src/main/java/com/github/damontecres/dolphin/ui/main/MainPage.kt b/app/src/main/java/com/github/damontecres/dolphin/ui/main/MainPage.kt index 61928665..1601f84a 100644 --- a/app/src/main/java/com/github/damontecres/dolphin/ui/main/MainPage.kt +++ b/app/src/main/java/com/github/damontecres/dolphin/ui/main/MainPage.kt @@ -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, ) } diff --git a/app/src/main/java/com/github/damontecres/dolphin/ui/nav/ApplicationContent.kt b/app/src/main/java/com/github/damontecres/dolphin/ui/nav/ApplicationContent.kt new file mode 100644 index 00000000..b7a57400 --- /dev/null +++ b/app/src/main/java/com/github/damontecres/dolphin/ui/nav/ApplicationContent.kt @@ -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, + ) + } + } + }, + ) +} diff --git a/app/src/main/java/com/github/damontecres/dolphin/ui/nav/Destination.kt b/app/src/main/java/com/github/damontecres/dolphin/ui/nav/Destination.kt new file mode 100644 index 00000000..7ac457de --- /dev/null +++ b/app/src/main/java/com/github/damontecres/dolphin/ui/nav/Destination.kt @@ -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) +} diff --git a/app/src/main/java/com/github/damontecres/dolphin/ui/nav/DestinationContent.kt b/app/src/main/java/com/github/damontecres/dolphin/ui/nav/DestinationContent.kt new file mode 100644 index 00000000..4e408c2f --- /dev/null +++ b/app/src/main/java/com/github/damontecres/dolphin/ui/nav/DestinationContent.kt @@ -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() + } +} diff --git a/app/src/main/java/com/github/damontecres/dolphin/ui/nav/NavDrawer.kt b/app/src/main/java/com/github/damontecres/dolphin/ui/nav/NavDrawer.kt new file mode 100644 index 00000000..fbeaa66f --- /dev/null +++ b/app/src/main/java/com/github/damontecres/dolphin/ui/nav/NavDrawer.kt @@ -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>(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, + ) + } +} diff --git a/app/src/main/java/com/github/damontecres/dolphin/ui/nav/NavigationManager.kt b/app/src/main/java/com/github/damontecres/dolphin/ui/nav/NavigationManager.kt new file mode 100644 index 00000000..b458256f --- /dev/null +++ b/app/src/main/java/com/github/damontecres/dolphin/ui/nav/NavigationManager.kt @@ -0,0 +1,9 @@ +package com.github.damontecres.dolphin.ui.nav + +interface NavigationManager { + fun navigateTo(destination: Destination) + + fun goBack() + + fun goToHome() +} diff --git a/app/src/main/java/com/github/damontecres/dolphin/util/UuidSerializer.kt b/app/src/main/java/com/github/damontecres/dolphin/util/UuidSerializer.kt new file mode 100644 index 00000000..b0e4053d --- /dev/null +++ b/app/src/main/java/com/github/damontecres/dolphin/util/UuidSerializer.kt @@ -0,0 +1,24 @@ +package com.github.damontecres.dolphin.util + +import kotlinx.serialization.KSerializer +import kotlinx.serialization.descriptors.PrimitiveKind +import kotlinx.serialization.descriptors.PrimitiveSerialDescriptor +import kotlinx.serialization.descriptors.SerialDescriptor +import kotlinx.serialization.encoding.Decoder +import kotlinx.serialization.encoding.Encoder +import java.util.UUID + +class UuidSerializer : KSerializer { + override val descriptor: SerialDescriptor + get() = PrimitiveSerialDescriptor("UUID", PrimitiveKind.LONG) + + override fun deserialize(decoder: Decoder): UUID = UUID(decoder.decodeLong(), decoder.decodeLong()) + + override fun serialize( + encoder: Encoder, + value: UUID, + ) { + encoder.encodeLong(value.mostSignificantBits) + encoder.encodeLong(value.leastSignificantBits) + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index f9a34f11..2d9e5904 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,6 @@ [versions] agp = "8.13.0" +hiltNavigationCompose = "1.3.0" kotlin = "2.2.20" ksp = "2.2.20-2.0.2" # https://github.com/google/ksp/issues/2596 2.0.3 is broken coreKtx = "1.17.0" @@ -25,6 +26,7 @@ protobuf-javalite = "4.32.1" hilt = "2.57.1" room = "2.8.0" material3 = "1.3.2" +lifecycleViewmodelNavigation3 = "2.10.0-alpha03" [libraries] androidx-core-ktx = { group = "androidx.core", name = "core-ktx", version.ref = "coreKtx" } @@ -40,6 +42,7 @@ androidx-compose-material3 = { group = "androidx.compose.material3", name = "mat androidx-compose-runtime = { module = "androidx.compose.runtime:runtime-android", version.ref = "compose-runtime" } androidx-compose-runtime-livedata = { module = "androidx.compose.runtime:runtime-livedata", version.ref = "compose-runtime" } +androidx-hilt-navigation-compose = { module = "androidx.hilt:hilt-navigation-compose", version.ref = "hiltNavigationCompose" } androidx-tv-foundation = { group = "androidx.tv", name = "tv-foundation", version.ref = "tvFoundation" } androidx-tv-material = { group = "androidx.tv", name = "tv-material", version.ref = "tvMaterial" } androidx-lifecycle-runtime-ktx = { group = "androidx.lifecycle", name = "lifecycle-runtime-ktx", version.ref = "lifecycleRuntimeKtx" } @@ -79,6 +82,7 @@ androidx-room-ktx = { group = "androidx.room", name = "room-ktx", version.ref = androidx-room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" } timber = { module = "com.jakewharton.timber:timber", version.ref = "timber" } +lifecycle-viewmodel-navigation3 = { group = "androidx.lifecycle", name = "lifecycle-viewmodel-navigation3", version.ref = "lifecycleViewmodelNavigation3" } [plugins] android-application = { id = "com.android.application", version.ref = "agp" }