mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-09 16:11:20 +02:00
Refactoring switching server or user (#205)
Now when initiating a user or server switch, you can't back out of it and must select a user/server. This will be useful when PIN code are implemented down the road. The app will now disconnect the web socket when backgrounded. Also lots of internal refactoring
This commit is contained in:
parent
2e3e4e3f5f
commit
58395e9adf
31 changed files with 442 additions and 277 deletions
|
|
@ -108,7 +108,7 @@ class CollectionFolderViewModel
|
|||
|
||||
val sortAndDirection =
|
||||
if (initialSortAndDirection == null) {
|
||||
serverRepository.currentUser?.let { user ->
|
||||
serverRepository.currentUser.value?.let { user ->
|
||||
libraryDisplayInfoDao.getItem(user, itemId)?.sortAndDirection
|
||||
} ?: SortAndDirection.DEFAULT
|
||||
} else {
|
||||
|
|
@ -123,7 +123,7 @@ class CollectionFolderViewModel
|
|||
recursive: Boolean,
|
||||
filter: GetItemsFilter,
|
||||
) {
|
||||
serverRepository.currentUser?.let { user ->
|
||||
serverRepository.currentUser.value?.let { user ->
|
||||
viewModelScope.launch(Dispatchers.IO) {
|
||||
val libraryDisplayInfo =
|
||||
LibraryDisplayInfo(
|
||||
|
|
|
|||
|
|
@ -133,7 +133,7 @@ class RecommendedMovieViewModel
|
|||
|
||||
val suggestionsRequest =
|
||||
GetSuggestionsRequest(
|
||||
userId = serverRepository.currentUser?.id,
|
||||
userId = serverRepository.currentUser.value?.id,
|
||||
type = listOf(BaseItemKind.MOVIE),
|
||||
)
|
||||
val suggestedItems = ApiRequestPager(api, suggestionsRequest, GetSuggestionsRequestHandler, viewModelScope)
|
||||
|
|
|
|||
|
|
@ -161,7 +161,7 @@ class RecommendedTvShowViewModel
|
|||
|
||||
val suggestionsRequest =
|
||||
GetSuggestionsRequest(
|
||||
userId = serverRepository.currentUser?.id,
|
||||
userId = serverRepository.currentUser.value?.id,
|
||||
type = listOf(BaseItemKind.SERIES),
|
||||
)
|
||||
val suggestedItems = ApiRequestPager(api, suggestionsRequest, GetSuggestionsRequestHandler, viewModelScope)
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ class LiveTvCollectionViewModel
|
|||
viewModelScope.launchIO {
|
||||
val folders =
|
||||
api.liveTvApi
|
||||
.getRecordingFolders(userId = serverRepository.currentUser?.id)
|
||||
.getRecordingFolders(userId = serverRepository.currentUser.value?.id)
|
||||
.content.items
|
||||
.map { TabId(it.name ?: "Recordings", it.id) }
|
||||
this@LiveTvCollectionViewModel.recordingFolders.setValueOnMain(folders)
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ class DebugViewModel
|
|||
|
||||
init {
|
||||
viewModelScope.launchIO {
|
||||
serverRepository.currentUser?.rowId?.let {
|
||||
serverRepository.currentUser.value?.rowId?.let {
|
||||
val results = itemPlaybackDao.getItems(it)
|
||||
withContext(Dispatchers.Main) {
|
||||
itemPlaybacks.value = results
|
||||
|
|
|
|||
|
|
@ -303,7 +303,7 @@ fun MovieDetails(
|
|||
ItemDetailsDialog(
|
||||
info = info,
|
||||
showFilePath =
|
||||
viewModel.serverRepository.currentUserDto
|
||||
viewModel.serverRepository.currentUserDto.value
|
||||
?.policy
|
||||
?.isAdministrator == true,
|
||||
onDismissRequest = { overviewDialog = null },
|
||||
|
|
|
|||
|
|
@ -147,7 +147,7 @@ class MovieViewModel
|
|||
api.libraryApi
|
||||
.getSimilarItems(
|
||||
GetSimilarItemsRequest(
|
||||
userId = serverRepository.currentUser?.id,
|
||||
userId = serverRepository.currentUser.value?.id,
|
||||
itemId = itemId,
|
||||
fields = SlimItemFields,
|
||||
limit = 25,
|
||||
|
|
|
|||
|
|
@ -346,7 +346,7 @@ fun SeriesOverview(
|
|||
ItemDetailsDialog(
|
||||
info = info,
|
||||
showFilePath =
|
||||
viewModel.serverRepository.currentUserDto
|
||||
viewModel.serverRepository.currentUserDto.value
|
||||
?.policy
|
||||
?.isAdministrator == true,
|
||||
onDismissRequest = { overviewDialog = null },
|
||||
|
|
|
|||
|
|
@ -121,7 +121,7 @@ class SeriesViewModel
|
|||
api.libraryApi
|
||||
.getSimilarItems(
|
||||
GetSimilarItemsRequest(
|
||||
userId = serverRepository.currentUser?.id,
|
||||
userId = serverRepository.currentUser.value?.id,
|
||||
itemId = itemId,
|
||||
fields = SlimItemFields,
|
||||
limit = 25,
|
||||
|
|
|
|||
|
|
@ -77,7 +77,7 @@ class HomeViewModel
|
|||
) {
|
||||
Timber.d("init HomeViewModel")
|
||||
|
||||
serverRepository.currentUserDto?.let { userDto ->
|
||||
serverRepository.currentUserDto.value?.let { userDto ->
|
||||
val includedIds =
|
||||
navDrawerItemRepository
|
||||
.getFilteredNavDrawerItems(navDrawerItemRepository.getNavDrawerItems())
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import androidx.navigation3.ui.NavDisplay
|
|||
import com.github.damontecres.wholphin.data.model.JellyfinServer
|
||||
import com.github.damontecres.wholphin.data.model.JellyfinUser
|
||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||
import org.jellyfin.sdk.model.api.DeviceProfile
|
||||
|
||||
/**
|
||||
|
|
@ -45,7 +46,7 @@ fun ApplicationContent(
|
|||
deviceProfile = deviceProfile,
|
||||
modifier = modifier.fillMaxSize(),
|
||||
)
|
||||
} else {
|
||||
} else if (user != null && server != null) {
|
||||
NavDrawer(
|
||||
destination = key,
|
||||
preferences = preferences,
|
||||
|
|
@ -54,6 +55,8 @@ fun ApplicationContent(
|
|||
server = server,
|
||||
modifier = modifier,
|
||||
)
|
||||
} else {
|
||||
ErrorMessage("Trying to go to $key without a user logged in", null)
|
||||
}
|
||||
}
|
||||
},
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ 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.detail.series.SeasonEpisode
|
||||
import com.github.damontecres.wholphin.ui.detail.series.SeasonEpisodeIds
|
||||
import com.github.damontecres.wholphin.ui.preferences.PreferenceScreenOption
|
||||
|
|
@ -29,7 +30,9 @@ sealed class Destination(
|
|||
data object ServerList : Destination(true)
|
||||
|
||||
@Serializable
|
||||
data object UserList : Destination(true)
|
||||
data class UserList(
|
||||
val server: JellyfinServer,
|
||||
) : Destination(true)
|
||||
|
||||
@Serializable
|
||||
data class Home(
|
||||
|
|
|
|||
|
|
@ -54,7 +54,7 @@ fun DestinationContent(
|
|||
)
|
||||
|
||||
Destination.ServerList -> SwitchServerContent(modifier)
|
||||
Destination.UserList -> SwitchUserContent(modifier)
|
||||
is Destination.UserList -> SwitchUserContent(destination.server, modifier)
|
||||
|
||||
is Destination.Settings ->
|
||||
PreferencesPage(
|
||||
|
|
|
|||
|
|
@ -84,7 +84,6 @@ import kotlinx.coroutines.isActive
|
|||
import kotlinx.coroutines.withContext
|
||||
import org.jellyfin.sdk.model.api.CollectionType
|
||||
import org.jellyfin.sdk.model.api.DeviceProfile
|
||||
import timber.log.Timber
|
||||
import java.time.LocalTime
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
|
|
@ -141,7 +140,7 @@ class NavDrawerViewModel
|
|||
null
|
||||
}
|
||||
}
|
||||
Timber.v("Found $index => $key")
|
||||
// Timber.v("Found $index => $key")
|
||||
if (index != null) {
|
||||
selectedIndex.setValueOnMain(index)
|
||||
break
|
||||
|
|
@ -198,8 +197,8 @@ data class ServerNavDrawerItem(
|
|||
fun NavDrawer(
|
||||
destination: Destination,
|
||||
preferences: UserPreferences,
|
||||
user: JellyfinUser?,
|
||||
server: JellyfinServer?,
|
||||
user: JellyfinUser,
|
||||
server: JellyfinServer,
|
||||
deviceProfile: DeviceProfile,
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: NavDrawerViewModel =
|
||||
|
|
@ -319,7 +318,11 @@ fun NavDrawer(
|
|||
selected = false,
|
||||
interactionSource = interactionSource,
|
||||
onClick = {
|
||||
viewModel.navigationManager.navigateTo(Destination.UserList)
|
||||
viewModel.navigationManager.navigateToFromDrawer(
|
||||
Destination.UserList(
|
||||
server,
|
||||
),
|
||||
)
|
||||
},
|
||||
modifier = Modifier.animateItem(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -28,7 +28,12 @@ class NavigationManager
|
|||
*/
|
||||
fun navigateToFromDrawer(destination: Destination) {
|
||||
goToHome()
|
||||
backStack.add(destination)
|
||||
if (destination == Destination.ServerList || destination is Destination.UserList) {
|
||||
backStack.add(destination)
|
||||
(0..<backStack.size - 1).forEach { _ -> backStack.removeAt(0) }
|
||||
} else {
|
||||
backStack.add(destination)
|
||||
}
|
||||
log()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -268,13 +268,14 @@ class PlaybackViewModel
|
|||
if (itemPlayback != null) {
|
||||
itemPlayback
|
||||
} else {
|
||||
val user = serverRepository.currentUser!!
|
||||
itemPlaybackDao.getItem(user, base.id)?.let {
|
||||
Timber.v("Fetched itemPlayback from DB: %s", it)
|
||||
if (it.sourceId != null) {
|
||||
it
|
||||
} else {
|
||||
null
|
||||
serverRepository.currentUser.value?.let { user ->
|
||||
itemPlaybackDao.getItem(user, base.id)?.let {
|
||||
Timber.v("Fetched itemPlayback from DB: %s", it)
|
||||
if (it.sourceId != null) {
|
||||
it
|
||||
} else {
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ class PreferencesViewModel
|
|||
|
||||
init {
|
||||
viewModelScope.launchIO {
|
||||
serverRepository.currentUser?.let { user ->
|
||||
serverRepository.currentUser.value?.let { user ->
|
||||
allNavDrawerItems = navDrawerItemRepository.getNavDrawerItems()
|
||||
val pins = serverPreferencesDao.getNavDrawerPinnedItems(user)
|
||||
val navDrawerPins = allNavDrawerItems.associateWith { pins.isPinned(it.id) }
|
||||
|
|
@ -60,7 +60,7 @@ class PreferencesViewModel
|
|||
|
||||
fun updatePins(newSelectedItems: List<NavDrawerItem>) {
|
||||
viewModelScope.launchIO(ExceptionHandler(true)) {
|
||||
serverRepository.currentUser?.let { user ->
|
||||
serverRepository.currentUser.value?.let { user ->
|
||||
val disabledItems =
|
||||
mutableListOf<NavDrawerItem>().apply {
|
||||
addAll(allNavDrawerItems)
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ fun ServerList(
|
|||
servers: List<JellyfinServer>,
|
||||
connectionStatus: Map<UUID, ServerConnectionStatus>,
|
||||
onSwitchServer: (JellyfinServer) -> Unit,
|
||||
onTestServer: (JellyfinServer) -> Unit,
|
||||
onAddServer: () -> Unit,
|
||||
onRemoveServer: (JellyfinServer) -> Unit,
|
||||
allowAdd: Boolean,
|
||||
|
|
@ -61,7 +62,7 @@ fun ServerList(
|
|||
items(servers) { server ->
|
||||
val status = connectionStatus[server.id] ?: ServerConnectionStatus.Pending
|
||||
ListItem(
|
||||
enabled = status == ServerConnectionStatus.Success,
|
||||
enabled = true,
|
||||
selected = false,
|
||||
headlineContent = { Text(text = server.name?.ifBlank { null } ?: server.url) },
|
||||
supportingContent = { if (server.name.isNotNullOrBlank()) Text(text = server.url) },
|
||||
|
|
@ -77,12 +78,18 @@ fun ServerList(
|
|||
Icon(
|
||||
imageVector = Icons.Default.Warning,
|
||||
contentDescription = status.message,
|
||||
tint = MaterialTheme.colorScheme.error,
|
||||
tint = MaterialTheme.colorScheme.errorContainer,
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
onClick = { onSwitchServer.invoke(server) },
|
||||
onClick = {
|
||||
when (status) {
|
||||
ServerConnectionStatus.Success -> onSwitchServer.invoke(server)
|
||||
ServerConnectionStatus.Pending -> {}
|
||||
is ServerConnectionStatus.Error -> onTestServer.invoke(server)
|
||||
}
|
||||
},
|
||||
onLongClick = {
|
||||
if (allowDelete) {
|
||||
showDeleteDialog = server
|
||||
|
|
|
|||
|
|
@ -43,9 +43,8 @@ import com.github.damontecres.wholphin.util.LoadingState
|
|||
@Composable
|
||||
fun SwitchServerContent(
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: SwitchUserViewModel = hiltViewModel(),
|
||||
viewModel: SwitchServerViewModel = hiltViewModel(),
|
||||
) {
|
||||
val currentServer = viewModel.serverRepository.currentServer
|
||||
val servers by viewModel.servers.observeAsState(listOf())
|
||||
val serverStatus by viewModel.serverStatus.observeAsState(mapOf())
|
||||
|
||||
|
|
@ -90,6 +89,9 @@ fun SwitchServerContent(
|
|||
onSwitchServer = {
|
||||
viewModel.addServer(it.url)
|
||||
},
|
||||
onTestServer = {
|
||||
viewModel.testServer(it)
|
||||
},
|
||||
onAddServer = {
|
||||
showAddServer = true
|
||||
},
|
||||
|
|
@ -142,6 +144,9 @@ fun SwitchServerContent(
|
|||
onSwitchServer = {
|
||||
viewModel.addServer(it.url)
|
||||
},
|
||||
onTestServer = {
|
||||
viewModel.testServer(it)
|
||||
},
|
||||
onAddServer = {},
|
||||
onRemoveServer = {},
|
||||
allowAdd = false,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,214 @@
|
|||
package com.github.damontecres.wholphin.ui.setup
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.Toast
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
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.ui.launchIO
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.showToast
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jellyfin.sdk.Jellyfin
|
||||
import org.jellyfin.sdk.api.client.HttpClientOptions
|
||||
import org.jellyfin.sdk.api.client.extensions.quickConnectApi
|
||||
import org.jellyfin.sdk.api.client.extensions.systemApi
|
||||
import org.jellyfin.sdk.model.serializer.toUUID
|
||||
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
||||
import timber.log.Timber
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@HiltViewModel
|
||||
class SwitchServerViewModel
|
||||
@Inject
|
||||
constructor(
|
||||
@param:ApplicationContext private val context: Context,
|
||||
val jellyfin: Jellyfin,
|
||||
val serverRepository: ServerRepository,
|
||||
val serverDao: JellyfinServerDao,
|
||||
val navigationManager: NavigationManager,
|
||||
) : ViewModel() {
|
||||
val servers = MutableLiveData<List<JellyfinServer>>(listOf())
|
||||
val serverStatus = MutableLiveData<Map<UUID, ServerConnectionStatus>>(mapOf())
|
||||
val serverQuickConnect = MutableLiveData<Map<UUID, Boolean>>(mapOf())
|
||||
|
||||
val discoveredServers = MutableLiveData<List<JellyfinServer>>(listOf())
|
||||
|
||||
val addServerState = MutableLiveData<LoadingState>(LoadingState.Pending)
|
||||
|
||||
fun clearAddServerState() {
|
||||
addServerState.value = LoadingState.Pending
|
||||
}
|
||||
|
||||
init {
|
||||
init()
|
||||
}
|
||||
|
||||
fun init() {
|
||||
viewModelScope.launchIO {
|
||||
withContext(Dispatchers.Main) {
|
||||
serverStatus.value = mapOf()
|
||||
serverQuickConnect.value = mapOf()
|
||||
}
|
||||
|
||||
val allServers =
|
||||
serverDao
|
||||
.getServers()
|
||||
.map { it.server }
|
||||
.sortedWith(compareBy<JellyfinServer> { it.name }.thenBy { it.url })
|
||||
withContext(Dispatchers.Main) {
|
||||
servers.value = allServers
|
||||
}
|
||||
allServers.forEach { server ->
|
||||
internalTestServer(server)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun testServer(server: JellyfinServer) {
|
||||
serverStatus.value =
|
||||
serverStatus.value!!.toMutableMap().apply {
|
||||
put(
|
||||
server.id,
|
||||
ServerConnectionStatus.Pending,
|
||||
)
|
||||
}
|
||||
viewModelScope.launchIO {
|
||||
delay(1000)
|
||||
val result = internalTestServer(server)
|
||||
if (result == ServerConnectionStatus.Success) {
|
||||
showToast(context, context.getString(R.string.success), Toast.LENGTH_SHORT)
|
||||
} else if (result is ServerConnectionStatus.Error) {
|
||||
showToast(context, result.message ?: "Error", Toast.LENGTH_SHORT)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun internalTestServer(server: JellyfinServer): ServerConnectionStatus =
|
||||
try {
|
||||
jellyfin
|
||||
.createApi(
|
||||
server.url,
|
||||
httpClientOptions =
|
||||
HttpClientOptions(
|
||||
requestTimeout = 6.seconds,
|
||||
connectTimeout = 6.seconds,
|
||||
socketTimeout = 6.seconds,
|
||||
),
|
||||
).systemApi
|
||||
.getPublicSystemInfo()
|
||||
withContext(Dispatchers.Main) {
|
||||
serverStatus.value =
|
||||
serverStatus.value!!.toMutableMap().apply {
|
||||
put(
|
||||
server.id,
|
||||
ServerConnectionStatus.Success,
|
||||
)
|
||||
}
|
||||
}
|
||||
ServerConnectionStatus.Success
|
||||
} catch (ex: Exception) {
|
||||
val status = ServerConnectionStatus.Error(ex.localizedMessage)
|
||||
Timber.w(ex, "Error checking server ${server.url}")
|
||||
withContext(Dispatchers.Main) {
|
||||
serverStatus.value =
|
||||
serverStatus.value!!.toMutableMap().apply {
|
||||
put(
|
||||
server.id,
|
||||
status,
|
||||
)
|
||||
}
|
||||
}
|
||||
status
|
||||
}
|
||||
|
||||
fun addServer(serverUrl: String) {
|
||||
addServerState.value = LoadingState.Loading
|
||||
viewModelScope.launchIO {
|
||||
try {
|
||||
val serverInfo by jellyfin
|
||||
.createApi(serverUrl)
|
||||
.systemApi
|
||||
.getPublicSystemInfo()
|
||||
val id = serverInfo.id?.toUUIDOrNull()
|
||||
if (id != null && serverInfo.startupWizardCompleted == true) {
|
||||
val server =
|
||||
JellyfinServer(
|
||||
id = id,
|
||||
name = serverInfo.serverName,
|
||||
url = serverUrl,
|
||||
)
|
||||
serverRepository.addAndChangeServer(server)
|
||||
val quickConnect =
|
||||
jellyfin
|
||||
.createApi(serverUrl)
|
||||
.quickConnectApi
|
||||
.getQuickConnectEnabled()
|
||||
.content
|
||||
withContext(Dispatchers.Main) {
|
||||
serverQuickConnect.value =
|
||||
serverQuickConnect.value!!.toMutableMap().apply {
|
||||
put(id, quickConnect)
|
||||
}
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
addServerState.value = LoadingState.Success
|
||||
navigationManager.navigateTo(Destination.UserList(server))
|
||||
}
|
||||
} else {
|
||||
withContext(Dispatchers.Main) {
|
||||
addServerState.value =
|
||||
LoadingState.Error("Server returned invalid response")
|
||||
}
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
Timber.w(ex, "Error creating API for $serverUrl")
|
||||
withContext(Dispatchers.Main) {
|
||||
addServerState.value =
|
||||
LoadingState.Error(exception = ex)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun removeServer(server: JellyfinServer) {
|
||||
viewModelScope.launchIO {
|
||||
serverRepository.removeServer(server)
|
||||
init()
|
||||
}
|
||||
}
|
||||
|
||||
fun discoverServers() {
|
||||
viewModelScope.launchIO {
|
||||
jellyfin.discovery.discoverLocalServers().collect { server ->
|
||||
val newServerList =
|
||||
discoveredServers.value!!
|
||||
.toMutableList()
|
||||
.apply {
|
||||
add(
|
||||
JellyfinServer(
|
||||
server.id.toUUID(),
|
||||
server.name,
|
||||
server.address,
|
||||
),
|
||||
)
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
discoveredServers.value = newServerList
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -39,6 +39,7 @@ import androidx.tv.material3.MaterialTheme
|
|||
import androidx.tv.material3.Text
|
||||
import androidx.tv.material3.surfaceColorAtElevation
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.model.JellyfinServer
|
||||
import com.github.damontecres.wholphin.ui.components.BasicDialog
|
||||
import com.github.damontecres.wholphin.ui.components.CircularProgress
|
||||
import com.github.damontecres.wholphin.ui.components.EditTextBox
|
||||
|
|
@ -49,17 +50,20 @@ import com.github.damontecres.wholphin.util.LoadingState
|
|||
|
||||
@Composable
|
||||
fun SwitchUserContent(
|
||||
currentServer: JellyfinServer,
|
||||
modifier: Modifier = Modifier,
|
||||
viewModel: SwitchUserViewModel = hiltViewModel(),
|
||||
viewModel: SwitchUserViewModel =
|
||||
hiltViewModel<SwitchUserViewModel, SwitchUserViewModel.Factory>(
|
||||
creationCallback = { it.create(currentServer) },
|
||||
),
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
|
||||
val currentServer = viewModel.serverRepository.currentServer
|
||||
val currentUser = viewModel.serverRepository.currentUser
|
||||
// val currentServer by viewModel.serverRepository.currentServer.observeAsState()
|
||||
val currentUser by viewModel.serverRepository.currentUser.observeAsState()
|
||||
val users by viewModel.users.observeAsState(listOf())
|
||||
|
||||
val serverQuickConnect by viewModel.serverQuickConnect.observeAsState(mapOf())
|
||||
val quickConnectEnabled = currentServer?.let { serverQuickConnect[it.id] ?: false } ?: false
|
||||
val quickConnectEnabled by viewModel.serverQuickConnect.observeAsState(false)
|
||||
val quickConnect by viewModel.quickConnectState.observeAsState(null)
|
||||
var showAddUser by remember { mutableStateOf(false) }
|
||||
|
||||
|
|
@ -109,7 +113,7 @@ fun SwitchUserContent(
|
|||
users = users,
|
||||
currentUser = currentUser,
|
||||
onSwitchUser = { user ->
|
||||
viewModel.switchUser(server, user)
|
||||
viewModel.switchUser(user)
|
||||
},
|
||||
onAddUser = { showAddUser = true },
|
||||
onRemoveUser = { user ->
|
||||
|
|
|
|||
|
|
@ -8,13 +8,18 @@ import com.github.damontecres.wholphin.data.ServerRepository
|
|||
import com.github.damontecres.wholphin.data.model.JellyfinServer
|
||||
import com.github.damontecres.wholphin.data.model.JellyfinUser
|
||||
import com.github.damontecres.wholphin.ui.launchIO
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.nav.NavigationManager
|
||||
import com.github.damontecres.wholphin.ui.setValueOnMain
|
||||
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||
import com.github.damontecres.wholphin.util.LoadingState
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jellyfin.sdk.Jellyfin
|
||||
import org.jellyfin.sdk.api.client.HttpClientOptions
|
||||
|
|
@ -25,42 +30,41 @@ import org.jellyfin.sdk.api.client.extensions.systemApi
|
|||
import org.jellyfin.sdk.api.client.extensions.userApi
|
||||
import org.jellyfin.sdk.model.api.QuickConnectDto
|
||||
import org.jellyfin.sdk.model.api.QuickConnectResult
|
||||
import org.jellyfin.sdk.model.serializer.toUUID
|
||||
import org.jellyfin.sdk.model.serializer.toUUIDOrNull
|
||||
import timber.log.Timber
|
||||
import java.util.UUID
|
||||
import javax.inject.Inject
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
@HiltViewModel
|
||||
@HiltViewModel(assistedFactory = SwitchUserViewModel.Factory::class)
|
||||
class SwitchUserViewModel
|
||||
@Inject
|
||||
@AssistedInject
|
||||
constructor(
|
||||
val jellyfin: Jellyfin,
|
||||
val serverRepository: ServerRepository,
|
||||
val serverDao: JellyfinServerDao,
|
||||
val navigationManager: NavigationManager,
|
||||
@Assisted val server: JellyfinServer,
|
||||
) : ViewModel() {
|
||||
val servers = MutableLiveData<List<JellyfinServer>>(listOf())
|
||||
val serverStatus = MutableLiveData<Map<UUID, ServerConnectionStatus>>(mapOf())
|
||||
val serverQuickConnect = MutableLiveData<Map<UUID, Boolean>>(mapOf())
|
||||
@AssistedFactory
|
||||
interface Factory {
|
||||
fun create(server: JellyfinServer): SwitchUserViewModel
|
||||
}
|
||||
|
||||
init {
|
||||
viewModelScope.launch(Dispatchers.Main + ExceptionHandler()) {
|
||||
serverRepository.switchServerOrUser()
|
||||
}
|
||||
}
|
||||
|
||||
val serverQuickConnect = MutableLiveData<Boolean>(false)
|
||||
|
||||
val users = MutableLiveData<List<JellyfinUser>>(listOf())
|
||||
val quickConnectState = MutableLiveData<QuickConnectResult?>(null)
|
||||
|
||||
private var quickConnectJob: Job? = null
|
||||
|
||||
val discoveredServers = MutableLiveData<List<JellyfinServer>>(listOf())
|
||||
|
||||
val addServerState = MutableLiveData<LoadingState>(LoadingState.Pending)
|
||||
val switchUserState = MutableLiveData<LoadingState>(LoadingState.Pending)
|
||||
|
||||
val loginAttempts = MutableLiveData(0)
|
||||
|
||||
fun clearAddServerState() {
|
||||
addServerState.value = LoadingState.Pending
|
||||
}
|
||||
|
||||
fun clearSwitchUserState() {
|
||||
switchUserState.value = LoadingState.Pending
|
||||
}
|
||||
|
|
@ -74,91 +78,47 @@ class SwitchUserViewModel
|
|||
}
|
||||
|
||||
fun init() {
|
||||
quickConnectJob?.cancel()
|
||||
viewModelScope.launchIO {
|
||||
quickConnectJob?.cancel()
|
||||
users.setValueOnMain(listOf())
|
||||
val serverUsers =
|
||||
serverDao.getServer(server.id)?.users?.sortedBy { it.name } ?: listOf()
|
||||
withContext(Dispatchers.Main) {
|
||||
users.value = listOf()
|
||||
serverStatus.value = mapOf()
|
||||
serverQuickConnect.value = mapOf()
|
||||
}
|
||||
|
||||
val allServers =
|
||||
serverDao
|
||||
.getServers()
|
||||
.map { it.server }
|
||||
.sortedWith(compareBy<JellyfinServer> { it.name }.thenBy { it.url })
|
||||
withContext(Dispatchers.Main) {
|
||||
servers.value = allServers
|
||||
}
|
||||
allServers.forEach { server ->
|
||||
try {
|
||||
jellyfin
|
||||
.createApi(
|
||||
server.url,
|
||||
httpClientOptions =
|
||||
HttpClientOptions(
|
||||
requestTimeout = 6.seconds,
|
||||
connectTimeout = 6.seconds,
|
||||
socketTimeout = 6.seconds,
|
||||
),
|
||||
).systemApi
|
||||
.getPublicSystemInfo()
|
||||
val quickConnect by
|
||||
jellyfin
|
||||
.createApi(server.url)
|
||||
.quickConnectApi
|
||||
.getQuickConnectEnabled()
|
||||
withContext(Dispatchers.Main) {
|
||||
serverStatus.value =
|
||||
serverStatus.value!!.toMutableMap().apply {
|
||||
put(
|
||||
server.id,
|
||||
ServerConnectionStatus.Success,
|
||||
)
|
||||
}
|
||||
serverQuickConnect.value =
|
||||
serverQuickConnect.value!!.toMutableMap().apply {
|
||||
put(server.id, quickConnect)
|
||||
}
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
Timber.w(ex, "Error checking quick connect for server ${server.url}")
|
||||
withContext(Dispatchers.Main) {
|
||||
serverStatus.value =
|
||||
serverStatus.value!!.toMutableMap().apply {
|
||||
put(
|
||||
server.id,
|
||||
ServerConnectionStatus.Error(ex.localizedMessage),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
users.setValueOnMain(serverUsers)
|
||||
}
|
||||
}
|
||||
|
||||
viewModelScope.launchIO {
|
||||
serverRepository.currentServer?.let {
|
||||
val quickConnect =
|
||||
try {
|
||||
jellyfin
|
||||
.createApi(
|
||||
server.url,
|
||||
httpClientOptions =
|
||||
HttpClientOptions(
|
||||
requestTimeout = 6.seconds,
|
||||
connectTimeout = 6.seconds,
|
||||
socketTimeout = 6.seconds,
|
||||
),
|
||||
).systemApi
|
||||
.getPublicSystemInfo()
|
||||
val quickConnect by
|
||||
jellyfin
|
||||
.createApi(it.url)
|
||||
.createApi(server.url)
|
||||
.quickConnectApi
|
||||
.getQuickConnectEnabled()
|
||||
.content
|
||||
val serverUsers = serverDao.getServer(it.id)?.users?.sortedBy { it.name } ?: listOf()
|
||||
withContext(Dispatchers.Main) {
|
||||
serverQuickConnect.value =
|
||||
serverQuickConnect.value!!.toMutableMap().apply {
|
||||
put(it.id, quickConnect)
|
||||
}
|
||||
users.value = serverUsers
|
||||
serverQuickConnect.value = quickConnect
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
Timber.w(ex, "Error checking quick connect for server ${server.url}")
|
||||
withContext(Dispatchers.Main) {
|
||||
serverQuickConnect.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun switchUser(
|
||||
server: JellyfinServer,
|
||||
user: JellyfinUser,
|
||||
) {
|
||||
fun switchUser(user: JellyfinUser) {
|
||||
viewModelScope.launchIO {
|
||||
try {
|
||||
serverRepository.changeUser(server, user)
|
||||
|
|
@ -242,10 +202,7 @@ class SwitchUserViewModel
|
|||
if (ex is InvalidStatusException && ex.status == 401) {
|
||||
withContext(Dispatchers.Main) {
|
||||
quickConnectState.value = null
|
||||
serverQuickConnect.value =
|
||||
serverQuickConnect.value!!.toMutableMap().apply {
|
||||
put(server.id, false)
|
||||
}
|
||||
serverQuickConnect.value = false
|
||||
}
|
||||
}
|
||||
setError("Error with Quick Connect", ex)
|
||||
|
|
@ -258,55 +215,6 @@ class SwitchUserViewModel
|
|||
quickConnectState.value = null
|
||||
}
|
||||
|
||||
fun addServer(serverUrl: String) {
|
||||
addServerState.value = LoadingState.Loading
|
||||
viewModelScope.launchIO {
|
||||
try {
|
||||
val serverInfo by jellyfin
|
||||
.createApi(serverUrl)
|
||||
.systemApi
|
||||
.getPublicSystemInfo()
|
||||
val id = serverInfo.id?.toUUIDOrNull()
|
||||
if (id != null && serverInfo.startupWizardCompleted == true) {
|
||||
serverRepository.addAndChangeServer(
|
||||
JellyfinServer(
|
||||
id = id,
|
||||
name = serverInfo.serverName,
|
||||
url = serverUrl,
|
||||
),
|
||||
)
|
||||
val quickConnect =
|
||||
jellyfin
|
||||
.createApi(serverUrl)
|
||||
.quickConnectApi
|
||||
.getQuickConnectEnabled()
|
||||
.content
|
||||
withContext(Dispatchers.Main) {
|
||||
serverQuickConnect.value =
|
||||
serverQuickConnect.value!!.toMutableMap().apply {
|
||||
put(id, quickConnect)
|
||||
}
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
addServerState.value = LoadingState.Success
|
||||
navigationManager.navigateTo(Destination.UserList)
|
||||
}
|
||||
} else {
|
||||
withContext(Dispatchers.Main) {
|
||||
addServerState.value =
|
||||
LoadingState.Error("Server returned invalid response")
|
||||
}
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
Timber.w(ex, "Error creating API for $serverUrl")
|
||||
withContext(Dispatchers.Main) {
|
||||
addServerState.value =
|
||||
LoadingState.Error(exception = ex)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun removeUser(user: JellyfinUser) {
|
||||
viewModelScope.launchIO {
|
||||
serverRepository.removeUser(user)
|
||||
|
|
@ -318,35 +226,6 @@ class SwitchUserViewModel
|
|||
}
|
||||
}
|
||||
|
||||
fun removeServer(server: JellyfinServer) {
|
||||
viewModelScope.launchIO {
|
||||
serverRepository.removeServer(server)
|
||||
init()
|
||||
}
|
||||
}
|
||||
|
||||
fun discoverServers() {
|
||||
viewModelScope.launchIO {
|
||||
jellyfin.discovery.discoverLocalServers().collect { server ->
|
||||
val newServerList =
|
||||
discoveredServers.value!!
|
||||
.toMutableList()
|
||||
.apply {
|
||||
add(
|
||||
JellyfinServer(
|
||||
server.id.toUUID(),
|
||||
server.name,
|
||||
server.address,
|
||||
),
|
||||
)
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
discoveredServers.value = newServerList
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun setError(
|
||||
msg: String? = null,
|
||||
ex: Exception? = null,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue