diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/SeerrServerRepository.kt b/app/src/main/java/com/github/damontecres/wholphin/services/SeerrServerRepository.kt index d846b6ee..e267a12f 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/SeerrServerRepository.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/SeerrServerRepository.kt @@ -7,12 +7,15 @@ import androidx.lifecycle.lifecycleScope import com.github.damontecres.wholphin.api.seerr.SeerrApiClient import com.github.damontecres.wholphin.api.seerr.model.AuthJellyfinPostRequest import com.github.damontecres.wholphin.api.seerr.model.AuthLocalPostRequest +import com.github.damontecres.wholphin.api.seerr.model.PublicSettings import com.github.damontecres.wholphin.api.seerr.model.User import com.github.damontecres.wholphin.data.SeerrServerDao import com.github.damontecres.wholphin.data.ServerRepository import com.github.damontecres.wholphin.data.model.SeerrAuthMethod +import com.github.damontecres.wholphin.data.model.SeerrPermission import com.github.damontecres.wholphin.data.model.SeerrServer import com.github.damontecres.wholphin.data.model.SeerrUser +import com.github.damontecres.wholphin.data.model.hasPermission import com.github.damontecres.wholphin.services.hilt.StandardOkHttpClient import com.github.damontecres.wholphin.ui.launchIO import com.github.damontecres.wholphin.util.LoadingState @@ -60,8 +63,9 @@ class SeerrServerRepository user: SeerrUser, userConfig: SeerrUserConfig, ) { + val publicSettings = seerrApi.api.settingsApi.settingsPublicGet() _current.update { - CurrentSeerr(server, user, userConfig) + CurrentSeerr(server, user, userConfig, publicSettings) } } @@ -134,13 +138,8 @@ class SeerrServerRepository passwordOrApiKey: String, ): LoadingState { val api = SeerrApiClient(url, passwordOrApiKey, okHttpClient) - try { - login(api, authMethod, username, passwordOrApiKey) - return LoadingState.Success - } catch (ex: Exception) { - Timber.w(ex, "Error testing seerr connection") - return LoadingState.Error(ex) - } + login(api, authMethod, username, passwordOrApiKey) + return LoadingState.Success } suspend fun removeServer() { @@ -159,7 +158,18 @@ data class CurrentSeerr( val server: SeerrServer, val user: SeerrUser, val config: SeerrUserConfig, -) + val serverConfig: PublicSettings, +) { + val request4kMovieEnabled: Boolean + get() = + (serverConfig.movie4kEnabled ?: false) && + config.hasPermission(SeerrPermission.REQUEST_4K_MOVIE) + + val request4kTvEnabled: Boolean + get() = + (serverConfig.series4kEnabled ?: false) && + config.hasPermission(SeerrPermission.REQUEST_4K_TV) +} private suspend fun login( client: SeerrApiClient, diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieDetails.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieDetails.kt index 7a308d40..40df8ee7 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieDetails.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieDetails.kt @@ -52,6 +52,7 @@ import com.github.damontecres.wholphin.ui.Cards import com.github.damontecres.wholphin.ui.cards.DiscoverItemCard import com.github.damontecres.wholphin.ui.cards.ItemRow import com.github.damontecres.wholphin.ui.cards.SeasonCard +import com.github.damontecres.wholphin.ui.components.DialogItem import com.github.damontecres.wholphin.ui.components.DialogParams import com.github.damontecres.wholphin.ui.components.DialogPopup import com.github.damontecres.wholphin.ui.components.ErrorMessage @@ -94,10 +95,14 @@ fun DiscoverMovieDetails( val recommended by viewModel.similar.observeAsState(listOf()) val loading by viewModel.loading.observeAsState(LoadingState.Loading) val userConfig by viewModel.userConfig.collectAsState(null) + val request4kEnabled by viewModel.request4kEnabled.collectAsState(false) var overviewDialog by remember { mutableStateOf(null) } var moreDialog by remember { mutableStateOf(null) } + val requestStr = stringResource(R.string.request) + val request4kStr = stringResource(R.string.request_4k) + val moreActions = MoreDialogActions( navigateTo = viewModel::navigateTo, @@ -129,7 +134,28 @@ fun DiscoverMovieDetails( similar = similar, recommended = recommended, requestOnClick = { - movie.id?.let { viewModel.request(it) } + movie.id?.let { id -> + if (request4kEnabled) { + moreDialog = + DialogParams( + fromLongClick = false, + title = movie.title + " (${movie.releaseDate ?: ""})", + items = + listOf( + DialogItem( + text = requestStr, + onClick = { viewModel.request(id, false) }, + ), + DialogItem( + text = request4kStr, + onClick = { viewModel.request(id, true) }, + ), + ), + ) + } else { + viewModel.request(id, false) + } + } }, cancelOnClick = { movie.id?.let { viewModel.cancelRequest(it) } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieViewModel.kt index b267e74e..1e55d1f6 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverMovieViewModel.kt @@ -64,6 +64,7 @@ class DiscoverMovieViewModel val recommended = MutableLiveData>(listOf()) val userConfig = seerrServerRepository.current.map { it?.config } + val request4kEnabled = seerrServerRepository.current.map { it?.request4kMovieEnabled ?: false } init { init() @@ -145,12 +146,15 @@ class DiscoverMovieViewModel navigationManager.navigateTo(destination) } - fun request(id: Int) { + fun request( + id: Int, + is4k: Boolean, + ) { viewModelScope.launchIO { val request = seerrService.api.requestApi.requestPost( RequestPostRequest( - is4k = false, + is4k = is4k, mediaId = id, mediaType = RequestPostRequest.MediaType.MOVIE, ), diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverSeriesDetails.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverSeriesDetails.kt index 5a8d5610..102ed668 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverSeriesDetails.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverSeriesDetails.kt @@ -93,9 +93,14 @@ fun DiscoverSeriesDetails( val similar by viewModel.similar.observeAsState(listOf()) val recommended by viewModel.recommended.observeAsState(listOf()) val userConfig by viewModel.userConfig.collectAsState(null) + val request4kEnabled by viewModel.request4kEnabled.collectAsState(false) var overviewDialog by remember { mutableStateOf(null) } var seasonDialog by remember { mutableStateOf(null) } + var moreDialog by remember { mutableStateOf(null) } + + val requestStr = stringResource(R.string.request) + val request4kStr = stringResource(R.string.request_4k) when (val state = loading) { is LoadingState.Error -> { @@ -151,7 +156,28 @@ fun DiscoverSeriesDetails( }, trailers = listOf(), requestOnClick = { - item.id?.let { viewModel.request(it) } + item.id?.let { id -> + if (request4kEnabled) { + moreDialog = + DialogParams( + fromLongClick = false, + title = item.name ?: "", + items = + listOf( + DialogItem( + text = requestStr, + onClick = { viewModel.request(id, false) }, + ), + DialogItem( + text = request4kStr, + onClick = { viewModel.request(id, true) }, + ), + ), + ) + } else { + viewModel.request(id, false) + } + } }, cancelOnClick = { item.id?.let { viewModel.cancelRequest(it) } @@ -180,6 +206,16 @@ fun DiscoverSeriesDetails( onDismissRequest = { seasonDialog = null }, ) } + moreDialog?.let { params -> + DialogPopup( + showDialog = true, + title = params.title, + dialogItems = params.items, + onDismissRequest = { moreDialog = null }, + dismissOnClick = true, + waitToLoad = params.fromLongClick, + ) + } } private const val HEADER_ROW = 0 diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverSeriesViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverSeriesViewModel.kt index bfbb5bf6..21c259f0 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverSeriesViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/discover/DiscoverSeriesViewModel.kt @@ -63,6 +63,7 @@ class DiscoverSeriesViewModel val recommended = MutableLiveData>(listOf()) val userConfig = seerrServerRepository.current.map { it?.config } + val request4kEnabled = seerrServerRepository.current.map { it?.request4kMovieEnabled ?: false } init { init() @@ -136,12 +137,15 @@ class DiscoverSeriesViewModel navigationManager.navigateTo(destination) } - fun request(id: Int) { + fun request( + id: Int, + is4k: Boolean, + ) { viewModelScope.launchIO { val request = seerrService.api.requestApi.requestPost( RequestPostRequest( - is4k = false, + is4k = is4k, mediaId = id, mediaType = RequestPostRequest.MediaType.TV, seasons = RequestPostRequest.Seasons.ALL, // TODO handle picking seasons diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferencesContent.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferencesContent.kt index 1c10e565..e79fdc96 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferencesContent.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/preferences/PreferencesContent.kt @@ -466,6 +466,12 @@ fun PreferencesContent( } } if (showSeerrServerDialog) { + val status by seerrVm.serverConnectionStatus.collectAsState(LoadingState.Pending) + LaunchedEffect(status) { + if (status == LoadingState.Success) { + showSeerrServerDialog = false + } + } if (seerrIntegrationEnabled) { ConfirmDialog( title = stringResource(R.string.remove_seerr_server), @@ -478,12 +484,6 @@ fun PreferencesContent( ) } else { val currentUser by seerrVm.currentUser.observeAsState() - val status by seerrVm.serverConnectionStatus.collectAsState(LoadingState.Pending) - LaunchedEffect(status) { - if (status == LoadingState.Success) { - showSeerrServerDialog = false - } - } AddSeerServerDialog( currentUsername = currentUser?.name, status = status, diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/seerr/SwitchSeerrViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/seerr/SwitchSeerrViewModel.kt index 3ce82483..c0cf1759 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/seerr/SwitchSeerrViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/seerr/SwitchSeerrViewModel.kt @@ -2,6 +2,7 @@ package com.github.damontecres.wholphin.ui.setup.seerr import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import com.github.damontecres.wholphin.api.seerr.infrastructure.ClientException import com.github.damontecres.wholphin.data.ServerRepository import com.github.damontecres.wholphin.data.model.SeerrAuthMethod import com.github.damontecres.wholphin.services.SeerrServerRepository @@ -13,6 +14,7 @@ import jakarta.inject.Inject import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.update import okhttp3.HttpUrl.Companion.toHttpUrlOrNull +import timber.log.Timber @HiltViewModel class SwitchSeerrViewModel @@ -47,12 +49,21 @@ class SwitchSeerrViewModel viewModelScope.launchIO { val url = cleanUrl(url) val result = - seerrServerRepository.testConnection( - authMethod = SeerrAuthMethod.API_KEY, - url = url, - username = null, - passwordOrApiKey = apiKey, - ) + try { + seerrServerRepository.testConnection( + authMethod = SeerrAuthMethod.API_KEY, + url = url, + username = null, + passwordOrApiKey = apiKey, + ) + } catch (ex: ClientException) { + Timber.w(ex, "Error logging in via API Key") + if (ex.statusCode == 401 || ex.statusCode == 403) { + LoadingState.Error("Invalid credentials", ex) + } else { + LoadingState.Error(ex) + } + } if (result is LoadingState.Success) { seerrServerRepository.addAndChangeServer(url, apiKey) } @@ -69,12 +80,21 @@ class SwitchSeerrViewModel viewModelScope.launchIO { val url = cleanUrl(url) val result = - seerrServerRepository.testConnection( - authMethod = authMethod, - url = url, - username = username, - passwordOrApiKey = password, - ) + try { + seerrServerRepository.testConnection( + authMethod = authMethod, + url = url, + username = username, + passwordOrApiKey = password, + ) + } catch (ex: ClientException) { + Timber.w(ex, "Error logging in via %s", authMethod) + if (ex.statusCode == 401 || ex.statusCode == 403) { + LoadingState.Error("Invalid credentials", ex) + } else { + LoadingState.Error(ex) + } + } if (result is LoadingState.Success) { seerrServerRepository.addAndChangeServer(url, authMethod, username, password) } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 670264ab..d428a76a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -440,6 +440,7 @@ Trending Upcoming Movies Upcoming TV Shows + Request in 4K Disabled diff --git a/app/src/main/seerr/seerr-api.yml b/app/src/main/seerr/seerr-api.yml index f8efad03..e8c9c685 100644 --- a/app/src/main/seerr/seerr-api.yml +++ b/app/src/main/seerr/seerr-api.yml @@ -707,6 +707,10 @@ components: initialized: type: boolean example: false + movie4kEnabled: + type: boolean + series4kEnabled: + type: boolean MovieResult: type: object required: