mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 23:51:21 +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.collectAsState
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.key
|
import androidx.compose.runtime.key
|
||||||
|
import androidx.compose.runtime.livedata.observeAsState
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
|
@ -96,7 +97,6 @@ class MainActivity : AppCompatActivity() {
|
||||||
) {
|
) {
|
||||||
var isRestoringSession by remember { mutableStateOf(true) }
|
var isRestoringSession by remember { mutableStateOf(true) }
|
||||||
LaunchedEffect(Unit) {
|
LaunchedEffect(Unit) {
|
||||||
if (appPreferences.currentServerId.isNotBlank() && appPreferences.currentUserId.isNotBlank()) {
|
|
||||||
try {
|
try {
|
||||||
serverRepository.restoreSession(
|
serverRepository.restoreSession(
|
||||||
appPreferences.currentServerId?.toUUIDOrNull(),
|
appPreferences.currentServerId?.toUUIDOrNull(),
|
||||||
|
|
@ -105,13 +105,11 @@ class MainActivity : AppCompatActivity() {
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
Timber.e(ex, "Exception restoring session")
|
Timber.e(ex, "Exception restoring session")
|
||||||
}
|
}
|
||||||
Timber.d("MainActivity session restored")
|
|
||||||
}
|
|
||||||
isRestoringSession = false
|
isRestoringSession = false
|
||||||
}
|
}
|
||||||
val server = serverRepository.currentServer
|
val server by serverRepository.currentServer.observeAsState()
|
||||||
val user = serverRepository.currentUser
|
val user by serverRepository.currentUser.observeAsState()
|
||||||
val userDto = serverRepository.currentUserDto
|
val userDto by serverRepository.currentUserDto.observeAsState()
|
||||||
|
|
||||||
val preferences =
|
val preferences =
|
||||||
UserPreferences(
|
UserPreferences(
|
||||||
|
|
@ -139,8 +137,6 @@ class MainActivity : AppCompatActivity() {
|
||||||
val initialDestination =
|
val initialDestination =
|
||||||
if (server != null && user != null) {
|
if (server != null && user != null) {
|
||||||
Destination.Home()
|
Destination.Home()
|
||||||
} else if (server != null) {
|
|
||||||
Destination.UserList
|
|
||||||
} else {
|
} else {
|
||||||
Destination.ServerList
|
Destination.ServerList
|
||||||
}
|
}
|
||||||
|
|
@ -155,9 +151,6 @@ class MainActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
LaunchedEffect(server, user) {
|
|
||||||
serverEventListener.init(server, user)
|
|
||||||
}
|
|
||||||
ApplicationContent(
|
ApplicationContent(
|
||||||
user = user,
|
user = user,
|
||||||
server = server,
|
server = server,
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ class ItemPlaybackRepository
|
||||||
itemId: UUID,
|
itemId: UUID,
|
||||||
item: BaseItem,
|
item: BaseItem,
|
||||||
): ChosenStreams? =
|
): ChosenStreams? =
|
||||||
serverRepository.currentUser?.let { user ->
|
serverRepository.currentUser.value?.let { user ->
|
||||||
val itemPlayback = itemPlaybackDao.getItem(user = user, itemId = itemId)
|
val itemPlayback = itemPlaybackDao.getItem(user = user, itemId = itemId)
|
||||||
if (itemPlayback != null) {
|
if (itemPlayback != null) {
|
||||||
Timber.v("Got itemPlayback for %s", itemId)
|
Timber.v("Got itemPlayback for %s", itemId)
|
||||||
|
|
@ -72,7 +72,7 @@ class ItemPlaybackRepository
|
||||||
sourceId: UUID,
|
sourceId: UUID,
|
||||||
): ItemPlayback? =
|
): ItemPlayback? =
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
serverRepository.currentUser?.let { user ->
|
serverRepository.currentUser.value?.let { user ->
|
||||||
val itemPlayback =
|
val itemPlayback =
|
||||||
ItemPlayback(
|
ItemPlayback(
|
||||||
userId = user.rowId,
|
userId = user.rowId,
|
||||||
|
|
@ -89,7 +89,7 @@ class ItemPlaybackRepository
|
||||||
itemPlayback: ItemPlayback?,
|
itemPlayback: ItemPlayback?,
|
||||||
trackIndex: Int,
|
trackIndex: Int,
|
||||||
type: MediaStreamType,
|
type: MediaStreamType,
|
||||||
) = serverRepository.currentUser?.let { user ->
|
) = serverRepository.currentUser.value?.let { user ->
|
||||||
var toSave =
|
var toSave =
|
||||||
itemPlayback ?: ItemPlayback(
|
itemPlayback ?: ItemPlayback(
|
||||||
userId = user.rowId,
|
userId = user.rowId,
|
||||||
|
|
@ -113,7 +113,9 @@ class ItemPlaybackRepository
|
||||||
val toSave =
|
val toSave =
|
||||||
if (itemPlayback.userId < 0) {
|
if (itemPlayback.userId < 0) {
|
||||||
val userRowId =
|
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")
|
?: throw IllegalStateException("Trying to save an ItemPlayback without a user, but there is no current user")
|
||||||
itemPlayback.copy(userId = userRowId)
|
itemPlayback.copy(userId = userRowId)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ class NavDrawerItemRepository
|
||||||
val user = serverRepository.currentUser
|
val user = serverRepository.currentUser
|
||||||
val userViews =
|
val userViews =
|
||||||
api.userViewsApi
|
api.userViewsApi
|
||||||
.getUserViews(userId = user?.id)
|
.getUserViews(userId = user.value?.id)
|
||||||
.content.items
|
.content.items
|
||||||
|
|
||||||
val builtins = listOf(NavDrawerItem.Favorites)
|
val builtins = listOf(NavDrawerItem.Favorites)
|
||||||
|
|
@ -46,7 +46,7 @@ class NavDrawerItemRepository
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun getFilteredNavDrawerItems(items: List<NavDrawerItem>): List<NavDrawerItem> {
|
suspend fun getFilteredNavDrawerItems(items: List<NavDrawerItem>): List<NavDrawerItem> {
|
||||||
val user = serverRepository.currentUser
|
val user = serverRepository.currentUser.value
|
||||||
val navDrawerPins =
|
val navDrawerPins =
|
||||||
user
|
user
|
||||||
?.let {
|
?.let {
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,15 @@ package com.github.damontecres.wholphin.data
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.SharedPreferences
|
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.core.content.edit
|
||||||
import androidx.datastore.core.DataStore
|
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.JellyfinServer
|
||||||
import com.github.damontecres.wholphin.data.model.JellyfinUser
|
import com.github.damontecres.wholphin.data.model.JellyfinUser
|
||||||
import com.github.damontecres.wholphin.preferences.AppPreferences
|
import com.github.damontecres.wholphin.preferences.AppPreferences
|
||||||
|
import com.github.damontecres.wholphin.ui.setValueOnMain
|
||||||
import com.github.damontecres.wholphin.ui.toServerString
|
import com.github.damontecres.wholphin.ui.toServerString
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
|
@ -41,12 +42,12 @@ class ServerRepository
|
||||||
) {
|
) {
|
||||||
private val sharedPreferences = getServerSharedPreferences(context)
|
private val sharedPreferences = getServerSharedPreferences(context)
|
||||||
|
|
||||||
private var _currentServer by mutableStateOf<JellyfinServer?>(null)
|
private var _current = MutableLiveData<CurrentUser?>(null)
|
||||||
val currentServer get() = _currentServer
|
val current: LiveData<CurrentUser?> = _current
|
||||||
private var _currentUser by mutableStateOf<JellyfinUser?>(null)
|
|
||||||
val currentUser get() = _currentUser
|
val currentServer: LiveData<JellyfinServer?> get() = _current.map { it?.server }
|
||||||
private var _currentUserDto by mutableStateOf<UserDto?>(null)
|
val currentUser: LiveData<JellyfinUser?> get() = _current.map { it?.user }
|
||||||
val currentUserDto get() = _currentUserDto
|
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
|
* 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) {
|
withContext(Dispatchers.IO) {
|
||||||
serverDao.addOrUpdateServer(server)
|
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)
|
apiClient.update(baseUrl = server.url, accessToken = null)
|
||||||
_currentServer = server
|
_current.setValueOnMain(null)
|
||||||
_currentUser = null
|
|
||||||
_currentUserDto = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -109,9 +99,7 @@ class ServerRepository
|
||||||
}.build()
|
}.build()
|
||||||
}
|
}
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
_currentUserDto = userDto
|
_current.value = CurrentUser(updatedServer, updatedUser, userDto)
|
||||||
_currentServer = updatedServer
|
|
||||||
_currentUser = updatedUser
|
|
||||||
}
|
}
|
||||||
sharedPreferences.edit(true) {
|
sharedPreferences.edit(true) {
|
||||||
putString(SERVER_URL_KEY, updatedServer.url)
|
putString(SERVER_URL_KEY, updatedServer.url)
|
||||||
|
|
@ -127,6 +115,7 @@ class ServerRepository
|
||||||
userId: UUID?,
|
userId: UUID?,
|
||||||
): Boolean {
|
): Boolean {
|
||||||
if (serverId == null || userId == null) {
|
if (serverId == null || userId == null) {
|
||||||
|
_current.setValueOnMain(null)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
val serverAndUsers =
|
val serverAndUsers =
|
||||||
|
|
@ -186,8 +175,9 @@ class ServerRepository
|
||||||
|
|
||||||
suspend fun removeUser(user: JellyfinUser) {
|
suspend fun removeUser(user: JellyfinUser) {
|
||||||
if (currentUser == user) {
|
if (currentUser == user) {
|
||||||
_currentUser = null
|
withContext(Dispatchers.Main) {
|
||||||
_currentUserDto = null
|
_current.value = null
|
||||||
|
}
|
||||||
userPreferencesDataStore.updateData {
|
userPreferencesDataStore.updateData {
|
||||||
it
|
it
|
||||||
.toBuilder()
|
.toBuilder()
|
||||||
|
|
@ -204,9 +194,9 @@ class ServerRepository
|
||||||
|
|
||||||
suspend fun removeServer(server: JellyfinServer) {
|
suspend fun removeServer(server: JellyfinServer) {
|
||||||
if (currentServer == server) {
|
if (currentServer == server) {
|
||||||
_currentServer = null
|
withContext(Dispatchers.Main) {
|
||||||
_currentUser = null
|
_current.value = null
|
||||||
_currentUserDto = null
|
}
|
||||||
userPreferencesDataStore.updateData {
|
userPreferencesDataStore.updateData {
|
||||||
it
|
it
|
||||||
.toBuilder()
|
.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 {
|
companion object {
|
||||||
fun getServerSharedPreferences(context: Context): SharedPreferences =
|
fun getServerSharedPreferences(context: Context): SharedPreferences =
|
||||||
context.getSharedPreferences(
|
context.getSharedPreferences(
|
||||||
|
|
@ -233,3 +235,9 @@ class ServerRepository
|
||||||
const val ACCESS_TOKEN_KEY = "current.accessToken"
|
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
|
package com.github.damontecres.wholphin.data.model
|
||||||
|
|
||||||
import androidx.room.ColumnInfo
|
import androidx.room.ColumnInfo
|
||||||
|
|
@ -8,9 +10,13 @@ import androidx.room.Index
|
||||||
import androidx.room.PrimaryKey
|
import androidx.room.PrimaryKey
|
||||||
import androidx.room.Relation
|
import androidx.room.Relation
|
||||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
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
|
import java.util.UUID
|
||||||
|
|
||||||
@Entity(tableName = "servers")
|
@Entity(tableName = "servers")
|
||||||
|
@Serializable
|
||||||
data class JellyfinServer(
|
data class JellyfinServer(
|
||||||
@PrimaryKey val id: UUID,
|
@PrimaryKey val id: UUID,
|
||||||
val name: String?,
|
val name: String?,
|
||||||
|
|
|
||||||
|
|
@ -145,7 +145,7 @@ class PlaylistCreator
|
||||||
name: String,
|
name: String,
|
||||||
initialItems: List<UUID>,
|
initialItems: List<UUID>,
|
||||||
): UUID? =
|
): UUID? =
|
||||||
serverRepository.currentUser?.let { user ->
|
serverRepository.currentUser.value?.let { user ->
|
||||||
api.playlistsApi
|
api.playlistsApi
|
||||||
.createPlaylist(
|
.createPlaylist(
|
||||||
CreatePlaylistDto(
|
CreatePlaylistDto(
|
||||||
|
|
|
||||||
|
|
@ -95,7 +95,7 @@ object AppModule {
|
||||||
.addInterceptor {
|
.addInterceptor {
|
||||||
val request = it.request()
|
val request = it.request()
|
||||||
val newRequest =
|
val newRequest =
|
||||||
serverRepository.currentUser?.accessToken?.let { token ->
|
serverRepository.currentUser.value?.accessToken?.let { token ->
|
||||||
request
|
request
|
||||||
.newBuilder()
|
.newBuilder()
|
||||||
.addHeader(
|
.addHeader(
|
||||||
|
|
@ -105,7 +105,7 @@ object AppModule {
|
||||||
clientVersion = clientInfo.version,
|
clientVersion = clientInfo.version,
|
||||||
deviceId = deviceInfo.id,
|
deviceId = deviceInfo.id,
|
||||||
deviceName = deviceInfo.name,
|
deviceName = deviceInfo.name,
|
||||||
accessToken = serverRepository.currentUser?.accessToken,
|
accessToken = token,
|
||||||
),
|
),
|
||||||
).build()
|
).build()
|
||||||
}
|
}
|
||||||
|
|
@ -149,7 +149,7 @@ object AppModule {
|
||||||
appPreference: DataStore<AppPreferences>,
|
appPreference: DataStore<AppPreferences>,
|
||||||
@IoCoroutineScope scope: CoroutineScope,
|
@IoCoroutineScope scope: CoroutineScope,
|
||||||
) = object : RememberTabManager {
|
) = 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(
|
override fun getRememberedTab(
|
||||||
preferences: UserPreferences,
|
preferences: UserPreferences,
|
||||||
|
|
|
||||||
|
|
@ -108,7 +108,7 @@ class CollectionFolderViewModel
|
||||||
|
|
||||||
val sortAndDirection =
|
val sortAndDirection =
|
||||||
if (initialSortAndDirection == null) {
|
if (initialSortAndDirection == null) {
|
||||||
serverRepository.currentUser?.let { user ->
|
serverRepository.currentUser.value?.let { user ->
|
||||||
libraryDisplayInfoDao.getItem(user, itemId)?.sortAndDirection
|
libraryDisplayInfoDao.getItem(user, itemId)?.sortAndDirection
|
||||||
} ?: SortAndDirection.DEFAULT
|
} ?: SortAndDirection.DEFAULT
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -123,7 +123,7 @@ class CollectionFolderViewModel
|
||||||
recursive: Boolean,
|
recursive: Boolean,
|
||||||
filter: GetItemsFilter,
|
filter: GetItemsFilter,
|
||||||
) {
|
) {
|
||||||
serverRepository.currentUser?.let { user ->
|
serverRepository.currentUser.value?.let { user ->
|
||||||
viewModelScope.launch(Dispatchers.IO) {
|
viewModelScope.launch(Dispatchers.IO) {
|
||||||
val libraryDisplayInfo =
|
val libraryDisplayInfo =
|
||||||
LibraryDisplayInfo(
|
LibraryDisplayInfo(
|
||||||
|
|
|
||||||
|
|
@ -133,7 +133,7 @@ class RecommendedMovieViewModel
|
||||||
|
|
||||||
val suggestionsRequest =
|
val suggestionsRequest =
|
||||||
GetSuggestionsRequest(
|
GetSuggestionsRequest(
|
||||||
userId = serverRepository.currentUser?.id,
|
userId = serverRepository.currentUser.value?.id,
|
||||||
type = listOf(BaseItemKind.MOVIE),
|
type = listOf(BaseItemKind.MOVIE),
|
||||||
)
|
)
|
||||||
val suggestedItems = ApiRequestPager(api, suggestionsRequest, GetSuggestionsRequestHandler, viewModelScope)
|
val suggestedItems = ApiRequestPager(api, suggestionsRequest, GetSuggestionsRequestHandler, viewModelScope)
|
||||||
|
|
|
||||||
|
|
@ -161,7 +161,7 @@ class RecommendedTvShowViewModel
|
||||||
|
|
||||||
val suggestionsRequest =
|
val suggestionsRequest =
|
||||||
GetSuggestionsRequest(
|
GetSuggestionsRequest(
|
||||||
userId = serverRepository.currentUser?.id,
|
userId = serverRepository.currentUser.value?.id,
|
||||||
type = listOf(BaseItemKind.SERIES),
|
type = listOf(BaseItemKind.SERIES),
|
||||||
)
|
)
|
||||||
val suggestedItems = ApiRequestPager(api, suggestionsRequest, GetSuggestionsRequestHandler, viewModelScope)
|
val suggestedItems = ApiRequestPager(api, suggestionsRequest, GetSuggestionsRequestHandler, viewModelScope)
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,7 @@ class LiveTvCollectionViewModel
|
||||||
viewModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
val folders =
|
val folders =
|
||||||
api.liveTvApi
|
api.liveTvApi
|
||||||
.getRecordingFolders(userId = serverRepository.currentUser?.id)
|
.getRecordingFolders(userId = serverRepository.currentUser.value?.id)
|
||||||
.content.items
|
.content.items
|
||||||
.map { TabId(it.name ?: "Recordings", it.id) }
|
.map { TabId(it.name ?: "Recordings", it.id) }
|
||||||
this@LiveTvCollectionViewModel.recordingFolders.setValueOnMain(folders)
|
this@LiveTvCollectionViewModel.recordingFolders.setValueOnMain(folders)
|
||||||
|
|
|
||||||
|
|
@ -61,7 +61,7 @@ class DebugViewModel
|
||||||
|
|
||||||
init {
|
init {
|
||||||
viewModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
serverRepository.currentUser?.rowId?.let {
|
serverRepository.currentUser.value?.rowId?.let {
|
||||||
val results = itemPlaybackDao.getItems(it)
|
val results = itemPlaybackDao.getItems(it)
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
itemPlaybacks.value = results
|
itemPlaybacks.value = results
|
||||||
|
|
|
||||||
|
|
@ -303,7 +303,7 @@ fun MovieDetails(
|
||||||
ItemDetailsDialog(
|
ItemDetailsDialog(
|
||||||
info = info,
|
info = info,
|
||||||
showFilePath =
|
showFilePath =
|
||||||
viewModel.serverRepository.currentUserDto
|
viewModel.serverRepository.currentUserDto.value
|
||||||
?.policy
|
?.policy
|
||||||
?.isAdministrator == true,
|
?.isAdministrator == true,
|
||||||
onDismissRequest = { overviewDialog = null },
|
onDismissRequest = { overviewDialog = null },
|
||||||
|
|
|
||||||
|
|
@ -147,7 +147,7 @@ class MovieViewModel
|
||||||
api.libraryApi
|
api.libraryApi
|
||||||
.getSimilarItems(
|
.getSimilarItems(
|
||||||
GetSimilarItemsRequest(
|
GetSimilarItemsRequest(
|
||||||
userId = serverRepository.currentUser?.id,
|
userId = serverRepository.currentUser.value?.id,
|
||||||
itemId = itemId,
|
itemId = itemId,
|
||||||
fields = SlimItemFields,
|
fields = SlimItemFields,
|
||||||
limit = 25,
|
limit = 25,
|
||||||
|
|
|
||||||
|
|
@ -346,7 +346,7 @@ fun SeriesOverview(
|
||||||
ItemDetailsDialog(
|
ItemDetailsDialog(
|
||||||
info = info,
|
info = info,
|
||||||
showFilePath =
|
showFilePath =
|
||||||
viewModel.serverRepository.currentUserDto
|
viewModel.serverRepository.currentUserDto.value
|
||||||
?.policy
|
?.policy
|
||||||
?.isAdministrator == true,
|
?.isAdministrator == true,
|
||||||
onDismissRequest = { overviewDialog = null },
|
onDismissRequest = { overviewDialog = null },
|
||||||
|
|
|
||||||
|
|
@ -121,7 +121,7 @@ class SeriesViewModel
|
||||||
api.libraryApi
|
api.libraryApi
|
||||||
.getSimilarItems(
|
.getSimilarItems(
|
||||||
GetSimilarItemsRequest(
|
GetSimilarItemsRequest(
|
||||||
userId = serverRepository.currentUser?.id,
|
userId = serverRepository.currentUser.value?.id,
|
||||||
itemId = itemId,
|
itemId = itemId,
|
||||||
fields = SlimItemFields,
|
fields = SlimItemFields,
|
||||||
limit = 25,
|
limit = 25,
|
||||||
|
|
|
||||||
|
|
@ -77,7 +77,7 @@ class HomeViewModel
|
||||||
) {
|
) {
|
||||||
Timber.d("init HomeViewModel")
|
Timber.d("init HomeViewModel")
|
||||||
|
|
||||||
serverRepository.currentUserDto?.let { userDto ->
|
serverRepository.currentUserDto.value?.let { userDto ->
|
||||||
val includedIds =
|
val includedIds =
|
||||||
navDrawerItemRepository
|
navDrawerItemRepository
|
||||||
.getFilteredNavDrawerItems(navDrawerItemRepository.getNavDrawerItems())
|
.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.JellyfinServer
|
||||||
import com.github.damontecres.wholphin.data.model.JellyfinUser
|
import com.github.damontecres.wholphin.data.model.JellyfinUser
|
||||||
import com.github.damontecres.wholphin.preferences.UserPreferences
|
import com.github.damontecres.wholphin.preferences.UserPreferences
|
||||||
|
import com.github.damontecres.wholphin.ui.components.ErrorMessage
|
||||||
import org.jellyfin.sdk.model.api.DeviceProfile
|
import org.jellyfin.sdk.model.api.DeviceProfile
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -45,7 +46,7 @@ fun ApplicationContent(
|
||||||
deviceProfile = deviceProfile,
|
deviceProfile = deviceProfile,
|
||||||
modifier = modifier.fillMaxSize(),
|
modifier = modifier.fillMaxSize(),
|
||||||
)
|
)
|
||||||
} else {
|
} else if (user != null && server != null) {
|
||||||
NavDrawer(
|
NavDrawer(
|
||||||
destination = key,
|
destination = key,
|
||||||
preferences = preferences,
|
preferences = preferences,
|
||||||
|
|
@ -54,6 +55,8 @@ fun ApplicationContent(
|
||||||
server = server,
|
server = server,
|
||||||
modifier = modifier,
|
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.BaseItem
|
||||||
import com.github.damontecres.wholphin.data.model.GetItemsFilter
|
import com.github.damontecres.wholphin.data.model.GetItemsFilter
|
||||||
import com.github.damontecres.wholphin.data.model.ItemPlayback
|
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.SeasonEpisode
|
||||||
import com.github.damontecres.wholphin.ui.detail.series.SeasonEpisodeIds
|
import com.github.damontecres.wholphin.ui.detail.series.SeasonEpisodeIds
|
||||||
import com.github.damontecres.wholphin.ui.preferences.PreferenceScreenOption
|
import com.github.damontecres.wholphin.ui.preferences.PreferenceScreenOption
|
||||||
|
|
@ -29,7 +30,9 @@ sealed class Destination(
|
||||||
data object ServerList : Destination(true)
|
data object ServerList : Destination(true)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data object UserList : Destination(true)
|
data class UserList(
|
||||||
|
val server: JellyfinServer,
|
||||||
|
) : Destination(true)
|
||||||
|
|
||||||
@Serializable
|
@Serializable
|
||||||
data class Home(
|
data class Home(
|
||||||
|
|
|
||||||
|
|
@ -54,7 +54,7 @@ fun DestinationContent(
|
||||||
)
|
)
|
||||||
|
|
||||||
Destination.ServerList -> SwitchServerContent(modifier)
|
Destination.ServerList -> SwitchServerContent(modifier)
|
||||||
Destination.UserList -> SwitchUserContent(modifier)
|
is Destination.UserList -> SwitchUserContent(destination.server, modifier)
|
||||||
|
|
||||||
is Destination.Settings ->
|
is Destination.Settings ->
|
||||||
PreferencesPage(
|
PreferencesPage(
|
||||||
|
|
|
||||||
|
|
@ -84,7 +84,6 @@ import kotlinx.coroutines.isActive
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.jellyfin.sdk.model.api.CollectionType
|
import org.jellyfin.sdk.model.api.CollectionType
|
||||||
import org.jellyfin.sdk.model.api.DeviceProfile
|
import org.jellyfin.sdk.model.api.DeviceProfile
|
||||||
import timber.log.Timber
|
|
||||||
import java.time.LocalTime
|
import java.time.LocalTime
|
||||||
import java.util.UUID
|
import java.util.UUID
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
@ -141,7 +140,7 @@ class NavDrawerViewModel
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Timber.v("Found $index => $key")
|
// Timber.v("Found $index => $key")
|
||||||
if (index != null) {
|
if (index != null) {
|
||||||
selectedIndex.setValueOnMain(index)
|
selectedIndex.setValueOnMain(index)
|
||||||
break
|
break
|
||||||
|
|
@ -198,8 +197,8 @@ data class ServerNavDrawerItem(
|
||||||
fun NavDrawer(
|
fun NavDrawer(
|
||||||
destination: Destination,
|
destination: Destination,
|
||||||
preferences: UserPreferences,
|
preferences: UserPreferences,
|
||||||
user: JellyfinUser?,
|
user: JellyfinUser,
|
||||||
server: JellyfinServer?,
|
server: JellyfinServer,
|
||||||
deviceProfile: DeviceProfile,
|
deviceProfile: DeviceProfile,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
viewModel: NavDrawerViewModel =
|
viewModel: NavDrawerViewModel =
|
||||||
|
|
@ -319,7 +318,11 @@ fun NavDrawer(
|
||||||
selected = false,
|
selected = false,
|
||||||
interactionSource = interactionSource,
|
interactionSource = interactionSource,
|
||||||
onClick = {
|
onClick = {
|
||||||
viewModel.navigationManager.navigateTo(Destination.UserList)
|
viewModel.navigationManager.navigateToFromDrawer(
|
||||||
|
Destination.UserList(
|
||||||
|
server,
|
||||||
|
),
|
||||||
|
)
|
||||||
},
|
},
|
||||||
modifier = Modifier.animateItem(),
|
modifier = Modifier.animateItem(),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,12 @@ class NavigationManager
|
||||||
*/
|
*/
|
||||||
fun navigateToFromDrawer(destination: Destination) {
|
fun navigateToFromDrawer(destination: Destination) {
|
||||||
goToHome()
|
goToHome()
|
||||||
|
if (destination == Destination.ServerList || destination is Destination.UserList) {
|
||||||
backStack.add(destination)
|
backStack.add(destination)
|
||||||
|
(0..<backStack.size - 1).forEach { _ -> backStack.removeAt(0) }
|
||||||
|
} else {
|
||||||
|
backStack.add(destination)
|
||||||
|
}
|
||||||
log()
|
log()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -268,7 +268,7 @@ class PlaybackViewModel
|
||||||
if (itemPlayback != null) {
|
if (itemPlayback != null) {
|
||||||
itemPlayback
|
itemPlayback
|
||||||
} else {
|
} else {
|
||||||
val user = serverRepository.currentUser!!
|
serverRepository.currentUser.value?.let { user ->
|
||||||
itemPlaybackDao.getItem(user, base.id)?.let {
|
itemPlaybackDao.getItem(user, base.id)?.let {
|
||||||
Timber.v("Fetched itemPlayback from DB: %s", it)
|
Timber.v("Fetched itemPlayback from DB: %s", it)
|
||||||
if (it.sourceId != null) {
|
if (it.sourceId != null) {
|
||||||
|
|
@ -278,6 +278,7 @@ class PlaybackViewModel
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
val mediaSource = chooseSource(base, playbackConfig)
|
val mediaSource = chooseSource(base, playbackConfig)
|
||||||
|
|
||||||
if (mediaSource == null) {
|
if (mediaSource == null) {
|
||||||
|
|
|
||||||
|
|
@ -49,7 +49,7 @@ class PreferencesViewModel
|
||||||
|
|
||||||
init {
|
init {
|
||||||
viewModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
serverRepository.currentUser?.let { user ->
|
serverRepository.currentUser.value?.let { user ->
|
||||||
allNavDrawerItems = navDrawerItemRepository.getNavDrawerItems()
|
allNavDrawerItems = navDrawerItemRepository.getNavDrawerItems()
|
||||||
val pins = serverPreferencesDao.getNavDrawerPinnedItems(user)
|
val pins = serverPreferencesDao.getNavDrawerPinnedItems(user)
|
||||||
val navDrawerPins = allNavDrawerItems.associateWith { pins.isPinned(it.id) }
|
val navDrawerPins = allNavDrawerItems.associateWith { pins.isPinned(it.id) }
|
||||||
|
|
@ -60,7 +60,7 @@ class PreferencesViewModel
|
||||||
|
|
||||||
fun updatePins(newSelectedItems: List<NavDrawerItem>) {
|
fun updatePins(newSelectedItems: List<NavDrawerItem>) {
|
||||||
viewModelScope.launchIO(ExceptionHandler(true)) {
|
viewModelScope.launchIO(ExceptionHandler(true)) {
|
||||||
serverRepository.currentUser?.let { user ->
|
serverRepository.currentUser.value?.let { user ->
|
||||||
val disabledItems =
|
val disabledItems =
|
||||||
mutableListOf<NavDrawerItem>().apply {
|
mutableListOf<NavDrawerItem>().apply {
|
||||||
addAll(allNavDrawerItems)
|
addAll(allNavDrawerItems)
|
||||||
|
|
|
||||||
|
|
@ -49,6 +49,7 @@ fun ServerList(
|
||||||
servers: List<JellyfinServer>,
|
servers: List<JellyfinServer>,
|
||||||
connectionStatus: Map<UUID, ServerConnectionStatus>,
|
connectionStatus: Map<UUID, ServerConnectionStatus>,
|
||||||
onSwitchServer: (JellyfinServer) -> Unit,
|
onSwitchServer: (JellyfinServer) -> Unit,
|
||||||
|
onTestServer: (JellyfinServer) -> Unit,
|
||||||
onAddServer: () -> Unit,
|
onAddServer: () -> Unit,
|
||||||
onRemoveServer: (JellyfinServer) -> Unit,
|
onRemoveServer: (JellyfinServer) -> Unit,
|
||||||
allowAdd: Boolean,
|
allowAdd: Boolean,
|
||||||
|
|
@ -61,7 +62,7 @@ fun ServerList(
|
||||||
items(servers) { server ->
|
items(servers) { server ->
|
||||||
val status = connectionStatus[server.id] ?: ServerConnectionStatus.Pending
|
val status = connectionStatus[server.id] ?: ServerConnectionStatus.Pending
|
||||||
ListItem(
|
ListItem(
|
||||||
enabled = status == ServerConnectionStatus.Success,
|
enabled = true,
|
||||||
selected = false,
|
selected = false,
|
||||||
headlineContent = { Text(text = server.name?.ifBlank { null } ?: server.url) },
|
headlineContent = { Text(text = server.name?.ifBlank { null } ?: server.url) },
|
||||||
supportingContent = { if (server.name.isNotNullOrBlank()) Text(text = server.url) },
|
supportingContent = { if (server.name.isNotNullOrBlank()) Text(text = server.url) },
|
||||||
|
|
@ -77,12 +78,18 @@ fun ServerList(
|
||||||
Icon(
|
Icon(
|
||||||
imageVector = Icons.Default.Warning,
|
imageVector = Icons.Default.Warning,
|
||||||
contentDescription = status.message,
|
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 = {
|
onLongClick = {
|
||||||
if (allowDelete) {
|
if (allowDelete) {
|
||||||
showDeleteDialog = server
|
showDeleteDialog = server
|
||||||
|
|
|
||||||
|
|
@ -43,9 +43,8 @@ import com.github.damontecres.wholphin.util.LoadingState
|
||||||
@Composable
|
@Composable
|
||||||
fun SwitchServerContent(
|
fun SwitchServerContent(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
viewModel: SwitchUserViewModel = hiltViewModel(),
|
viewModel: SwitchServerViewModel = hiltViewModel(),
|
||||||
) {
|
) {
|
||||||
val currentServer = viewModel.serverRepository.currentServer
|
|
||||||
val servers by viewModel.servers.observeAsState(listOf())
|
val servers by viewModel.servers.observeAsState(listOf())
|
||||||
val serverStatus by viewModel.serverStatus.observeAsState(mapOf())
|
val serverStatus by viewModel.serverStatus.observeAsState(mapOf())
|
||||||
|
|
||||||
|
|
@ -90,6 +89,9 @@ fun SwitchServerContent(
|
||||||
onSwitchServer = {
|
onSwitchServer = {
|
||||||
viewModel.addServer(it.url)
|
viewModel.addServer(it.url)
|
||||||
},
|
},
|
||||||
|
onTestServer = {
|
||||||
|
viewModel.testServer(it)
|
||||||
|
},
|
||||||
onAddServer = {
|
onAddServer = {
|
||||||
showAddServer = true
|
showAddServer = true
|
||||||
},
|
},
|
||||||
|
|
@ -142,6 +144,9 @@ fun SwitchServerContent(
|
||||||
onSwitchServer = {
|
onSwitchServer = {
|
||||||
viewModel.addServer(it.url)
|
viewModel.addServer(it.url)
|
||||||
},
|
},
|
||||||
|
onTestServer = {
|
||||||
|
viewModel.testServer(it)
|
||||||
|
},
|
||||||
onAddServer = {},
|
onAddServer = {},
|
||||||
onRemoveServer = {},
|
onRemoveServer = {},
|
||||||
allowAdd = false,
|
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.Text
|
||||||
import androidx.tv.material3.surfaceColorAtElevation
|
import androidx.tv.material3.surfaceColorAtElevation
|
||||||
import com.github.damontecres.wholphin.R
|
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.BasicDialog
|
||||||
import com.github.damontecres.wholphin.ui.components.CircularProgress
|
import com.github.damontecres.wholphin.ui.components.CircularProgress
|
||||||
import com.github.damontecres.wholphin.ui.components.EditTextBox
|
import com.github.damontecres.wholphin.ui.components.EditTextBox
|
||||||
|
|
@ -49,17 +50,20 @@ import com.github.damontecres.wholphin.util.LoadingState
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun SwitchUserContent(
|
fun SwitchUserContent(
|
||||||
|
currentServer: JellyfinServer,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
viewModel: SwitchUserViewModel = hiltViewModel(),
|
viewModel: SwitchUserViewModel =
|
||||||
|
hiltViewModel<SwitchUserViewModel, SwitchUserViewModel.Factory>(
|
||||||
|
creationCallback = { it.create(currentServer) },
|
||||||
|
),
|
||||||
) {
|
) {
|
||||||
val context = LocalContext.current
|
val context = LocalContext.current
|
||||||
|
|
||||||
val currentServer = viewModel.serverRepository.currentServer
|
// val currentServer by viewModel.serverRepository.currentServer.observeAsState()
|
||||||
val currentUser = viewModel.serverRepository.currentUser
|
val currentUser by viewModel.serverRepository.currentUser.observeAsState()
|
||||||
val users by viewModel.users.observeAsState(listOf())
|
val users by viewModel.users.observeAsState(listOf())
|
||||||
|
|
||||||
val serverQuickConnect by viewModel.serverQuickConnect.observeAsState(mapOf())
|
val quickConnectEnabled by viewModel.serverQuickConnect.observeAsState(false)
|
||||||
val quickConnectEnabled = currentServer?.let { serverQuickConnect[it.id] ?: false } ?: false
|
|
||||||
val quickConnect by viewModel.quickConnectState.observeAsState(null)
|
val quickConnect by viewModel.quickConnectState.observeAsState(null)
|
||||||
var showAddUser by remember { mutableStateOf(false) }
|
var showAddUser by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
|
|
@ -109,7 +113,7 @@ fun SwitchUserContent(
|
||||||
users = users,
|
users = users,
|
||||||
currentUser = currentUser,
|
currentUser = currentUser,
|
||||||
onSwitchUser = { user ->
|
onSwitchUser = { user ->
|
||||||
viewModel.switchUser(server, user)
|
viewModel.switchUser(user)
|
||||||
},
|
},
|
||||||
onAddUser = { showAddUser = true },
|
onAddUser = { showAddUser = true },
|
||||||
onRemoveUser = { user ->
|
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.JellyfinServer
|
||||||
import com.github.damontecres.wholphin.data.model.JellyfinUser
|
import com.github.damontecres.wholphin.data.model.JellyfinUser
|
||||||
import com.github.damontecres.wholphin.ui.launchIO
|
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.nav.NavigationManager
|
||||||
|
import com.github.damontecres.wholphin.ui.setValueOnMain
|
||||||
|
import com.github.damontecres.wholphin.util.ExceptionHandler
|
||||||
import com.github.damontecres.wholphin.util.LoadingState
|
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 dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import org.jellyfin.sdk.Jellyfin
|
import org.jellyfin.sdk.Jellyfin
|
||||||
import org.jellyfin.sdk.api.client.HttpClientOptions
|
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.api.client.extensions.userApi
|
||||||
import org.jellyfin.sdk.model.api.QuickConnectDto
|
import org.jellyfin.sdk.model.api.QuickConnectDto
|
||||||
import org.jellyfin.sdk.model.api.QuickConnectResult
|
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 timber.log.Timber
|
||||||
import java.util.UUID
|
|
||||||
import javax.inject.Inject
|
|
||||||
import kotlin.time.Duration.Companion.seconds
|
import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
@HiltViewModel
|
@HiltViewModel(assistedFactory = SwitchUserViewModel.Factory::class)
|
||||||
class SwitchUserViewModel
|
class SwitchUserViewModel
|
||||||
@Inject
|
@AssistedInject
|
||||||
constructor(
|
constructor(
|
||||||
val jellyfin: Jellyfin,
|
val jellyfin: Jellyfin,
|
||||||
val serverRepository: ServerRepository,
|
val serverRepository: ServerRepository,
|
||||||
val serverDao: JellyfinServerDao,
|
val serverDao: JellyfinServerDao,
|
||||||
val navigationManager: NavigationManager,
|
val navigationManager: NavigationManager,
|
||||||
|
@Assisted val server: JellyfinServer,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
val servers = MutableLiveData<List<JellyfinServer>>(listOf())
|
@AssistedFactory
|
||||||
val serverStatus = MutableLiveData<Map<UUID, ServerConnectionStatus>>(mapOf())
|
interface Factory {
|
||||||
val serverQuickConnect = MutableLiveData<Map<UUID, Boolean>>(mapOf())
|
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 users = MutableLiveData<List<JellyfinUser>>(listOf())
|
||||||
val quickConnectState = MutableLiveData<QuickConnectResult?>(null)
|
val quickConnectState = MutableLiveData<QuickConnectResult?>(null)
|
||||||
|
|
||||||
private var quickConnectJob: Job? = 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 switchUserState = MutableLiveData<LoadingState>(LoadingState.Pending)
|
||||||
|
|
||||||
val loginAttempts = MutableLiveData(0)
|
val loginAttempts = MutableLiveData(0)
|
||||||
|
|
||||||
fun clearAddServerState() {
|
|
||||||
addServerState.value = LoadingState.Pending
|
|
||||||
}
|
|
||||||
|
|
||||||
fun clearSwitchUserState() {
|
fun clearSwitchUserState() {
|
||||||
switchUserState.value = LoadingState.Pending
|
switchUserState.value = LoadingState.Pending
|
||||||
}
|
}
|
||||||
|
|
@ -74,23 +78,17 @@ class SwitchUserViewModel
|
||||||
}
|
}
|
||||||
|
|
||||||
fun init() {
|
fun init() {
|
||||||
viewModelScope.launchIO {
|
|
||||||
quickConnectJob?.cancel()
|
quickConnectJob?.cancel()
|
||||||
|
viewModelScope.launchIO {
|
||||||
|
users.setValueOnMain(listOf())
|
||||||
|
val serverUsers =
|
||||||
|
serverDao.getServer(server.id)?.users?.sortedBy { it.name } ?: listOf()
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
users.value = listOf()
|
users.setValueOnMain(serverUsers)
|
||||||
serverStatus.value = mapOf()
|
}
|
||||||
serverQuickConnect.value = mapOf()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val allServers =
|
viewModelScope.launchIO {
|
||||||
serverDao
|
|
||||||
.getServers()
|
|
||||||
.map { it.server }
|
|
||||||
.sortedWith(compareBy<JellyfinServer> { it.name }.thenBy { it.url })
|
|
||||||
withContext(Dispatchers.Main) {
|
|
||||||
servers.value = allServers
|
|
||||||
}
|
|
||||||
allServers.forEach { server ->
|
|
||||||
try {
|
try {
|
||||||
jellyfin
|
jellyfin
|
||||||
.createApi(
|
.createApi(
|
||||||
|
|
@ -109,56 +107,18 @@ class SwitchUserViewModel
|
||||||
.quickConnectApi
|
.quickConnectApi
|
||||||
.getQuickConnectEnabled()
|
.getQuickConnectEnabled()
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
serverStatus.value =
|
serverQuickConnect.value = quickConnect
|
||||||
serverStatus.value!!.toMutableMap().apply {
|
|
||||||
put(
|
|
||||||
server.id,
|
|
||||||
ServerConnectionStatus.Success,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
serverQuickConnect.value =
|
|
||||||
serverQuickConnect.value!!.toMutableMap().apply {
|
|
||||||
put(server.id, quickConnect)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (ex: Exception) {
|
} catch (ex: Exception) {
|
||||||
Timber.w(ex, "Error checking quick connect for server ${server.url}")
|
Timber.w(ex, "Error checking quick connect for server ${server.url}")
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
serverStatus.value =
|
serverQuickConnect.value = false
|
||||||
serverStatus.value!!.toMutableMap().apply {
|
|
||||||
put(
|
|
||||||
server.id,
|
|
||||||
ServerConnectionStatus.Error(ex.localizedMessage),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
viewModelScope.launchIO {
|
|
||||||
serverRepository.currentServer?.let {
|
|
||||||
val quickConnect =
|
|
||||||
jellyfin
|
|
||||||
.createApi(it.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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun switchUser(
|
fun switchUser(user: JellyfinUser) {
|
||||||
server: JellyfinServer,
|
|
||||||
user: JellyfinUser,
|
|
||||||
) {
|
|
||||||
viewModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
try {
|
try {
|
||||||
serverRepository.changeUser(server, user)
|
serverRepository.changeUser(server, user)
|
||||||
|
|
@ -242,10 +202,7 @@ class SwitchUserViewModel
|
||||||
if (ex is InvalidStatusException && ex.status == 401) {
|
if (ex is InvalidStatusException && ex.status == 401) {
|
||||||
withContext(Dispatchers.Main) {
|
withContext(Dispatchers.Main) {
|
||||||
quickConnectState.value = null
|
quickConnectState.value = null
|
||||||
serverQuickConnect.value =
|
serverQuickConnect.value = false
|
||||||
serverQuickConnect.value!!.toMutableMap().apply {
|
|
||||||
put(server.id, false)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
setError("Error with Quick Connect", ex)
|
setError("Error with Quick Connect", ex)
|
||||||
|
|
@ -258,55 +215,6 @@ class SwitchUserViewModel
|
||||||
quickConnectState.value = null
|
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) {
|
fun removeUser(user: JellyfinUser) {
|
||||||
viewModelScope.launchIO {
|
viewModelScope.launchIO {
|
||||||
serverRepository.removeUser(user)
|
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(
|
private suspend fun setError(
|
||||||
msg: String? = null,
|
msg: String? = null,
|
||||||
ex: Exception? = null,
|
ex: Exception? = null,
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,17 @@ package com.github.damontecres.wholphin.util
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.widget.Toast
|
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.JellyfinServer
|
||||||
import com.github.damontecres.wholphin.data.model.JellyfinUser
|
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.launchIO
|
||||||
import com.github.damontecres.wholphin.ui.showToast
|
import com.github.damontecres.wholphin.ui.showToast
|
||||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
import dagger.hilt.android.qualifiers.ActivityContext
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import dagger.hilt.android.scopes.ActivityScoped
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.launchIn
|
import kotlinx.coroutines.flow.launchIn
|
||||||
import kotlinx.coroutines.flow.onEach
|
import kotlinx.coroutines.flow.onEach
|
||||||
|
|
@ -20,24 +24,36 @@ import org.jellyfin.sdk.model.api.GeneralCommandType
|
||||||
import org.jellyfin.sdk.model.api.MediaType
|
import org.jellyfin.sdk.model.api.MediaType
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
|
||||||
|
|
||||||
@Singleton
|
@ActivityScoped
|
||||||
class ServerEventListener
|
class ServerEventListener
|
||||||
@Inject
|
@Inject
|
||||||
constructor(
|
constructor(
|
||||||
@param:ApplicationContext private val context: Context,
|
@param:ActivityContext private val context: Context,
|
||||||
private val api: ApiClient,
|
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
|
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(
|
fun init(
|
||||||
server: JellyfinServer?,
|
server: JellyfinServer?,
|
||||||
user: JellyfinUser?,
|
user: JellyfinUser?,
|
||||||
) {
|
) {
|
||||||
if (server != null && user != null && api.baseUrl != null && api.accessToken != null) {
|
if (server != null && user != null && api.baseUrl != null && api.accessToken != null) {
|
||||||
scope.launchIO {
|
(context as AppCompatActivity).lifecycleScope.launchIO {
|
||||||
api.sessionApi.postCapabilities(
|
api.sessionApi.postCapabilities(
|
||||||
playableMediaTypes = listOf(MediaType.VIDEO),
|
playableMediaTypes = listOf(MediaType.VIDEO),
|
||||||
supportedCommands =
|
supportedCommands =
|
||||||
|
|
@ -53,6 +69,7 @@ class ServerEventListener
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setupListeners() {
|
fun setupListeners() {
|
||||||
|
serverRepository.currentUser
|
||||||
Timber.v("Subscribing to WebSocket")
|
Timber.v("Subscribing to WebSocket")
|
||||||
listenJob?.cancel()
|
listenJob?.cancel()
|
||||||
listenJob =
|
listenJob =
|
||||||
|
|
@ -73,6 +90,20 @@ class ServerEventListener
|
||||||
.joinToString("\n")
|
.joinToString("\n")
|
||||||
showToast(context, toast, Toast.LENGTH_LONG)
|
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="italic_font">Italicize font</string>
|
||||||
<string name="font">Font</string>
|
<string name="font">Font</string>
|
||||||
<string name="background">Background</string>
|
<string name="background">Background</string>
|
||||||
|
<string name="success">Success</string>
|
||||||
|
|
||||||
<plurals name="downloads">
|
<plurals name="downloads">
|
||||||
<item quantity="zero">%s downloads</item>
|
<item quantity="zero">%s downloads</item>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue