mirror of
https://github.com/bbara04/Purefin.git
synced 2026-07-23 19:26:50 +00:00
feat: redesign login screen on both app and app-tv
This commit is contained in:
@@ -6,18 +6,25 @@ import android.util.Log
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import hu.bbara.purefin.core.data.PlaybackMethod
|
||||
import hu.bbara.purefin.core.data.PlaybackReportContext
|
||||
import hu.bbara.purefin.core.data.JellyfinServerCandidate
|
||||
import hu.bbara.purefin.core.data.QuickConnectSession
|
||||
import hu.bbara.purefin.core.data.UserSessionRepository
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.flowOn
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jellyfin.sdk.api.client.Response
|
||||
import org.jellyfin.sdk.api.client.extensions.authenticateWithQuickConnect
|
||||
import org.jellyfin.sdk.api.client.extensions.authenticateUserByName
|
||||
import org.jellyfin.sdk.api.client.extensions.genresApi
|
||||
import org.jellyfin.sdk.api.client.extensions.itemsApi
|
||||
import org.jellyfin.sdk.api.client.extensions.mediaInfoApi
|
||||
import org.jellyfin.sdk.api.client.extensions.mediaSegmentsApi
|
||||
import org.jellyfin.sdk.api.client.extensions.playStateApi
|
||||
import org.jellyfin.sdk.api.client.extensions.quickConnectApi
|
||||
import org.jellyfin.sdk.api.client.extensions.suggestionsApi
|
||||
import org.jellyfin.sdk.api.client.extensions.tvShowsApi
|
||||
import org.jellyfin.sdk.api.client.extensions.userApi
|
||||
@@ -26,6 +33,8 @@ import org.jellyfin.sdk.api.client.extensions.userViewsApi
|
||||
import org.jellyfin.sdk.api.client.extensions.videosApi
|
||||
import org.jellyfin.sdk.api.operations.SystemApi
|
||||
import org.jellyfin.sdk.createJellyfin
|
||||
import org.jellyfin.sdk.discovery.RecommendedServerInfo
|
||||
import org.jellyfin.sdk.discovery.RecommendedServerInfoScore
|
||||
import org.jellyfin.sdk.model.ClientInfo
|
||||
import org.jellyfin.sdk.model.api.AuthenticationResult
|
||||
import org.jellyfin.sdk.model.api.BaseItemDto
|
||||
@@ -90,6 +99,30 @@ class JellyfinApiClient @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun discoverServers(): Flow<JellyfinServerCandidate> =
|
||||
jellyfin.discovery.discoverLocalServers()
|
||||
.map { JellyfinServerCandidate(name = it.name, address = it.address) }
|
||||
.flowOn(Dispatchers.IO)
|
||||
|
||||
suspend fun findServer(input: String): JellyfinServerCandidate? = withContext(Dispatchers.IO) {
|
||||
logRequest("findServer") {
|
||||
val address = input.trim()
|
||||
if (address.isBlank()) {
|
||||
return@logRequest null
|
||||
}
|
||||
|
||||
val recommendedServers = jellyfin.discovery.getRecommendedServers(
|
||||
input = address,
|
||||
minimumScore = RecommendedServerInfoScore.OK
|
||||
)
|
||||
val server = recommendedServers.bestServer() ?: return@logRequest null
|
||||
JellyfinServerCandidate(
|
||||
name = server.systemInfo.getOrNull()?.serverName,
|
||||
address = server.address
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun authenticate(
|
||||
url: String,
|
||||
username: String,
|
||||
@@ -106,6 +139,50 @@ class JellyfinApiClient @Inject constructor(
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun isQuickConnectEnabled(url: String): Boolean = withContext(Dispatchers.IO) {
|
||||
logRequest("isQuickConnectEnabled") {
|
||||
val trimmedUrl = url.trim()
|
||||
if (trimmedUrl.isBlank()) {
|
||||
return@logRequest false
|
||||
}
|
||||
api.update(baseUrl = trimmedUrl)
|
||||
api.quickConnectApi.getQuickConnectEnabled().content
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun initiateQuickConnect(url: String): QuickConnectSession? = withContext(Dispatchers.IO) {
|
||||
logRequest("initiateQuickConnect") {
|
||||
val trimmedUrl = url.trim()
|
||||
if (trimmedUrl.isBlank()) {
|
||||
return@logRequest null
|
||||
}
|
||||
api.update(baseUrl = trimmedUrl)
|
||||
api.quickConnectApi.initiateQuickConnect().content.toQuickConnectSession()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun getQuickConnectState(url: String, secret: String): QuickConnectSession? = withContext(Dispatchers.IO) {
|
||||
logRequest("getQuickConnectState") {
|
||||
val trimmedUrl = url.trim()
|
||||
if (trimmedUrl.isBlank() || secret.isBlank()) {
|
||||
return@logRequest null
|
||||
}
|
||||
api.update(baseUrl = trimmedUrl)
|
||||
api.quickConnectApi.getQuickConnectState(secret).content.toQuickConnectSession()
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun authenticateWithQuickConnect(url: String, secret: String): AuthenticationResult? = withContext(Dispatchers.IO) {
|
||||
logRequest("authenticateWithQuickConnect") {
|
||||
val trimmedUrl = url.trim()
|
||||
if (trimmedUrl.isBlank() || secret.isBlank()) {
|
||||
return@logRequest null
|
||||
}
|
||||
api.update(baseUrl = trimmedUrl)
|
||||
api.userApi.authenticateWithQuickConnect(secret).content
|
||||
}
|
||||
}
|
||||
|
||||
suspend fun searchBySearchTerm(searchTerm: String): List<BaseItemDto> = withContext(Dispatchers.IO) {
|
||||
logRequest("searchBySearchTerm") {
|
||||
if (!ensureConfigured()) {
|
||||
@@ -547,6 +624,18 @@ class JellyfinApiClient @Inject constructor(
|
||||
PlaybackMethod.TRANSCODE -> PlayMethod.TRANSCODE
|
||||
}
|
||||
|
||||
private fun Collection<RecommendedServerInfo>.bestServer(): RecommendedServerInfo? =
|
||||
firstOrNull { it.score == RecommendedServerInfoScore.GREAT }
|
||||
?: firstOrNull { it.score == RecommendedServerInfoScore.GOOD }
|
||||
?: firstOrNull { it.score == RecommendedServerInfoScore.OK }
|
||||
|
||||
private fun org.jellyfin.sdk.model.api.QuickConnectResult.toQuickConnectSession(): QuickConnectSession =
|
||||
QuickConnectSession(
|
||||
code = code,
|
||||
secret = secret,
|
||||
authenticated = authenticated
|
||||
)
|
||||
|
||||
companion object {
|
||||
private const val TAG = "JellyfinApiClient"
|
||||
}
|
||||
|
||||
@@ -2,8 +2,12 @@ package hu.bbara.purefin.data.jellyfin.session
|
||||
|
||||
import android.util.Log
|
||||
import hu.bbara.purefin.core.data.AuthenticationRepository
|
||||
import hu.bbara.purefin.core.data.JellyfinServerCandidate
|
||||
import hu.bbara.purefin.core.data.QuickConnectSession
|
||||
import hu.bbara.purefin.core.data.UserSessionRepository
|
||||
import hu.bbara.purefin.data.jellyfin.client.JellyfinApiClient
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import org.jellyfin.sdk.model.api.AuthenticationResult
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
|
||||
@@ -12,23 +16,61 @@ class JellyfinAuthenticationRepository @Inject constructor(
|
||||
private val jellyfinApiClient: JellyfinApiClient,
|
||||
private val userSessionRepository: UserSessionRepository,
|
||||
) : AuthenticationRepository {
|
||||
override fun discoverServers(): Flow<JellyfinServerCandidate> =
|
||||
jellyfinApiClient.discoverServers()
|
||||
|
||||
override suspend fun findServer(input: String): JellyfinServerCandidate? =
|
||||
runCatching { jellyfinApiClient.findServer(input) }
|
||||
.onFailure { Log.e(TAG, "Server search failed", it) }
|
||||
.getOrNull()
|
||||
|
||||
override suspend fun isQuickConnectEnabled(url: String): Boolean =
|
||||
runCatching { jellyfinApiClient.isQuickConnectEnabled(url) }
|
||||
.onFailure { Log.e(TAG, "Quick Connect check failed", it) }
|
||||
.getOrDefault(false)
|
||||
|
||||
override suspend fun initiateQuickConnect(url: String): QuickConnectSession? =
|
||||
runCatching { jellyfinApiClient.initiateQuickConnect(url) }
|
||||
.onFailure { Log.e(TAG, "Quick Connect initiation failed", it) }
|
||||
.getOrNull()
|
||||
|
||||
override suspend fun getQuickConnectState(url: String, secret: String): QuickConnectSession? =
|
||||
runCatching { jellyfinApiClient.getQuickConnectState(url, secret) }
|
||||
.onFailure { Log.e(TAG, "Quick Connect state check failed", it) }
|
||||
.getOrNull()
|
||||
|
||||
override suspend fun login(url: String, username: String, password: String): Boolean {
|
||||
return try {
|
||||
val authResult = jellyfinApiClient.authenticate(url = url, username = username, password = password)
|
||||
?: return false
|
||||
val token = authResult.accessToken ?: return false
|
||||
val userId = authResult.user?.id ?: return false
|
||||
|
||||
userSessionRepository.setAccessToken(accessToken = token)
|
||||
userSessionRepository.setUserId(userId)
|
||||
userSessionRepository.setLoggedIn(true)
|
||||
true
|
||||
saveAuthenticationResult(authResult)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Login failed", e)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun loginWithQuickConnect(url: String, secret: String): Boolean {
|
||||
return try {
|
||||
val authResult = jellyfinApiClient.authenticateWithQuickConnect(url = url, secret = secret)
|
||||
?: return false
|
||||
saveAuthenticationResult(authResult)
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Quick Connect login failed", e)
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun saveAuthenticationResult(authResult: AuthenticationResult): Boolean {
|
||||
val token = authResult.accessToken ?: return false
|
||||
val userId = authResult.user?.id ?: return false
|
||||
|
||||
userSessionRepository.setAccessToken(accessToken = token)
|
||||
userSessionRepository.setUserId(userId)
|
||||
userSessionRepository.setLoggedIn(true)
|
||||
return true
|
||||
}
|
||||
|
||||
private companion object {
|
||||
private const val TAG = "JellyfinAuthRepo"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user