mirror of
https://github.com/JustinZeus/Wholphin.git
synced 2026-07-08 15:50:16 +02:00
Fixes & improvements to Jellyseer integration (#783)
## Description Fixes several bugs and makes it easier to log in to a Jellyseer instance - Fix initial focus & next focus on the add server dialog - Don't use an API key for non-API key auth (credit to @voc0der in #734 for suggesting this) - Show trailers for TV shows - Fix incorrect/missing similar and recommended rows for both movies & TV shows - Allow empty password for Jellyfin users without a password Log in will now try variations of the input URL such as add the protocol and/or default port. This is similar to the regular Jellyfin login process. ### Related issues Fixes #778 Fixes #750 Fixes #740 Might help with #747 Fixes https://github.com/damontecres/Wholphin/issues/764#issuecomment-3801324648
This commit is contained in:
parent
3428f2b208
commit
e93100c788
11 changed files with 234 additions and 96 deletions
|
|
@ -30,6 +30,7 @@ import okhttp3.OkHttpClient
|
|||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
/**
|
||||
* Manages saves/loading Seerr servers from the local DB. Also will update the current [SeerrApi] as needed.
|
||||
|
|
@ -137,7 +138,17 @@ class SeerrServerRepository
|
|||
username: String?,
|
||||
passwordOrApiKey: String,
|
||||
): LoadingState {
|
||||
val api = SeerrApiClient(url, passwordOrApiKey, okHttpClient)
|
||||
val apiKey = passwordOrApiKey.takeIf { authMethod == SeerrAuthMethod.API_KEY }
|
||||
val api =
|
||||
SeerrApiClient(
|
||||
url,
|
||||
apiKey,
|
||||
okHttpClient
|
||||
.newBuilder()
|
||||
.connectTimeout(5.seconds)
|
||||
.readTimeout(6.seconds)
|
||||
.build(),
|
||||
)
|
||||
login(api, authMethod, username, passwordOrApiKey)
|
||||
return LoadingState.Success
|
||||
}
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ fun DiscoverMovieDetails(
|
|||
val people by viewModel.people.observeAsState(listOf())
|
||||
val trailers by viewModel.trailers.observeAsState(listOf())
|
||||
val similar by viewModel.similar.observeAsState(listOf())
|
||||
val recommended by viewModel.similar.observeAsState(listOf())
|
||||
val recommended by viewModel.recommended.observeAsState(listOf())
|
||||
val loading by viewModel.loading.observeAsState(LoadingState.Loading)
|
||||
val userConfig by viewModel.userConfig.collectAsState(null)
|
||||
val request4kEnabled by viewModel.request4kEnabled.collectAsState(false)
|
||||
|
|
@ -365,7 +365,7 @@ fun DiscoverMovieDetailsContent(
|
|||
item {
|
||||
ItemRow(
|
||||
title = stringResource(R.string.recommended),
|
||||
items = similar,
|
||||
items = recommended,
|
||||
onClickItem = { index, item ->
|
||||
position = RECOMMENDED_ROW
|
||||
onClickItem.invoke(index, item)
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ import kotlinx.coroutines.flow.update
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import timber.log.Timber
|
||||
|
||||
@HiltViewModel(assistedFactory = DiscoverMovieViewModel.Factory::class)
|
||||
class DiscoverMovieViewModel
|
||||
|
|
@ -67,8 +68,8 @@ class DiscoverMovieViewModel
|
|||
|
||||
val trailers = MutableLiveData<List<Trailer>>(listOf())
|
||||
val people = MutableLiveData<List<DiscoverItem>>(listOf())
|
||||
val similar = MutableLiveData<List<DiscoverItem>>(listOf())
|
||||
val recommended = MutableLiveData<List<DiscoverItem>>(listOf())
|
||||
val similar = MutableLiveData<List<DiscoverItem>>()
|
||||
val recommended = MutableLiveData<List<DiscoverItem>>()
|
||||
val canCancelRequest = MutableStateFlow(false)
|
||||
|
||||
val userConfig = seerrServerRepository.current.map { it?.config }
|
||||
|
|
@ -99,6 +100,7 @@ class DiscoverMovieViewModel
|
|||
"Error fetching movie",
|
||||
),
|
||||
) {
|
||||
Timber.v("Init for movie %s", item.id)
|
||||
val movie = fetchAndSetItem().await()
|
||||
val discoveredItem = DiscoverItem(movie)
|
||||
backdropService.submit(discoveredItem)
|
||||
|
|
@ -116,7 +118,7 @@ class DiscoverMovieViewModel
|
|||
viewModelScope.launchIO {
|
||||
val result =
|
||||
seerrService.api.moviesApi
|
||||
.movieMovieIdSimilarGet(movieId = item.id, page = 2)
|
||||
.movieMovieIdSimilarGet(movieId = item.id, page = 1)
|
||||
.results
|
||||
?.map(::DiscoverItem)
|
||||
.orEmpty()
|
||||
|
|
@ -125,11 +127,11 @@ class DiscoverMovieViewModel
|
|||
viewModelScope.launchIO {
|
||||
val result =
|
||||
seerrService.api.moviesApi
|
||||
.movieMovieIdRecommendationsGet(movieId = item.id, page = 2)
|
||||
.movieMovieIdRecommendationsGet(movieId = item.id, page = 1)
|
||||
.results
|
||||
?.map(::DiscoverItem)
|
||||
.orEmpty()
|
||||
similar.setValueOnMain(result)
|
||||
recommended.setValueOnMain(result)
|
||||
}
|
||||
}
|
||||
val people =
|
||||
|
|
@ -147,7 +149,7 @@ class DiscoverMovieViewModel
|
|||
?.filter { it.type == RelatedVideo.Type.TRAILER }
|
||||
?.filter { it.name.isNotNullOrBlank() && it.url.isNotNullOrBlank() }
|
||||
?.map {
|
||||
RemoteTrailer(it.name!!, it.url!!, null)
|
||||
RemoteTrailer(it.name!!, it.url!!, it.site)
|
||||
}.orEmpty()
|
||||
this@DiscoverMovieViewModel.trailers.setValueOnMain(trailers)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -89,6 +89,7 @@ fun DiscoverSeriesDetails(
|
|||
val item by viewModel.tvSeries.observeAsState()
|
||||
val seasons by viewModel.seasons.observeAsState(listOf())
|
||||
val people by viewModel.people.observeAsState(listOf())
|
||||
val trailers by viewModel.trailers.observeAsState(listOf())
|
||||
val similar by viewModel.similar.observeAsState(listOf())
|
||||
val recommended by viewModel.recommended.observeAsState(listOf())
|
||||
val userConfig by viewModel.userConfig.collectAsState(null)
|
||||
|
|
@ -155,7 +156,7 @@ fun DiscoverSeriesDetails(
|
|||
trailerOnClick = {
|
||||
TrailerService.onClick(context, it, viewModel::navigateTo)
|
||||
},
|
||||
trailers = listOf(),
|
||||
trailers = trailers,
|
||||
requestOnClick = {
|
||||
item.id?.let { id ->
|
||||
if (request4kEnabled) {
|
||||
|
|
@ -255,7 +256,7 @@ fun DiscoverSeriesDetailsContent(
|
|||
val bringIntoViewRequester = remember { BringIntoViewRequester() }
|
||||
|
||||
var position by rememberInt()
|
||||
val focusRequesters = remember { List(SIMILAR_ROW + 1) { FocusRequester() } }
|
||||
val focusRequesters = remember { List(RECOMMENDED_ROW + 1) { FocusRequester() } }
|
||||
val playFocusRequester = remember { FocusRequester() }
|
||||
LaunchedEffect(Unit) {
|
||||
focusRequesters.getOrNull(position)?.tryRequestFocus()
|
||||
|
|
@ -398,7 +399,7 @@ fun DiscoverSeriesDetailsContent(
|
|||
item {
|
||||
ItemRow(
|
||||
title = stringResource(R.string.recommended),
|
||||
items = similar,
|
||||
items = recommended,
|
||||
onClickItem = { index, item ->
|
||||
position = RECOMMENDED_ROW
|
||||
onClickItem.invoke(index, item)
|
||||
|
|
|
|||
|
|
@ -4,17 +4,20 @@ import android.content.Context
|
|||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.github.damontecres.wholphin.api.seerr.model.RelatedVideo
|
||||
import com.github.damontecres.wholphin.api.seerr.model.RequestPostRequest
|
||||
import com.github.damontecres.wholphin.api.seerr.model.Season
|
||||
import com.github.damontecres.wholphin.api.seerr.model.TvDetails
|
||||
import com.github.damontecres.wholphin.data.ServerRepository
|
||||
import com.github.damontecres.wholphin.data.model.DiscoverItem
|
||||
import com.github.damontecres.wholphin.data.model.DiscoverRating
|
||||
import com.github.damontecres.wholphin.data.model.RemoteTrailer
|
||||
import com.github.damontecres.wholphin.data.model.Trailer
|
||||
import com.github.damontecres.wholphin.services.BackdropService
|
||||
import com.github.damontecres.wholphin.services.NavigationManager
|
||||
import com.github.damontecres.wholphin.services.SeerrServerRepository
|
||||
import com.github.damontecres.wholphin.services.SeerrService
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
import com.github.damontecres.wholphin.ui.launchIO
|
||||
import com.github.damontecres.wholphin.ui.nav.Destination
|
||||
import com.github.damontecres.wholphin.ui.setValueOnMain
|
||||
|
|
@ -36,6 +39,7 @@ import kotlinx.coroutines.flow.update
|
|||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jellyfin.sdk.api.client.ApiClient
|
||||
import timber.log.Timber
|
||||
|
||||
@HiltViewModel(assistedFactory = DiscoverSeriesViewModel.Factory::class)
|
||||
class DiscoverSeriesViewModel
|
||||
|
|
@ -62,8 +66,8 @@ class DiscoverSeriesViewModel
|
|||
val seasons = MutableLiveData<List<Season>>(listOf())
|
||||
val trailers = MutableLiveData<List<Trailer>>(listOf())
|
||||
val people = MutableLiveData<List<DiscoverItem>>(listOf())
|
||||
val similar = MutableLiveData<List<DiscoverItem>>(listOf())
|
||||
val recommended = MutableLiveData<List<DiscoverItem>>(listOf())
|
||||
val similar = MutableLiveData<List<DiscoverItem>>()
|
||||
val recommended = MutableLiveData<List<DiscoverItem>>()
|
||||
val canCancelRequest = MutableStateFlow(false)
|
||||
|
||||
val userConfig = seerrServerRepository.current.map { it?.config }
|
||||
|
|
@ -94,6 +98,7 @@ class DiscoverSeriesViewModel
|
|||
"Error fetching movie",
|
||||
),
|
||||
) {
|
||||
Timber.v("Init for tv %s", item.id)
|
||||
val tv = fetchAndSetItem().await()
|
||||
val discoveredItem = DiscoverItem(tv)
|
||||
backdropService.submit(discoveredItem)
|
||||
|
|
@ -110,8 +115,8 @@ class DiscoverSeriesViewModel
|
|||
if (!similar.isInitialized) {
|
||||
viewModelScope.launchIO {
|
||||
val result =
|
||||
seerrService.api.moviesApi
|
||||
.movieMovieIdSimilarGet(movieId = item.id, page = 2)
|
||||
seerrService.api.tvApi
|
||||
.tvTvIdSimilarGet(tvId = item.id, page = 1)
|
||||
.results
|
||||
?.map(::DiscoverItem)
|
||||
.orEmpty()
|
||||
|
|
@ -119,12 +124,12 @@ class DiscoverSeriesViewModel
|
|||
}
|
||||
viewModelScope.launchIO {
|
||||
val result =
|
||||
seerrService.api.moviesApi
|
||||
.movieMovieIdRecommendationsGet(movieId = item.id, page = 2)
|
||||
seerrService.api.tvApi
|
||||
.tvTvIdRecommendationsGet(tvId = item.id, page = 1)
|
||||
.results
|
||||
?.map(::DiscoverItem)
|
||||
.orEmpty()
|
||||
similar.setValueOnMain(result)
|
||||
recommended.setValueOnMain(result)
|
||||
}
|
||||
}
|
||||
val people =
|
||||
|
|
@ -137,6 +142,15 @@ class DiscoverSeriesViewModel
|
|||
?.map(::DiscoverItem)
|
||||
.orEmpty()
|
||||
this@DiscoverSeriesViewModel.people.setValueOnMain(people)
|
||||
|
||||
val trailers =
|
||||
tv.relatedVideos
|
||||
?.filter { it.type == RelatedVideo.Type.TRAILER }
|
||||
?.filter { it.name.isNotNullOrBlank() && it.url.isNotNullOrBlank() }
|
||||
?.map {
|
||||
RemoteTrailer(it.name!!, it.url!!, it.site)
|
||||
}.orEmpty()
|
||||
this@DiscoverSeriesViewModel.trailers.setValueOnMain(trailers)
|
||||
}
|
||||
|
||||
fun navigateTo(destination: Destination) {
|
||||
|
|
|
|||
|
|
@ -43,7 +43,6 @@ import androidx.tv.material3.surfaceColorAtElevation
|
|||
import coil3.SingletonImageLoader
|
||||
import coil3.imageLoader
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.data.model.SeerrAuthMethod
|
||||
import com.github.damontecres.wholphin.preferences.AppPreference
|
||||
import com.github.damontecres.wholphin.preferences.AppPreferences
|
||||
import com.github.damontecres.wholphin.preferences.ExoPlayerPreferences
|
||||
|
|
@ -504,13 +503,7 @@ fun PreferencesContent(
|
|||
AddSeerServerDialog(
|
||||
currentUsername = currentUser?.name,
|
||||
status = status,
|
||||
onSubmit = { url: String, username: String?, passwordOrApiKey: String, method: SeerrAuthMethod ->
|
||||
if (method == SeerrAuthMethod.API_KEY) {
|
||||
seerrVm.submitServer(url, passwordOrApiKey)
|
||||
} else {
|
||||
seerrVm.submitServer(url, username ?: "", passwordOrApiKey, method)
|
||||
}
|
||||
},
|
||||
onSubmit = seerrVm::submitServer,
|
||||
onDismissRequest = { seerrDialogMode = SeerrDialogMode.None },
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,10 +6,12 @@ import androidx.compose.foundation.layout.Column
|
|||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.layout.width
|
||||
import androidx.compose.foundation.layout.wrapContentSize
|
||||
import androidx.compose.foundation.text.KeyboardActions
|
||||
import androidx.compose.foundation.text.KeyboardOptions
|
||||
import androidx.compose.material3.CircularProgressIndicator
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
|
|
@ -30,6 +32,7 @@ import androidx.tv.material3.MaterialTheme
|
|||
import androidx.tv.material3.Text
|
||||
import com.github.damontecres.wholphin.R
|
||||
import com.github.damontecres.wholphin.ui.PreviewTvSpec
|
||||
import com.github.damontecres.wholphin.ui.components.Button
|
||||
import com.github.damontecres.wholphin.ui.components.EditTextBox
|
||||
import com.github.damontecres.wholphin.ui.components.TextButton
|
||||
import com.github.damontecres.wholphin.ui.isNotNullOrBlank
|
||||
|
|
@ -240,7 +243,7 @@ fun AddSeerrServerUsername(
|
|||
},
|
||||
),
|
||||
isInputValid = { true },
|
||||
modifier = Modifier.focusRequester(focusRequester),
|
||||
modifier = Modifier.focusRequester(usernameFocusRequester),
|
||||
)
|
||||
}
|
||||
Row(
|
||||
|
|
@ -283,12 +286,23 @@ fun AddSeerrServerUsername(
|
|||
style = MaterialTheme.typography.titleLarge,
|
||||
)
|
||||
}
|
||||
TextButton(
|
||||
stringRes = R.string.submit,
|
||||
Button(
|
||||
onClick = { onSubmit.invoke(url, username, password) },
|
||||
enabled = error.isNullOrBlank() && url.isNotNullOrBlank() && username.isNotNullOrBlank() && password.isNotNullOrBlank(),
|
||||
enabled =
|
||||
error.isNullOrBlank() && url.isNotNullOrBlank() && username.isNotNullOrBlank() &&
|
||||
status != LoadingState.Loading,
|
||||
modifier = Modifier.align(Alignment.CenterHorizontally),
|
||||
)
|
||||
) {
|
||||
if (status != LoadingState.Loading) {
|
||||
Text(text = stringResource(R.string.submit))
|
||||
} else {
|
||||
CircularProgressIndicator(
|
||||
color = MaterialTheme.colorScheme.border,
|
||||
modifier =
|
||||
Modifier.size(24.dp),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ import com.github.damontecres.wholphin.util.LoadingState
|
|||
fun AddSeerServerDialog(
|
||||
currentUsername: String?,
|
||||
status: LoadingState,
|
||||
onSubmit: (url: String, username: String?, passwordOrApiKey: String, method: SeerrAuthMethod) -> Unit,
|
||||
onSubmit: (url: String, username: String, passwordOrApiKey: String, method: SeerrAuthMethod) -> Unit,
|
||||
onDismissRequest: () -> Unit,
|
||||
) {
|
||||
var authMethod by remember { mutableStateOf<SeerrAuthMethod?>(null) }
|
||||
|
|
@ -49,7 +49,7 @@ fun AddSeerServerDialog(
|
|||
) {
|
||||
AddSeerrServerApiKey(
|
||||
onSubmit = { url, apiKey ->
|
||||
onSubmit.invoke(url, null, apiKey, SeerrAuthMethod.API_KEY)
|
||||
onSubmit.invoke(url, "", apiKey, SeerrAuthMethod.API_KEY)
|
||||
},
|
||||
status = status,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.github.damontecres.wholphin.ui.setup.seerr
|
||||
|
||||
import android.content.Context
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.github.damontecres.wholphin.api.seerr.infrastructure.ClientException
|
||||
|
|
@ -8,18 +9,22 @@ import com.github.damontecres.wholphin.data.model.SeerrAuthMethod
|
|||
import com.github.damontecres.wholphin.services.SeerrServerRepository
|
||||
import com.github.damontecres.wholphin.services.SeerrService
|
||||
import com.github.damontecres.wholphin.ui.launchIO
|
||||
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 jakarta.inject.Inject
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
|
||||
import okhttp3.HttpUrl
|
||||
import okhttp3.HttpUrl.Companion.toHttpUrl
|
||||
import timber.log.Timber
|
||||
|
||||
@HiltViewModel
|
||||
class SwitchSeerrViewModel
|
||||
@Inject
|
||||
constructor(
|
||||
@param:ApplicationContext private val context: Context,
|
||||
private val seerrServerRepository: SeerrServerRepository,
|
||||
private val seerrService: SeerrService,
|
||||
private val serverRepository: ServerRepository,
|
||||
|
|
@ -28,75 +33,70 @@ class SwitchSeerrViewModel
|
|||
|
||||
val serverConnectionStatus = MutableStateFlow<LoadingState>(LoadingState.Pending)
|
||||
|
||||
private fun cleanUrl(url: String) =
|
||||
if (!url.endsWith("/api/v1")) {
|
||||
url
|
||||
.toHttpUrlOrNull()
|
||||
?.newBuilder()
|
||||
?.apply {
|
||||
addPathSegment("api")
|
||||
addPathSegment("v1")
|
||||
}?.build()
|
||||
.toString()
|
||||
} else {
|
||||
url
|
||||
}
|
||||
|
||||
fun submitServer(
|
||||
url: String,
|
||||
apiKey: String,
|
||||
) {
|
||||
viewModelScope.launchIO {
|
||||
val url = cleanUrl(url)
|
||||
val result =
|
||||
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)
|
||||
}
|
||||
serverConnectionStatus.update { result }
|
||||
}
|
||||
}
|
||||
|
||||
fun submitServer(
|
||||
url: String,
|
||||
username: String,
|
||||
password: String,
|
||||
passwordOrApiKey: String,
|
||||
authMethod: SeerrAuthMethod,
|
||||
) {
|
||||
viewModelScope.launchIO {
|
||||
val url = cleanUrl(url)
|
||||
val result =
|
||||
serverConnectionStatus.update { LoadingState.Loading }
|
||||
val urls =
|
||||
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 {
|
||||
createUrls(url)
|
||||
} catch (ex: IllegalArgumentException) {
|
||||
showToast(context, "Invalid URL")
|
||||
serverConnectionStatus.update { LoadingState.Error("Invalid URL", ex) }
|
||||
return@launchIO
|
||||
}
|
||||
var result: LoadingState = LoadingState.Error("No url")
|
||||
for (url in urls) {
|
||||
Timber.d("Trying %s", url)
|
||||
result =
|
||||
try {
|
||||
seerrServerRepository.testConnection(
|
||||
authMethod = authMethod,
|
||||
url = url.toString(),
|
||||
username = username.takeIf { authMethod != SeerrAuthMethod.API_KEY },
|
||||
passwordOrApiKey = passwordOrApiKey,
|
||||
)
|
||||
} catch (ex: ClientException) {
|
||||
Timber.w(ex, "Error logging in")
|
||||
if (ex.statusCode == 401 || ex.statusCode == 403) {
|
||||
showToast(context, "Invalid credentials")
|
||||
LoadingState.Error("Invalid credentials", ex)
|
||||
break
|
||||
} else {
|
||||
LoadingState.Error("Could not connect with URL")
|
||||
}
|
||||
} catch (ex: Exception) {
|
||||
LoadingState.Error(ex)
|
||||
}
|
||||
if (result is LoadingState.Success) {
|
||||
when (authMethod) {
|
||||
SeerrAuthMethod.LOCAL,
|
||||
SeerrAuthMethod.JELLYFIN,
|
||||
-> {
|
||||
seerrServerRepository.addAndChangeServer(
|
||||
url.toString(),
|
||||
authMethod,
|
||||
username,
|
||||
passwordOrApiKey,
|
||||
)
|
||||
}
|
||||
|
||||
SeerrAuthMethod.API_KEY -> {
|
||||
seerrServerRepository.addAndChangeServer(
|
||||
url.toString(),
|
||||
passwordOrApiKey,
|
||||
)
|
||||
}
|
||||
}
|
||||
break
|
||||
}
|
||||
if (result is LoadingState.Success) {
|
||||
seerrServerRepository.addAndChangeServer(url, authMethod, username, password)
|
||||
}
|
||||
if (result is LoadingState.Error) {
|
||||
showToast(context, "Error: ${result.message}")
|
||||
}
|
||||
serverConnectionStatus.update { result }
|
||||
}
|
||||
|
|
@ -112,3 +112,39 @@ class SwitchSeerrViewModel
|
|||
serverConnectionStatus.update { LoadingState.Pending }
|
||||
}
|
||||
}
|
||||
|
||||
fun createUrls(url: String): List<HttpUrl> {
|
||||
val urls = mutableListOf<String>()
|
||||
if (url.startsWith("http://") || url.startsWith("https://")) {
|
||||
urls.add(url)
|
||||
val httpUrl = url.toHttpUrl()
|
||||
if (HttpUrl.defaultPort(httpUrl.scheme) == httpUrl.port) {
|
||||
urls.add("$url:5055")
|
||||
}
|
||||
} else {
|
||||
urls.add("http://$url")
|
||||
val httpUrl = "http://$url".toHttpUrl()
|
||||
if (httpUrl.port == 80) {
|
||||
urls.add("https://$url")
|
||||
urls.add("http://$url:5055")
|
||||
urls.add("https://$url:5055")
|
||||
} else {
|
||||
urls.add("https://$url")
|
||||
}
|
||||
}
|
||||
return urls.map { cleanUrl(it).toHttpUrl() }
|
||||
}
|
||||
|
||||
private fun cleanUrl(url: String) =
|
||||
if (!url.endsWith("/api/v1")) {
|
||||
url
|
||||
.toHttpUrl()
|
||||
.newBuilder()
|
||||
.apply {
|
||||
addPathSegment("api")
|
||||
addPathSegment("v1")
|
||||
}.build()
|
||||
.toString()
|
||||
} else {
|
||||
url
|
||||
}
|
||||
|
|
|
|||
|
|
@ -897,8 +897,6 @@ components:
|
|||
- Bloopers
|
||||
site:
|
||||
type: string
|
||||
enum:
|
||||
- 'YouTube'
|
||||
MovieDetails:
|
||||
type: object
|
||||
properties:
|
||||
|
|
@ -1226,6 +1224,10 @@ components:
|
|||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/WatchProviders'
|
||||
relatedVideos:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/RelatedVideo'
|
||||
MediaRequest:
|
||||
type: object
|
||||
properties:
|
||||
|
|
|
|||
|
|
@ -0,0 +1,65 @@
|
|||
package com.github.damontecres.wholphin.test
|
||||
|
||||
import com.github.damontecres.wholphin.ui.setup.seerr.createUrls
|
||||
import org.junit.Assert
|
||||
import org.junit.Test
|
||||
|
||||
class TestSeerr {
|
||||
@Test
|
||||
fun testCreateUrls() {
|
||||
val urls =
|
||||
createUrls("jellyseerr.com")
|
||||
.map { it.toString() }
|
||||
|
||||
val expected =
|
||||
listOf(
|
||||
"http://jellyseerr.com/api/v1",
|
||||
"https://jellyseerr.com/api/v1",
|
||||
"http://jellyseerr.com:5055/api/v1",
|
||||
"https://jellyseerr.com:5055/api/v1",
|
||||
)
|
||||
Assert.assertEquals(expected, urls)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCreateUrls2() {
|
||||
val urls =
|
||||
createUrls("https://jellyseerr.com")
|
||||
.map { it.toString() }
|
||||
|
||||
val expected =
|
||||
listOf(
|
||||
"https://jellyseerr.com/api/v1",
|
||||
"https://jellyseerr.com:5055/api/v1",
|
||||
)
|
||||
Assert.assertEquals(expected, urls)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCreateUrls3() {
|
||||
val urls =
|
||||
createUrls("http://jellyseerr.com")
|
||||
.map { it.toString() }
|
||||
|
||||
val expected =
|
||||
listOf(
|
||||
"http://jellyseerr.com/api/v1",
|
||||
"http://jellyseerr.com:5055/api/v1",
|
||||
)
|
||||
Assert.assertEquals(expected, urls)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testCreateUrls4() {
|
||||
val urls =
|
||||
createUrls("jellyseerr.com:5055")
|
||||
.map { it.toString() }
|
||||
|
||||
val expected =
|
||||
listOf(
|
||||
"http://jellyseerr.com:5055/api/v1",
|
||||
"https://jellyseerr.com:5055/api/v1",
|
||||
)
|
||||
Assert.assertEquals(expected, urls)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue