Merge remote-tracking branch 'upstream/main'
This commit is contained in:
commit
722267ced2
38 changed files with 307 additions and 232 deletions
|
@ -1,7 +1,6 @@
|
|||
package dev.jdtech.jellyfin
|
||||
|
||||
import android.os.Bundle
|
||||
import android.os.Environment
|
||||
import android.view.View
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
|
@ -52,7 +51,6 @@ class MainActivity : AppCompatActivity() {
|
|||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
scheduleUserDataSync()
|
||||
cleanUpOldDownloads()
|
||||
applyTheme()
|
||||
setupActivity()
|
||||
}
|
||||
|
@ -148,31 +146,6 @@ class MainActivity : AppCompatActivity() {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Temp to remove old downloads, will be removed in a future version
|
||||
*/
|
||||
private fun cleanUpOldDownloads() {
|
||||
if (appPreferences.downloadsMigrated) {
|
||||
return
|
||||
}
|
||||
|
||||
lifecycleScope.launch {
|
||||
val oldDir = applicationContext.getExternalFilesDir(Environment.DIRECTORY_MOVIES)
|
||||
if (oldDir == null) {
|
||||
appPreferences.downloadsMigrated = true
|
||||
return@launch
|
||||
}
|
||||
|
||||
try {
|
||||
for (file in oldDir.listFiles()!!) {
|
||||
file.delete()
|
||||
}
|
||||
} catch (_: Exception) {}
|
||||
|
||||
appPreferences.downloadsMigrated = true
|
||||
}
|
||||
}
|
||||
|
||||
private fun scheduleUserDataSync() {
|
||||
val syncWorkRequest = OneTimeWorkRequestBuilder<SyncWorker>()
|
||||
.setConstraints(
|
||||
|
|
|
@ -42,6 +42,7 @@ import dev.jdtech.jellyfin.mpv.TrackType
|
|||
import dev.jdtech.jellyfin.utils.PlayerGestureHelper
|
||||
import dev.jdtech.jellyfin.utils.PreviewScrubListener
|
||||
import dev.jdtech.jellyfin.viewmodels.PlayerActivityViewModel
|
||||
import dev.jdtech.jellyfin.viewmodels.PlayerEvents
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
@ -169,8 +170,10 @@ class PlayerActivity : BasePlayerActivity() {
|
|||
}
|
||||
|
||||
launch {
|
||||
viewModel.navigateBack.collect {
|
||||
if (it) finish()
|
||||
viewModel.eventsChannelFlow.collect { event ->
|
||||
when (event) {
|
||||
is PlayerEvents.NavigateBack -> finish()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -17,6 +17,7 @@ import androidx.navigation.fragment.findNavController
|
|||
import dagger.hilt.android.AndroidEntryPoint
|
||||
import dev.jdtech.jellyfin.adapters.DiscoveredServerListAdapter
|
||||
import dev.jdtech.jellyfin.databinding.FragmentAddServerBinding
|
||||
import dev.jdtech.jellyfin.viewmodels.AddServerEvent
|
||||
import dev.jdtech.jellyfin.viewmodels.AddServerViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
@ -81,9 +82,9 @@ class AddServerFragment : Fragment() {
|
|||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
viewModel.navigateToLogin.collect {
|
||||
if (it) {
|
||||
navigateToLoginFragment()
|
||||
viewModel.eventsChannelFlow.collect { event ->
|
||||
when (event) {
|
||||
is AddServerEvent.NavigateToLogin -> navigateToLoginFragment()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import dev.jdtech.jellyfin.models.FindroidItem
|
|||
import dev.jdtech.jellyfin.models.FindroidMovie
|
||||
import dev.jdtech.jellyfin.models.FindroidShow
|
||||
import dev.jdtech.jellyfin.utils.restart
|
||||
import dev.jdtech.jellyfin.viewmodels.DownloadsEvent
|
||||
import dev.jdtech.jellyfin.viewmodels.DownloadsViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
@ -48,7 +49,9 @@ class DownloadsFragment : Fragment() {
|
|||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
launch {
|
||||
viewModel.connectionError.collect {
|
||||
viewModel.eventsChannelFlow.collect { event ->
|
||||
when (event) {
|
||||
is DownloadsEvent.ConnectionError -> {
|
||||
Snackbar.make(binding.root, CoreR.string.no_server_connection, Snackbar.LENGTH_INDEFINITE)
|
||||
.setTextMaxLines(2)
|
||||
.setAction(CoreR.string.offline_mode) {
|
||||
|
@ -58,6 +61,8 @@ class DownloadsFragment : Fragment() {
|
|||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
launch {
|
||||
viewModel.uiState.collect { uiState ->
|
||||
Timber.d("$uiState")
|
||||
|
|
|
@ -33,6 +33,7 @@ import dev.jdtech.jellyfin.models.UiText
|
|||
import dev.jdtech.jellyfin.models.isDownloaded
|
||||
import dev.jdtech.jellyfin.models.isDownloading
|
||||
import dev.jdtech.jellyfin.utils.setIconTintColorAttribute
|
||||
import dev.jdtech.jellyfin.viewmodels.EpisodeBottomSheetEvent
|
||||
import dev.jdtech.jellyfin.viewmodels.EpisodeBottomSheetViewModel
|
||||
import dev.jdtech.jellyfin.viewmodels.PlayerViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -122,15 +123,12 @@ class EpisodeBottomSheetFragment : BottomSheetDialogFragment() {
|
|||
}
|
||||
|
||||
launch {
|
||||
viewModel.downloadError.collect { uiText ->
|
||||
createErrorDialog(uiText)
|
||||
viewModel.eventsChannelFlow.collect { event ->
|
||||
when (event) {
|
||||
is EpisodeBottomSheetEvent.NavigateBack -> findNavController().navigateUp()
|
||||
is EpisodeBottomSheetEvent.DownloadError -> createErrorDialog(event.uiText)
|
||||
}
|
||||
}
|
||||
|
||||
launch {
|
||||
viewModel.navigateBack.collect {
|
||||
if (it) findNavController().navigateUp()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -19,6 +19,7 @@ import dev.jdtech.jellyfin.AppPreferences
|
|||
import dev.jdtech.jellyfin.adapters.UserLoginListAdapter
|
||||
import dev.jdtech.jellyfin.database.ServerDatabaseDao
|
||||
import dev.jdtech.jellyfin.databinding.FragmentLoginBinding
|
||||
import dev.jdtech.jellyfin.viewmodels.LoginEvent
|
||||
import dev.jdtech.jellyfin.viewmodels.LoginViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
@ -123,9 +124,9 @@ class LoginFragment : Fragment() {
|
|||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
viewModel.navigateToMain.collect {
|
||||
if (it) {
|
||||
navigateToHomeFragment()
|
||||
viewModel.eventsChannelFlow.collect { event ->
|
||||
when (event) {
|
||||
is LoginEvent.NavigateToHome -> navigateToHomeFragment()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ import dev.jdtech.jellyfin.models.isDownloaded
|
|||
import dev.jdtech.jellyfin.models.isDownloading
|
||||
import dev.jdtech.jellyfin.utils.checkIfLoginRequired
|
||||
import dev.jdtech.jellyfin.utils.setIconTintColorAttribute
|
||||
import dev.jdtech.jellyfin.viewmodels.MovieEvent
|
||||
import dev.jdtech.jellyfin.viewmodels.MovieViewModel
|
||||
import dev.jdtech.jellyfin.viewmodels.PlayerViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
|
@ -118,15 +119,12 @@ class MovieFragment : Fragment() {
|
|||
}
|
||||
|
||||
launch {
|
||||
viewModel.downloadError.collect { uiText ->
|
||||
createErrorDialog(uiText)
|
||||
viewModel.eventsChannelFlow.collect { event ->
|
||||
when (event) {
|
||||
is MovieEvent.NavigateBack -> findNavController().navigateUp()
|
||||
is MovieEvent.DownloadError -> createErrorDialog(event.uiText)
|
||||
}
|
||||
}
|
||||
|
||||
launch {
|
||||
viewModel.navigateBack.collect {
|
||||
if (it) findNavController().navigateUp()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ import dev.jdtech.jellyfin.models.FindroidEpisode
|
|||
import dev.jdtech.jellyfin.models.PlayerItem
|
||||
import dev.jdtech.jellyfin.utils.checkIfLoginRequired
|
||||
import dev.jdtech.jellyfin.viewmodels.PlayerViewModel
|
||||
import dev.jdtech.jellyfin.viewmodels.SeasonEvent
|
||||
import dev.jdtech.jellyfin.viewmodels.SeasonViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
@ -60,8 +61,10 @@ class SeasonFragment : Fragment() {
|
|||
}
|
||||
|
||||
launch {
|
||||
viewModel.navigateBack.collect {
|
||||
if (it) findNavController().navigateUp()
|
||||
viewModel.eventsChannelFlow.collect { event ->
|
||||
when (event) {
|
||||
is SeasonEvent.NavigateBack -> findNavController().navigateUp()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import dev.jdtech.jellyfin.adapters.ServerAddressAdapter
|
|||
import dev.jdtech.jellyfin.databinding.FragmentServerAddressesBinding
|
||||
import dev.jdtech.jellyfin.dialogs.AddServerAddressDialog
|
||||
import dev.jdtech.jellyfin.dialogs.DeleteServerAddressDialog
|
||||
import dev.jdtech.jellyfin.viewmodels.ServerAddressesEvent
|
||||
import dev.jdtech.jellyfin.viewmodels.ServerAddressesViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
@ -57,9 +58,9 @@ class ServerAddressesFragment : Fragment() {
|
|||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
viewModel.navigateToMain.collect {
|
||||
if (it) {
|
||||
navigateToMainActivity()
|
||||
viewModel.eventsChannelFlow.collect { event ->
|
||||
when (event) {
|
||||
is ServerAddressesEvent.NavigateToHome -> navigateToMainActivity()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ import dagger.hilt.android.AndroidEntryPoint
|
|||
import dev.jdtech.jellyfin.adapters.ServerGridAdapter
|
||||
import dev.jdtech.jellyfin.databinding.FragmentServerSelectBinding
|
||||
import dev.jdtech.jellyfin.dialogs.DeleteServerDialogFragment
|
||||
import dev.jdtech.jellyfin.viewmodels.ServerSelectEvent
|
||||
import dev.jdtech.jellyfin.viewmodels.ServerSelectViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
@ -61,14 +62,12 @@ class ServerSelectFragment : Fragment() {
|
|||
}
|
||||
}
|
||||
launch {
|
||||
viewModel.navigateToMain.collect {
|
||||
if (it) navigateToMainActivity()
|
||||
viewModel.eventsChannelFlow.collect { event ->
|
||||
when (event) {
|
||||
is ServerSelectEvent.NavigateToHome -> navigateToMainActivity()
|
||||
is ServerSelectEvent.NavigateToLogin -> navigateToLoginFragment()
|
||||
}
|
||||
}
|
||||
launch {
|
||||
viewModel.navigateToLogin.collect {
|
||||
if (it) navigateToLoginFragment()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,6 +32,7 @@ import dev.jdtech.jellyfin.models.isDownloaded
|
|||
import dev.jdtech.jellyfin.utils.checkIfLoginRequired
|
||||
import dev.jdtech.jellyfin.utils.setIconTintColorAttribute
|
||||
import dev.jdtech.jellyfin.viewmodels.PlayerViewModel
|
||||
import dev.jdtech.jellyfin.viewmodels.ShowEvent
|
||||
import dev.jdtech.jellyfin.viewmodels.ShowViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
@ -79,8 +80,10 @@ class ShowFragment : Fragment() {
|
|||
}
|
||||
|
||||
launch {
|
||||
viewModel.navigateBack.collect {
|
||||
if (it) findNavController().navigateUp()
|
||||
viewModel.eventsChannelFlow.collect { event ->
|
||||
when (event) {
|
||||
is ShowEvent.NavigateBack -> findNavController().navigateUp()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -16,6 +16,7 @@ import dev.jdtech.jellyfin.AppNavigationDirections
|
|||
import dev.jdtech.jellyfin.adapters.UserListAdapter
|
||||
import dev.jdtech.jellyfin.databinding.FragmentUsersBinding
|
||||
import dev.jdtech.jellyfin.dialogs.DeleteUserDialogFragment
|
||||
import dev.jdtech.jellyfin.viewmodels.UsersEvent
|
||||
import dev.jdtech.jellyfin.viewmodels.UsersViewModel
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
|
@ -54,9 +55,9 @@ class UsersFragment : Fragment() {
|
|||
|
||||
viewLifecycleOwner.lifecycleScope.launch {
|
||||
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
viewModel.navigateToMain.collect {
|
||||
if (it) {
|
||||
navigateToMainActivity()
|
||||
viewModel.eventsChannelFlow.collect { event ->
|
||||
when (event) {
|
||||
is UsersEvent.NavigateToHome -> navigateToMainActivity()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -25,6 +25,6 @@ object DatabaseModule {
|
|||
.fallbackToDestructiveMigration()
|
||||
.allowMainThreadQueries()
|
||||
.build()
|
||||
.serverDatabaseDao
|
||||
.getServerDatabaseDao()
|
||||
}
|
||||
}
|
||||
|
|
|
@ -152,9 +152,11 @@ class DownloaderImpl(
|
|||
val remainingEpisodes = database.getEpisodesBySeasonId(item.seasonId)
|
||||
if (remainingEpisodes.isEmpty()) {
|
||||
database.deleteSeason(item.seasonId)
|
||||
database.deleteUserData(item.seasonId)
|
||||
val remainingSeasons = database.getSeasonsByShowId(item.seriesId)
|
||||
if (remainingSeasons.isEmpty()) {
|
||||
database.deleteShow(item.seriesId)
|
||||
database.deleteUserData(item.seriesId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,10 +15,10 @@ import dev.jdtech.jellyfin.models.ServerAddress
|
|||
import dev.jdtech.jellyfin.models.UiText
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jellyfin.sdk.discovery.RecommendedServerInfo
|
||||
|
@ -38,11 +38,12 @@ constructor(
|
|||
) : ViewModel() {
|
||||
private val _uiState = MutableStateFlow<UiState>(UiState.Normal)
|
||||
val uiState = _uiState.asStateFlow()
|
||||
private val _navigateToLogin = MutableSharedFlow<Boolean>()
|
||||
val navigateToLogin = _navigateToLogin.asSharedFlow()
|
||||
private val _discoveredServersState = MutableStateFlow<DiscoveredServersState>(DiscoveredServersState.Loading)
|
||||
val discoveredServersState = _discoveredServersState.asStateFlow()
|
||||
|
||||
private val eventsChannel = Channel<AddServerEvent>()
|
||||
val eventsChannelFlow = eventsChannel.receiveAsFlow()
|
||||
|
||||
private val discoveredServers = mutableListOf<DiscoveredServer>()
|
||||
private var serverFound = false
|
||||
|
||||
|
@ -206,7 +207,7 @@ constructor(
|
|||
}
|
||||
|
||||
_uiState.emit(UiState.Normal)
|
||||
_navigateToLogin.emit(true)
|
||||
eventsChannel.send(AddServerEvent.NavigateToLogin)
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -269,3 +270,7 @@ constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface AddServerEvent {
|
||||
data object NavigateToLogin : AddServerEvent
|
||||
}
|
||||
|
|
|
@ -11,11 +11,11 @@ import dev.jdtech.jellyfin.models.FindroidMovie
|
|||
import dev.jdtech.jellyfin.models.FindroidShow
|
||||
import dev.jdtech.jellyfin.models.UiText
|
||||
import dev.jdtech.jellyfin.repository.JellyfinRepository
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
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.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -28,8 +28,9 @@ constructor(
|
|||
) : ViewModel() {
|
||||
private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
|
||||
val uiState = _uiState.asStateFlow()
|
||||
private val _connectionError = MutableSharedFlow<Exception>()
|
||||
val connectionError = _connectionError.asSharedFlow()
|
||||
|
||||
private val eventsChannel = Channel<DownloadsEvent>()
|
||||
val eventsChannelFlow = eventsChannel.receiveAsFlow()
|
||||
|
||||
sealed class UiState {
|
||||
data class Normal(val sections: List<FavoriteSection>) : UiState()
|
||||
|
@ -49,7 +50,7 @@ constructor(
|
|||
// Give the UI a chance to load
|
||||
delay(100)
|
||||
} catch (e: Exception) {
|
||||
_connectionError.emit(e)
|
||||
eventsChannel.send(DownloadsEvent.ConnectionError(e))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -88,3 +89,7 @@ constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface DownloadsEvent {
|
||||
data class ConnectionError(val error: Exception) : DownloadsEvent
|
||||
}
|
||||
|
|
|
@ -13,10 +13,10 @@ import dev.jdtech.jellyfin.models.UiText
|
|||
import dev.jdtech.jellyfin.models.isDownloading
|
||||
import dev.jdtech.jellyfin.repository.JellyfinRepository
|
||||
import dev.jdtech.jellyfin.utils.Downloader
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import java.io.File
|
||||
import java.util.UUID
|
||||
|
@ -37,11 +37,8 @@ constructor(
|
|||
private val _downloadStatus = MutableStateFlow(Pair(0, 0))
|
||||
val downloadStatus = _downloadStatus.asStateFlow()
|
||||
|
||||
private val _downloadError = MutableSharedFlow<UiText>()
|
||||
val downloadError = _downloadError.asSharedFlow()
|
||||
|
||||
private val _navigateBack = MutableSharedFlow<Boolean>()
|
||||
val navigateBack = _navigateBack.asSharedFlow()
|
||||
private val eventsChannel = Channel<EpisodeBottomSheetEvent>()
|
||||
val eventsChannelFlow = eventsChannel.receiveAsFlow()
|
||||
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
|
||||
|
@ -75,7 +72,7 @@ constructor(
|
|||
)
|
||||
} catch (_: NullPointerException) {
|
||||
// Navigate back because item does not exist (probably because it's been deleted)
|
||||
_navigateBack.emit(true)
|
||||
eventsChannel.send(EpisodeBottomSheetEvent.NavigateBack)
|
||||
} catch (e: Exception) {
|
||||
_uiState.emit(UiState.Error(e))
|
||||
}
|
||||
|
@ -133,7 +130,7 @@ constructor(
|
|||
_downloadStatus.emit(Pair(10, Random.nextInt()))
|
||||
|
||||
if (result.second != null) {
|
||||
_downloadError.emit(result.second!!)
|
||||
eventsChannel.send(EpisodeBottomSheetEvent.DownloadError(result.second!!))
|
||||
}
|
||||
|
||||
loadEpisode(item.id)
|
||||
|
@ -188,3 +185,8 @@ constructor(
|
|||
handler.removeCallbacksAndMessages(null)
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface EpisodeBottomSheetEvent {
|
||||
data object NavigateBack : EpisodeBottomSheetEvent
|
||||
data class DownloadError(val uiText: UiText) : EpisodeBottomSheetEvent
|
||||
}
|
||||
|
|
|
@ -11,11 +11,11 @@ import dev.jdtech.jellyfin.models.UiText
|
|||
import dev.jdtech.jellyfin.models.User
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
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.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jellyfin.sdk.api.client.extensions.authenticateWithQuickConnect
|
||||
|
@ -38,8 +38,9 @@ constructor(
|
|||
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 val eventsChannel = Channel<LoginEvent>()
|
||||
val eventsChannelFlow = eventsChannel.receiveAsFlow()
|
||||
|
||||
private var quickConnectJob: Job? = null
|
||||
|
||||
|
@ -121,7 +122,7 @@ constructor(
|
|||
saveAuthenticationResult(authenticationResult)
|
||||
|
||||
_uiState.emit(UiState.Normal)
|
||||
_navigateToMain.emit(true)
|
||||
eventsChannel.send(LoginEvent.NavigateToHome)
|
||||
} catch (e: Exception) {
|
||||
val message =
|
||||
if (e.message?.contains("401") == true) {
|
||||
|
@ -157,7 +158,7 @@ constructor(
|
|||
saveAuthenticationResult(authenticationResult)
|
||||
|
||||
_quickConnectUiState.emit(QuickConnectUiState.Normal)
|
||||
_navigateToMain.emit(true)
|
||||
eventsChannel.send(LoginEvent.NavigateToHome)
|
||||
} catch (_: Exception) {
|
||||
_quickConnectUiState.emit(QuickConnectUiState.Normal)
|
||||
}
|
||||
|
@ -189,3 +190,7 @@ constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface LoginEvent {
|
||||
data object NavigateToHome : LoginEvent
|
||||
}
|
||||
|
|
|
@ -21,10 +21,10 @@ import dev.jdtech.jellyfin.repository.JellyfinRepository
|
|||
import dev.jdtech.jellyfin.utils.Downloader
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Runnable
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jellyfin.sdk.model.api.BaseItemPerson
|
||||
|
@ -49,11 +49,8 @@ constructor(
|
|||
private val _downloadStatus = MutableStateFlow(Pair(0, 0))
|
||||
val downloadStatus = _downloadStatus.asStateFlow()
|
||||
|
||||
private val _downloadError = MutableSharedFlow<UiText>()
|
||||
val downloadError = _downloadError.asSharedFlow()
|
||||
|
||||
private val _navigateBack = MutableSharedFlow<Boolean>()
|
||||
val navigateBack = _navigateBack.asSharedFlow()
|
||||
private val eventsChannel = Channel<MovieEvent>()
|
||||
val eventsChannelFlow = eventsChannel.receiveAsFlow()
|
||||
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
|
||||
|
@ -115,7 +112,7 @@ constructor(
|
|||
)
|
||||
} catch (_: NullPointerException) {
|
||||
// Navigate back because item does not exist (probably because it's been deleted)
|
||||
_navigateBack.emit(true)
|
||||
eventsChannel.send(MovieEvent.NavigateBack)
|
||||
} catch (e: Exception) {
|
||||
_uiState.emit(UiState.Error(e))
|
||||
}
|
||||
|
@ -330,7 +327,7 @@ constructor(
|
|||
_downloadStatus.emit(Pair(10, Random.nextInt()))
|
||||
|
||||
if (result.second != null) {
|
||||
_downloadError.emit(result.second!!)
|
||||
eventsChannel.send(MovieEvent.DownloadError(result.second!!))
|
||||
}
|
||||
|
||||
loadData(item.id)
|
||||
|
@ -385,3 +382,8 @@ constructor(
|
|||
handler.removeCallbacksAndMessages(null)
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface MovieEvent {
|
||||
data object NavigateBack : MovieEvent
|
||||
data class DownloadError(val uiText: UiText) : MovieEvent
|
||||
}
|
||||
|
|
|
@ -6,10 +6,10 @@ import dagger.hilt.android.lifecycle.HiltViewModel
|
|||
import dev.jdtech.jellyfin.models.EpisodeItem
|
||||
import dev.jdtech.jellyfin.models.FindroidSeason
|
||||
import dev.jdtech.jellyfin.repository.JellyfinRepository
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import org.jellyfin.sdk.model.api.ItemFields
|
||||
import java.util.UUID
|
||||
|
@ -24,8 +24,8 @@ constructor(
|
|||
private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
|
||||
val uiState = _uiState.asStateFlow()
|
||||
|
||||
private val _navigateBack = MutableSharedFlow<Boolean>()
|
||||
val navigateBack = _navigateBack.asSharedFlow()
|
||||
private val eventsChannel = Channel<SeasonEvent>()
|
||||
val eventsChannelFlow = eventsChannel.receiveAsFlow()
|
||||
|
||||
sealed class UiState {
|
||||
data class Normal(val episodes: List<EpisodeItem>) : UiState()
|
||||
|
@ -44,7 +44,7 @@ constructor(
|
|||
_uiState.emit(UiState.Normal(episodes))
|
||||
} catch (_: NullPointerException) {
|
||||
// Navigate back because item does not exist (probably because it's been deleted)
|
||||
_navigateBack.emit(true)
|
||||
eventsChannel.send(SeasonEvent.NavigateBack)
|
||||
} catch (e: Exception) {
|
||||
_uiState.emit(UiState.Error(e))
|
||||
}
|
||||
|
@ -63,3 +63,7 @@ constructor(
|
|||
return listOf(header) + episodes.map { EpisodeItem.Episode(it) }
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface SeasonEvent {
|
||||
data object NavigateBack : SeasonEvent
|
||||
}
|
||||
|
|
|
@ -7,10 +7,10 @@ import dev.jdtech.jellyfin.api.JellyfinApi
|
|||
import dev.jdtech.jellyfin.database.ServerDatabaseDao
|
||||
import dev.jdtech.jellyfin.models.ServerAddress
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import java.util.UUID
|
||||
|
@ -32,8 +32,8 @@ constructor(
|
|||
data class Error(val error: Exception) : UiState()
|
||||
}
|
||||
|
||||
private val _navigateToMain = MutableSharedFlow<Boolean>()
|
||||
val navigateToMain = _navigateToMain.asSharedFlow()
|
||||
private val eventsChannel = Channel<ServerAddressesEvent>()
|
||||
val eventsChannelFlow = eventsChannel.receiveAsFlow()
|
||||
|
||||
private var currentServerId: String = ""
|
||||
|
||||
|
@ -75,7 +75,7 @@ constructor(
|
|||
|
||||
jellyfinApi.api.baseUrl = address.address
|
||||
|
||||
_navigateToMain.emit(true)
|
||||
eventsChannel.send(ServerAddressesEvent.NavigateToHome)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -87,3 +87,7 @@ constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface ServerAddressesEvent {
|
||||
data object NavigateToHome : ServerAddressesEvent
|
||||
}
|
||||
|
|
|
@ -8,10 +8,10 @@ import dev.jdtech.jellyfin.api.JellyfinApi
|
|||
import dev.jdtech.jellyfin.database.ServerDatabaseDao
|
||||
import dev.jdtech.jellyfin.models.Server
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
|
@ -26,11 +26,8 @@ constructor(
|
|||
private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
|
||||
val uiState = _uiState.asStateFlow()
|
||||
|
||||
private val _navigateToMain = MutableSharedFlow<Boolean>()
|
||||
val navigateToMain = _navigateToMain.asSharedFlow()
|
||||
|
||||
private val _navigateToLogin = MutableSharedFlow<Boolean>()
|
||||
val navigateToLogin = _navigateToLogin.asSharedFlow()
|
||||
private val eventsChannel = Channel<ServerSelectEvent>()
|
||||
val eventsChannelFlow = eventsChannel.receiveAsFlow()
|
||||
|
||||
sealed class UiState {
|
||||
data class Normal(val servers: List<Server>) : UiState()
|
||||
|
@ -75,7 +72,7 @@ constructor(
|
|||
userId = null
|
||||
}
|
||||
appPreferences.currentServer = server.id
|
||||
_navigateToLogin.emit(true)
|
||||
eventsChannel.send(ServerSelectEvent.NavigateToLogin)
|
||||
return@launch
|
||||
}
|
||||
|
||||
|
@ -87,7 +84,12 @@ constructor(
|
|||
|
||||
appPreferences.currentServer = server.id
|
||||
|
||||
_navigateToMain.emit(true)
|
||||
eventsChannel.send(ServerSelectEvent.NavigateToHome)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface ServerSelectEvent {
|
||||
data object NavigateToHome : ServerSelectEvent
|
||||
data object NavigateToLogin : ServerSelectEvent
|
||||
}
|
||||
|
|
|
@ -8,10 +8,10 @@ import dev.jdtech.jellyfin.models.FindroidSeason
|
|||
import dev.jdtech.jellyfin.models.FindroidShow
|
||||
import dev.jdtech.jellyfin.repository.JellyfinRepository
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.jellyfin.sdk.model.api.BaseItemPerson
|
||||
|
@ -27,8 +27,8 @@ constructor(
|
|||
private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
|
||||
val uiState = _uiState.asStateFlow()
|
||||
|
||||
private val _navigateBack = MutableSharedFlow<Boolean>()
|
||||
val navigateBack = _navigateBack.asSharedFlow()
|
||||
private val eventsChannel = Channel<ShowEvent>()
|
||||
val eventsChannelFlow = eventsChannel.receiveAsFlow()
|
||||
|
||||
sealed class UiState {
|
||||
data class Normal(
|
||||
|
@ -93,7 +93,7 @@ constructor(
|
|||
)
|
||||
} catch (_: NullPointerException) {
|
||||
// Navigate back because item does not exist (probably because it's been deleted)
|
||||
_navigateBack.emit(true)
|
||||
eventsChannel.send(ShowEvent.NavigateBack)
|
||||
} catch (e: Exception) {
|
||||
_uiState.emit(UiState.Error(e))
|
||||
}
|
||||
|
@ -189,3 +189,7 @@ constructor(
|
|||
return dateRange.joinToString(separator = " - ")
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface ShowEvent {
|
||||
data object NavigateBack : ShowEvent
|
||||
}
|
||||
|
|
|
@ -7,10 +7,10 @@ import dev.jdtech.jellyfin.api.JellyfinApi
|
|||
import dev.jdtech.jellyfin.database.ServerDatabaseDao
|
||||
import dev.jdtech.jellyfin.models.User
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.asSharedFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
@ -31,8 +31,8 @@ constructor(
|
|||
data class Error(val error: Exception) : UiState()
|
||||
}
|
||||
|
||||
private val _navigateToMain = MutableSharedFlow<Boolean>()
|
||||
val navigateToMain = _navigateToMain.asSharedFlow()
|
||||
private val eventsChannel = Channel<UsersEvent>()
|
||||
val eventsChannelFlow = eventsChannel.receiveAsFlow()
|
||||
|
||||
private var currentServerId: String = ""
|
||||
|
||||
|
@ -77,7 +77,11 @@ constructor(
|
|||
userId = user.id
|
||||
}
|
||||
|
||||
_navigateToMain.emit(true)
|
||||
eventsChannel.send(UsersEvent.NavigateToHome)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface UsersEvent {
|
||||
data object NavigateToHome : UsersEvent
|
||||
}
|
||||
|
|
|
@ -20,7 +20,7 @@
|
|||
<string name="remove">Törlés</string>
|
||||
<string name="cancel">Mégsem</string>
|
||||
<string name="title_home">Kezdőlap</string>
|
||||
<string name="title_media">Én médiám</string>
|
||||
<string name="title_media">Könyvtáraid</string>
|
||||
<string name="title_favorite">Kedvencek</string>
|
||||
<string name="title_settings">Beállítások</string>
|
||||
<string name="view_all">Összes megtekintése</string>
|
||||
|
|
|
@ -173,4 +173,7 @@
|
|||
<string name="remove_server_address">서버 주소 제</string>
|
||||
<string name="temp">임시</string>
|
||||
<string name="subtitle_chip_text">CC</string>
|
||||
<string name="picture_in_picture_gesture">Picture-in-picture 홈 제스쳐</string>
|
||||
<string name="picture_in_picture_gesture_summary">영상 재생 중 홈 버튼 또는 제스쳐를 이용해 picture-in-picture 변경</string>
|
||||
<string name="picture_in_picture">Picture-in-picture 기능</string>
|
||||
</resources>
|
|
@ -139,4 +139,41 @@
|
|||
<string name="pref_player_intro_skipper_summary">Yêu cầu phần mở rộng \"Intro Skipper\" của ConfusedPolarBear đã được cài đặt trên máy chủ</string>
|
||||
<string name="player_gestures_seek_summary">Vuốt ngang để tua tới hoặc tua lui</string>
|
||||
<string name="player_gestures_seek">Cử chỉ tua</string>
|
||||
<string name="offline_mode_icon">Biểu tượng Chế độ Ngoại tuyến</string>
|
||||
<string name="offline_mode_go_online">Trực tuyến trở lại</string>
|
||||
<string name="size">Kích thước</string>
|
||||
<string name="downloading_error">Lỗi trong khi tải về</string>
|
||||
<string name="offline_mode">Chế độ Ngoại tuyến</string>
|
||||
<string name="no_server_connection">Không có kết nối tới máy chủ Jellyfin, để xem ngoại tuyến xin hãy bật Chế độ Ngoại tuyến</string>
|
||||
<string name="internal">Nội</string>
|
||||
<string name="not_enough_storage">Nội dùng này yêu cầu %1$s bộ nhớ trống nhưng chỉ có %2$s là có sẵn</string>
|
||||
<string name="privacy_policy_notice">Bằng việc sử dụng Findroid, bạn đồng ý với <a href="https://raw.githubusercontent.com/jarnedemeulemeester/findroid/main/PRIVACY">Chính sách Bảo mật</a> rằng chúng tôi không thu thập bất cứ dữ liệu nào</string>
|
||||
<string name="external">Ngoại</string>
|
||||
<string name="preparing_download">Đang chuẩn bị tải về</string>
|
||||
<string name="cancel_download">Hủy tải về</string>
|
||||
<string name="audio">Âm thanh</string>
|
||||
<string name="subtitle">Phụ đề</string>
|
||||
<string name="subtitle_chip_text">Phụ đề chi tiết</string>
|
||||
<string name="temp">tạm</string>
|
||||
<string name="extra_info">Hiện thị thêm thông tin</string>
|
||||
<string name="video">Video</string>
|
||||
<string name="pref_player_trick_play">Tua mượt mà</string>
|
||||
<string name="extra_info_summary">Hiển thị thêm các thông tin về âm thanh, video và phụ đề</string>
|
||||
<string name="pref_player_trick_play_summary">Yêu cầu phần mở rộng Jellyscrub của nicknsy đã được cài đặt trên máy chủ</string>
|
||||
<string name="storage_name">%1$s (%2$d MB trống)</string>
|
||||
<string name="amoled_theme">Chủ đề màu tối (AMOLED)</string>
|
||||
<string name="amoled_theme_summary">Sử dụng chủ đề cho màn hình AMOLED với nền màu đen tuyệt đối</string>
|
||||
<string name="remove_server_address">Xoá địa chỉ máy chủ</string>
|
||||
<string name="downloaded_indicator">Dấu hiệu đã tải về</string>
|
||||
<string name="episode_name_extended_with_end">S%1$d:E%2$d-%3$d - %4$s</string>
|
||||
<string name="remove_server_address_dialog_text">Bạn có chắc chắn muốn xoá địa chỉ máy chủ %1$s không</string>
|
||||
<string name="episode_name_with_end">%1$d-%2$d. %3$s</string>
|
||||
<string name="cancel_download_message">Bạn có chắc là muốn hủy tải về không?</string>
|
||||
<string name="stop_download">Dừng tải về</string>
|
||||
<string name="picture_in_picture">Hình trong hình</string>
|
||||
<string name="picture_in_picture_gesture_summary">Sử dùng phím hoặc cử chỉ về màn hình chính để vào chế độ Hình trong Hình khi video đang phát</string>
|
||||
<string name="picture_in_picture_gesture">Cử chỉ về màn hình chính khi đang hình trong hình</string>
|
||||
<string name="app_language">Ngôn ngữ của ứng dụng</string>
|
||||
<string name="select_storage_location">Chọn vị trí để lưu trữ</string>
|
||||
<string name="storage_unavailable">Vị trí để lưu trữ hiện không có sẵn</string>
|
||||
</resources>
|
|
@ -174,4 +174,6 @@
|
|||
<string name="picture_in_picture_gesture_summary">影片播放時使用主頁按鈕或手勢進入畫中畫</string>
|
||||
<string name="remove_server_address">刪除伺服器位址</string>
|
||||
<string name="remove_server_address_dialog_text">您確定要刪除伺服器位址嗎%1$s</string>
|
||||
<string name="pref_player_trick_play">跳轉預覽</string>
|
||||
<string name="temp">臨時文件</string>
|
||||
</resources>
|
|
@ -21,6 +21,7 @@ android {
|
|||
|
||||
ksp {
|
||||
arg("room.schemaLocation", "$projectDir/schemas")
|
||||
arg("room.generateKotlin", "true")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -26,5 +26,5 @@ import dev.jdtech.jellyfin.models.User
|
|||
)
|
||||
@TypeConverters(Converters::class)
|
||||
abstract class ServerDatabase : RoomDatabase() {
|
||||
abstract val serverDatabaseDao: ServerDatabaseDao
|
||||
abstract fun getServerDatabaseDao(): ServerDatabaseDao
|
||||
}
|
||||
|
|
|
@ -24,210 +24,210 @@ import dev.jdtech.jellyfin.models.User
|
|||
import java.util.UUID
|
||||
|
||||
@Dao
|
||||
abstract class ServerDatabaseDao {
|
||||
interface ServerDatabaseDao {
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
abstract fun insertServer(server: Server)
|
||||
fun insertServer(server: Server)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
abstract fun insertServerAddress(address: ServerAddress)
|
||||
fun insertServerAddress(address: ServerAddress)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
abstract fun insertUser(user: User)
|
||||
fun insertUser(user: User)
|
||||
|
||||
@Update
|
||||
abstract fun update(server: Server)
|
||||
fun update(server: Server)
|
||||
|
||||
@Query("SELECT * FROM servers WHERE id = :id")
|
||||
abstract fun get(id: String): Server?
|
||||
fun get(id: String): Server?
|
||||
|
||||
@Query("SELECT * FROM users WHERE id = :id")
|
||||
abstract fun getUser(id: UUID): User?
|
||||
fun getUser(id: UUID): User?
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT * FROM servers WHERE id = :id")
|
||||
abstract fun getServerWithAddresses(id: String): ServerWithAddresses
|
||||
fun getServerWithAddresses(id: String): ServerWithAddresses
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT * FROM servers WHERE id = :id")
|
||||
abstract fun getServerWithUsers(id: String): ServerWithUsers
|
||||
fun getServerWithUsers(id: String): ServerWithUsers
|
||||
|
||||
@Transaction
|
||||
@Query("SELECT * FROM servers WHERE id = :id")
|
||||
abstract fun getServerWithAddressesAndUsers(id: String): ServerWithAddressesAndUsers?
|
||||
fun getServerWithAddressesAndUsers(id: String): ServerWithAddressesAndUsers?
|
||||
|
||||
@Query("DELETE FROM servers")
|
||||
abstract fun clear()
|
||||
fun clear()
|
||||
|
||||
@Query("SELECT * FROM servers")
|
||||
abstract fun getAllServersSync(): List<Server>
|
||||
fun getAllServersSync(): List<Server>
|
||||
|
||||
@Query("SELECT COUNT(*) FROM servers")
|
||||
abstract fun getServersCount(): Int
|
||||
fun getServersCount(): Int
|
||||
|
||||
@Query("DELETE FROM servers WHERE id = :id")
|
||||
abstract fun delete(id: String)
|
||||
fun delete(id: String)
|
||||
|
||||
@Query("DELETE FROM users WHERE id = :id")
|
||||
abstract fun deleteUser(id: UUID)
|
||||
fun deleteUser(id: UUID)
|
||||
|
||||
@Query("DELETE FROM serverAddresses WHERE id = :id")
|
||||
abstract fun deleteServerAddress(id: UUID)
|
||||
fun deleteServerAddress(id: UUID)
|
||||
|
||||
@Query("UPDATE servers SET currentUserId = :userId WHERE id = :serverId")
|
||||
abstract fun updateServerCurrentUser(serverId: String, userId: UUID)
|
||||
fun updateServerCurrentUser(serverId: String, userId: UUID)
|
||||
|
||||
@Query("SELECT * FROM users WHERE id = (SELECT currentUserId FROM servers WHERE id = :serverId)")
|
||||
abstract fun getServerCurrentUser(serverId: String): User?
|
||||
fun getServerCurrentUser(serverId: String): User?
|
||||
|
||||
@Query("SELECT * FROM serverAddresses WHERE id = (SELECT currentServerAddressId FROM servers WHERE id = :serverId)")
|
||||
abstract fun getServerCurrentAddress(serverId: String): ServerAddress?
|
||||
fun getServerCurrentAddress(serverId: String): ServerAddress?
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
abstract fun insertMovie(movie: FindroidMovieDto)
|
||||
fun insertMovie(movie: FindroidMovieDto)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
abstract fun insertSource(source: FindroidSourceDto)
|
||||
fun insertSource(source: FindroidSourceDto)
|
||||
|
||||
@Query("SELECT * FROM movies WHERE id = :id")
|
||||
abstract fun getMovie(id: UUID): FindroidMovieDto
|
||||
fun getMovie(id: UUID): FindroidMovieDto
|
||||
|
||||
@Query("SELECT * FROM movies JOIN sources ON movies.id = sources.itemId ORDER BY movies.name ASC")
|
||||
abstract fun getMoviesAndSources(): Map<FindroidMovieDto, List<FindroidSourceDto>>
|
||||
fun getMoviesAndSources(): Map<FindroidMovieDto, List<FindroidSourceDto>>
|
||||
|
||||
@Query("SELECT * FROM sources WHERE itemId = :itemId")
|
||||
abstract fun getSources(itemId: UUID): List<FindroidSourceDto>
|
||||
fun getSources(itemId: UUID): List<FindroidSourceDto>
|
||||
|
||||
@Query("SELECT * FROM sources WHERE downloadId = :downloadId")
|
||||
abstract fun getSourceByDownloadId(downloadId: Long): FindroidSourceDto?
|
||||
fun getSourceByDownloadId(downloadId: Long): FindroidSourceDto?
|
||||
|
||||
@Query("UPDATE sources SET downloadId = :downloadId WHERE id = :id")
|
||||
abstract fun setSourceDownloadId(id: String, downloadId: Long)
|
||||
fun setSourceDownloadId(id: String, downloadId: Long)
|
||||
|
||||
@Query("UPDATE sources SET path = :path WHERE id = :id")
|
||||
abstract fun setSourcePath(id: String, path: String)
|
||||
fun setSourcePath(id: String, path: String)
|
||||
|
||||
@Query("DELETE FROM sources WHERE id = :id")
|
||||
abstract fun deleteSource(id: String)
|
||||
fun deleteSource(id: String)
|
||||
|
||||
@Query("DELETE FROM movies WHERE id = :id")
|
||||
abstract fun deleteMovie(id: UUID)
|
||||
fun deleteMovie(id: UUID)
|
||||
|
||||
@Query("UPDATE userdata SET playbackPositionTicks = :playbackPositionTicks WHERE itemId = :itemId AND userid = :userId")
|
||||
abstract fun setPlaybackPositionTicks(itemId: UUID, userId: UUID, playbackPositionTicks: Long)
|
||||
fun setPlaybackPositionTicks(itemId: UUID, userId: UUID, playbackPositionTicks: Long)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
abstract fun insertMediaStream(mediaStream: FindroidMediaStreamDto)
|
||||
fun insertMediaStream(mediaStream: FindroidMediaStreamDto)
|
||||
|
||||
@Query("SELECT * FROM mediastreams WHERE sourceId = :sourceId")
|
||||
abstract fun getMediaStreamsBySourceId(sourceId: String): List<FindroidMediaStreamDto>
|
||||
fun getMediaStreamsBySourceId(sourceId: String): List<FindroidMediaStreamDto>
|
||||
|
||||
@Query("SELECT * FROM mediastreams WHERE downloadId = :downloadId")
|
||||
abstract fun getMediaStreamByDownloadId(downloadId: Long): FindroidMediaStreamDto?
|
||||
fun getMediaStreamByDownloadId(downloadId: Long): FindroidMediaStreamDto?
|
||||
|
||||
@Query("UPDATE mediastreams SET downloadId = :downloadId WHERE id = :id")
|
||||
abstract fun setMediaStreamDownloadId(id: UUID, downloadId: Long)
|
||||
fun setMediaStreamDownloadId(id: UUID, downloadId: Long)
|
||||
|
||||
@Query("UPDATE mediastreams SET path = :path WHERE id = :id")
|
||||
abstract fun setMediaStreamPath(id: UUID, path: String)
|
||||
fun setMediaStreamPath(id: UUID, path: String)
|
||||
|
||||
@Query("DELETE FROM mediastreams WHERE id = :id")
|
||||
abstract fun deleteMediaStream(id: UUID)
|
||||
fun deleteMediaStream(id: UUID)
|
||||
|
||||
@Query("DELETE FROM mediastreams WHERE sourceId = :sourceId")
|
||||
abstract fun deleteMediaStreamsBySourceId(sourceId: String)
|
||||
fun deleteMediaStreamsBySourceId(sourceId: String)
|
||||
|
||||
@Query("UPDATE userdata SET played = :played WHERE userId = :userId AND itemId = :itemId")
|
||||
abstract fun setPlayed(userId: UUID, itemId: UUID, played: Boolean)
|
||||
fun setPlayed(userId: UUID, itemId: UUID, played: Boolean)
|
||||
|
||||
@Query("UPDATE userdata SET favorite = :favorite WHERE userId = :userId AND itemId = :itemId")
|
||||
abstract fun setFavorite(userId: UUID, itemId: UUID, favorite: Boolean)
|
||||
fun setFavorite(userId: UUID, itemId: UUID, favorite: Boolean)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
abstract fun insertTrickPlayManifest(trickPlayManifestDto: TrickPlayManifestDto)
|
||||
fun insertTrickPlayManifest(trickPlayManifestDto: TrickPlayManifestDto)
|
||||
|
||||
@Query("SELECT * FROM trickPlayManifests WHERE itemId = :itemId")
|
||||
abstract fun getTrickPlayManifest(itemId: UUID): TrickPlayManifestDto?
|
||||
fun getTrickPlayManifest(itemId: UUID): TrickPlayManifestDto?
|
||||
|
||||
@Query("DELETE FROM trickPlayManifests WHERE itemId = :itemId")
|
||||
abstract fun deleteTrickPlayManifest(itemId: UUID)
|
||||
fun deleteTrickPlayManifest(itemId: UUID)
|
||||
|
||||
@Query("SELECT * FROM movies ORDER BY name ASC")
|
||||
abstract fun getMovies(): List<FindroidMovieDto>
|
||||
fun getMovies(): List<FindroidMovieDto>
|
||||
|
||||
@Query("SELECT * FROM movies WHERE serverId = :serverId ORDER BY name ASC")
|
||||
abstract fun getMoviesByServerId(serverId: String): List<FindroidMovieDto>
|
||||
fun getMoviesByServerId(serverId: String): List<FindroidMovieDto>
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
abstract fun insertShow(show: FindroidShowDto)
|
||||
fun insertShow(show: FindroidShowDto)
|
||||
|
||||
@Query("SELECT * FROM shows WHERE id = :id")
|
||||
abstract fun getShow(id: UUID): FindroidShowDto
|
||||
fun getShow(id: UUID): FindroidShowDto
|
||||
|
||||
@Query("SELECT * FROM shows ORDER BY name ASC")
|
||||
abstract fun getShows(): List<FindroidShowDto>
|
||||
fun getShows(): List<FindroidShowDto>
|
||||
|
||||
@Query("SELECT * FROM shows WHERE serverId = :serverId ORDER BY name ASC")
|
||||
abstract fun getShowsByServerId(serverId: String): List<FindroidShowDto>
|
||||
fun getShowsByServerId(serverId: String): List<FindroidShowDto>
|
||||
|
||||
@Query("DELETE FROM shows WHERE id = :id")
|
||||
abstract fun deleteShow(id: UUID)
|
||||
fun deleteShow(id: UUID)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
abstract fun insertSeason(show: FindroidSeasonDto)
|
||||
fun insertSeason(show: FindroidSeasonDto)
|
||||
|
||||
@Query("SELECT * FROM seasons WHERE id = :id")
|
||||
abstract fun getSeason(id: UUID): FindroidSeasonDto
|
||||
fun getSeason(id: UUID): FindroidSeasonDto
|
||||
|
||||
@Query("SELECT * FROM seasons WHERE seriesId = :seriesId ORDER BY indexNumber ASC")
|
||||
abstract fun getSeasonsByShowId(seriesId: UUID): List<FindroidSeasonDto>
|
||||
fun getSeasonsByShowId(seriesId: UUID): List<FindroidSeasonDto>
|
||||
|
||||
@Query("DELETE FROM seasons WHERE id = :id")
|
||||
abstract fun deleteSeason(id: UUID)
|
||||
fun deleteSeason(id: UUID)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.IGNORE)
|
||||
abstract fun insertEpisode(episode: FindroidEpisodeDto)
|
||||
fun insertEpisode(episode: FindroidEpisodeDto)
|
||||
|
||||
@Query("SELECT * FROM episodes WHERE id = :id")
|
||||
abstract fun getEpisode(id: UUID): FindroidEpisodeDto
|
||||
fun getEpisode(id: UUID): FindroidEpisodeDto
|
||||
|
||||
@Query("SELECT * FROM episodes WHERE seriesId = :seriesId ORDER BY parentIndexNumber ASC, indexNumber ASC")
|
||||
abstract fun getEpisodesByShowId(seriesId: UUID): List<FindroidEpisodeDto>
|
||||
fun getEpisodesByShowId(seriesId: UUID): List<FindroidEpisodeDto>
|
||||
|
||||
@Query("SELECT * FROM episodes WHERE seasonId = :seasonId ORDER BY indexNumber ASC")
|
||||
abstract fun getEpisodesBySeasonId(seasonId: UUID): List<FindroidEpisodeDto>
|
||||
fun getEpisodesBySeasonId(seasonId: UUID): List<FindroidEpisodeDto>
|
||||
|
||||
@Query("SELECT * FROM episodes WHERE serverId = :serverId ORDER BY seriesName ASC, parentIndexNumber ASC, indexNumber ASC")
|
||||
abstract fun getEpisodesByServerId(serverId: String): List<FindroidEpisodeDto>
|
||||
fun getEpisodesByServerId(serverId: String): List<FindroidEpisodeDto>
|
||||
|
||||
@Query("SELECT episodes.id, episodes.serverId, episodes.seasonId, episodes.seriesId, episodes.name, episodes.seriesName, episodes.overview, episodes.indexNumber, episodes.indexNumberEnd, episodes.parentIndexNumber, episodes.runtimeTicks, episodes.premiereDate, episodes.communityRating FROM episodes INNER JOIN userdata ON episodes.id = userdata.itemId WHERE serverId = :serverId AND playbackPositionTicks > 0 ORDER BY episodes.parentIndexNumber ASC, episodes.indexNumber ASC")
|
||||
abstract fun getEpisodeResumeItems(serverId: String): List<FindroidEpisodeDto>
|
||||
fun getEpisodeResumeItems(serverId: String): List<FindroidEpisodeDto>
|
||||
|
||||
@Query("DELETE FROM episodes WHERE id = :id")
|
||||
abstract fun deleteEpisode(id: UUID)
|
||||
fun deleteEpisode(id: UUID)
|
||||
|
||||
@Query("DELETE FROM episodes WHERE seasonId = :seasonId")
|
||||
abstract fun deleteEpisodesBySeasonId(seasonId: UUID)
|
||||
fun deleteEpisodesBySeasonId(seasonId: UUID)
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
abstract fun insertIntro(intro: IntroDto)
|
||||
fun insertIntro(intro: IntroDto)
|
||||
|
||||
@Query("SELECT * FROM intros WHERE itemId = :itemId")
|
||||
abstract fun getIntro(itemId: UUID): IntroDto?
|
||||
fun getIntro(itemId: UUID): IntroDto?
|
||||
|
||||
@Query("DELETE FROM intros WHERE itemId = :itemId")
|
||||
abstract fun deleteIntro(itemId: UUID)
|
||||
fun deleteIntro(itemId: UUID)
|
||||
|
||||
@Query("SELECT * FROM seasons")
|
||||
abstract fun getSeasons(): List<FindroidSeasonDto>
|
||||
fun getSeasons(): List<FindroidSeasonDto>
|
||||
|
||||
@Query("SELECT * FROM episodes")
|
||||
abstract fun getEpisodes(): List<FindroidEpisodeDto>
|
||||
fun getEpisodes(): List<FindroidEpisodeDto>
|
||||
|
||||
@Query("SELECT * FROM userdata WHERE itemId = :itemId AND userId = :userId")
|
||||
abstract fun getUserData(itemId: UUID, userId: UUID): FindroidUserDataDto?
|
||||
fun getUserData(itemId: UUID, userId: UUID): FindroidUserDataDto?
|
||||
|
||||
@Transaction
|
||||
open fun getUserDataOrCreateNew(itemId: UUID, userId: UUID): FindroidUserDataDto {
|
||||
fun getUserDataOrCreateNew(itemId: UUID, userId: UUID): FindroidUserDataDto {
|
||||
var userData = getUserData(itemId, userId)
|
||||
|
||||
// Create user data when there is none
|
||||
|
@ -246,23 +246,23 @@ abstract class ServerDatabaseDao {
|
|||
}
|
||||
|
||||
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
||||
abstract fun insertUserData(userData: FindroidUserDataDto)
|
||||
fun insertUserData(userData: FindroidUserDataDto)
|
||||
|
||||
@Query("DELETE FROM userdata WHERE itemId = :itemId")
|
||||
abstract fun deleteUserData(itemId: UUID)
|
||||
fun deleteUserData(itemId: UUID)
|
||||
|
||||
@Query("SELECT * FROM userdata WHERE userId = :userId AND itemId = :itemId AND toBeSynced = 1")
|
||||
abstract fun getUserDataToBeSynced(userId: UUID, itemId: UUID): FindroidUserDataDto?
|
||||
fun getUserDataToBeSynced(userId: UUID, itemId: UUID): FindroidUserDataDto?
|
||||
|
||||
@Query("UPDATE userdata SET toBeSynced = :toBeSynced WHERE itemId = :itemId AND userId = :userId")
|
||||
abstract fun setUserDataToBeSynced(userId: UUID, itemId: UUID, toBeSynced: Boolean)
|
||||
fun setUserDataToBeSynced(userId: UUID, itemId: UUID, toBeSynced: Boolean)
|
||||
|
||||
@Query("SELECT * FROM movies WHERE serverId = :serverId AND name LIKE '%' || :name || '%'")
|
||||
abstract fun searchMovies(serverId: String, name: String): List<FindroidMovieDto>
|
||||
fun searchMovies(serverId: String, name: String): List<FindroidMovieDto>
|
||||
|
||||
@Query("SELECT * FROM shows WHERE serverId = :serverId AND name LIKE '%' || :name || '%'")
|
||||
abstract fun searchShows(serverId: String, name: String): List<FindroidShowDto>
|
||||
fun searchShows(serverId: String, name: String): List<FindroidShowDto>
|
||||
|
||||
@Query("SELECT * FROM episodes WHERE serverId = :serverId AND name LIKE '%' || :name || '%'")
|
||||
abstract fun searchEpisodes(serverId: String, name: String): List<FindroidEpisodeDto>
|
||||
fun searchEpisodes(serverId: String, name: String): List<FindroidEpisodeDto>
|
||||
}
|
||||
|
|
|
@ -1,14 +1,14 @@
|
|||
[versions]
|
||||
aboutlibraries = "10.9.1"
|
||||
aboutlibraries = "10.9.2"
|
||||
android-plugin = "8.1.2"
|
||||
androidx-activity = "1.8.0"
|
||||
androidx-appcompat = "1.6.1"
|
||||
androidx-constraintlayout = "2.1.4"
|
||||
androidx-core = "1.12.0"
|
||||
androidx-hilt = "1.1.0-rc01"
|
||||
androidx-hilt = "1.1.0"
|
||||
androidx-lifecycle = "2.6.2"
|
||||
androidx-media3 = "1.1.1"
|
||||
androidx-navigation = "2.7.4"
|
||||
androidx-navigation = "2.7.5"
|
||||
androidx-paging = "3.2.1"
|
||||
androidx-preference = "1.2.1"
|
||||
androidx-recyclerview = "1.3.2"
|
||||
|
@ -17,7 +17,7 @@ androidx-swiperefreshlayout = "1.1.0"
|
|||
androidx-work = "2.8.1"
|
||||
coil = "2.4.0"
|
||||
hilt = "2.48.1"
|
||||
jellyfin = "1.4.4"
|
||||
jellyfin = "1.4.5"
|
||||
kotlin = "1.9.10"
|
||||
kotlinx-serialization = "1.6.0"
|
||||
ksp = "1.9.10-1.0.13"
|
||||
|
|
|
@ -27,11 +27,11 @@ import dev.jdtech.jellyfin.utils.bif.BifUtil
|
|||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.GlobalScope
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
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.flow.receiveAsFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
|
@ -60,8 +60,8 @@ constructor(
|
|||
)
|
||||
val uiState = _uiState.asStateFlow()
|
||||
|
||||
private val _navigateBack = MutableSharedFlow<Boolean>()
|
||||
val navigateBack = _navigateBack.asSharedFlow()
|
||||
private val eventsChannel = Channel<PlayerEvents>()
|
||||
val eventsChannelFlow = eventsChannel.receiveAsFlow()
|
||||
|
||||
private val intros: MutableMap<UUID, Intro> = mutableMapOf()
|
||||
|
||||
|
@ -317,7 +317,7 @@ constructor(
|
|||
}
|
||||
ExoPlayer.STATE_ENDED -> {
|
||||
stateString = "ExoPlayer.STATE_ENDED -"
|
||||
_navigateBack.tryEmit(true)
|
||||
eventsChannel.trySend(PlayerEvents.NavigateBack)
|
||||
}
|
||||
}
|
||||
Timber.d("Changed player state to $stateString")
|
||||
|
@ -366,3 +366,7 @@ constructor(
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
sealed interface PlayerEvents {
|
||||
data object NavigateBack : PlayerEvents
|
||||
}
|
||||
|
|
|
@ -15,4 +15,5 @@
|
|||
<string name="player_controls_skip_forward">Saltar adelante</string>
|
||||
<string name="player_controls_progress">Barra de progreso</string>
|
||||
<string name="player_trickplay">Avance</string>
|
||||
<string name="player_controls_picture_in_picture">Entrar en modo PIP</string>
|
||||
</resources>
|
|
@ -15,4 +15,5 @@
|
|||
<string name="player_controls_play_pause">재생/일시정지</string>
|
||||
<string name="player_controls_skip_forward">앞으로 건너뛰기</string>
|
||||
<string name="player_controls_progress">진행 표시줄</string>
|
||||
<string name="player_controls_picture_in_picture">picture-in-picture 전환</string>
|
||||
</resources>
|
|
@ -9,4 +9,11 @@
|
|||
<string name="player_controls_rewind">Tua lùi</string>
|
||||
<string name="player_controls_fast_forward">Tua tới</string>
|
||||
<string name="player_controls_skip_intro">Bỏ qua đoạn mở đầu</string>
|
||||
<string name="player_controls_skip_back">Bỏ qua / Trở về</string>
|
||||
<string name="player_controls_lock">Khoá trình phát</string>
|
||||
<string name="player_controls_play_pause">Phát / Tạm dừng</string>
|
||||
<string name="player_controls_picture_in_picture">Vào chế độ Hình trong Hình</string>
|
||||
<string name="player_controls_skip_forward">Tiến tới</string>
|
||||
<string name="player_trickplay">Tua mượt mà</string>
|
||||
<string name="player_controls_progress">Thanh tiến trình</string>
|
||||
</resources>
|
|
@ -160,11 +160,4 @@ constructor(
|
|||
putString(Constants.PREF_SORT_ORDER, value)
|
||||
}
|
||||
}
|
||||
|
||||
// Temp
|
||||
var downloadsMigrated
|
||||
get() = sharedPreferences.getBoolean("downloadsMigrated", false)
|
||||
set(value) = sharedPreferences.edit {
|
||||
putBoolean("downloadsMigrated", value)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
"labels": ["dependencies"],
|
||||
"extends": [
|
||||
"config:base",
|
||||
"schedule:weekly",
|
||||
":semanticCommits"
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue