mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 23:51:21 +02:00
Add setting to toggle automatic sign in (#538)
## Description The major user facing change in this PR is adding back the setting to toggle automatic sign in on or off. This controls whether to restore the app to where you left off or go the user selection page. If enabled: re-opening the app will restore the previous user's session If disabled: re-opening the app will always start with the user selection page for the most recent server, or if there isn't a recent server, the server selection page This also separate this setting from the PIN protection settings in previous PRs. ### Dev changes This PR refactors the "setup" UI (server & user selection) to be separate from the "app" UI (the actually app content). There is now a `NavDisplay` for the setup flow which ends with displaying the `ApplicationContent`'s `NavDisplay`. Most of the logic is moved out of Compose code and into a `ViewModel`. This means to switch users/server, the `SetupNavigationManager` must be used instead of `NavigationManager`. ### Related issues Closes #396 Fixes #376
This commit is contained in:
parent
20fe5e2626
commit
eb4e9b93be
14 changed files with 302 additions and 198 deletions
|
|
@ -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<AppPreferences>
|
||||
|
|
@ -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<AppPreferences>,
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue