Log in with Quick Connect (#234)
* Log in with Quick Connect * Clean up LoginViewModel * Cancel Quick Connect by tapping the button again * Make quickConnectJob private
This commit is contained in:
parent
76121925d7
commit
f107e79b72
5 changed files with 134 additions and 15 deletions
|
@ -1,10 +1,12 @@
|
|||
package dev.jdtech.jellyfin.fragments
|
||||
|
||||
import android.os.Bundle
|
||||
import android.util.TypedValue
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.inputmethod.EditorInfo
|
||||
import androidx.annotation.ColorInt
|
||||
import androidx.appcompat.widget.AppCompatEditText
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.fragment.app.Fragment
|
||||
|
@ -19,6 +21,7 @@ import dev.jdtech.jellyfin.adapters.UserLoginListAdapter
|
|||
import dev.jdtech.jellyfin.database.ServerDatabaseDao
|
||||
import dev.jdtech.jellyfin.databinding.FragmentLoginBinding
|
||||
import dev.jdtech.jellyfin.AppPreferences
|
||||
import dev.jdtech.jellyfin.R
|
||||
import dev.jdtech.jellyfin.viewmodels.LoginViewModel
|
||||
import javax.inject.Inject
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -66,6 +69,10 @@ class LoginFragment : Fragment() {
|
|||
login()
|
||||
}
|
||||
|
||||
binding.buttonQuickconnect.setOnClickListener {
|
||||
viewModel.useQuickConnect()
|
||||
}
|
||||
|
||||
binding.usersRecyclerView.adapter = UserLoginListAdapter { user ->
|
||||
(binding.editTextUsername as AppCompatEditText).setText(user.name)
|
||||
(binding.editTextPassword as AppCompatEditText).requestFocus()
|
||||
|
@ -95,6 +102,32 @@ class LoginFragment : Fragment() {
|
|||
}
|
||||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
viewModel.quickConnectUiState.collect { quickConnectUiState ->
|
||||
when (quickConnectUiState) {
|
||||
is LoginViewModel.QuickConnectUiState.Disabled -> {
|
||||
binding.buttonQuickconnectLayout.isVisible = false
|
||||
}
|
||||
is LoginViewModel.QuickConnectUiState.Normal -> {
|
||||
binding.buttonQuickconnectLayout.isVisible = true
|
||||
binding.buttonQuickconnect.text = resources.getString(R.string.quick_connect)
|
||||
val typedValue = TypedValue()
|
||||
requireActivity().theme.resolveAttribute(R.attr.colorPrimary, typedValue, true)
|
||||
@ColorInt val textColor: Int = typedValue.data
|
||||
binding.buttonQuickconnect.setTextColor(textColor)
|
||||
binding.buttonQuickconnectProgress.isVisible = false
|
||||
}
|
||||
is LoginViewModel.QuickConnectUiState.Waiting -> {
|
||||
binding.buttonQuickconnect.text = quickConnectUiState.code
|
||||
binding.buttonQuickconnect.setTextColor(resources.getColor(android.R.color.white, requireActivity().theme))
|
||||
binding.buttonQuickconnectProgress.isVisible = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
viewModel.navigateToMain.collect {
|
||||
|
|
|
@ -116,6 +116,31 @@
|
|||
android:visibility="invisible" />
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/button_quickconnect_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="24dp"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<Button
|
||||
android:id="@+id/button_quickconnect"
|
||||
style="@style/Widget.Material3.Button.ElevatedButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="@string/quick_connect" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/button_quickconnect_progress"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:elevation="8dp"
|
||||
android:indeterminateTint="?attr/colorPrimary"
|
||||
android:padding="8dp"
|
||||
android:visibility="invisible" />
|
||||
</RelativeLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
|
|
@ -12,13 +12,17 @@ import dev.jdtech.jellyfin.AppPreferences
|
|||
import javax.inject.Inject
|
||||
import kotlin.Exception
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jellyfin.sdk.api.client.extensions.authenticateWithQuickConnect
|
||||
import org.jellyfin.sdk.model.api.AuthenticateUserByName
|
||||
import org.jellyfin.sdk.model.api.AuthenticationResult
|
||||
|
||||
@HiltViewModel
|
||||
class LoginViewModel
|
||||
|
@ -32,9 +36,13 @@ constructor(
|
|||
val uiState = _uiState.asStateFlow()
|
||||
private val _usersState = MutableStateFlow<UsersState>(UsersState.Loading)
|
||||
val usersState = _usersState.asStateFlow()
|
||||
private val _quickConnectUiState = MutableStateFlow<QuickConnectUiState>(QuickConnectUiState.Disabled)
|
||||
val quickConnectUiState = _quickConnectUiState.asStateFlow()
|
||||
private val _navigateToMain = MutableSharedFlow<Boolean>()
|
||||
val navigateToMain = _navigateToMain.asSharedFlow()
|
||||
|
||||
private var quickConnectJob: Job? = null
|
||||
|
||||
sealed class UiState {
|
||||
object Normal : UiState()
|
||||
object Loading : UiState()
|
||||
|
@ -46,8 +54,15 @@ constructor(
|
|||
data class Users(val users: List<User>) : UsersState()
|
||||
}
|
||||
|
||||
sealed class QuickConnectUiState {
|
||||
object Disabled : QuickConnectUiState()
|
||||
object Normal : QuickConnectUiState()
|
||||
data class Waiting(val code: String) : QuickConnectUiState()
|
||||
}
|
||||
|
||||
init {
|
||||
loadPublicUsers()
|
||||
loadQuickConnectAvailable()
|
||||
}
|
||||
|
||||
private fun loadPublicUsers() {
|
||||
|
@ -74,6 +89,17 @@ constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun loadQuickConnectAvailable() {
|
||||
viewModelScope.launch {
|
||||
try {
|
||||
val isEnabled by jellyfinApi.quickConnectApi.getEnabled()
|
||||
if (isEnabled) {
|
||||
_quickConnectUiState.emit(QuickConnectUiState.Normal)
|
||||
}
|
||||
} catch (_: Exception) {}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a authentication request to the Jellyfin server
|
||||
*
|
||||
|
@ -92,21 +118,7 @@ constructor(
|
|||
)
|
||||
)
|
||||
|
||||
val serverInfo by jellyfinApi.systemApi.getPublicSystemInfo()
|
||||
|
||||
val user = User(
|
||||
id = authenticationResult.user!!.id,
|
||||
name = authenticationResult.user!!.name!!,
|
||||
serverId = serverInfo.id!!,
|
||||
accessToken = authenticationResult.accessToken!!
|
||||
)
|
||||
|
||||
insertUser(appPreferences.currentServer!!, user)
|
||||
|
||||
jellyfinApi.apply {
|
||||
api.accessToken = authenticationResult.accessToken
|
||||
userId = authenticationResult.user?.id
|
||||
}
|
||||
saveAuthenticationResult(authenticationResult)
|
||||
|
||||
_uiState.emit(UiState.Normal)
|
||||
_navigateToMain.emit(true)
|
||||
|
@ -120,6 +132,52 @@ constructor(
|
|||
}
|
||||
}
|
||||
|
||||
fun useQuickConnect() {
|
||||
if (quickConnectJob != null && quickConnectJob!!.isActive) {
|
||||
quickConnectJob!!.cancel()
|
||||
return
|
||||
}
|
||||
quickConnectJob = viewModelScope.launch {
|
||||
try {
|
||||
var quickConnectState = jellyfinApi.quickConnectApi.initiate().content
|
||||
_quickConnectUiState.emit(QuickConnectUiState.Waiting(quickConnectState.code))
|
||||
|
||||
while (!quickConnectState.authenticated) {
|
||||
quickConnectState = jellyfinApi.quickConnectApi.connect(quickConnectState.secret).content
|
||||
delay(5000L)
|
||||
}
|
||||
val authenticationResult by jellyfinApi.userApi.authenticateWithQuickConnect(
|
||||
secret = quickConnectState.secret
|
||||
)
|
||||
|
||||
saveAuthenticationResult(authenticationResult)
|
||||
|
||||
_quickConnectUiState.emit(QuickConnectUiState.Normal)
|
||||
_navigateToMain.emit(true)
|
||||
} catch (_: Exception) {
|
||||
_quickConnectUiState.emit(QuickConnectUiState.Normal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun saveAuthenticationResult(authenticationResult: AuthenticationResult) {
|
||||
val serverInfo by jellyfinApi.systemApi.getPublicSystemInfo()
|
||||
|
||||
val user = User(
|
||||
id = authenticationResult.user!!.id,
|
||||
name = authenticationResult.user!!.name!!,
|
||||
serverId = serverInfo.id!!,
|
||||
accessToken = authenticationResult.accessToken!!
|
||||
)
|
||||
|
||||
insertUser(appPreferences.currentServer!!, user)
|
||||
|
||||
jellyfinApi.apply {
|
||||
api.accessToken = authenticationResult.accessToken
|
||||
userId = authenticationResult.user?.id
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun insertUser(serverId: String, user: User) {
|
||||
withContext(Dispatchers.IO) {
|
||||
database.insertUser(user)
|
||||
|
|
|
@ -147,4 +147,5 @@
|
|||
<string name="add_address">Add address</string>
|
||||
<string name="add_server_address">Add server address</string>
|
||||
<string name="add">Add</string>
|
||||
<string name="quick_connect">Quick Connect</string>
|
||||
</resources>
|
|
@ -9,6 +9,7 @@ import org.jellyfin.sdk.api.client.extensions.devicesApi
|
|||
import org.jellyfin.sdk.api.client.extensions.itemsApi
|
||||
import org.jellyfin.sdk.api.client.extensions.mediaInfoApi
|
||||
import org.jellyfin.sdk.api.client.extensions.playStateApi
|
||||
import org.jellyfin.sdk.api.client.extensions.quickConnectApi
|
||||
import org.jellyfin.sdk.api.client.extensions.sessionApi
|
||||
import org.jellyfin.sdk.api.client.extensions.systemApi
|
||||
import org.jellyfin.sdk.api.client.extensions.tvShowsApi
|
||||
|
@ -57,6 +58,7 @@ class JellyfinApi(
|
|||
val videosApi = api.videosApi
|
||||
val mediaInfoApi = api.mediaInfoApi
|
||||
val playStateApi = api.playStateApi
|
||||
val quickConnectApi = api.quickConnectApi
|
||||
|
||||
companion object {
|
||||
@Volatile
|
||||
|
|
Loading…
Reference in a new issue