diff --git a/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt b/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt index 52a63020..22e41a49 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/MainActivity.kt @@ -2,6 +2,7 @@ package com.github.damontecres.wholphin import android.os.Bundle import androidx.activity.compose.setContent +import androidx.activity.viewModels import androidx.appcompat.app.AppCompatActivity import androidx.compose.foundation.background import androidx.compose.foundation.layout.Box @@ -12,18 +13,18 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState import androidx.compose.runtime.getValue -import androidx.compose.runtime.key -import androidx.compose.runtime.livedata.observeAsState -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -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.lifecycle.ViewModel import androidx.lifecycle.lifecycleScope -import androidx.navigation3.runtime.rememberNavBackStack +import androidx.lifecycle.viewModelScope +import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator +import androidx.navigation3.runtime.NavEntry +import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator +import androidx.navigation3.ui.NavDisplay import androidx.tv.material3.ExperimentalTvMaterial3Api import androidx.tv.material3.MaterialTheme import androidx.tv.material3.Surface @@ -33,24 +34,30 @@ import com.github.damontecres.wholphin.preferences.AppPreferences import com.github.damontecres.wholphin.preferences.DefaultUserConfiguration import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.services.AppUpgradeHandler +import com.github.damontecres.wholphin.services.BackdropService import com.github.damontecres.wholphin.services.DeviceProfileService import com.github.damontecres.wholphin.services.ImageUrlService import com.github.damontecres.wholphin.services.NavigationManager import com.github.damontecres.wholphin.services.PlaybackLifecycleObserver import com.github.damontecres.wholphin.services.RefreshRateService -import com.github.damontecres.wholphin.services.ServerEventListener +import com.github.damontecres.wholphin.services.SetupDestination +import com.github.damontecres.wholphin.services.SetupNavigationManager import com.github.damontecres.wholphin.services.UpdateChecker import com.github.damontecres.wholphin.services.hilt.AuthOkHttpClient import com.github.damontecres.wholphin.ui.CoilConfig import com.github.damontecres.wholphin.ui.LocalImageUrlService import com.github.damontecres.wholphin.ui.launchIO import com.github.damontecres.wholphin.ui.nav.ApplicationContent -import com.github.damontecres.wholphin.ui.nav.Destination +import com.github.damontecres.wholphin.ui.setup.SwitchServerContent +import com.github.damontecres.wholphin.ui.setup.SwitchUserContent import com.github.damontecres.wholphin.ui.theme.WholphinTheme import com.github.damontecres.wholphin.ui.util.ProvideLocalClock import com.github.damontecres.wholphin.util.DebugLogTree import dagger.hilt.android.AndroidEntryPoint +import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import okhttp3.OkHttpClient import org.jellyfin.sdk.model.serializer.toUUIDOrNull @@ -59,8 +66,7 @@ import javax.inject.Inject @AndroidEntryPoint class MainActivity : AppCompatActivity() { - @Inject - lateinit var serverRepository: ServerRepository + private val viewModel: MainActivityViewModel by viewModels() @Inject lateinit var userPreferencesDataStore: DataStore @@ -72,27 +78,26 @@ class MainActivity : AppCompatActivity() { @Inject lateinit var navigationManager: NavigationManager + @Inject + lateinit var setupNavigationManager: SetupNavigationManager + @Inject lateinit var updateChecker: UpdateChecker @Inject lateinit var appUpgradeHandler: AppUpgradeHandler - @Inject - lateinit var serverEventListener: ServerEventListener - @Inject lateinit var playbackLifecycleObserver: PlaybackLifecycleObserver - @Inject - lateinit var deviceProfileService: DeviceProfileService - @Inject lateinit var imageUrlService: ImageUrlService @Inject lateinit var refreshRateService: RefreshRateService + private var signInAuto = true + @OptIn(ExperimentalTvMaterial3Api::class) override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -109,9 +114,13 @@ class MainActivity : AppCompatActivity() { window.attributes = attrs.apply { preferredRefreshRate = mode.refreshRate } } } + viewModel.appStart() setContent { val appPreferences by userPreferencesDataStore.data.collectAsState(null) appPreferences?.let { appPreferences -> + LaunchedEffect(appPreferences.signInAutomatically) { + signInAuto = appPreferences.signInAutomatically + } CoilConfig( diskCacheSizeBytes = appPreferences.advancedPreferences.imageDiskCacheSizeBytes.let { @@ -140,100 +149,71 @@ class MainActivity : AppCompatActivity() { .background(MaterialTheme.colorScheme.background), shape = RectangleShape, ) { - var isRestoringSession by remember { mutableStateOf(true) } - LaunchedEffect(Unit) { - // TODO PIN-related -// if (appPreferences.signInAutomatically) { - try { - serverRepository.restoreSession( - appPreferences.currentServerId?.toUUIDOrNull(), - appPreferences.currentUserId?.toUUIDOrNull(), - ) - } catch (ex: Exception) { - Timber.e(ex, "Exception restoring session") - } -// } - isRestoringSession = false - } - val current by serverRepository.current.observeAsState() +// val backStack = rememberNavBackStack(SetupDestination.Loading) +// setupNavigationManager.backStack = backStack + val backStack = setupNavigationManager.backStack + NavDisplay( + backStack = backStack, + onBack = { backStack.removeLastOrNull() }, + entryDecorators = + listOf( + rememberSaveableStateHolderNavEntryDecorator(), + rememberViewModelStoreNavEntryDecorator(), + ), + entryProvider = { key -> + key as SetupDestination + NavEntry(key) { + when (key) { + SetupDestination.Loading -> { + Box( + modifier = Modifier.size(200.dp), + contentAlignment = Alignment.Center, + ) { + CircularProgressIndicator( + color = MaterialTheme.colorScheme.border, + modifier = Modifier.align(Alignment.Center), + ) + } + } - val preferences = - UserPreferences( - appPreferences, - current?.userDto?.configuration ?: DefaultUserConfiguration, - ) + SetupDestination.ServerList -> { + SwitchServerContent(Modifier.fillMaxSize()) + } - if (isRestoringSession) { - Box( - modifier = Modifier.size(200.dp), - contentAlignment = Alignment.Center, - ) { - CircularProgressIndicator( - color = MaterialTheme.colorScheme.border, - modifier = Modifier.align(Alignment.Center), - ) - } - } else { - // TODO PIN-related -// DisposableEffect(Unit) { -// onDispose { -// if (!appPreferences.signInAutomatically || current?.user?.hasPin == true) { -// serverRepository.closeSession() -// } -// } -// } - key(current?.server?.id, current?.user?.id) { - // TODO PIN-related -// LaunchedEffect(current?.user?.pin) { -// if (current?.user?.pin?.isNotNullOrBlank() == true) { -// // If user has a pin, then obscure the window in previews -// window?.setFlags( -// WindowManager.LayoutParams.FLAG_SECURE, -// WindowManager.LayoutParams.FLAG_SECURE, -// ) -// } else { -// window?.clearFlags(WindowManager.LayoutParams.FLAG_SECURE) -// } -// } - val initialDestination = - when { - current != null -> Destination.Home() + is SetupDestination.UserList -> { + SwitchUserContent( + currentServer = key.server, + Modifier.fillMaxSize(), + ) + } - // TODO PIN-related - // !appPreferences.signInAutomatically -> Destination.ServerList - - else -> Destination.ServerList - } - val backStack = rememberNavBackStack(initialDestination) - navigationManager.backStack = backStack - if (UpdateChecker.ACTIVE && appPreferences.autoCheckForUpdates) { - LaunchedEffect(Unit) { - try { - updateChecker.maybeShowUpdateToast(appPreferences.updateUrl) - } catch (ex: Exception) { - Timber.w(ex, "Failed to check for update") + is SetupDestination.AppContent -> { + val current = key.current + val preferences = + UserPreferences( + appPreferences, + current.userDto.configuration + ?: DefaultUserConfiguration, + ) + ProvideLocalClock { + LaunchedEffect(Unit) { + updateChecker.maybeShowUpdateToast( + appPreferences.updateUrl, + ) + } + ApplicationContent( + user = current.user, + server = current.server, + navigationManager = navigationManager, + preferences = preferences, + modifier = Modifier.fillMaxSize(), + ) + } } } } - LaunchedEffect(current, preferences) { - withContext(Dispatchers.IO) { - deviceProfileService.getOrCreateDeviceProfile( - preferences.appPreferences.playbackPreferences, - current?.server?.serverVersion, - ) - } - } - ProvideLocalClock { - ApplicationContent( - user = current?.user, - server = current?.server, - navigationManager = navigationManager, - preferences = preferences, - modifier = Modifier.fillMaxSize(), - ) - } - } - } + }, + ) } } } @@ -250,12 +230,69 @@ class MainActivity : AppCompatActivity() { override fun onRestart() { super.onRestart() - // TODO PIN-related + Timber.i("onRestart") + viewModel.appStart() // val signInAutomatically = // runBlocking { userPreferencesDataStore.data.firstOrNull()?.signInAutomatically } ?: true -// Timber.i("onRestart: signInAutomatically=$signInAutomatically") -// if (!signInAutomatically || serverRepository.currentUser.value?.hasPin == true) { + +// // TODO PIN-related +// // if (!signInAutomatically || serverRepository.currentUser.value?.hasPin == true) { +// if (!signInAutomatically) { // serverRepository.closeSession() // } } } + +@HiltViewModel +class MainActivityViewModel + @Inject + constructor( + private val preferences: DataStore, + private val serverRepository: ServerRepository, + private val navigationManager: SetupNavigationManager, + private val deviceProfileService: DeviceProfileService, + private val backdropService: BackdropService, + ) : ViewModel() { + fun appStart() { + viewModelScope.launch { + val prefs = preferences.data.firstOrNull() ?: AppPreferences.getDefaultInstance() + if (prefs.signInAutomatically) { + val current = + withContext(Dispatchers.IO) { + serverRepository.restoreSession( + prefs.currentServerId?.toUUIDOrNull(), + prefs.currentUserId?.toUUIDOrNull(), + ) + } + if (current != null) { + // Restored + navigationManager.navigateTo(SetupDestination.AppContent(current)) + } else { + // Did not restore + navigationManager.navigateTo(SetupDestination.ServerList) + } + } else { + navigationManager.navigateTo(SetupDestination.Loading) + backdropService.clearBackdrop() + val currentServerId = prefs.currentServerId?.toUUIDOrNull() + if (currentServerId != null) { + val currentServer = + withContext(Dispatchers.IO) { + serverRepository.serverDao.getServer(currentServerId)?.server + } + if (currentServer != null) { + navigationManager.navigateTo(SetupDestination.UserList(currentServer)) + } else { + navigationManager.navigateTo(SetupDestination.ServerList) + } + } else { + navigationManager.navigateTo(SetupDestination.ServerList) + } + } + } + viewModelScope.launchIO { + // Create the mediaCodecCapabilitiesTest if needed + deviceProfileService.mediaCodecCapabilitiesTest.supportsAVC() + } + } + } diff --git a/app/src/main/java/com/github/damontecres/wholphin/data/ServerRepository.kt b/app/src/main/java/com/github/damontecres/wholphin/data/ServerRepository.kt index 48bea5b3..539c8ac4 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/data/ServerRepository.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/data/ServerRepository.kt @@ -15,6 +15,7 @@ import com.github.damontecres.wholphin.util.EqualityMutableLiveData import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext +import kotlinx.serialization.Serializable import org.jellyfin.sdk.Jellyfin import org.jellyfin.sdk.api.client.ApiClient import org.jellyfin.sdk.api.client.extensions.systemApi @@ -68,44 +69,46 @@ class ServerRepository suspend fun changeUser( server: JellyfinServer, user: JellyfinUser, - ) = withContext(Dispatchers.IO) { - if (server.id != user.serverId) { - throw IllegalStateException("User is not part of the server") - } - Timber.v("Changing user to ${user.name} on ${server.url}") - apiClient.update(baseUrl = server.url, accessToken = user.accessToken) - val userDto by apiClient.userApi.getCurrentUser() - val updatedServer = - try { - val sysInfo by apiClient.systemApi.getPublicSystemInfo() - server.copy(name = sysInfo.serverName, version = sysInfo.version) - } catch (ex: Exception) { - Timber.w(ex, "Exception fetching public system info") - server + ): CurrentUser? = + withContext(Dispatchers.IO) { + if (server.id != user.serverId) { + throw IllegalStateException("User is not part of the server") } - var updatedUser = - user.copy( - id = userDto.id, - name = userDto.name, - ) - serverDao.addOrUpdateServer(updatedServer) - updatedUser = serverDao.addOrUpdateUser(updatedUser) - userPreferencesDataStore.updateData { - it - .toBuilder() - .apply { - currentServerId = updatedServer.id.toServerString() - currentUserId = updatedUser.id.toServerString() - }.build() + Timber.v("Changing user to ${user.name} on ${server.url}") + apiClient.update(baseUrl = server.url, accessToken = user.accessToken) + val userDto by apiClient.userApi.getCurrentUser() + val updatedServer = + try { + val sysInfo by apiClient.systemApi.getPublicSystemInfo() + server.copy(name = sysInfo.serverName, version = sysInfo.version) + } catch (ex: Exception) { + Timber.w(ex, "Exception fetching public system info") + server + } + var updatedUser = + user.copy( + id = userDto.id, + name = userDto.name, + ) + serverDao.addOrUpdateServer(updatedServer) + updatedUser = serverDao.addOrUpdateUser(updatedUser) + userPreferencesDataStore.updateData { + it + .toBuilder() + .apply { + currentServerId = updatedServer.id.toServerString() + currentUserId = updatedUser.id.toServerString() + }.build() + } + withContext(Dispatchers.Main) { + _current.value = CurrentUser(updatedServer, updatedUser, userDto) + } + sharedPreferences.edit(true) { + putString(SERVER_URL_KEY, updatedServer.url) + putString(ACCESS_TOKEN_KEY, updatedUser.accessToken) + } + return@withContext _current.value } - withContext(Dispatchers.Main) { - _current.value = CurrentUser(updatedServer, updatedUser, userDto) - } - sharedPreferences.edit(true) { - putString(SERVER_URL_KEY, updatedServer.url) - putString(ACCESS_TOKEN_KEY, updatedUser.accessToken) - } - } /** * Restores a session for the given server & user such as when the app reopens @@ -115,10 +118,10 @@ class ServerRepository suspend fun restoreSession( serverId: UUID?, userId: UUID?, - ): Boolean { + ): CurrentUser? { if (serverId == null || userId == null) { _current.setValueOnMain(null) - return false + return null } val serverAndUsers = withContext(Dispatchers.IO) { @@ -129,13 +132,17 @@ class ServerRepository if (user != null) { // TODO pin-related // if (user != null && !user.hasPin) { - changeUser(serverAndUsers.server, user) - return true + return changeUser(serverAndUsers.server, user) } } - return false + return null } + suspend fun fetchLastUsedServer(serverId: UUID?): JellyfinServer? = + withContext(Dispatchers.IO) { + serverId?.let { serverDao.getServer(serverId)?.server } + } + fun closeSession() { _current.value = null } @@ -170,7 +177,7 @@ class ServerRepository ) } if (user != null) { - changeUser(server, user) + return@withContext changeUser(server, user) } else { throw IllegalArgumentException("Authentication result's user was null") } @@ -260,6 +267,7 @@ class ServerRepository } } +@Serializable data class CurrentUser( val server: JellyfinServer, val user: JellyfinUser, diff --git a/app/src/main/java/com/github/damontecres/wholphin/data/model/JellyfinServer.kt b/app/src/main/java/com/github/damontecres/wholphin/data/model/JellyfinServer.kt index d8e40779..2c516272 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/data/model/JellyfinServer.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/data/model/JellyfinServer.kt @@ -41,6 +41,7 @@ data class JellyfinServer( ], indices = [Index("id", "serverId", unique = true)], ) +@Serializable data class JellyfinUser( @PrimaryKey(autoGenerate = true) val rowId: Int = 0, diff --git a/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt b/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt index 4d6154d6..6bca9d38 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/preferences/AppPreference.kt @@ -851,8 +851,7 @@ val basicPreferences = title = R.string.ui_interface, preferences = listOf( - // TODO PIN-related - // AppPreference.SignInAuto, + AppPreference.SignInAuto, AppPreference.HomePageItems, AppPreference.CombineContinueNext, AppPreference.RewatchNextUp, diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/DeviceProfileService.kt b/app/src/main/java/com/github/damontecres/wholphin/services/DeviceProfileService.kt index ed04c3f2..9ce70b21 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/DeviceProfileService.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/DeviceProfileService.kt @@ -20,7 +20,7 @@ class DeviceProfileService constructor( @param:ApplicationContext private val context: Context, ) { - private val mediaCodecCapabilitiesTest by lazy { + val mediaCodecCapabilitiesTest by lazy { // Created lazily below on the IO thread since it cn take time MediaCodecCapabilitiesTest(context) } diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/NavigationManager.kt b/app/src/main/java/com/github/damontecres/wholphin/services/NavigationManager.kt index a387ac8c..136cb746 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/NavigationManager.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/NavigationManager.kt @@ -29,12 +29,7 @@ class NavigationManager */ fun navigateToFromDrawer(destination: Destination) { goToHome() - if (destination == Destination.ServerList || destination is Destination.UserList) { - backStack.add(destination) - (0.. backStack.removeAt(0) } - } else { - backStack.add(destination) - } + backStack.add(destination) log() } diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/SetupNavigationManager.kt b/app/src/main/java/com/github/damontecres/wholphin/services/SetupNavigationManager.kt new file mode 100644 index 00000000..bd95ea1d --- /dev/null +++ b/app/src/main/java/com/github/damontecres/wholphin/services/SetupNavigationManager.kt @@ -0,0 +1,54 @@ +package com.github.damontecres.wholphin.services + +import androidx.compose.runtime.mutableStateListOf +import androidx.navigation3.runtime.NavKey +import com.github.damontecres.wholphin.data.CurrentUser +import com.github.damontecres.wholphin.data.model.JellyfinServer +import kotlinx.serialization.Serializable +import org.acra.ACRA +import timber.log.Timber +import javax.inject.Inject +import javax.inject.Singleton + +/** + * Manages navigating for setup + */ +@Singleton +class SetupNavigationManager + @Inject + constructor() { + var backStack: MutableList = mutableStateListOf(SetupDestination.Loading) + + /** + * Go to the specified [SetupDestination] + */ + fun navigateTo(destination: SetupDestination) { + backStack[0] = destination + log() + } + + private fun log() { + val dest = backStack.lastOrNull().toString() + Timber.i("Current setup destination: %s", dest) + ACRA.errorReporter.putCustomData("setupDestination", dest) + } + } + +@Serializable +sealed interface SetupDestination : NavKey { + @Serializable + data object Loading : SetupDestination + + @Serializable + data object ServerList : SetupDestination + + @Serializable + data class UserList( + val server: JellyfinServer, + ) : SetupDestination + + @Serializable + data class AppContent( + val current: CurrentUser, + ) : SetupDestination +} diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/nav/ApplicationContent.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/nav/ApplicationContent.kt index 2d3200ba..7e7d0d2b 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/nav/ApplicationContent.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/nav/ApplicationContent.kt @@ -8,6 +8,7 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue +import androidx.compose.runtime.saveable.rememberSerializable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha @@ -24,8 +25,12 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.navigation3.rememberViewModelStoreNavEntryDecorator +import androidx.navigation3.runtime.NavBackStack import androidx.navigation3.runtime.NavEntry +import androidx.navigation3.runtime.NavKey import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator +import androidx.navigation3.runtime.serialization.NavBackStackSerializer +import androidx.navigation3.runtime.serialization.NavKeySerializer import androidx.navigation3.ui.NavDisplay import androidx.tv.material3.MaterialTheme import coil3.compose.AsyncImage @@ -62,13 +67,22 @@ class ApplicationContentViewModel */ @Composable fun ApplicationContent( - server: JellyfinServer?, - user: JellyfinUser?, + server: JellyfinServer, + user: JellyfinUser, navigationManager: NavigationManager, preferences: UserPreferences, modifier: Modifier = Modifier, viewModel: ApplicationContentViewModel = hiltViewModel(), ) { + val backStack: MutableList = + rememberSerializable( + server, + user, + serializer = NavBackStackSerializer(elementSerializer = NavKeySerializer()), + ) { + NavBackStack(Destination.Home()) + } + navigationManager.backStack = backStack val backdrop by viewModel.backdropService.backdropFlow.collectAsStateWithLifecycle() val backdropStyle = preferences.appPreferences.interfacePreferences.backdropStyle Box( diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/nav/Destination.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/nav/Destination.kt index 9310d0fa..b9452daa 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/nav/Destination.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/nav/Destination.kt @@ -7,7 +7,6 @@ import androidx.navigation3.runtime.NavKey import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.GetItemsFilter import com.github.damontecres.wholphin.data.model.ItemPlayback -import com.github.damontecres.wholphin.data.model.JellyfinServer import com.github.damontecres.wholphin.ui.data.SortAndDirection import com.github.damontecres.wholphin.ui.detail.series.SeasonEpisode import com.github.damontecres.wholphin.ui.detail.series.SeasonEpisodeIds @@ -28,14 +27,6 @@ import java.util.UUID sealed class Destination( val fullScreen: Boolean = false, ) : NavKey { - @Serializable - data object ServerList : Destination(true) - - @Serializable - data class UserList( - val server: JellyfinServer, - ) : Destination(true) - @Serializable data class Home( val id: Long = 0L, diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/nav/DestinationContent.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/nav/DestinationContent.kt index 2f6feb7a..038f231a 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/nav/DestinationContent.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/nav/DestinationContent.kt @@ -29,8 +29,6 @@ import com.github.damontecres.wholphin.ui.main.SearchPage import com.github.damontecres.wholphin.ui.playback.PlaybackPage import com.github.damontecres.wholphin.ui.preferences.PreferencesPage import com.github.damontecres.wholphin.ui.setup.InstallUpdatePage -import com.github.damontecres.wholphin.ui.setup.SwitchServerContent -import com.github.damontecres.wholphin.ui.setup.SwitchUserContent import org.jellyfin.sdk.model.api.BaseItemKind import org.jellyfin.sdk.model.api.CollectionType import timber.log.Timber @@ -66,14 +64,6 @@ fun DestinationContent( ) } - Destination.ServerList -> { - SwitchServerContent(modifier) - } - - is Destination.UserList -> { - SwitchUserContent(destination.server, modifier) - } - is Destination.Settings -> { PreferencesPage( preferences.appPreferences, diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/nav/NavDrawer.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/nav/NavDrawer.kt index 9a7d3559..ef858eb7 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/nav/NavDrawer.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/nav/NavDrawer.kt @@ -79,6 +79,8 @@ import com.github.damontecres.wholphin.preferences.AppThemeColors import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.services.BackdropService import com.github.damontecres.wholphin.services.NavigationManager +import com.github.damontecres.wholphin.services.SetupDestination +import com.github.damontecres.wholphin.services.SetupNavigationManager import com.github.damontecres.wholphin.ui.FontAwesome import com.github.damontecres.wholphin.ui.components.TimeDisplay import com.github.damontecres.wholphin.ui.ifElse @@ -106,6 +108,7 @@ class NavDrawerViewModel constructor( private val navDrawerItemRepository: NavDrawerItemRepository, val navigationManager: NavigationManager, + val setupNavigationManager: SetupNavigationManager, val backdropService: BackdropService, ) : ViewModel() { private var all: List? = null @@ -361,10 +364,8 @@ fun NavDrawer( drawerOpen = drawerState.isOpen, interactionSource = interactionSource, onClick = { - viewModel.navigationManager.navigateToFromDrawer( - Destination.UserList( - server, - ), + viewModel.setupNavigationManager.navigateTo( + SetupDestination.UserList(server), ) }, modifier = Modifier.padding(start = drawerPadding), diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchServerViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchServerViewModel.kt index 75af5155..f9e7d0a3 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchServerViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchServerViewModel.kt @@ -9,9 +9,9 @@ import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.data.JellyfinServerDao import com.github.damontecres.wholphin.data.ServerRepository import com.github.damontecres.wholphin.data.model.JellyfinServer -import com.github.damontecres.wholphin.services.NavigationManager +import com.github.damontecres.wholphin.services.SetupDestination +import com.github.damontecres.wholphin.services.SetupNavigationManager import com.github.damontecres.wholphin.ui.launchIO -import com.github.damontecres.wholphin.ui.nav.Destination import com.github.damontecres.wholphin.ui.setValueOnMain import com.github.damontecres.wholphin.ui.showToast import com.github.damontecres.wholphin.util.LoadingState @@ -41,7 +41,7 @@ class SwitchServerViewModel val jellyfin: Jellyfin, val serverRepository: ServerRepository, val serverDao: JellyfinServerDao, - val navigationManager: NavigationManager, + val navigationManager: SetupNavigationManager, ) : ViewModel() { val servers = MutableLiveData>(listOf()) val serverStatus = MutableLiveData>(mapOf()) @@ -156,7 +156,7 @@ class SwitchServerViewModel ) serverRepository.addAndChangeServer(updatedServer) withContext(Dispatchers.Main) { - navigationManager.navigateTo(Destination.UserList(server)) + navigationManager.navigateTo(SetupDestination.UserList(updatedServer)) } } else if (result is ServerConnectionStatus.Error) { showToast(context, "Error connecting: $${result.message}") @@ -201,7 +201,7 @@ class SwitchServerViewModel } withContext(Dispatchers.Main) { addServerState.value = LoadingState.Success - navigationManager.navigateTo(Destination.UserList(server)) + navigationManager.navigateTo(SetupDestination.UserList(server)) } } else { withContext(Dispatchers.Main) { diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchUserContent.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchUserContent.kt index 19c00f18..b71e0af0 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchUserContent.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchUserContent.kt @@ -37,6 +37,7 @@ import androidx.tv.material3.Text import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.data.model.JellyfinServer import com.github.damontecres.wholphin.data.model.JellyfinUser +import com.github.damontecres.wholphin.services.SetupDestination import com.github.damontecres.wholphin.ui.components.BasicDialog import com.github.damontecres.wholphin.ui.components.CircularProgress import com.github.damontecres.wholphin.ui.components.EditTextBox @@ -130,7 +131,9 @@ fun SwitchUserContent( viewModel.removeUser(user) }, onSwitchServer = { - viewModel.navigationManager.navigateTo(Destination.ServerList) + viewModel.setupNavigationManager.navigateTo( + SetupDestination.ServerList, + ) }, modifier = Modifier.fillMaxWidth(), ) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchUserViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchUserViewModel.kt index 7da64ca7..68b9e583 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchUserViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchUserViewModel.kt @@ -9,6 +9,8 @@ import com.github.damontecres.wholphin.data.model.JellyfinServer import com.github.damontecres.wholphin.data.model.JellyfinUser import com.github.damontecres.wholphin.services.ImageUrlService import com.github.damontecres.wholphin.services.NavigationManager +import com.github.damontecres.wholphin.services.SetupDestination +import com.github.damontecres.wholphin.services.SetupNavigationManager import com.github.damontecres.wholphin.ui.launchIO import com.github.damontecres.wholphin.ui.setValueOnMain import com.github.damontecres.wholphin.util.ExceptionHandler @@ -43,6 +45,7 @@ class SwitchUserViewModel val serverRepository: ServerRepository, val serverDao: JellyfinServerDao, val navigationManager: NavigationManager, + val setupNavigationManager: SetupNavigationManager, val imageUrlService: ImageUrlService, @Assisted val server: JellyfinServer, ) : ViewModel() { @@ -120,9 +123,11 @@ class SwitchUserViewModel fun switchUser(user: JellyfinUser) { viewModelScope.launchIO { try { - serverRepository.changeUser(server, user) - withContext(Dispatchers.Main) { - navigationManager.goToHome() + val current = serverRepository.changeUser(server, user) + if (current != null) { + withContext(Dispatchers.Main) { + setupNavigationManager.navigateTo(SetupDestination.AppContent(current)) + } } } catch (ex: Exception) { Timber.e(ex, "Error switching user") @@ -144,9 +149,11 @@ class SwitchUserViewModel username = username, password = password, ) - serverRepository.changeUser(server.url, authenticationResult) - withContext(Dispatchers.Main) { - navigationManager.goToHome() + val current = serverRepository.changeUser(server.url, authenticationResult) + if (current != null) { + withContext(Dispatchers.Main) { + setupNavigationManager.navigateTo(SetupDestination.AppContent(current)) + } } } catch (ex: Exception) { Timber.e(ex, "Error logging in user") @@ -192,9 +199,13 @@ class SwitchUserViewModel val authenticationResult by api.userApi.authenticateWithQuickConnect( QuickConnectDto(secret = state.secret), ) - serverRepository.changeUser(server.url, authenticationResult) - withContext(Dispatchers.Main) { - navigationManager.goToHome() + val current = serverRepository.changeUser(server.url, authenticationResult) + if (current != null) { + withContext(Dispatchers.Main) { + setupNavigationManager.navigateTo( + SetupDestination.AppContent(current), + ) + } } } catch (ex: Exception) { Timber.e(ex, "Error during quick connect")