Various fixes (#33)

Fixes a few issues to prepare for a release.
This commit is contained in:
damontecres 2025-10-18 18:45:08 -04:00 committed by GitHub
parent 405e170750
commit dc3193e2dc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 127 additions and 61 deletions

View file

@ -21,6 +21,10 @@ This is not a fork of the [official client](https://github.com/jellyfin/jellyfin
- Subtly show playback position along the bottom of the screen while seeking w/ D-Pad - Subtly show playback position along the bottom of the screen while seeking w/ D-Pad
- Force Continue Watching & Next Up TV episodes to use their Series posters - Force Continue Watching & Next Up TV episodes to use their Series posters
### Roadmap
See [here for the roadmap](https://github.com/damontecres/Wholphin/wiki#roadmap)
## Installation ## Installation
Downloader Code: `8668671` Downloader Code: `8668671`

View file

@ -106,9 +106,21 @@ class ItemPlaybackRepository
saveItemPlayback(toSave) saveItemPlayback(toSave)
} }
/**
* Saves the [ItemPlayback] into the database, returning the same object with the rowId updated if needed
*/
suspend fun saveItemPlayback(itemPlayback: ItemPlayback): ItemPlayback { suspend fun saveItemPlayback(itemPlayback: ItemPlayback): ItemPlayback {
val rowId = itemPlaybackDao.saveItem(itemPlayback) val toSave =
return itemPlayback.copy(rowId = rowId) if (itemPlayback.userId < 0) {
val userRowId =
serverRepository.currentUser?.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 {
itemPlayback
}
val id = itemPlaybackDao.saveItem(toSave)
return toSave.copy(rowId = id)
} }
} }

View file

@ -84,10 +84,23 @@ interface JellyfinServerDao {
fun addOrUpdateUser(user: JellyfinUser) { fun addOrUpdateUser(user: JellyfinUser) {
val result = addUser(user) val result = addUser(user)
if (result == -1L) { if (result == -1L) {
updateUser(user) val toSave =
if (user.rowId <= 0) {
val temp = getUser(user.serverId, user.id)!!
user.copy(rowId = temp.rowId)
} else {
user
}
updateUser(toSave)
} }
} }
@Query("SELECT * FROM users WHERE serverId = :serverId AND id = :userId")
fun getUser(
serverId: UUID,
userId: UUID,
): JellyfinUser?
@Query("DELETE FROM servers WHERE id = :serverId") @Query("DELETE FROM servers WHERE id = :serverId")
fun deleteServer(serverId: UUID) fun deleteServer(serverId: UUID)

View file

@ -87,6 +87,8 @@ class ServerRepository
id = userDto.id, id = userDto.id,
name = userDto.name, name = userDto.name,
) )
if (updatedUser.rowId <= 0) {
}
serverDao.addOrUpdateServer(updatedServer) serverDao.addOrUpdateServer(updatedServer)
serverDao.addOrUpdateUser(updatedUser) serverDao.addOrUpdateUser(updatedUser)
userPreferencesDataStore.updateData { userPreferencesDataStore.updateData {
@ -119,7 +121,6 @@ class ServerRepository
serverDao.getServer(serverId) serverDao.getServer(serverId)
} }
if (serverAndUsers != null) { if (serverAndUsers != null) {
_currentServer = serverAndUsers.server
val user = serverAndUsers.users.firstOrNull { it.id == userId } val user = serverAndUsers.users.firstOrNull { it.id == userId }
if (user != null) { if (user != null) {
changeUser(serverAndUsers.server, user) changeUser(serverAndUsers.server, user)

View file

@ -39,6 +39,19 @@ val DefaultItemFields =
ItemFields.TRICKPLAY, ItemFields.TRICKPLAY,
ItemFields.SORT_NAME, ItemFields.SORT_NAME,
ItemFields.CHAPTERS, ItemFields.CHAPTERS,
ItemFields.MEDIA_SOURCES,
)
/**
* [ItemFields] for higher level displays such as grids or rows
*/
val SlimItemFields =
listOf(
ItemFields.PRIMARY_IMAGE_ASPECT_RATIO,
ItemFields.SEASON_USER_DATA,
ItemFields.CHILD_COUNT,
ItemFields.OVERVIEW,
ItemFields.SORT_NAME,
) )
val DefaultButtonPadding = val DefaultButtonPadding =

View file

@ -29,8 +29,8 @@ import androidx.tv.material3.Text
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.preferences.UserPreferences import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.ui.DefaultItemFields
import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect
import com.github.damontecres.wholphin.ui.SlimItemFields
import com.github.damontecres.wholphin.ui.data.MovieSortOptions import com.github.damontecres.wholphin.ui.data.MovieSortOptions
import com.github.damontecres.wholphin.ui.data.SeriesSortOptions import com.github.damontecres.wholphin.ui.data.SeriesSortOptions
import com.github.damontecres.wholphin.ui.data.SortAndDirection import com.github.damontecres.wholphin.ui.data.SortAndDirection
@ -135,7 +135,7 @@ class CollectionFolderViewModel
SortOrder.ASCENDING, SortOrder.ASCENDING,
SortOrder.ASCENDING, SortOrder.ASCENDING,
), ),
fields = DefaultItemFields, fields = SlimItemFields,
), ),
) )
val newPager = val newPager =

View file

@ -16,8 +16,8 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
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.ui.DefaultItemFields
import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect
import com.github.damontecres.wholphin.ui.SlimItemFields
import com.github.damontecres.wholphin.ui.cards.GridCard import com.github.damontecres.wholphin.ui.cards.GridCard
import com.github.damontecres.wholphin.ui.detail.CardGrid import com.github.damontecres.wholphin.ui.detail.CardGrid
import com.github.damontecres.wholphin.ui.nav.Destination import com.github.damontecres.wholphin.ui.nav.Destination
@ -54,7 +54,7 @@ class GenreViewModel
val request = val request =
GetGenresRequest( GetGenresRequest(
parentId = itemId, parentId = itemId,
fields = DefaultItemFields, fields = SlimItemFields,
) )
val pager = ApiRequestPager(api, request, GetGenresRequestHandler, viewModelScope).init() val pager = ApiRequestPager(api, request, GetGenresRequestHandler, viewModelScope).init()
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {

View file

@ -10,8 +10,8 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.ui.DefaultItemFields
import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect
import com.github.damontecres.wholphin.ui.SlimItemFields
import com.github.damontecres.wholphin.ui.main.HomePageContent import com.github.damontecres.wholphin.ui.main.HomePageContent
import com.github.damontecres.wholphin.ui.main.HomeRow import com.github.damontecres.wholphin.ui.main.HomeRow
import com.github.damontecres.wholphin.ui.main.HomeSection import com.github.damontecres.wholphin.ui.main.HomeSection
@ -52,7 +52,7 @@ class RecommendedMovieViewModel
val resumeItemsRequest = val resumeItemsRequest =
GetResumeItemsRequest( GetResumeItemsRequest(
parentId = parentId, parentId = parentId,
fields = DefaultItemFields, fields = SlimItemFields,
includeItemTypes = listOf(BaseItemKind.MOVIE), includeItemTypes = listOf(BaseItemKind.MOVIE),
enableUserData = true, enableUserData = true,
) )
@ -62,7 +62,7 @@ class RecommendedMovieViewModel
val recentlyReleasedRequest = val recentlyReleasedRequest =
GetItemsRequest( GetItemsRequest(
parentId = parentId, parentId = parentId,
fields = DefaultItemFields, fields = SlimItemFields,
includeItemTypes = listOf(BaseItemKind.MOVIE), includeItemTypes = listOf(BaseItemKind.MOVIE),
recursive = true, recursive = true,
enableUserData = true, enableUserData = true,
@ -75,7 +75,7 @@ class RecommendedMovieViewModel
val recentlyAddedRequest = val recentlyAddedRequest =
GetItemsRequest( GetItemsRequest(
parentId = parentId, parentId = parentId,
fields = DefaultItemFields, fields = SlimItemFields,
includeItemTypes = listOf(BaseItemKind.MOVIE), includeItemTypes = listOf(BaseItemKind.MOVIE),
recursive = true, recursive = true,
enableUserData = true, enableUserData = true,
@ -94,7 +94,7 @@ class RecommendedMovieViewModel
val unwatchedTopRatedRequest = val unwatchedTopRatedRequest =
GetItemsRequest( GetItemsRequest(
parentId = parentId, parentId = parentId,
fields = DefaultItemFields, fields = SlimItemFields,
includeItemTypes = listOf(BaseItemKind.MOVIE), includeItemTypes = listOf(BaseItemKind.MOVIE),
recursive = true, recursive = true,
enableUserData = true, enableUserData = true,

View file

@ -10,8 +10,8 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.ui.DefaultItemFields
import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect import com.github.damontecres.wholphin.ui.OneTimeLaunchedEffect
import com.github.damontecres.wholphin.ui.SlimItemFields
import com.github.damontecres.wholphin.ui.main.HomePageContent import com.github.damontecres.wholphin.ui.main.HomePageContent
import com.github.damontecres.wholphin.ui.main.HomeRow import com.github.damontecres.wholphin.ui.main.HomeRow
import com.github.damontecres.wholphin.ui.main.HomeSection import com.github.damontecres.wholphin.ui.main.HomeSection
@ -54,7 +54,7 @@ class RecommendedTvShowViewModel
val resumeItemsRequest = val resumeItemsRequest =
GetResumeItemsRequest( GetResumeItemsRequest(
parentId = parentId, parentId = parentId,
fields = DefaultItemFields, fields = SlimItemFields,
includeItemTypes = listOf(BaseItemKind.EPISODE), includeItemTypes = listOf(BaseItemKind.EPISODE),
enableUserData = true, enableUserData = true,
) )
@ -64,7 +64,7 @@ class RecommendedTvShowViewModel
val nextUpRequest = val nextUpRequest =
GetNextUpRequest( GetNextUpRequest(
parentId = parentId, parentId = parentId,
fields = DefaultItemFields, fields = SlimItemFields,
enableUserData = true, enableUserData = true,
) )
val nextUpItems = ApiRequestPager(api, nextUpRequest, GetNextUpRequestHandler, viewModelScope, useSeriesForPrimary = true) val nextUpItems = ApiRequestPager(api, nextUpRequest, GetNextUpRequestHandler, viewModelScope, useSeriesForPrimary = true)
@ -72,7 +72,7 @@ class RecommendedTvShowViewModel
val recentlyReleasedRequest = val recentlyReleasedRequest =
GetItemsRequest( GetItemsRequest(
parentId = parentId, parentId = parentId,
fields = DefaultItemFields, fields = SlimItemFields,
includeItemTypes = listOf(BaseItemKind.EPISODE), includeItemTypes = listOf(BaseItemKind.EPISODE),
recursive = true, recursive = true,
enableUserData = true, enableUserData = true,
@ -85,7 +85,7 @@ class RecommendedTvShowViewModel
val recentlyAddedRequest = val recentlyAddedRequest =
GetItemsRequest( GetItemsRequest(
parentId = parentId, parentId = parentId,
fields = DefaultItemFields, fields = SlimItemFields,
includeItemTypes = listOf(BaseItemKind.EPISODE), includeItemTypes = listOf(BaseItemKind.EPISODE),
recursive = true, recursive = true,
enableUserData = true, enableUserData = true,
@ -104,7 +104,7 @@ class RecommendedTvShowViewModel
val unwatchedTopRatedRequest = val unwatchedTopRatedRequest =
GetItemsRequest( GetItemsRequest(
parentId = parentId, parentId = parentId,
fields = DefaultItemFields, fields = SlimItemFields,
includeItemTypes = listOf(BaseItemKind.SERIES), includeItemTypes = listOf(BaseItemKind.SERIES),
recursive = true, recursive = true,
enableUserData = true, enableUserData = true,

View file

@ -2,9 +2,7 @@ package com.github.damontecres.wholphin.ui.detail
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.filled.ArrowForward import androidx.compose.material.icons.automirrored.filled.ArrowForward
import androidx.compose.material.icons.filled.ArrowForward
import androidx.compose.material.icons.filled.PlayArrow import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material.icons.filled.Settings
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import com.github.damontecres.wholphin.R import com.github.damontecres.wholphin.R
import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.BaseItem
@ -87,8 +85,7 @@ fun buildMoreDialogItems(
add( add(
DialogItem( DialogItem(
"Choose Version", "Choose Version",
Icons.Default.ArrowForward, R.string.fa_file_video,
iconColor = Color.Green.copy(alpha = .8f),
) { ) {
onChooseVersion.invoke() onChooseVersion.invoke()
}, },
@ -104,7 +101,7 @@ fun buildMoreDialogItems(
add( add(
DialogItem( DialogItem(
"Choose audio", "Choose audio",
Icons.Default.Settings, R.string.fa_volume_low,
) { ) {
onChooseTracks.invoke(MediaStreamType.AUDIO) onChooseTracks.invoke(MediaStreamType.AUDIO)
}, },
@ -114,7 +111,7 @@ fun buildMoreDialogItems(
add( add(
DialogItem( DialogItem(
"Choose subtitles", "Choose subtitles",
Icons.Default.Settings, R.string.fa_closed_captioning,
) { ) {
onChooseTracks.invoke(MediaStreamType.SUBTITLE) onChooseTracks.invoke(MediaStreamType.SUBTITLE)
}, },

View file

@ -30,6 +30,7 @@ import com.github.damontecres.wholphin.util.GetEpisodesRequestHandler
import com.github.damontecres.wholphin.util.GetItemsRequestHandler import com.github.damontecres.wholphin.util.GetItemsRequestHandler
import com.github.damontecres.wholphin.util.LoadingExceptionHandler import com.github.damontecres.wholphin.util.LoadingExceptionHandler
import com.github.damontecres.wholphin.util.LoadingState import com.github.damontecres.wholphin.util.LoadingState
import com.github.damontecres.wholphin.util.profile.Codec
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -135,7 +136,13 @@ class SeriesViewModel
val url = val url =
api.universalAudioApi.getUniversalAudioStreamUrl( api.universalAudioApi.getUniversalAudioStreamUrl(
theme.id, theme.id,
container = listOf("opus", "mp3", "aaa", "flac"), container =
listOf(
Codec.Audio.OPUS,
Codec.Audio.MP3,
Codec.Audio.AAC,
Codec.Audio.FLAC,
),
) )
Timber.Forest.v("Found theme song for series $seriesId") Timber.Forest.v("Found theme song for series $seriesId")
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {

View file

@ -5,7 +5,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.ui.DefaultItemFields import com.github.damontecres.wholphin.ui.SlimItemFields
import com.github.damontecres.wholphin.ui.nav.NavigationManager import com.github.damontecres.wholphin.ui.nav.NavigationManager
import com.github.damontecres.wholphin.util.LoadingExceptionHandler import com.github.damontecres.wholphin.util.LoadingExceptionHandler
import com.github.damontecres.wholphin.util.LoadingState import com.github.damontecres.wholphin.util.LoadingState
@ -119,7 +119,7 @@ class HomeViewModel
val request = val request =
GetResumeItemsRequest( GetResumeItemsRequest(
userId = userId, userId = userId,
fields = DefaultItemFields, fields = SlimItemFields,
limit = limit, limit = limit,
includeItemTypes = supportItemKinds, includeItemTypes = supportItemKinds,
) )
@ -140,7 +140,7 @@ class HomeViewModel
val request = val request =
GetNextUpRequest( GetNextUpRequest(
userId = userId, userId = userId,
fields = DefaultItemFields, fields = SlimItemFields,
imageTypeLimit = 1, imageTypeLimit = 1,
parentId = null, parentId = null,
limit = limit, limit = limit,
@ -176,7 +176,7 @@ class HomeViewModel
view.name?.let { "Recently Added in $it" } view.name?.let { "Recently Added in $it" }
val request = val request =
GetLatestMediaRequest( GetLatestMediaRequest(
fields = DefaultItemFields, fields = SlimItemFields,
imageTypeLimit = 1, imageTypeLimit = 1,
parentId = view.id, parentId = view.id,
groupItems = true, groupItems = true,

View file

@ -26,7 +26,7 @@ import androidx.lifecycle.viewModelScope
import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.preferences.UserPreferences
import com.github.damontecres.wholphin.ui.Cards import com.github.damontecres.wholphin.ui.Cards
import com.github.damontecres.wholphin.ui.DefaultItemFields import com.github.damontecres.wholphin.ui.SlimItemFields
import com.github.damontecres.wholphin.ui.cards.EpisodeCard import com.github.damontecres.wholphin.ui.cards.EpisodeCard
import com.github.damontecres.wholphin.ui.cards.ItemRow import com.github.damontecres.wholphin.ui.cards.ItemRow
import com.github.damontecres.wholphin.ui.cards.SeasonCard import com.github.damontecres.wholphin.ui.cards.SeasonCard
@ -78,7 +78,7 @@ class SearchViewModel
searchTerm = query, searchTerm = query,
recursive = true, recursive = true,
includeItemTypes = listOf(BaseItemKind.MOVIE), includeItemTypes = listOf(BaseItemKind.MOVIE),
fields = DefaultItemFields, fields = SlimItemFields,
limit = 25, limit = 25,
) )
val pager = ApiRequestPager(api, request, GetItemsRequestHandler, viewModelScope) val pager = ApiRequestPager(api, request, GetItemsRequestHandler, viewModelScope)
@ -93,7 +93,7 @@ class SearchViewModel
searchTerm = query, searchTerm = query,
recursive = true, recursive = true,
includeItemTypes = listOf(BaseItemKind.SERIES), includeItemTypes = listOf(BaseItemKind.SERIES),
fields = DefaultItemFields, fields = SlimItemFields,
limit = 25, limit = 25,
) )
val pager = ApiRequestPager(api, request, GetItemsRequestHandler, viewModelScope) val pager = ApiRequestPager(api, request, GetItemsRequestHandler, viewModelScope)
@ -108,7 +108,7 @@ class SearchViewModel
searchTerm = query, searchTerm = query,
recursive = true, recursive = true,
includeItemTypes = listOf(BaseItemKind.EPISODE), includeItemTypes = listOf(BaseItemKind.EPISODE),
fields = DefaultItemFields, fields = SlimItemFields,
limit = 25, limit = 25,
) )
val pager = ApiRequestPager(api, request, GetItemsRequestHandler, viewModelScope) val pager = ApiRequestPager(api, request, GetItemsRequestHandler, viewModelScope)

View file

@ -17,6 +17,7 @@ import androidx.media3.common.Tracks
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer import androidx.media3.exoplayer.ExoPlayer
import com.github.damontecres.wholphin.data.ItemPlaybackDao import com.github.damontecres.wholphin.data.ItemPlaybackDao
import com.github.damontecres.wholphin.data.ItemPlaybackRepository
import com.github.damontecres.wholphin.data.ServerRepository import com.github.damontecres.wholphin.data.ServerRepository
import com.github.damontecres.wholphin.data.model.BaseItem import com.github.damontecres.wholphin.data.model.BaseItem
import com.github.damontecres.wholphin.data.model.Chapter import com.github.damontecres.wholphin.data.model.Chapter
@ -100,6 +101,7 @@ class PlaybackViewModel
val navigationManager: NavigationManager, val navigationManager: NavigationManager,
val itemPlaybackDao: ItemPlaybackDao, val itemPlaybackDao: ItemPlaybackDao,
val serverRepository: ServerRepository, val serverRepository: ServerRepository,
val itemPlaybackRepository: ItemPlaybackRepository,
) : ViewModel(), ) : ViewModel(),
Player.Listener { Player.Listener {
val player: ExoPlayer = val player: ExoPlayer =
@ -461,10 +463,9 @@ class PlaybackViewModel
if (userInitiated) { if (userInitiated) {
viewModelScope.launch(Dispatchers.IO + ExceptionHandler()) { viewModelScope.launch(Dispatchers.IO + ExceptionHandler()) {
Timber.v("Saving user initiated item playback: %s", itemPlayback) Timber.v("Saving user initiated item playback: %s", itemPlayback)
val rowId = itemPlaybackDao.saveItem(itemPlayback) val updated = itemPlaybackRepository.saveItemPlayback(itemPlayback)
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
this@PlaybackViewModel.currentItemPlayback.value = this@PlaybackViewModel.currentItemPlayback.value = updated
itemPlayback.copy(rowId = rowId)
} }
} }
} }

View file

@ -125,9 +125,7 @@ fun SwitchUserContent(
LaunchedEffect(Unit) { LaunchedEffect(Unit) {
viewModel.clearSwitchUserState() viewModel.clearSwitchUserState()
if (useQuickConnect) { if (useQuickConnect) {
viewModel.initiateQuickConnect(server) { viewModel.initiateQuickConnect(server)
viewModel.navigationManager.goToHome()
}
} }
} }
BasicDialog( BasicDialog(
@ -180,10 +178,18 @@ fun SwitchUserContent(
) )
when (val s = userState) { when (val s = userState) {
is LoadingState.Error -> { is LoadingState.Error -> {
Text( s.message?.let {
text = s.message ?: s.exception?.localizedMessage ?: "Error", Text(
color = MaterialTheme.colorScheme.error, text = it,
) color = MaterialTheme.colorScheme.error,
)
}
s.exception?.localizedMessage?.let {
Text(
text = it,
color = MaterialTheme.colorScheme.error,
)
}
} }
else -> {} else -> {}
@ -208,6 +214,7 @@ fun SwitchUserContent(
autoCorrectEnabled = false, autoCorrectEnabled = false,
keyboardType = KeyboardType.Text, keyboardType = KeyboardType.Text,
), ),
isInputValid = { userState !is LoadingState.Error },
modifier = Modifier.focusRequester(focusRequester), modifier = Modifier.focusRequester(focusRequester),
) )
} }
@ -235,6 +242,7 @@ fun SwitchUserContent(
KeyboardActions( KeyboardActions(
onDone = { onSubmit.invoke() }, onDone = { onSubmit.invoke() },
), ),
isInputValid = { userState !is LoadingState.Error },
modifier = Modifier, modifier = Modifier,
) )
} }

View file

@ -154,7 +154,7 @@ class SwitchUserViewModel
} }
} catch (ex: Exception) { } catch (ex: Exception) {
Timber.e(ex, "Error switching user") Timber.e(ex, "Error switching user")
setError(ex) setError("Error switching user", ex)
} }
} }
} }
@ -178,15 +178,19 @@ class SwitchUserViewModel
} }
} catch (ex: Exception) { } catch (ex: Exception) {
Timber.e(ex, "Error logging in user") Timber.e(ex, "Error logging in user")
setError(ex) if (ex is InvalidStatusException && ex.status == 401) {
withContext(Dispatchers.Main) {
switchUserState.value =
LoadingState.Error("Invalid username or password")
}
} else {
setError("Error during login", ex)
}
} }
} }
} }
fun initiateQuickConnect( fun initiateQuickConnect(server: JellyfinServer) {
server: JellyfinServer,
onAuthenticated: () -> Unit,
) {
quickConnectJob?.cancel() quickConnectJob?.cancel()
viewModelScope.launchIO { viewModelScope.launchIO {
try { try {
@ -218,19 +222,21 @@ class SwitchUserViewModel
) )
serverRepository.changeUser(server.url, authenticationResult) serverRepository.changeUser(server.url, authenticationResult)
withContext(Dispatchers.Main) { withContext(Dispatchers.Main) {
onAuthenticated.invoke() navigationManager.goToHome()
} }
} }
} catch (ex: Exception) { } catch (ex: Exception) {
Timber.e(ex, "Error during quick connect") Timber.e(ex, "Error during quick connect")
if (ex is InvalidStatusException && ex.status == 401) { if (ex is InvalidStatusException && ex.status == 401) {
quickConnectState.value = null withContext(Dispatchers.Main) {
serverQuickConnect.value = quickConnectState.value = null
serverQuickConnect.value!!.toMutableMap().apply { serverQuickConnect.value =
put(server.id, false) serverQuickConnect.value!!.toMutableMap().apply {
} put(server.id, false)
}
}
} }
setError(ex) setError("Error with Quick Connect", ex)
} }
} }
} }
@ -329,8 +335,10 @@ class SwitchUserViewModel
} }
} }
private suspend fun setError(ex: Exception) = private suspend fun setError(
withContext(Dispatchers.Main) { msg: String? = null,
switchUserState.value = LoadingState.Error(ex) ex: Exception? = null,
} ) = withContext(Dispatchers.Main) {
switchUserState.value = LoadingState.Error(msg, ex)
}
} }

View file

@ -34,4 +34,6 @@
<string name="fa_shuffle" translatable="false">&#xf074;</string> <string name="fa_shuffle" translatable="false">&#xf074;</string>
<string name="fa_open_folder" translatable="false">&#xf07c;</string> <string name="fa_open_folder" translatable="false">&#xf07c;</string>
<string name="fa_list_ul" translatable="false">&#xf0ca;</string> <string name="fa_list_ul" translatable="false">&#xf0ca;</string>
<string name="fa_file_video" translatable="false">&#xf1c8;</string>
<string name="fa_volume_low" translatable="false">&#xf027;</string>
</resources> </resources>