Move NavigationManager to hilt

This commit is contained in:
Damontecres 2025-10-08 18:13:28 -04:00
parent b63e933386
commit 730a578117
No known key found for this signature in database
31 changed files with 159 additions and 145 deletions

View file

@ -103,7 +103,7 @@ android {
buildTypes {
release {
isMinifyEnabled = false
signingConfig = signingConfigs.getByName("debug")
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro",

View file

@ -53,6 +53,9 @@ class MainActivity : AppCompatActivity() {
@Inject
lateinit var okHttpClient: OkHttpClient
@Inject
lateinit var navigationManager: NavigationManager
@OptIn(ExperimentalTvMaterial3Api::class)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -95,7 +98,7 @@ class MainActivity : AppCompatActivity() {
createDeviceProfile(this@MainActivity, preferences, false)
}
val backStack = rememberNavBackStack(Destination.Main())
val navigationManager = NavigationManager(backStack)
navigationManager.backStack = backStack
if (server != null && user != null) {
ApplicationContent(
@ -119,12 +122,10 @@ class MainActivity : AppCompatActivity() {
} else if (server != null) {
// Have server but no user, go to user selection
SwitchUserContent(
navigationManager = navigationManager,
modifier = Modifier.fillMaxSize(),
)
} else {
SwitchServerContent(
navigationManager = navigationManager,
modifier = Modifier.fillMaxSize(),
)
}

View file

@ -8,13 +8,13 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.github.damontecres.dolphin.data.model.BaseItem
import com.github.damontecres.dolphin.preferences.UserPreferences
import com.github.damontecres.dolphin.ui.DefaultItemFields
import com.github.damontecres.dolphin.ui.OneTimeLaunchedEffect
import com.github.damontecres.dolphin.ui.main.HomePageContent
import com.github.damontecres.dolphin.ui.main.HomeRow
import com.github.damontecres.dolphin.ui.main.HomeSection
import com.github.damontecres.dolphin.ui.nav.NavigationManager
import com.github.damontecres.dolphin.util.ApiRequestPager
import com.github.damontecres.dolphin.util.ExceptionHandler
import com.github.damontecres.dolphin.util.GetItemsRequestHandler
@ -126,9 +126,9 @@ class RecommendedMovieViewModel
@Composable
fun RecommendedMovie(
preferences: UserPreferences,
navigationManager: NavigationManager,
parentId: UUID,
modifier: Modifier,
onClickItem: (BaseItem) -> Unit,
modifier: Modifier = Modifier,
viewModel: RecommendedMovieViewModel = hiltViewModel(),
) {
OneTimeLaunchedEffect {
@ -144,8 +144,8 @@ fun RecommendedMovie(
LoadingState.Success ->
HomePageContent(
navigationManager = navigationManager,
homeRows = rows,
onClickItem = onClickItem,
modifier = modifier,
)
}

View file

@ -8,13 +8,13 @@ import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import com.github.damontecres.dolphin.data.model.BaseItem
import com.github.damontecres.dolphin.preferences.UserPreferences
import com.github.damontecres.dolphin.ui.DefaultItemFields
import com.github.damontecres.dolphin.ui.OneTimeLaunchedEffect
import com.github.damontecres.dolphin.ui.main.HomePageContent
import com.github.damontecres.dolphin.ui.main.HomeRow
import com.github.damontecres.dolphin.ui.main.HomeSection
import com.github.damontecres.dolphin.ui.nav.NavigationManager
import com.github.damontecres.dolphin.util.ApiRequestPager
import com.github.damontecres.dolphin.util.ExceptionHandler
import com.github.damontecres.dolphin.util.GetItemsRequestHandler
@ -138,9 +138,9 @@ class RecommendedTvShowViewModel
@Composable
fun RecommendedTvShow(
preferences: UserPreferences,
navigationManager: NavigationManager,
parentId: UUID,
modifier: Modifier,
onClickItem: (BaseItem) -> Unit,
modifier: Modifier = Modifier,
viewModel: RecommendedTvShowViewModel = hiltViewModel(),
) {
OneTimeLaunchedEffect {
@ -156,8 +156,8 @@ fun RecommendedTvShow(
LoadingState.Success ->
HomePageContent(
navigationManager = navigationManager,
homeRows = rows,
onClickItem = onClickItem,
modifier = modifier,
)
}

View file

@ -58,7 +58,7 @@ private const val DEBUG = false
@Composable
fun CardGrid(
pager: List<BaseItem?>,
itemOnClick: (BaseItem) -> Unit,
onClickItem: (BaseItem) -> Unit,
longClicker: (BaseItem) -> Unit,
letterPosition: suspend (Char) -> Int,
gridFocusRequester: FocusRequester,
@ -240,7 +240,7 @@ fun CardGrid(
onClick = {
if (item != null) {
focusedIndex = index
itemOnClick.invoke(item)
onClickItem.invoke(item)
}
},
onLongClick = { if (item != null) longClicker.invoke(item) },

View file

@ -39,7 +39,6 @@ import com.github.damontecres.dolphin.ui.data.SeriesSortOptions
import com.github.damontecres.dolphin.ui.data.SortAndDirection
import com.github.damontecres.dolphin.ui.data.VideoSortOptions
import com.github.damontecres.dolphin.ui.nav.Destination
import com.github.damontecres.dolphin.ui.nav.NavigationManager
import com.github.damontecres.dolphin.ui.tryRequestFocus
import com.github.damontecres.dolphin.util.ApiRequestPager
import com.github.damontecres.dolphin.util.GetItemsRequestHandler
@ -134,8 +133,8 @@ class CollectionFolderViewModel
@Composable
fun CollectionFolderDetails(
preferences: UserPreferences,
navigationManager: NavigationManager,
destination: Destination.MediaItem,
onClickItem: (BaseItem) -> Unit,
modifier: Modifier = Modifier,
viewModel: CollectionFolderViewModel = hiltViewModel(),
initialSortAndDirection: SortAndDirection =
@ -162,12 +161,12 @@ fun CollectionFolderDetails(
pager?.let { pager ->
CollectionDetails(
preferences,
navigationManager,
library!!,
item!!,
pager,
sortAndDirection = sortAndDirection,
modifier = modifier,
onClickItem = onClickItem,
onSortChange = {
viewModel.loadResults(it)
},
@ -182,11 +181,11 @@ fun CollectionFolderDetails(
@Composable
fun CollectionDetails(
preferences: UserPreferences,
navigationManager: NavigationManager,
library: Library,
item: BaseItem,
pager: List<BaseItem?>,
sortAndDirection: SortAndDirection,
onClickItem: (BaseItem) -> Unit,
onSortChange: (SortAndDirection) -> Unit,
modifier: Modifier = Modifier,
showTitle: Boolean = true,
@ -232,15 +231,7 @@ fun CollectionDetails(
}
CardGrid(
pager = pager,
itemOnClick = {
navigationManager.navigateTo(
Destination.MediaItem(
it.id,
it.type,
it,
),
)
},
onClickItem = onClickItem,
longClicker = {},
letterPosition = { 0 },
gridFocusRequester = gridFocusRequester,

View file

@ -0,0 +1,36 @@
package com.github.damontecres.dolphin.ui.detail
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
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 com.github.damontecres.dolphin.preferences.UserPreferences
import com.github.damontecres.dolphin.ui.nav.Destination
import com.github.damontecres.dolphin.ui.preferences.PreferencesViewModel
@Composable
fun CollectionFolderGeneric(
preferences: UserPreferences,
destination: Destination.MediaItem,
modifier: Modifier = Modifier,
preferencesViewModel: PreferencesViewModel = hiltViewModel(),
) {
var showHeader by remember { mutableStateOf(true) }
CollectionFolderDetails(
preferences = preferences,
onClickItem = { preferencesViewModel.navigationManager.navigateTo(it.destination()) },
destination = destination,
showTitle = showHeader,
modifier =
modifier
.padding(start = 16.dp),
positionCallback = { columns, position ->
showHeader = position < columns
},
)
}

View file

@ -29,20 +29,19 @@ import androidx.tv.material3.TabDefaults
import androidx.tv.material3.TabRow
import androidx.tv.material3.TabRowDefaults
import androidx.tv.material3.Text
import com.github.damontecres.dolphin.data.model.BaseItem
import com.github.damontecres.dolphin.preferences.UserPreferences
import com.github.damontecres.dolphin.preferences.rememberTab
import com.github.damontecres.dolphin.ui.components.ErrorMessage
import com.github.damontecres.dolphin.ui.components.RecommendedMovie
import com.github.damontecres.dolphin.ui.ifElse
import com.github.damontecres.dolphin.ui.nav.Destination
import com.github.damontecres.dolphin.ui.nav.NavigationManager
import com.github.damontecres.dolphin.ui.preferences.PreferencesViewModel
import com.github.damontecres.dolphin.ui.tryRequestFocus
@Composable
fun CollectionFolderMovie(
preferences: UserPreferences,
navigationManager: NavigationManager,
destination: Destination.MediaItem,
modifier: Modifier = Modifier,
preferencesViewModel: PreferencesViewModel = hiltViewModel(),
@ -72,6 +71,10 @@ fun CollectionFolderMovie(
}
var showHeader by rememberSaveable { mutableStateOf(true) }
val onClickItem = { item: BaseItem ->
preferencesViewModel.navigationManager.navigateTo(item.destination())
}
LaunchedEffect(Unit) { focusRequester.tryRequestFocus() }
Column(
modifier = modifier,
@ -137,7 +140,7 @@ fun CollectionFolderMovie(
0 -> {
RecommendedMovie(
preferences = preferences,
navigationManager = navigationManager,
onClickItem = onClickItem,
parentId = destination.itemId,
modifier =
Modifier
@ -149,7 +152,7 @@ fun CollectionFolderMovie(
1 -> {
CollectionFolderDetails(
preferences = preferences,
navigationManager = navigationManager,
onClickItem = onClickItem,
destination = destination,
showTitle = false,
modifier =

View file

@ -29,20 +29,19 @@ import androidx.tv.material3.TabDefaults
import androidx.tv.material3.TabRow
import androidx.tv.material3.TabRowDefaults
import androidx.tv.material3.Text
import com.github.damontecres.dolphin.data.model.BaseItem
import com.github.damontecres.dolphin.preferences.UserPreferences
import com.github.damontecres.dolphin.preferences.rememberTab
import com.github.damontecres.dolphin.ui.components.ErrorMessage
import com.github.damontecres.dolphin.ui.components.RecommendedTvShow
import com.github.damontecres.dolphin.ui.ifElse
import com.github.damontecres.dolphin.ui.nav.Destination
import com.github.damontecres.dolphin.ui.nav.NavigationManager
import com.github.damontecres.dolphin.ui.preferences.PreferencesViewModel
import com.github.damontecres.dolphin.ui.tryRequestFocus
@Composable
fun CollectionFolderTv(
preferences: UserPreferences,
navigationManager: NavigationManager,
destination: Destination.MediaItem,
modifier: Modifier = Modifier,
preferencesViewModel: PreferencesViewModel = hiltViewModel(),
@ -70,6 +69,9 @@ fun CollectionFolderTv(
}
}
}
val onClickItem = { item: BaseItem ->
preferencesViewModel.navigationManager.navigateTo(item.destination())
}
var showHeader by rememberSaveable { mutableStateOf(true) }
@ -138,8 +140,8 @@ fun CollectionFolderTv(
0 -> {
RecommendedTvShow(
preferences = preferences,
navigationManager = navigationManager,
parentId = destination.itemId,
onClickItem = onClickItem,
modifier =
Modifier
.padding(start = 16.dp)
@ -150,7 +152,6 @@ fun CollectionFolderTv(
1 -> {
CollectionFolderDetails(
preferences = preferences,
navigationManager = navigationManager,
destination = destination,
showTitle = false,
modifier =
@ -161,6 +162,7 @@ fun CollectionFolderTv(
positionCallback = { columns, position ->
showHeader = position < columns
},
onClickItem = onClickItem,
)
}
else -> ErrorMessage("Invalid tab index $selectedTabIndex", null)

View file

@ -33,7 +33,6 @@ import com.github.damontecres.dolphin.ui.components.LoadingPage
import com.github.damontecres.dolphin.ui.components.details.VideoDetailsHeader
import com.github.damontecres.dolphin.ui.isNotNullOrBlank
import com.github.damontecres.dolphin.ui.nav.Destination
import com.github.damontecres.dolphin.ui.nav.NavigationManager
import com.github.damontecres.dolphin.util.LoadingState
import com.github.damontecres.dolphin.util.seasonEpisode
import dagger.hilt.android.lifecycle.HiltViewModel
@ -54,7 +53,6 @@ class EpisodeViewModel
@Composable
fun EpisodeDetails(
preferences: UserPreferences,
navigationManager: NavigationManager,
destination: Destination.MediaItem,
modifier: Modifier = Modifier,
viewModel: EpisodeViewModel = hiltViewModel(),

View file

@ -41,6 +41,7 @@ class SeasonViewModel
@Inject
constructor(
api: ApiClient,
val navigationManager: NavigationManager,
) : ItemViewModel<Video>(api) {
val episodes = MutableLiveData<List<BaseItem?>>(listOf())
@ -75,7 +76,6 @@ class SeasonViewModel
@Composable
fun SeasonDetails(
preferences: UserPreferences,
navigationManager: NavigationManager,
destination: Destination.MediaItem,
modifier: Modifier = Modifier,
viewModel: SeasonViewModel = hiltViewModel(),
@ -100,7 +100,7 @@ fun SeasonDetails(
ItemRow(
title = "Episodes",
items = episodes,
onClickItem = { navigationManager.navigateTo(it.destination()) },
onClickItem = { viewModel.navigationManager.navigateTo(it.destination()) },
onLongClickItem = { },
cardOnFocus = { isFocused, index ->
if (isFocused) focusedChild = index

View file

@ -63,7 +63,6 @@ import com.github.damontecres.dolphin.ui.data.ItemDetailsDialogInfo
import com.github.damontecres.dolphin.ui.isNotNullOrBlank
import com.github.damontecres.dolphin.ui.letNotEmpty
import com.github.damontecres.dolphin.ui.nav.Destination
import com.github.damontecres.dolphin.ui.nav.NavigationManager
import com.github.damontecres.dolphin.ui.playOnClickSound
import com.github.damontecres.dolphin.ui.playSoundOnFocus
import com.github.damontecres.dolphin.ui.roundMinutes
@ -75,7 +74,6 @@ import kotlin.time.Duration
@Composable
fun SeriesDetails(
preferences: UserPreferences,
navigationManager: NavigationManager,
destination: Destination.MediaItem,
modifier: Modifier = Modifier,
viewModel: SeriesViewModel = hiltViewModel(),
@ -100,12 +98,12 @@ fun SeriesDetails(
val played = item.data.userData?.played ?: false
SeriesDetailsContent(
preferences = preferences,
navigationManager = navigationManager,
series = item,
seasons = seasons,
people = people,
played = played,
modifier = modifier,
onClickItem = { viewModel.navigateTo(it.destination()) },
overviewOnClick = {
overviewDialog =
ItemDetailsDialogInfo(
@ -114,7 +112,7 @@ fun SeriesDetails(
files = listOf(),
)
},
playOnClick = { viewModel.playNextUp(navigationManager) },
playOnClick = { viewModel.playNextUp() },
watchOnClick = { showWatchConfirmation = true },
)
if (showWatchConfirmation) {
@ -144,11 +142,11 @@ fun SeriesDetails(
@Composable
fun SeriesDetailsContent(
preferences: UserPreferences,
navigationManager: NavigationManager,
series: BaseItem,
seasons: ItemListAndMapping,
people: List<Person>,
played: Boolean,
onClickItem: (BaseItem) -> Unit,
overviewOnClick: () -> Unit,
playOnClick: () -> Unit,
watchOnClick: () -> Unit,
@ -222,7 +220,7 @@ fun SeriesDetailsContent(
ItemRow(
title = "Seasons",
items = seasons.items,
onClickItem = { navigationManager.navigateTo(it.destination()) },
onClickItem = onClickItem,
onLongClickItem = { },
cardOnFocus = { isFocused, index ->
// if (isFocused) {

View file

@ -56,6 +56,7 @@ class SeriesViewModel
api: ApiClient,
@param:ApplicationContext val context: Context,
@param:AuthOkHttpClient private val okHttpClient: OkHttpClient,
private val navigationManager: NavigationManager,
) : ItemViewModel<Video>(api) {
private var player: Player? = null
private lateinit var seriesId: UUID
@ -275,7 +276,7 @@ class SeriesViewModel
}
}
fun playNextUp(nav: NavigationManager) {
fun playNextUp() {
viewModelScope.launch(ExceptionHandler() + Dispatchers.IO) {
val result by api.tvShowsApi.getNextUp(seriesId = seriesId)
val nextUp =
@ -286,7 +287,7 @@ class SeriesViewModel
).content.items
.firstOrNull()
if (nextUp != null) {
nav.navigateTo(Destination.Playback(nextUp.id, 0L))
navigateTo(Destination.Playback(nextUp.id, 0L))
} else {
Toast
.makeText(context, "Could not find an episode to play", Toast.LENGTH_SHORT)
@ -294,6 +295,11 @@ class SeriesViewModel
}
}
}
fun navigateTo(destination: Destination) {
release()
navigationManager.navigateTo(destination)
}
}
data class ItemListAndMapping(

View file

@ -31,12 +31,12 @@ class VideoViewModel
@Inject
constructor(
api: ApiClient,
val navigationManager: NavigationManager,
) : LoadingItemViewModel<Video>(api)
@Composable
fun VideoDetails(
preferences: UserPreferences,
navigationManager: NavigationManager,
destination: Destination.MediaItem,
modifier: Modifier = Modifier,
viewModel: MovieViewModel = hiltViewModel(),
@ -70,7 +70,7 @@ fun VideoDetails(
item {
Button(
onClick = {
navigationManager.navigateTo(
viewModel.navigationManager.navigateTo(
Destination.Playback(
item.id,
it.inWholeMilliseconds,
@ -87,7 +87,7 @@ fun VideoDetails(
item {
Button(
onClick = {
navigationManager.navigateTo(
viewModel.navigationManager.navigateTo(
Destination.Playback(
item.id,
0L,

View file

@ -76,6 +76,7 @@ class MovieViewModel
@Inject
constructor(
api: ApiClient,
val navigationManager: NavigationManager,
) : LoadingItemViewModel<Video>(api) {
private lateinit var itemId: UUID
val people = MutableLiveData<List<Person>>(listOf())
@ -113,7 +114,6 @@ class MovieViewModel
@Composable
fun MovieDetails(
preferences: UserPreferences,
navigationManager: NavigationManager,
destination: Destination.MediaItem,
modifier: Modifier = Modifier,
viewModel: MovieViewModel = hiltViewModel(),
@ -136,12 +136,11 @@ fun MovieDetails(
item?.let { movie ->
MovieDetailsContent(
preferences = preferences,
navigationManager = navigationManager,
movie = movie,
people = people,
chapters = chapters,
playOnClick = {
navigationManager.navigateTo(
viewModel.navigationManager.navigateTo(
Destination.Playback(
movie.id,
it.inWholeMilliseconds,
@ -172,12 +171,8 @@ fun MovieDetails(
Icons.Default.PlayArrow,
iconColor = Color.Green.copy(alpha = .8f),
) {
navigationManager.navigateTo(
Destination.Playback(
movie.id,
movie.resumeMs ?: 0L,
movie,
),
viewModel.navigationManager.navigateTo(
Destination.Playback(movie),
)
},
DialogItem(
@ -226,7 +221,6 @@ fun MovieDetails(
@Composable
fun MovieDetailsContent(
preferences: UserPreferences,
navigationManager: NavigationManager,
movie: BaseItem,
people: List<Person>,
chapters: List<Chapter>,

View file

@ -29,7 +29,6 @@ import com.github.damontecres.dolphin.ui.data.ItemDetailsDialogInfo
import com.github.damontecres.dolphin.ui.detail.ItemListAndMapping
import com.github.damontecres.dolphin.ui.detail.SeriesViewModel
import com.github.damontecres.dolphin.ui.nav.Destination
import com.github.damontecres.dolphin.ui.nav.NavigationManager
import com.github.damontecres.dolphin.ui.tryRequestFocus
import com.github.damontecres.dolphin.util.LoadingState
import com.github.damontecres.dolphin.util.seasonEpisode
@ -55,7 +54,6 @@ data class SeriesOverviewPosition(
@Composable
fun SeriesOverview(
preferences: UserPreferences,
navigationManager: NavigationManager,
destination: Destination.SeriesOverview,
modifier: Modifier = Modifier,
viewModel: SeriesViewModel = hiltViewModel(),
@ -146,7 +144,7 @@ fun SeriesOverview(
it.data.userData
?.playbackPositionTicks
?.ticks ?: Duration.ZERO
navigationManager.navigateTo(
viewModel.navigateTo(
Destination.Playback(
it.id,
resumePosition.inWholeMilliseconds,
@ -160,7 +158,7 @@ fun SeriesOverview(
playOnClick = { resume ->
episodes.items.getOrNull(position.episodeRowIndex)?.let {
viewModel.release()
navigationManager.navigateTo(
viewModel.navigateTo(
Destination.Playback(
it.id,
resume.inWholeMilliseconds,
@ -189,7 +187,7 @@ fun SeriesOverview(
iconColor = Color.Green.copy(alpha = .8f),
) {
viewModel.release()
navigationManager.navigateTo(
viewModel.navigateTo(
Destination.Playback(
ep.id,
ep.resumeMs ?: 0L,
@ -203,7 +201,7 @@ fun SeriesOverview(
// iconColor = Color.Green.copy(alpha = .8f),
) {
viewModel.release()
navigationManager.navigateTo(
viewModel.navigateTo(
Destination.MediaItem(
series.id,
BaseItemKind.SERIES,

View file

@ -48,7 +48,6 @@ import com.github.damontecres.dolphin.ui.data.RowColumn
import com.github.damontecres.dolphin.ui.data.RowColumnSaver
import com.github.damontecres.dolphin.ui.ifElse
import com.github.damontecres.dolphin.ui.isNotNullOrBlank
import com.github.damontecres.dolphin.ui.nav.NavigationManager
import com.github.damontecres.dolphin.ui.roundMinutes
import com.github.damontecres.dolphin.ui.timeRemaining
import com.github.damontecres.dolphin.ui.tryRequestFocus
@ -66,7 +65,6 @@ data class HomeRow(
@Composable
fun HomePage(
preferences: UserPreferences,
navigationManager: NavigationManager,
modifier: Modifier = Modifier,
viewModel: HomeViewModel = hiltViewModel(),
) {
@ -81,15 +79,21 @@ fun HomePage(
LoadingState.Success -> {
val homeRows by viewModel.homeRows.observeAsState(listOf())
HomePageContent(navigationManager, homeRows, modifier)
HomePageContent(
homeRows,
onClickItem = {
viewModel.navigationManager.navigateTo(it.destination())
},
modifier,
)
}
}
}
@Composable
fun HomePageContent(
navigationManager: NavigationManager,
homeRows: List<HomeRow>,
onClickItem: (BaseItem) -> Unit,
modifier: Modifier = Modifier,
) {
var position by rememberSaveable(stateSaver = RowColumnSaver) {
@ -160,9 +164,7 @@ fun HomePageContent(
ItemRow(
title = row.title ?: stringResource(row.section.nameRes),
items = row.items,
onClickItem = {
navigationManager.navigateTo(it.destination())
},
onClickItem = onClickItem,
cardOnFocus = { isFocused, index ->
if (isFocused) {
focusedItem = row.items.getOrNull(index)

View file

@ -6,6 +6,7 @@ import androidx.lifecycle.viewModelScope
import com.github.damontecres.dolphin.data.model.BaseItem
import com.github.damontecres.dolphin.preferences.UserPreferences
import com.github.damontecres.dolphin.ui.DefaultItemFields
import com.github.damontecres.dolphin.ui.nav.NavigationManager
import com.github.damontecres.dolphin.util.LoadingExceptionHandler
import com.github.damontecres.dolphin.util.LoadingState
import com.github.damontecres.dolphin.util.supportItemKinds
@ -33,6 +34,7 @@ class HomeViewModel
@Inject
constructor(
val api: ApiClient,
val navigationManager: NavigationManager,
) : ViewModel() {
val loadingState = MutableLiveData<LoadingState>(LoadingState.Loading)
val homeRows = MutableLiveData<List<HomeRow>>()

View file

@ -48,6 +48,7 @@ class SearchViewModel
@Inject
constructor(
val api: ApiClient,
val navigationManager: NavigationManager,
) : ViewModel() {
val movies = MutableLiveData<List<BaseItem?>>(listOf())
val series = MutableLiveData<List<BaseItem?>>(listOf())
@ -111,7 +112,6 @@ class SearchViewModel
@Composable
fun SearchPage(
navigationManager: NavigationManager,
userPreferences: UserPreferences,
modifier: Modifier = Modifier,
viewModel: SearchViewModel = hiltViewModel(),
@ -170,7 +170,7 @@ fun SearchPage(
},
items = items,
onClickItem = {
navigationManager.navigateTo(it.destination())
viewModel.navigationManager.navigateTo(it.destination())
},
onLongClickItem = {},
modifier = Modifier.focusRequester(resultsFocusRequester),

View file

@ -41,7 +41,6 @@ fun ApplicationContent(
DestinationContent(
destination = key,
preferences = preferences,
navigationManager = navigationManager,
deviceProfile = deviceProfile,
modifier = modifier.fillMaxSize(),
)
@ -49,7 +48,6 @@ fun ApplicationContent(
NavDrawer(
destination = key,
preferences = preferences,
navigationManager = navigationManager,
deviceProfile = deviceProfile,
user = user,
server = server,

View file

@ -61,6 +61,8 @@ sealed class Destination(
@Transient val item: BaseItem? = null,
) : Destination(true) {
override fun toString(): String = "Playback(itemId=$itemId, positionMs=$positionMs)"
constructor(item: BaseItem) : this(item.id, item.resumeMs ?: 0, item)
}
@Serializable

View file

@ -5,7 +5,7 @@ import androidx.compose.ui.Modifier
import androidx.tv.material3.Text
import com.github.damontecres.dolphin.preferences.UserPreferences
import com.github.damontecres.dolphin.ui.components.LicenseInfo
import com.github.damontecres.dolphin.ui.detail.CollectionFolderDetails
import com.github.damontecres.dolphin.ui.detail.CollectionFolderGeneric
import com.github.damontecres.dolphin.ui.detail.CollectionFolderMovie
import com.github.damontecres.dolphin.ui.detail.CollectionFolderTv
import com.github.damontecres.dolphin.ui.detail.EpisodeDetails
@ -28,7 +28,6 @@ import org.jellyfin.sdk.model.api.DeviceProfile
fun DestinationContent(
destination: Destination,
preferences: UserPreferences,
navigationManager: NavigationManager,
deviceProfile: DeviceProfile,
modifier: Modifier = Modifier,
) {
@ -36,25 +35,22 @@ fun DestinationContent(
is Destination.Main ->
HomePage(
preferences = preferences,
navigationManager = navigationManager,
modifier = modifier,
)
is Destination.Playback ->
PlaybackContent(
preferences = preferences,
navigationManager = navigationManager,
deviceProfile = deviceProfile,
destination = destination,
modifier = modifier,
)
Destination.ServerList -> SwitchServerContent(navigationManager, modifier)
Destination.UserList -> SwitchUserContent(navigationManager, modifier)
Destination.ServerList -> SwitchServerContent(modifier)
Destination.UserList -> SwitchUserContent(modifier)
Destination.Settings ->
PreferencesPage(
navigationManager,
preferences.appPreferences,
PreferenceScreenOption.BASIC,
modifier,
@ -63,7 +59,6 @@ fun DestinationContent(
is Destination.SeriesOverview ->
SeriesOverview(
preferences,
navigationManager,
destination,
modifier,
initialSeasonEpisode = destination.seasonEpisode,
@ -74,7 +69,6 @@ fun DestinationContent(
BaseItemKind.SERIES ->
SeriesDetails(
preferences,
navigationManager,
destination,
modifier,
)
@ -82,7 +76,6 @@ fun DestinationContent(
BaseItemKind.SEASON ->
SeasonDetails(
preferences,
navigationManager,
destination,
modifier,
)
@ -90,7 +83,6 @@ fun DestinationContent(
BaseItemKind.EPISODE ->
EpisodeDetails(
preferences,
navigationManager,
destination,
modifier,
)
@ -98,7 +90,6 @@ fun DestinationContent(
BaseItemKind.MOVIE ->
MovieDetails(
preferences,
navigationManager,
destination,
modifier,
)
@ -107,7 +98,6 @@ fun DestinationContent(
// TODO Use VideoDetails
MovieDetails(
preferences,
navigationManager,
destination,
modifier,
)
@ -117,7 +107,6 @@ fun DestinationContent(
CollectionType.TVSHOWS ->
CollectionFolderTv(
preferences,
navigationManager,
destination,
modifier,
)
@ -125,15 +114,13 @@ fun DestinationContent(
CollectionType.MOVIES ->
CollectionFolderMovie(
preferences,
navigationManager,
destination,
modifier,
)
else ->
CollectionFolderDetails(
CollectionFolderGeneric(
preferences,
navigationManager,
destination,
modifier,
)
@ -149,7 +136,6 @@ fun DestinationContent(
Destination.Search ->
SearchPage(
navigationManager = navigationManager,
userPreferences = preferences,
modifier = modifier,
)

View file

@ -78,6 +78,7 @@ class NavDrawerViewModel
constructor(
val serverRepository: ServerRepository,
val api: ApiClient,
val navigationManager: NavigationManager,
) : ViewModel() {
val libraries = MutableLiveData<List<BaseItem>>(listOf())
val selectedIndex = MutableLiveData<Int>(-1)
@ -104,7 +105,6 @@ class NavDrawerViewModel
fun NavDrawer(
destination: Destination,
preferences: UserPreferences,
navigationManager: NavigationManager,
user: JellyfinUser?,
server: JellyfinServer?,
deviceProfile: DeviceProfile,
@ -157,7 +157,7 @@ fun NavDrawer(
icon = Icons.Default.AccountCircle,
selected = false,
onClick = {
navigationManager.navigateTo(Destination.UserList)
viewModel.navigationManager.navigateTo(Destination.UserList)
},
)
}
@ -168,7 +168,7 @@ fun NavDrawer(
selected = selectedIndex == -2,
onClick = {
viewModel.setIndex(-2)
navigationManager.navigateToFromDrawer(Destination.Search)
viewModel.navigationManager.navigateToFromDrawer(Destination.Search)
},
modifier =
Modifier.ifElse(
@ -185,9 +185,9 @@ fun NavDrawer(
onClick = {
viewModel.setIndex(-1)
if (destination is Destination.Main) {
navigationManager.reloadHome()
viewModel.navigationManager.reloadHome()
} else {
navigationManager.goToHome()
viewModel.navigationManager.goToHome()
}
},
modifier =
@ -203,7 +203,7 @@ fun NavDrawer(
selected = selectedIndex == index,
onClick = {
viewModel.setIndex(index)
navigationManager.navigateToFromDrawer(it.destination())
viewModel.navigationManager.navigateToFromDrawer(it.destination())
},
modifier =
Modifier.ifElse(
@ -218,7 +218,7 @@ fun NavDrawer(
icon = Icons.Default.Settings,
selected = false,
onClick = {
navigationManager.navigateTo(Destination.Settings)
viewModel.navigationManager.navigateTo(Destination.Settings)
},
modifier = Modifier,
)
@ -231,7 +231,6 @@ fun NavDrawer(
DestinationContent(
destination = destination,
preferences = preferences,
navigationManager = navigationManager,
deviceProfile = deviceProfile,
modifier = Modifier.fillMaxSize(),
)

View file

@ -1,11 +1,15 @@
package com.github.damontecres.dolphin.ui.nav
import androidx.navigation3.runtime.NavBackStack
import androidx.navigation3.runtime.NavKey
import javax.inject.Inject
import javax.inject.Singleton
@Singleton
class NavigationManager
@Inject
constructor() {
var backStack: MutableList<NavKey> = mutableListOf()
class NavigationManager(
val backStack: NavBackStack<NavKey>,
) {
fun navigateTo(destination: Destination) {
backStack.add(destination)
}
@ -30,4 +34,4 @@ class NavigationManager(
val id = (backStack[0] as Destination.Main).id + 1
backStack[0] = Destination.Main(id)
}
}
}

View file

@ -61,7 +61,6 @@ import com.github.damontecres.dolphin.preferences.skipBackOnResume
import com.github.damontecres.dolphin.ui.OneTimeLaunchedEffect
import com.github.damontecres.dolphin.ui.components.LoadingPage
import com.github.damontecres.dolphin.ui.nav.Destination
import com.github.damontecres.dolphin.ui.nav.NavigationManager
import com.github.damontecres.dolphin.ui.tryRequestFocus
import com.github.damontecres.dolphin.util.seasonEpisode
import kotlinx.coroutines.delay
@ -73,7 +72,6 @@ import kotlin.time.Duration.Companion.seconds
@Composable
fun PlaybackContent(
preferences: UserPreferences,
navigationManager: NavigationManager,
deviceProfile: DeviceProfile,
destination: Destination.Playback,
modifier: Modifier = Modifier,

View file

@ -50,17 +50,17 @@ import com.github.damontecres.dolphin.ui.components.DialogItem
import com.github.damontecres.dolphin.ui.components.DialogParams
import com.github.damontecres.dolphin.ui.components.DialogPopup
import com.github.damontecres.dolphin.ui.components.EditTextBox
import com.github.damontecres.dolphin.ui.nav.NavigationManager
import com.github.damontecres.dolphin.ui.nav.Destination
import com.github.damontecres.dolphin.util.ExceptionHandler
import kotlinx.coroutines.launch
@Suppress("UNCHECKED_CAST")
@Composable
fun <T> ComposablePreference(
navigationManager: NavigationManager,
preference: AppPreference<T>,
value: T?,
onValueChange: (T) -> Unit,
onNavigate: (Destination) -> Unit,
modifier: Modifier = Modifier,
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
) {
@ -92,7 +92,7 @@ fun <T> ComposablePreference(
ClickPreference(
title = title,
onClick = {
navigationManager.navigateTo(preference.destination)
onNavigate.invoke(preference.destination)
},
summary = preference.summary(context, value),
interactionSource = interactionSource,

View file

@ -45,7 +45,6 @@ import com.github.damontecres.dolphin.preferences.advancedPreferences
import com.github.damontecres.dolphin.preferences.basicPreferences
import com.github.damontecres.dolphin.preferences.uiPreferences
import com.github.damontecres.dolphin.ui.ifElse
import com.github.damontecres.dolphin.ui.nav.NavigationManager
import com.github.damontecres.dolphin.ui.playOnClickSound
import com.github.damontecres.dolphin.ui.playSoundOnFocus
import com.github.damontecres.dolphin.ui.tryRequestFocus
@ -58,7 +57,6 @@ data class Release(
@Composable
fun PreferencesContent(
navigationManager: NavigationManager,
initialPreferences: AppPreferences,
preferenceScreenOption: PreferenceScreenOption,
modifier: Modifier = Modifier,
@ -267,9 +265,9 @@ fun PreferencesContent(
else -> {
val value = pref.getter.invoke(preferences)
ComposablePreference(
navigationManager = navigationManager,
preference = pref,
value = value,
onNavigate = viewModel.navigationManager::navigateTo,
onValueChange = { newValue ->
val validation = pref.validate(newValue)
when (validation) {
@ -312,7 +310,6 @@ fun PreferencesContent(
@Composable
fun PreferencesPage(
navigationManager: NavigationManager,
initialPreferences: AppPreferences,
preferenceScreenOption: PreferenceScreenOption,
modifier: Modifier = Modifier,
@ -321,7 +318,6 @@ fun PreferencesPage(
modifier = modifier.background(MaterialTheme.colorScheme.background),
) {
PreferencesContent(
navigationManager,
initialPreferences,
preferenceScreenOption,
Modifier

View file

@ -3,6 +3,7 @@ package com.github.damontecres.dolphin.ui.preferences
import androidx.datastore.core.DataStore
import androidx.lifecycle.ViewModel
import com.github.damontecres.dolphin.preferences.AppPreferences
import com.github.damontecres.dolphin.ui.nav.NavigationManager
import dagger.hilt.android.lifecycle.HiltViewModel
import javax.inject.Inject
@ -11,4 +12,5 @@ class PreferencesViewModel
@Inject
constructor(
val preferenceDataStore: DataStore<AppPreferences>,
val navigationManager: NavigationManager,
) : ViewModel()

View file

@ -28,11 +28,9 @@ import androidx.tv.material3.surfaceColorAtElevation
import com.github.damontecres.dolphin.ui.components.BasicDialog
import com.github.damontecres.dolphin.ui.components.EditTextBox
import com.github.damontecres.dolphin.ui.nav.Destination
import com.github.damontecres.dolphin.ui.nav.NavigationManager
@Composable
fun SwitchServerContent(
navigationManager: NavigationManager,
modifier: Modifier = Modifier,
viewModel: SwitchUserViewModel = hiltViewModel(),
) {
@ -68,7 +66,7 @@ fun SwitchServerContent(
connectionStatus = serverStatus,
onSwitchServer = {
viewModel.addServer(it.url) {
navigationManager.navigateTo(Destination.UserList)
viewModel.navigationManager.navigateTo(Destination.UserList)
}
},
onAddServer = {
@ -92,7 +90,7 @@ fun SwitchServerContent(
val submit = {
showAddServer = false
viewModel.addServer(url) {
navigationManager.navigateTo(Destination.UserList)
viewModel.navigationManager.navigateTo(Destination.UserList)
}
}
BasicDialog(

View file

@ -31,11 +31,9 @@ import com.github.damontecres.dolphin.ui.components.BasicDialog
import com.github.damontecres.dolphin.ui.components.EditTextBox
import com.github.damontecres.dolphin.ui.isNotNullOrBlank
import com.github.damontecres.dolphin.ui.nav.Destination
import com.github.damontecres.dolphin.ui.nav.NavigationManager
@Composable
fun SwitchUserContent(
navigationManager: NavigationManager,
modifier: Modifier = Modifier,
viewModel: SwitchUserViewModel = hiltViewModel(),
) {
@ -80,7 +78,7 @@ fun SwitchUserContent(
currentUser = currentUser,
onSwitchUser = { user ->
viewModel.switchUser(server, user) {
navigationManager.goToHome()
viewModel.navigationManager.goToHome()
}
},
onAddUser = { showAddUser = true },
@ -88,7 +86,7 @@ fun SwitchUserContent(
viewModel.removeUser(user)
},
onSwitchServer = {
navigationManager.navigateTo(Destination.ServerList)
viewModel.navigationManager.navigateTo(Destination.ServerList)
},
modifier =
Modifier
@ -106,7 +104,7 @@ fun SwitchUserContent(
LaunchedEffect(Unit) {
if (useQuickConnect) {
viewModel.initiateQuickConnect(server) {
navigationManager.goToHome()
viewModel.navigationManager.goToHome()
}
}
}
@ -151,7 +149,7 @@ fun SwitchUserContent(
val onSubmit = {
viewModel.login(server, username, password) {
showAddUser = false
navigationManager.goToHome()
viewModel.navigationManager.goToHome()
}
}
Row(

View file

@ -7,6 +7,7 @@ import com.github.damontecres.dolphin.data.JellyfinServer
import com.github.damontecres.dolphin.data.JellyfinServerDao
import com.github.damontecres.dolphin.data.JellyfinUser
import com.github.damontecres.dolphin.data.ServerRepository
import com.github.damontecres.dolphin.ui.nav.NavigationManager
import com.github.damontecres.dolphin.util.ExceptionHandler
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.Dispatchers
@ -32,6 +33,7 @@ class SwitchUserViewModel
val jellyfin: Jellyfin,
val serverRepository: ServerRepository,
val serverDao: JellyfinServerDao,
val navigationManager: NavigationManager,
) : ViewModel() {
val servers = MutableLiveData<List<JellyfinServer>>(listOf())
val serverStatus = MutableLiveData<Map<String, ServerConnectionStatus>>(mapOf())