mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +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
|
|
@ -12,6 +12,7 @@ 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
|
||||
|
|
@ -96,22 +97,19 @@ class MainActivity : AppCompatActivity() {
|
|||
) {
|
||||
var isRestoringSession by remember { mutableStateOf(true) }
|
||||
LaunchedEffect(Unit) {
|
||||
if (appPreferences.currentServerId.isNotBlank() && appPreferences.currentUserId.isNotBlank()) {
|
||||
try {
|
||||
serverRepository.restoreSession(
|
||||
appPreferences.currentServerId?.toUUIDOrNull(),
|
||||
appPreferences.currentUserId?.toUUIDOrNull(),
|
||||
)
|
||||
} catch (ex: Exception) {
|
||||
Timber.e(ex, "Exception restoring session")
|
||||
}
|
||||
Timber.d("MainActivity session restored")
|
||||
try {
|
||||
serverRepository.restoreSession(
|
||||
appPreferences.currentServerId?.toUUIDOrNull(),
|
||||
appPreferences.currentUserId?.toUUIDOrNull(),
|
||||
)
|
||||
} catch (ex: Exception) {
|
||||
Timber.e(ex, "Exception restoring session")
|
||||
}
|
||||
isRestoringSession = false
|
||||
}
|
||||
val server = serverRepository.currentServer
|
||||
val user = serverRepository.currentUser
|
||||
val userDto = serverRepository.currentUserDto
|
||||
val server by serverRepository.currentServer.observeAsState()
|
||||
val user by serverRepository.currentUser.observeAsState()
|
||||
val userDto by serverRepository.currentUserDto.observeAsState()
|
||||
|
||||
val preferences =
|
||||
UserPreferences(
|
||||
|
|
@ -139,8 +137,6 @@ class MainActivity : AppCompatActivity() {
|
|||
val initialDestination =
|
||||
if (server != null && user != null) {
|
||||
Destination.Home()
|
||||
} else if (server != null) {
|
||||
Destination.UserList
|
||||
} else {
|
||||
Destination.ServerList
|
||||
}
|
||||
|
|
@ -155,9 +151,6 @@ class MainActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
}
|
||||
LaunchedEffect(server, user) {
|
||||
serverEventListener.init(server, user)
|
||||
}
|
||||
ApplicationContent(
|
||||
user = user,
|
||||
server = server,
|
||||
|
|
|
|||
|
|
@ -25,7 +25,7 @@ class ItemPlaybackRepository
|
|||
itemId: UUID,
|
||||
item: BaseItem,
|
||||
): ChosenStreams? =
|
||||
serverRepository.currentUser?.let { user ->
|
||||
serverRepository.currentUser.value?.let { user ->
|
||||
val itemPlayback = itemPlaybackDao.getItem(user = user, itemId = itemId)
|
||||
if (itemPlayback != null) {
|
||||
Timber.v("Got itemPlayback for %s", itemId)
|
||||
|
|
@ -72,7 +72,7 @@ class ItemPlaybackRepository
|
|||
sourceId: UUID,
|
||||
): ItemPlayback? =
|
||||
withContext(Dispatchers.IO) {
|
||||
serverRepository.currentUser?.let { user ->
|
||||
serverRepository.currentUser.value?.let { user ->
|
||||
val itemPlayback =
|
||||
ItemPlayback(
|
||||
userId = user.rowId,
|
||||
|
|
@ -89,7 +89,7 @@ class ItemPlaybackRepository
|
|||
itemPlayback: ItemPlayback?,
|
||||
trackIndex: Int,
|
||||
type: MediaStreamType,
|
||||
) = serverRepository.currentUser?.let { user ->
|
||||
) = serverRepository.currentUser.value?.let { user ->
|
||||
var toSave =
|
||||
itemPlayback ?: ItemPlayback(
|
||||
userId = user.rowId,
|
||||
|
|
@ -113,7 +113,9 @@ class ItemPlaybackRepository
|
|||
val toSave =
|
||||
if (itemPlayback.userId < 0) {
|
||||
val userRowId =
|
||||
serverRepository.currentUser?.rowId?.takeIf { it >= 0 }
|
||||
serverRepository.currentUser.value
|
||||
?.rowId
|
||||
?.takeIf { it >= 0 }
|
||||
?: throw IllegalStateException("Trying to save an ItemPlayback without a user, but there is no current user")
|
||||
itemPlayback.copy(userId = userRowId)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ class NavDrawerItemRepository
|
|||
val user = serverRepository.currentUser
|
||||
val userViews =
|
||||
api.userViewsApi
|
||||
.getUserViews(userId = user?.id)
|
||||
.getUserViews(userId = user.value?.id)
|
||||
.content.items
|
||||
|
||||
val builtins = listOf(NavDrawerItem.Favorites)
|
||||
|
|
@ -46,7 +46,7 @@ class NavDrawerItemRepository
|
|||
}
|
||||
|
||||
suspend fun getFilteredNavDrawerItems(items: List<NavDrawerItem>): List<NavDrawerItem> {
|
||||
val user = serverRepository.currentUser
|
||||
val user = serverRepository.currentUser.value
|
||||
val navDrawerPins =
|
||||
user
|
||||
?.let {
|
||||
|
|
|
|||
|
|
@ -2,14 +2,15 @@ package com.github.damontecres.wholphin.data
|
|||
|
||||
import android.content.Context
|
||||
import android.content.SharedPreferences
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.core.content.edit
|
||||
import androidx.datastore.core.DataStore
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.map
|
||||
import com.github.damontecres.wholphin.data.model.JellyfinServer
|
||||
import com.github.damontecres.wholphin.data.model.JellyfinUser
|
||||
import com.github.damontecres.wholphin.preferences.AppPreferences
|
||||
import com.github.damontecres.wholphin.ui.setValueOnMain
|
||||
import com.github.damontecres.wholphin.ui.toServerString
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
|
|
@ -41,12 +42,12 @@ class ServerRepository
|
|||
) {
|
||||
private val sharedPreferences = getServerSharedPreferences(context)
|
||||
|
||||
private var _currentServer by mutableStateOf<JellyfinServer?>(null)
|
||||
val currentServer get() = _currentServer
|
||||
private var _currentUser by mutableStateOf<JellyfinUser?>(null)
|
||||
val currentUser get() = _currentUser
|
||||
private var _currentUserDto by mutableStateOf<UserDto?>(null)
|
||||
val currentUserDto get() = _currentUserDto
|
||||
private var _current = MutableLiveData<CurrentUser?>(null)
|
||||
val current: LiveData<CurrentUser?> = _current
|
||||
|
||||
val currentServer: LiveData<JellyfinServer?> get() = _current.map { it?.server }
|
||||
val currentUser: LiveData<JellyfinUser?> get() = _current.map { it?.user }
|
||||
val currentUserDto: LiveData<UserDto?> get() = _current.map { it?.userDto }
|
||||
|
||||
/**
|
||||
* Adds a server to the app database and updated the [ApiClient] to the server's URL
|
||||
|
|
@ -57,19 +58,8 @@ class ServerRepository
|
|||
withContext(Dispatchers.IO) {
|
||||
serverDao.addOrUpdateServer(server)
|
||||
}
|
||||
changeServer(server)
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates the [ApiClient] to the server's URL
|
||||
*
|
||||
* The current user is removed
|
||||
*/
|
||||
fun changeServer(server: JellyfinServer) {
|
||||
apiClient.update(baseUrl = server.url, accessToken = null)
|
||||
_currentServer = server
|
||||
_currentUser = null
|
||||
_currentUserDto = null
|
||||
_current.setValueOnMain(null)
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
@ -109,9 +99,7 @@ class ServerRepository
|
|||
}.build()
|
||||
}
|
||||
withContext(Dispatchers.Main) {
|
||||
_currentUserDto = userDto
|
||||
_currentServer = updatedServer
|
||||
_currentUser = updatedUser
|
||||
_current.value = CurrentUser(updatedServer, updatedUser, userDto)
|
||||
}
|
||||
sharedPreferences.edit(true) {
|
||||
putString(SERVER_URL_KEY, updatedServer.url)
|
||||
|
|
@ -127,6 +115,7 @@ class ServerRepository
|
|||
userId: UUID?,
|
||||
): Boolean {
|
||||
if (serverId == null || userId == null) {
|
||||
_current.setValueOnMain(null)
|
||||
return false
|
||||
}
|
||||
val serverAndUsers =
|
||||
|
|
@ -186,8 +175,9 @@ class ServerRepository
|
|||
|
||||
suspend fun removeUser(user: JellyfinUser) {
|
||||
if (currentUser == user) {
|
||||
_currentUser = null
|
||||
_currentUserDto = null
|
||||
withContext(Dispatchers.Main) {
|
||||
_current.value = null
|
||||
}
|
||||
userPreferencesDataStore.updateData {
|
||||
it
|
||||
.toBuilder()
|
||||
|
|
@ -204,9 +194,9 @@ class ServerRepository
|
|||
|
||||
suspend fun removeServer(server: JellyfinServer) {
|
||||
if (currentServer == server) {
|
||||
_currentServer = null
|
||||
_currentUser = null
|
||||
_currentUserDto = null
|
||||
withContext(Dispatchers.Main) {
|
||||
_current.value = null
|
||||
}
|
||||
userPreferencesDataStore.updateData {
|
||||
it
|
||||
.toBuilder()
|
||||
|
|
@ -222,6 +212,18 @@ class ServerRepository
|
|||
}
|
||||
}
|
||||
|
||||
suspend fun switchServerOrUser() {
|
||||
apiClient.update(baseUrl = null, accessToken = null)
|
||||
userPreferencesDataStore.updateData {
|
||||
it
|
||||
.toBuilder()
|
||||
.apply {
|
||||
currentServerId = ""
|
||||
currentUserId = ""
|
||||
}.build()
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun getServerSharedPreferences(context: Context): SharedPreferences =
|
||||
context.getSharedPreferences(
|
||||
|
|
@ -233,3 +235,9 @@ class ServerRepository
|
|||
const val ACCESS_TOKEN_KEY = "current.accessToken"
|
||||
}
|
||||
}
|
||||
|
||||
data class CurrentUser(
|
||||
val server: JellyfinServer,
|
||||
val user: JellyfinUser,
|
||||
val userDto: UserDto,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
@file:UseSerializers(UUIDSerializer::class)
|
||||
|
||||
package com.github.damontecres.wholphin.data.model
|
||||
|
||||
import androidx.room.ColumnInfo
|
||||
|
|
@ -8,9 +10,13 @@ import androidx.room.Index
|
|||
import androidx.room.PrimaryKey
|
||||
import androidx.room.Relation
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import kotlinx.serialization.Serializable
|
||||
import kotlinx.serialization.UseSerializers
|
||||
import org.jellyfin.sdk.model.serializer.UUIDSerializer
|
||||
import java.util.UUID
|
||||
|
||||
@Entity(tableName = "servers")
|
||||
@Serializable
|
||||
data class JellyfinServer(
|
||||
@PrimaryKey val id: UUID,
|
||||
val name: String?,
|
||||
|
|
|
|||
|
|
@ -145,7 +145,7 @@ class PlaylistCreator
|
|||
name: String,
|
||||
initialItems: List<UUID>,
|
||||
): UUID? =
|
||||
serverRepository.currentUser?.let { user ->
|
||||
serverRepository.currentUser.value?.let { user ->
|
||||
api.playlistsApi
|
||||
.createPlaylist(
|
||||
CreatePlaylistDto(
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ object AppModule {
|
|||
.addInterceptor {
|
||||
val request = it.request()
|
||||
val newRequest =
|
||||
serverRepository.currentUser?.accessToken?.let { token ->
|
||||
serverRepository.currentUser.value?.accessToken?.let { token ->
|
||||
request
|
||||
.newBuilder()
|
||||
.addHeader(
|
||||
|
|
@ -105,7 +105,7 @@ object AppModule {
|
|||
clientVersion = clientInfo.version,
|
||||
deviceId = deviceInfo.id,
|
||||
deviceName = deviceInfo.name,
|
||||
accessToken = serverRepository.currentUser?.accessToken,
|
||||
accessToken = token,
|
||||
),
|
||||
).build()
|
||||
}
|
||||
|
|
@ -149,7 +149,7 @@ object AppModule {
|
|||
appPreference: DataStore<AppPreferences>,
|
||||
@IoCoroutineScope scope: CoroutineScope,
|
||||
) = object : RememberTabManager {
|
||||
fun key(itemId: String) = "${serverRepository.currentServer?.id}_${serverRepository.currentUser?.id}_$itemId"
|
||||
fun key(itemId: String) = "${serverRepository.currentServer.value?.id}_${serverRepository.currentUser.value?.id}_$itemId"
|
||||
|
||||
override fun getRememberedTab(
|
||||
preferences: UserPreferences,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -2,13 +2,17 @@ package com.github.damontecres.wholphin.util
|
|||
|
||||
import android.content.Context
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.DefaultLifecycleObserver
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
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.hilt.IoCoroutineScope
|
||||
import com.github.damontecres.wholphin.ui.launchIO
|
||||
import com.github.damontecres.wholphin.ui.showToast
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import dagger.hilt.android.qualifiers.ActivityContext
|
||||
import dagger.hilt.android.scopes.ActivityScoped
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.launchIn
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
|
|
@ -20,24 +24,36 @@ import org.jellyfin.sdk.model.api.GeneralCommandType
|
|||
import org.jellyfin.sdk.model.api.MediaType
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@Singleton
|
||||
@ActivityScoped
|
||||
class ServerEventListener
|
||||
@Inject
|
||||
constructor(
|
||||
@param:ApplicationContext private val context: Context,
|
||||
@param:ActivityContext private val context: Context,
|
||||
private val api: ApiClient,
|
||||
@param:IoCoroutineScope private val scope: CoroutineScope,
|
||||
) {
|
||||
private val serverRepository: ServerRepository,
|
||||
) : DefaultLifecycleObserver {
|
||||
private val activity = (context as AppCompatActivity)
|
||||
|
||||
private var listenJob: Job? = null
|
||||
|
||||
init {
|
||||
activity.lifecycle.addObserver(this)
|
||||
serverRepository.current.observe(activity) {
|
||||
Timber.d("New user/server: %s", it)
|
||||
listenJob?.cancel()
|
||||
if (it != null) {
|
||||
init(it.server, it.user)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun init(
|
||||
server: JellyfinServer?,
|
||||
user: JellyfinUser?,
|
||||
) {
|
||||
if (server != null && user != null && api.baseUrl != null && api.accessToken != null) {
|
||||
scope.launchIO {
|
||||
(context as AppCompatActivity).lifecycleScope.launchIO {
|
||||
api.sessionApi.postCapabilities(
|
||||
playableMediaTypes = listOf(MediaType.VIDEO),
|
||||
supportedCommands =
|
||||
|
|
@ -53,6 +69,7 @@ class ServerEventListener
|
|||
}
|
||||
|
||||
fun setupListeners() {
|
||||
serverRepository.currentUser
|
||||
Timber.v("Subscribing to WebSocket")
|
||||
listenJob?.cancel()
|
||||
listenJob =
|
||||
|
|
@ -73,6 +90,20 @@ class ServerEventListener
|
|||
.joinToString("\n")
|
||||
showToast(context, toast, Toast.LENGTH_LONG)
|
||||
}
|
||||
}.launchIn(scope)
|
||||
}.launchIn(activity.lifecycleScope)
|
||||
}
|
||||
|
||||
override fun onResume(owner: LifecycleOwner) {
|
||||
serverRepository.current.value?.let { init(it.server, it.user) }
|
||||
}
|
||||
|
||||
override fun onPause(owner: LifecycleOwner) {
|
||||
Timber.v("Cancelling WebSocket")
|
||||
listenJob?.cancel()
|
||||
}
|
||||
|
||||
override fun onStop(owner: LifecycleOwner) {
|
||||
Timber.v("Cancelling WebSocket")
|
||||
listenJob?.cancel()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -147,6 +147,7 @@
|
|||
<string name="italic_font">Italicize font</string>
|
||||
<string name="font">Font</string>
|
||||
<string name="background">Background</string>
|
||||
<string name="success">Success</string>
|
||||
|
||||
<plurals name="downloads">
|
||||
<item quantity="zero">%s downloads</item>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue