Merge remote-tracking branch 'upstream/main'

This commit is contained in:
Freya Winters 2023-12-28 15:48:58 +01:00
commit 722267ced2
38 changed files with 307 additions and 232 deletions

View file

@ -1,7 +1,6 @@
package dev.jdtech.jellyfin package dev.jdtech.jellyfin
import android.os.Bundle import android.os.Bundle
import android.os.Environment
import android.view.View import android.view.View
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
@ -52,7 +51,6 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
scheduleUserDataSync() scheduleUserDataSync()
cleanUpOldDownloads()
applyTheme() applyTheme()
setupActivity() 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() { private fun scheduleUserDataSync() {
val syncWorkRequest = OneTimeWorkRequestBuilder<SyncWorker>() val syncWorkRequest = OneTimeWorkRequestBuilder<SyncWorker>()
.setConstraints( .setConstraints(

View file

@ -42,6 +42,7 @@ import dev.jdtech.jellyfin.mpv.TrackType
import dev.jdtech.jellyfin.utils.PlayerGestureHelper import dev.jdtech.jellyfin.utils.PlayerGestureHelper
import dev.jdtech.jellyfin.utils.PreviewScrubListener import dev.jdtech.jellyfin.utils.PreviewScrubListener
import dev.jdtech.jellyfin.viewmodels.PlayerActivityViewModel import dev.jdtech.jellyfin.viewmodels.PlayerActivityViewModel
import dev.jdtech.jellyfin.viewmodels.PlayerEvents
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -169,8 +170,10 @@ class PlayerActivity : BasePlayerActivity() {
} }
launch { launch {
viewModel.navigateBack.collect { viewModel.eventsChannelFlow.collect { event ->
if (it) finish() when (event) {
is PlayerEvents.NavigateBack -> finish()
}
} }
} }
} }

View file

@ -17,6 +17,7 @@ import androidx.navigation.fragment.findNavController
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.adapters.DiscoveredServerListAdapter import dev.jdtech.jellyfin.adapters.DiscoveredServerListAdapter
import dev.jdtech.jellyfin.databinding.FragmentAddServerBinding import dev.jdtech.jellyfin.databinding.FragmentAddServerBinding
import dev.jdtech.jellyfin.viewmodels.AddServerEvent
import dev.jdtech.jellyfin.viewmodels.AddServerViewModel import dev.jdtech.jellyfin.viewmodels.AddServerViewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
@ -81,9 +82,9 @@ class AddServerFragment : Fragment() {
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.navigateToLogin.collect { viewModel.eventsChannelFlow.collect { event ->
if (it) { when (event) {
navigateToLoginFragment() is AddServerEvent.NavigateToLogin -> navigateToLoginFragment()
} }
} }
} }

View file

@ -20,6 +20,7 @@ import dev.jdtech.jellyfin.models.FindroidItem
import dev.jdtech.jellyfin.models.FindroidMovie import dev.jdtech.jellyfin.models.FindroidMovie
import dev.jdtech.jellyfin.models.FindroidShow import dev.jdtech.jellyfin.models.FindroidShow
import dev.jdtech.jellyfin.utils.restart import dev.jdtech.jellyfin.utils.restart
import dev.jdtech.jellyfin.viewmodels.DownloadsEvent
import dev.jdtech.jellyfin.viewmodels.DownloadsViewModel import dev.jdtech.jellyfin.viewmodels.DownloadsViewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
@ -48,14 +49,18 @@ class DownloadsFragment : Fragment() {
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
launch { launch {
viewModel.connectionError.collect { viewModel.eventsChannelFlow.collect { event ->
Snackbar.make(binding.root, CoreR.string.no_server_connection, Snackbar.LENGTH_INDEFINITE) when (event) {
.setTextMaxLines(2) is DownloadsEvent.ConnectionError -> {
.setAction(CoreR.string.offline_mode) { Snackbar.make(binding.root, CoreR.string.no_server_connection, Snackbar.LENGTH_INDEFINITE)
appPreferences.offlineMode = true .setTextMaxLines(2)
activity?.restart() .setAction(CoreR.string.offline_mode) {
appPreferences.offlineMode = true
activity?.restart()
}
.show()
} }
.show() }
} }
} }
launch { launch {

View file

@ -33,6 +33,7 @@ import dev.jdtech.jellyfin.models.UiText
import dev.jdtech.jellyfin.models.isDownloaded import dev.jdtech.jellyfin.models.isDownloaded
import dev.jdtech.jellyfin.models.isDownloading import dev.jdtech.jellyfin.models.isDownloading
import dev.jdtech.jellyfin.utils.setIconTintColorAttribute import dev.jdtech.jellyfin.utils.setIconTintColorAttribute
import dev.jdtech.jellyfin.viewmodels.EpisodeBottomSheetEvent
import dev.jdtech.jellyfin.viewmodels.EpisodeBottomSheetViewModel import dev.jdtech.jellyfin.viewmodels.EpisodeBottomSheetViewModel
import dev.jdtech.jellyfin.viewmodels.PlayerViewModel import dev.jdtech.jellyfin.viewmodels.PlayerViewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -122,14 +123,11 @@ class EpisodeBottomSheetFragment : BottomSheetDialogFragment() {
} }
launch { launch {
viewModel.downloadError.collect { uiText -> viewModel.eventsChannelFlow.collect { event ->
createErrorDialog(uiText) when (event) {
} is EpisodeBottomSheetEvent.NavigateBack -> findNavController().navigateUp()
} is EpisodeBottomSheetEvent.DownloadError -> createErrorDialog(event.uiText)
}
launch {
viewModel.navigateBack.collect {
if (it) findNavController().navigateUp()
} }
} }
} }

View file

@ -19,6 +19,7 @@ import dev.jdtech.jellyfin.AppPreferences
import dev.jdtech.jellyfin.adapters.UserLoginListAdapter import dev.jdtech.jellyfin.adapters.UserLoginListAdapter
import dev.jdtech.jellyfin.database.ServerDatabaseDao import dev.jdtech.jellyfin.database.ServerDatabaseDao
import dev.jdtech.jellyfin.databinding.FragmentLoginBinding import dev.jdtech.jellyfin.databinding.FragmentLoginBinding
import dev.jdtech.jellyfin.viewmodels.LoginEvent
import dev.jdtech.jellyfin.viewmodels.LoginViewModel import dev.jdtech.jellyfin.viewmodels.LoginViewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
@ -123,9 +124,9 @@ class LoginFragment : Fragment() {
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.navigateToMain.collect { viewModel.eventsChannelFlow.collect { event ->
if (it) { when (event) {
navigateToHomeFragment() is LoginEvent.NavigateToHome -> navigateToHomeFragment()
} }
} }
} }

View file

@ -37,6 +37,7 @@ import dev.jdtech.jellyfin.models.isDownloaded
import dev.jdtech.jellyfin.models.isDownloading import dev.jdtech.jellyfin.models.isDownloading
import dev.jdtech.jellyfin.utils.checkIfLoginRequired import dev.jdtech.jellyfin.utils.checkIfLoginRequired
import dev.jdtech.jellyfin.utils.setIconTintColorAttribute import dev.jdtech.jellyfin.utils.setIconTintColorAttribute
import dev.jdtech.jellyfin.viewmodels.MovieEvent
import dev.jdtech.jellyfin.viewmodels.MovieViewModel import dev.jdtech.jellyfin.viewmodels.MovieViewModel
import dev.jdtech.jellyfin.viewmodels.PlayerViewModel import dev.jdtech.jellyfin.viewmodels.PlayerViewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -118,14 +119,11 @@ class MovieFragment : Fragment() {
} }
launch { launch {
viewModel.downloadError.collect { uiText -> viewModel.eventsChannelFlow.collect { event ->
createErrorDialog(uiText) when (event) {
} is MovieEvent.NavigateBack -> findNavController().navigateUp()
} is MovieEvent.DownloadError -> createErrorDialog(event.uiText)
}
launch {
viewModel.navigateBack.collect {
if (it) findNavController().navigateUp()
} }
} }
} }

View file

@ -20,6 +20,7 @@ import dev.jdtech.jellyfin.models.FindroidEpisode
import dev.jdtech.jellyfin.models.PlayerItem import dev.jdtech.jellyfin.models.PlayerItem
import dev.jdtech.jellyfin.utils.checkIfLoginRequired import dev.jdtech.jellyfin.utils.checkIfLoginRequired
import dev.jdtech.jellyfin.viewmodels.PlayerViewModel import dev.jdtech.jellyfin.viewmodels.PlayerViewModel
import dev.jdtech.jellyfin.viewmodels.SeasonEvent
import dev.jdtech.jellyfin.viewmodels.SeasonViewModel import dev.jdtech.jellyfin.viewmodels.SeasonViewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
@ -60,8 +61,10 @@ class SeasonFragment : Fragment() {
} }
launch { launch {
viewModel.navigateBack.collect { viewModel.eventsChannelFlow.collect { event ->
if (it) findNavController().navigateUp() when (event) {
is SeasonEvent.NavigateBack -> findNavController().navigateUp()
}
} }
} }
} }

View file

@ -16,6 +16,7 @@ import dev.jdtech.jellyfin.adapters.ServerAddressAdapter
import dev.jdtech.jellyfin.databinding.FragmentServerAddressesBinding import dev.jdtech.jellyfin.databinding.FragmentServerAddressesBinding
import dev.jdtech.jellyfin.dialogs.AddServerAddressDialog import dev.jdtech.jellyfin.dialogs.AddServerAddressDialog
import dev.jdtech.jellyfin.dialogs.DeleteServerAddressDialog import dev.jdtech.jellyfin.dialogs.DeleteServerAddressDialog
import dev.jdtech.jellyfin.viewmodels.ServerAddressesEvent
import dev.jdtech.jellyfin.viewmodels.ServerAddressesViewModel import dev.jdtech.jellyfin.viewmodels.ServerAddressesViewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
@ -57,9 +58,9 @@ class ServerAddressesFragment : Fragment() {
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.navigateToMain.collect { viewModel.eventsChannelFlow.collect { event ->
if (it) { when (event) {
navigateToMainActivity() is ServerAddressesEvent.NavigateToHome -> navigateToMainActivity()
} }
} }
} }

View file

@ -14,6 +14,7 @@ import dagger.hilt.android.AndroidEntryPoint
import dev.jdtech.jellyfin.adapters.ServerGridAdapter import dev.jdtech.jellyfin.adapters.ServerGridAdapter
import dev.jdtech.jellyfin.databinding.FragmentServerSelectBinding import dev.jdtech.jellyfin.databinding.FragmentServerSelectBinding
import dev.jdtech.jellyfin.dialogs.DeleteServerDialogFragment import dev.jdtech.jellyfin.dialogs.DeleteServerDialogFragment
import dev.jdtech.jellyfin.viewmodels.ServerSelectEvent
import dev.jdtech.jellyfin.viewmodels.ServerSelectViewModel import dev.jdtech.jellyfin.viewmodels.ServerSelectViewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
@ -61,13 +62,11 @@ class ServerSelectFragment : Fragment() {
} }
} }
launch { launch {
viewModel.navigateToMain.collect { viewModel.eventsChannelFlow.collect { event ->
if (it) navigateToMainActivity() when (event) {
} is ServerSelectEvent.NavigateToHome -> navigateToMainActivity()
} is ServerSelectEvent.NavigateToLogin -> navigateToLoginFragment()
launch { }
viewModel.navigateToLogin.collect {
if (it) navigateToLoginFragment()
} }
} }
} }

View file

@ -32,6 +32,7 @@ import dev.jdtech.jellyfin.models.isDownloaded
import dev.jdtech.jellyfin.utils.checkIfLoginRequired import dev.jdtech.jellyfin.utils.checkIfLoginRequired
import dev.jdtech.jellyfin.utils.setIconTintColorAttribute import dev.jdtech.jellyfin.utils.setIconTintColorAttribute
import dev.jdtech.jellyfin.viewmodels.PlayerViewModel import dev.jdtech.jellyfin.viewmodels.PlayerViewModel
import dev.jdtech.jellyfin.viewmodels.ShowEvent
import dev.jdtech.jellyfin.viewmodels.ShowViewModel import dev.jdtech.jellyfin.viewmodels.ShowViewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
@ -79,8 +80,10 @@ class ShowFragment : Fragment() {
} }
launch { launch {
viewModel.navigateBack.collect { viewModel.eventsChannelFlow.collect { event ->
if (it) findNavController().navigateUp() when (event) {
is ShowEvent.NavigateBack -> findNavController().navigateUp()
}
} }
} }
} }

View file

@ -16,6 +16,7 @@ import dev.jdtech.jellyfin.AppNavigationDirections
import dev.jdtech.jellyfin.adapters.UserListAdapter import dev.jdtech.jellyfin.adapters.UserListAdapter
import dev.jdtech.jellyfin.databinding.FragmentUsersBinding import dev.jdtech.jellyfin.databinding.FragmentUsersBinding
import dev.jdtech.jellyfin.dialogs.DeleteUserDialogFragment import dev.jdtech.jellyfin.dialogs.DeleteUserDialogFragment
import dev.jdtech.jellyfin.viewmodels.UsersEvent
import dev.jdtech.jellyfin.viewmodels.UsersViewModel import dev.jdtech.jellyfin.viewmodels.UsersViewModel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
@ -54,9 +55,9 @@ class UsersFragment : Fragment() {
viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.lifecycleScope.launch {
viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.navigateToMain.collect { viewModel.eventsChannelFlow.collect { event ->
if (it) { when (event) {
navigateToMainActivity() is UsersEvent.NavigateToHome -> navigateToMainActivity()
} }
} }
} }

View file

@ -25,6 +25,6 @@ object DatabaseModule {
.fallbackToDestructiveMigration() .fallbackToDestructiveMigration()
.allowMainThreadQueries() .allowMainThreadQueries()
.build() .build()
.serverDatabaseDao .getServerDatabaseDao()
} }
} }

View file

@ -152,9 +152,11 @@ class DownloaderImpl(
val remainingEpisodes = database.getEpisodesBySeasonId(item.seasonId) val remainingEpisodes = database.getEpisodesBySeasonId(item.seasonId)
if (remainingEpisodes.isEmpty()) { if (remainingEpisodes.isEmpty()) {
database.deleteSeason(item.seasonId) database.deleteSeason(item.seasonId)
database.deleteUserData(item.seasonId)
val remainingSeasons = database.getSeasonsByShowId(item.seriesId) val remainingSeasons = database.getSeasonsByShowId(item.seriesId)
if (remainingSeasons.isEmpty()) { if (remainingSeasons.isEmpty()) {
database.deleteShow(item.seriesId) database.deleteShow(item.seriesId)
database.deleteUserData(item.seriesId)
} }
} }
} }

View file

@ -15,10 +15,10 @@ import dev.jdtech.jellyfin.models.ServerAddress
import dev.jdtech.jellyfin.models.UiText import dev.jdtech.jellyfin.models.UiText
import kotlinx.coroutines.CancellationException import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.jellyfin.sdk.discovery.RecommendedServerInfo import org.jellyfin.sdk.discovery.RecommendedServerInfo
@ -38,11 +38,12 @@ constructor(
) : ViewModel() { ) : ViewModel() {
private val _uiState = MutableStateFlow<UiState>(UiState.Normal) private val _uiState = MutableStateFlow<UiState>(UiState.Normal)
val uiState = _uiState.asStateFlow() val uiState = _uiState.asStateFlow()
private val _navigateToLogin = MutableSharedFlow<Boolean>()
val navigateToLogin = _navigateToLogin.asSharedFlow()
private val _discoveredServersState = MutableStateFlow<DiscoveredServersState>(DiscoveredServersState.Loading) private val _discoveredServersState = MutableStateFlow<DiscoveredServersState>(DiscoveredServersState.Loading)
val discoveredServersState = _discoveredServersState.asStateFlow() val discoveredServersState = _discoveredServersState.asStateFlow()
private val eventsChannel = Channel<AddServerEvent>()
val eventsChannelFlow = eventsChannel.receiveAsFlow()
private val discoveredServers = mutableListOf<DiscoveredServer>() private val discoveredServers = mutableListOf<DiscoveredServer>()
private var serverFound = false private var serverFound = false
@ -206,7 +207,7 @@ constructor(
} }
_uiState.emit(UiState.Normal) _uiState.emit(UiState.Normal)
_navigateToLogin.emit(true) eventsChannel.send(AddServerEvent.NavigateToLogin)
} }
/** /**
@ -269,3 +270,7 @@ constructor(
} }
} }
} }
sealed interface AddServerEvent {
data object NavigateToLogin : AddServerEvent
}

View file

@ -11,11 +11,11 @@ import dev.jdtech.jellyfin.models.FindroidMovie
import dev.jdtech.jellyfin.models.FindroidShow import dev.jdtech.jellyfin.models.FindroidShow
import dev.jdtech.jellyfin.models.UiText import dev.jdtech.jellyfin.models.UiText
import dev.jdtech.jellyfin.repository.JellyfinRepository import dev.jdtech.jellyfin.repository.JellyfinRepository
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
@ -28,8 +28,9 @@ constructor(
) : ViewModel() { ) : ViewModel() {
private val _uiState = MutableStateFlow<UiState>(UiState.Loading) private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
val uiState = _uiState.asStateFlow() 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 { sealed class UiState {
data class Normal(val sections: List<FavoriteSection>) : UiState() data class Normal(val sections: List<FavoriteSection>) : UiState()
@ -49,7 +50,7 @@ constructor(
// Give the UI a chance to load // Give the UI a chance to load
delay(100) delay(100)
} catch (e: Exception) { } 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
}

View file

@ -13,10 +13,10 @@ import dev.jdtech.jellyfin.models.UiText
import dev.jdtech.jellyfin.models.isDownloading import dev.jdtech.jellyfin.models.isDownloading
import dev.jdtech.jellyfin.repository.JellyfinRepository import dev.jdtech.jellyfin.repository.JellyfinRepository
import dev.jdtech.jellyfin.utils.Downloader 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.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.io.File import java.io.File
import java.util.UUID import java.util.UUID
@ -37,11 +37,8 @@ constructor(
private val _downloadStatus = MutableStateFlow(Pair(0, 0)) private val _downloadStatus = MutableStateFlow(Pair(0, 0))
val downloadStatus = _downloadStatus.asStateFlow() val downloadStatus = _downloadStatus.asStateFlow()
private val _downloadError = MutableSharedFlow<UiText>() private val eventsChannel = Channel<EpisodeBottomSheetEvent>()
val downloadError = _downloadError.asSharedFlow() val eventsChannelFlow = eventsChannel.receiveAsFlow()
private val _navigateBack = MutableSharedFlow<Boolean>()
val navigateBack = _navigateBack.asSharedFlow()
private val handler = Handler(Looper.getMainLooper()) private val handler = Handler(Looper.getMainLooper())
@ -75,7 +72,7 @@ constructor(
) )
} catch (_: NullPointerException) { } catch (_: NullPointerException) {
// Navigate back because item does not exist (probably because it's been deleted) // Navigate back because item does not exist (probably because it's been deleted)
_navigateBack.emit(true) eventsChannel.send(EpisodeBottomSheetEvent.NavigateBack)
} catch (e: Exception) { } catch (e: Exception) {
_uiState.emit(UiState.Error(e)) _uiState.emit(UiState.Error(e))
} }
@ -133,7 +130,7 @@ constructor(
_downloadStatus.emit(Pair(10, Random.nextInt())) _downloadStatus.emit(Pair(10, Random.nextInt()))
if (result.second != null) { if (result.second != null) {
_downloadError.emit(result.second!!) eventsChannel.send(EpisodeBottomSheetEvent.DownloadError(result.second!!))
} }
loadEpisode(item.id) loadEpisode(item.id)
@ -188,3 +185,8 @@ constructor(
handler.removeCallbacksAndMessages(null) handler.removeCallbacksAndMessages(null)
} }
} }
sealed interface EpisodeBottomSheetEvent {
data object NavigateBack : EpisodeBottomSheetEvent
data class DownloadError(val uiText: UiText) : EpisodeBottomSheetEvent
}

View file

@ -11,11 +11,11 @@ import dev.jdtech.jellyfin.models.UiText
import dev.jdtech.jellyfin.models.User import dev.jdtech.jellyfin.models.User
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.jellyfin.sdk.api.client.extensions.authenticateWithQuickConnect import org.jellyfin.sdk.api.client.extensions.authenticateWithQuickConnect
@ -38,8 +38,9 @@ constructor(
val usersState = _usersState.asStateFlow() val usersState = _usersState.asStateFlow()
private val _quickConnectUiState = MutableStateFlow<QuickConnectUiState>(QuickConnectUiState.Disabled) private val _quickConnectUiState = MutableStateFlow<QuickConnectUiState>(QuickConnectUiState.Disabled)
val quickConnectUiState = _quickConnectUiState.asStateFlow() 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 private var quickConnectJob: Job? = null
@ -121,7 +122,7 @@ constructor(
saveAuthenticationResult(authenticationResult) saveAuthenticationResult(authenticationResult)
_uiState.emit(UiState.Normal) _uiState.emit(UiState.Normal)
_navigateToMain.emit(true) eventsChannel.send(LoginEvent.NavigateToHome)
} catch (e: Exception) { } catch (e: Exception) {
val message = val message =
if (e.message?.contains("401") == true) { if (e.message?.contains("401") == true) {
@ -157,7 +158,7 @@ constructor(
saveAuthenticationResult(authenticationResult) saveAuthenticationResult(authenticationResult)
_quickConnectUiState.emit(QuickConnectUiState.Normal) _quickConnectUiState.emit(QuickConnectUiState.Normal)
_navigateToMain.emit(true) eventsChannel.send(LoginEvent.NavigateToHome)
} catch (_: Exception) { } catch (_: Exception) {
_quickConnectUiState.emit(QuickConnectUiState.Normal) _quickConnectUiState.emit(QuickConnectUiState.Normal)
} }
@ -189,3 +190,7 @@ constructor(
} }
} }
} }
sealed interface LoginEvent {
data object NavigateToHome : LoginEvent
}

View file

@ -21,10 +21,10 @@ import dev.jdtech.jellyfin.repository.JellyfinRepository
import dev.jdtech.jellyfin.utils.Downloader import dev.jdtech.jellyfin.utils.Downloader
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Runnable import kotlinx.coroutines.Runnable
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.jellyfin.sdk.model.api.BaseItemPerson import org.jellyfin.sdk.model.api.BaseItemPerson
@ -49,11 +49,8 @@ constructor(
private val _downloadStatus = MutableStateFlow(Pair(0, 0)) private val _downloadStatus = MutableStateFlow(Pair(0, 0))
val downloadStatus = _downloadStatus.asStateFlow() val downloadStatus = _downloadStatus.asStateFlow()
private val _downloadError = MutableSharedFlow<UiText>() private val eventsChannel = Channel<MovieEvent>()
val downloadError = _downloadError.asSharedFlow() val eventsChannelFlow = eventsChannel.receiveAsFlow()
private val _navigateBack = MutableSharedFlow<Boolean>()
val navigateBack = _navigateBack.asSharedFlow()
private val handler = Handler(Looper.getMainLooper()) private val handler = Handler(Looper.getMainLooper())
@ -115,7 +112,7 @@ constructor(
) )
} catch (_: NullPointerException) { } catch (_: NullPointerException) {
// Navigate back because item does not exist (probably because it's been deleted) // Navigate back because item does not exist (probably because it's been deleted)
_navigateBack.emit(true) eventsChannel.send(MovieEvent.NavigateBack)
} catch (e: Exception) { } catch (e: Exception) {
_uiState.emit(UiState.Error(e)) _uiState.emit(UiState.Error(e))
} }
@ -330,7 +327,7 @@ constructor(
_downloadStatus.emit(Pair(10, Random.nextInt())) _downloadStatus.emit(Pair(10, Random.nextInt()))
if (result.second != null) { if (result.second != null) {
_downloadError.emit(result.second!!) eventsChannel.send(MovieEvent.DownloadError(result.second!!))
} }
loadData(item.id) loadData(item.id)
@ -385,3 +382,8 @@ constructor(
handler.removeCallbacksAndMessages(null) handler.removeCallbacksAndMessages(null)
} }
} }
sealed interface MovieEvent {
data object NavigateBack : MovieEvent
data class DownloadError(val uiText: UiText) : MovieEvent
}

View file

@ -6,10 +6,10 @@ import dagger.hilt.android.lifecycle.HiltViewModel
import dev.jdtech.jellyfin.models.EpisodeItem import dev.jdtech.jellyfin.models.EpisodeItem
import dev.jdtech.jellyfin.models.FindroidSeason import dev.jdtech.jellyfin.models.FindroidSeason
import dev.jdtech.jellyfin.repository.JellyfinRepository 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.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import org.jellyfin.sdk.model.api.ItemFields import org.jellyfin.sdk.model.api.ItemFields
import java.util.UUID import java.util.UUID
@ -24,8 +24,8 @@ constructor(
private val _uiState = MutableStateFlow<UiState>(UiState.Loading) private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
val uiState = _uiState.asStateFlow() val uiState = _uiState.asStateFlow()
private val _navigateBack = MutableSharedFlow<Boolean>() private val eventsChannel = Channel<SeasonEvent>()
val navigateBack = _navigateBack.asSharedFlow() val eventsChannelFlow = eventsChannel.receiveAsFlow()
sealed class UiState { sealed class UiState {
data class Normal(val episodes: List<EpisodeItem>) : UiState() data class Normal(val episodes: List<EpisodeItem>) : UiState()
@ -44,7 +44,7 @@ constructor(
_uiState.emit(UiState.Normal(episodes)) _uiState.emit(UiState.Normal(episodes))
} catch (_: NullPointerException) { } catch (_: NullPointerException) {
// Navigate back because item does not exist (probably because it's been deleted) // Navigate back because item does not exist (probably because it's been deleted)
_navigateBack.emit(true) eventsChannel.send(SeasonEvent.NavigateBack)
} catch (e: Exception) { } catch (e: Exception) {
_uiState.emit(UiState.Error(e)) _uiState.emit(UiState.Error(e))
} }
@ -63,3 +63,7 @@ constructor(
return listOf(header) + episodes.map { EpisodeItem.Episode(it) } return listOf(header) + episodes.map { EpisodeItem.Episode(it) }
} }
} }
sealed interface SeasonEvent {
data object NavigateBack : SeasonEvent
}

View file

@ -7,10 +7,10 @@ import dev.jdtech.jellyfin.api.JellyfinApi
import dev.jdtech.jellyfin.database.ServerDatabaseDao import dev.jdtech.jellyfin.database.ServerDatabaseDao
import dev.jdtech.jellyfin.models.ServerAddress import dev.jdtech.jellyfin.models.ServerAddress
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import java.util.UUID import java.util.UUID
@ -32,8 +32,8 @@ constructor(
data class Error(val error: Exception) : UiState() data class Error(val error: Exception) : UiState()
} }
private val _navigateToMain = MutableSharedFlow<Boolean>() private val eventsChannel = Channel<ServerAddressesEvent>()
val navigateToMain = _navigateToMain.asSharedFlow() val eventsChannelFlow = eventsChannel.receiveAsFlow()
private var currentServerId: String = "" private var currentServerId: String = ""
@ -75,7 +75,7 @@ constructor(
jellyfinApi.api.baseUrl = address.address jellyfinApi.api.baseUrl = address.address
_navigateToMain.emit(true) eventsChannel.send(ServerAddressesEvent.NavigateToHome)
} }
} }
@ -87,3 +87,7 @@ constructor(
} }
} }
} }
sealed interface ServerAddressesEvent {
data object NavigateToHome : ServerAddressesEvent
}

View file

@ -8,10 +8,10 @@ import dev.jdtech.jellyfin.api.JellyfinApi
import dev.jdtech.jellyfin.database.ServerDatabaseDao import dev.jdtech.jellyfin.database.ServerDatabaseDao
import dev.jdtech.jellyfin.models.Server import dev.jdtech.jellyfin.models.Server
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import javax.inject.Inject import javax.inject.Inject
@ -26,11 +26,8 @@ constructor(
private val _uiState = MutableStateFlow<UiState>(UiState.Loading) private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
val uiState = _uiState.asStateFlow() val uiState = _uiState.asStateFlow()
private val _navigateToMain = MutableSharedFlow<Boolean>() private val eventsChannel = Channel<ServerSelectEvent>()
val navigateToMain = _navigateToMain.asSharedFlow() val eventsChannelFlow = eventsChannel.receiveAsFlow()
private val _navigateToLogin = MutableSharedFlow<Boolean>()
val navigateToLogin = _navigateToLogin.asSharedFlow()
sealed class UiState { sealed class UiState {
data class Normal(val servers: List<Server>) : UiState() data class Normal(val servers: List<Server>) : UiState()
@ -75,7 +72,7 @@ constructor(
userId = null userId = null
} }
appPreferences.currentServer = server.id appPreferences.currentServer = server.id
_navigateToLogin.emit(true) eventsChannel.send(ServerSelectEvent.NavigateToLogin)
return@launch return@launch
} }
@ -87,7 +84,12 @@ constructor(
appPreferences.currentServer = server.id appPreferences.currentServer = server.id
_navigateToMain.emit(true) eventsChannel.send(ServerSelectEvent.NavigateToHome)
} }
} }
} }
sealed interface ServerSelectEvent {
data object NavigateToHome : ServerSelectEvent
data object NavigateToLogin : ServerSelectEvent
}

View file

@ -8,10 +8,10 @@ import dev.jdtech.jellyfin.models.FindroidSeason
import dev.jdtech.jellyfin.models.FindroidShow import dev.jdtech.jellyfin.models.FindroidShow
import dev.jdtech.jellyfin.repository.JellyfinRepository import dev.jdtech.jellyfin.repository.JellyfinRepository
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import org.jellyfin.sdk.model.api.BaseItemPerson import org.jellyfin.sdk.model.api.BaseItemPerson
@ -27,8 +27,8 @@ constructor(
private val _uiState = MutableStateFlow<UiState>(UiState.Loading) private val _uiState = MutableStateFlow<UiState>(UiState.Loading)
val uiState = _uiState.asStateFlow() val uiState = _uiState.asStateFlow()
private val _navigateBack = MutableSharedFlow<Boolean>() private val eventsChannel = Channel<ShowEvent>()
val navigateBack = _navigateBack.asSharedFlow() val eventsChannelFlow = eventsChannel.receiveAsFlow()
sealed class UiState { sealed class UiState {
data class Normal( data class Normal(
@ -93,7 +93,7 @@ constructor(
) )
} catch (_: NullPointerException) { } catch (_: NullPointerException) {
// Navigate back because item does not exist (probably because it's been deleted) // Navigate back because item does not exist (probably because it's been deleted)
_navigateBack.emit(true) eventsChannel.send(ShowEvent.NavigateBack)
} catch (e: Exception) { } catch (e: Exception) {
_uiState.emit(UiState.Error(e)) _uiState.emit(UiState.Error(e))
} }
@ -189,3 +189,7 @@ constructor(
return dateRange.joinToString(separator = " - ") return dateRange.joinToString(separator = " - ")
} }
} }
sealed interface ShowEvent {
data object NavigateBack : ShowEvent
}

View file

@ -7,10 +7,10 @@ import dev.jdtech.jellyfin.api.JellyfinApi
import dev.jdtech.jellyfin.database.ServerDatabaseDao import dev.jdtech.jellyfin.database.ServerDatabaseDao
import dev.jdtech.jellyfin.models.User import dev.jdtech.jellyfin.models.User
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -31,8 +31,8 @@ constructor(
data class Error(val error: Exception) : UiState() data class Error(val error: Exception) : UiState()
} }
private val _navigateToMain = MutableSharedFlow<Boolean>() private val eventsChannel = Channel<UsersEvent>()
val navigateToMain = _navigateToMain.asSharedFlow() val eventsChannelFlow = eventsChannel.receiveAsFlow()
private var currentServerId: String = "" private var currentServerId: String = ""
@ -77,7 +77,11 @@ constructor(
userId = user.id userId = user.id
} }
_navigateToMain.emit(true) eventsChannel.send(UsersEvent.NavigateToHome)
} }
} }
} }
sealed interface UsersEvent {
data object NavigateToHome : UsersEvent
}

View file

@ -20,7 +20,7 @@
<string name="remove">Törlés</string> <string name="remove">Törlés</string>
<string name="cancel">Mégsem</string> <string name="cancel">Mégsem</string>
<string name="title_home">Kezdőlap</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_favorite">Kedvencek</string>
<string name="title_settings">Beállítások</string> <string name="title_settings">Beállítások</string>
<string name="view_all">Összes megtekintése</string> <string name="view_all">Összes megtekintése</string>

View file

@ -173,4 +173,7 @@
<string name="remove_server_address">서버 주소 제</string> <string name="remove_server_address">서버 주소 제</string>
<string name="temp">임시</string> <string name="temp">임시</string>
<string name="subtitle_chip_text">CC</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> </resources>

View file

@ -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="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_summary">Vuốt ngang để tua tới hoặc tua lui</string>
<string name="player_gestures_seek">Cử chỉ tua</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> </resources>

View file

@ -174,4 +174,6 @@
<string name="picture_in_picture_gesture_summary">影片播放時使用主頁按鈕或手勢進入畫中畫</string> <string name="picture_in_picture_gesture_summary">影片播放時使用主頁按鈕或手勢進入畫中畫</string>
<string name="remove_server_address">刪除伺服器位址</string> <string name="remove_server_address">刪除伺服器位址</string>
<string name="remove_server_address_dialog_text">您確定要刪除伺服器位址嗎%1$s</string> <string name="remove_server_address_dialog_text">您確定要刪除伺服器位址嗎%1$s</string>
<string name="pref_player_trick_play">跳轉預覽</string>
<string name="temp">臨時文件</string>
</resources> </resources>

View file

@ -21,6 +21,7 @@ android {
ksp { ksp {
arg("room.schemaLocation", "$projectDir/schemas") arg("room.schemaLocation", "$projectDir/schemas")
arg("room.generateKotlin", "true")
} }
} }

View file

@ -26,5 +26,5 @@ import dev.jdtech.jellyfin.models.User
) )
@TypeConverters(Converters::class) @TypeConverters(Converters::class)
abstract class ServerDatabase : RoomDatabase() { abstract class ServerDatabase : RoomDatabase() {
abstract val serverDatabaseDao: ServerDatabaseDao abstract fun getServerDatabaseDao(): ServerDatabaseDao
} }

View file

@ -24,210 +24,210 @@ import dev.jdtech.jellyfin.models.User
import java.util.UUID import java.util.UUID
@Dao @Dao
abstract class ServerDatabaseDao { interface ServerDatabaseDao {
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
abstract fun insertServer(server: Server) fun insertServer(server: Server)
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
abstract fun insertServerAddress(address: ServerAddress) fun insertServerAddress(address: ServerAddress)
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
abstract fun insertUser(user: User) fun insertUser(user: User)
@Update @Update
abstract fun update(server: Server) fun update(server: Server)
@Query("SELECT * FROM servers WHERE id = :id") @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") @Query("SELECT * FROM users WHERE id = :id")
abstract fun getUser(id: UUID): User? fun getUser(id: UUID): User?
@Transaction @Transaction
@Query("SELECT * FROM servers WHERE id = :id") @Query("SELECT * FROM servers WHERE id = :id")
abstract fun getServerWithAddresses(id: String): ServerWithAddresses fun getServerWithAddresses(id: String): ServerWithAddresses
@Transaction @Transaction
@Query("SELECT * FROM servers WHERE id = :id") @Query("SELECT * FROM servers WHERE id = :id")
abstract fun getServerWithUsers(id: String): ServerWithUsers fun getServerWithUsers(id: String): ServerWithUsers
@Transaction @Transaction
@Query("SELECT * FROM servers WHERE id = :id") @Query("SELECT * FROM servers WHERE id = :id")
abstract fun getServerWithAddressesAndUsers(id: String): ServerWithAddressesAndUsers? fun getServerWithAddressesAndUsers(id: String): ServerWithAddressesAndUsers?
@Query("DELETE FROM servers") @Query("DELETE FROM servers")
abstract fun clear() fun clear()
@Query("SELECT * FROM servers") @Query("SELECT * FROM servers")
abstract fun getAllServersSync(): List<Server> fun getAllServersSync(): List<Server>
@Query("SELECT COUNT(*) FROM servers") @Query("SELECT COUNT(*) FROM servers")
abstract fun getServersCount(): Int fun getServersCount(): Int
@Query("DELETE FROM servers WHERE id = :id") @Query("DELETE FROM servers WHERE id = :id")
abstract fun delete(id: String) fun delete(id: String)
@Query("DELETE FROM users WHERE id = :id") @Query("DELETE FROM users WHERE id = :id")
abstract fun deleteUser(id: UUID) fun deleteUser(id: UUID)
@Query("DELETE FROM serverAddresses WHERE id = :id") @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") @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)") @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)") @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) @Insert(onConflict = OnConflictStrategy.IGNORE)
abstract fun insertMovie(movie: FindroidMovieDto) fun insertMovie(movie: FindroidMovieDto)
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
abstract fun insertSource(source: FindroidSourceDto) fun insertSource(source: FindroidSourceDto)
@Query("SELECT * FROM movies WHERE id = :id") @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") @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") @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") @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") @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") @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") @Query("DELETE FROM sources WHERE id = :id")
abstract fun deleteSource(id: String) fun deleteSource(id: String)
@Query("DELETE FROM movies WHERE id = :id") @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") @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) @Insert(onConflict = OnConflictStrategy.REPLACE)
abstract fun insertMediaStream(mediaStream: FindroidMediaStreamDto) fun insertMediaStream(mediaStream: FindroidMediaStreamDto)
@Query("SELECT * FROM mediastreams WHERE sourceId = :sourceId") @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") @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") @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") @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") @Query("DELETE FROM mediastreams WHERE id = :id")
abstract fun deleteMediaStream(id: UUID) fun deleteMediaStream(id: UUID)
@Query("DELETE FROM mediastreams WHERE sourceId = :sourceId") @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") @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") @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) @Insert(onConflict = OnConflictStrategy.REPLACE)
abstract fun insertTrickPlayManifest(trickPlayManifestDto: TrickPlayManifestDto) fun insertTrickPlayManifest(trickPlayManifestDto: TrickPlayManifestDto)
@Query("SELECT * FROM trickPlayManifests WHERE itemId = :itemId") @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") @Query("DELETE FROM trickPlayManifests WHERE itemId = :itemId")
abstract fun deleteTrickPlayManifest(itemId: UUID) fun deleteTrickPlayManifest(itemId: UUID)
@Query("SELECT * FROM movies ORDER BY name ASC") @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") @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) @Insert(onConflict = OnConflictStrategy.IGNORE)
abstract fun insertShow(show: FindroidShowDto) fun insertShow(show: FindroidShowDto)
@Query("SELECT * FROM shows WHERE id = :id") @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") @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") @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") @Query("DELETE FROM shows WHERE id = :id")
abstract fun deleteShow(id: UUID) fun deleteShow(id: UUID)
@Insert(onConflict = OnConflictStrategy.IGNORE) @Insert(onConflict = OnConflictStrategy.IGNORE)
abstract fun insertSeason(show: FindroidSeasonDto) fun insertSeason(show: FindroidSeasonDto)
@Query("SELECT * FROM seasons WHERE id = :id") @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") @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") @Query("DELETE FROM seasons WHERE id = :id")
abstract fun deleteSeason(id: UUID) fun deleteSeason(id: UUID)
@Insert(onConflict = OnConflictStrategy.IGNORE) @Insert(onConflict = OnConflictStrategy.IGNORE)
abstract fun insertEpisode(episode: FindroidEpisodeDto) fun insertEpisode(episode: FindroidEpisodeDto)
@Query("SELECT * FROM episodes WHERE id = :id") @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") @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") @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") @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") @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") @Query("DELETE FROM episodes WHERE id = :id")
abstract fun deleteEpisode(id: UUID) fun deleteEpisode(id: UUID)
@Query("DELETE FROM episodes WHERE seasonId = :seasonId") @Query("DELETE FROM episodes WHERE seasonId = :seasonId")
abstract fun deleteEpisodesBySeasonId(seasonId: UUID) fun deleteEpisodesBySeasonId(seasonId: UUID)
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
abstract fun insertIntro(intro: IntroDto) fun insertIntro(intro: IntroDto)
@Query("SELECT * FROM intros WHERE itemId = :itemId") @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") @Query("DELETE FROM intros WHERE itemId = :itemId")
abstract fun deleteIntro(itemId: UUID) fun deleteIntro(itemId: UUID)
@Query("SELECT * FROM seasons") @Query("SELECT * FROM seasons")
abstract fun getSeasons(): List<FindroidSeasonDto> fun getSeasons(): List<FindroidSeasonDto>
@Query("SELECT * FROM episodes") @Query("SELECT * FROM episodes")
abstract fun getEpisodes(): List<FindroidEpisodeDto> fun getEpisodes(): List<FindroidEpisodeDto>
@Query("SELECT * FROM userdata WHERE itemId = :itemId AND userId = :userId") @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 @Transaction
open fun getUserDataOrCreateNew(itemId: UUID, userId: UUID): FindroidUserDataDto { fun getUserDataOrCreateNew(itemId: UUID, userId: UUID): FindroidUserDataDto {
var userData = getUserData(itemId, userId) var userData = getUserData(itemId, userId)
// Create user data when there is none // Create user data when there is none
@ -246,23 +246,23 @@ abstract class ServerDatabaseDao {
} }
@Insert(onConflict = OnConflictStrategy.REPLACE) @Insert(onConflict = OnConflictStrategy.REPLACE)
abstract fun insertUserData(userData: FindroidUserDataDto) fun insertUserData(userData: FindroidUserDataDto)
@Query("DELETE FROM userdata WHERE itemId = :itemId") @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") @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") @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 || '%'") @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 || '%'") @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 || '%'") @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>
} }

View file

@ -1,14 +1,14 @@
[versions] [versions]
aboutlibraries = "10.9.1" aboutlibraries = "10.9.2"
android-plugin = "8.1.2" android-plugin = "8.1.2"
androidx-activity = "1.8.0" androidx-activity = "1.8.0"
androidx-appcompat = "1.6.1" androidx-appcompat = "1.6.1"
androidx-constraintlayout = "2.1.4" androidx-constraintlayout = "2.1.4"
androidx-core = "1.12.0" androidx-core = "1.12.0"
androidx-hilt = "1.1.0-rc01" androidx-hilt = "1.1.0"
androidx-lifecycle = "2.6.2" androidx-lifecycle = "2.6.2"
androidx-media3 = "1.1.1" androidx-media3 = "1.1.1"
androidx-navigation = "2.7.4" androidx-navigation = "2.7.5"
androidx-paging = "3.2.1" androidx-paging = "3.2.1"
androidx-preference = "1.2.1" androidx-preference = "1.2.1"
androidx-recyclerview = "1.3.2" androidx-recyclerview = "1.3.2"
@ -17,7 +17,7 @@ androidx-swiperefreshlayout = "1.1.0"
androidx-work = "2.8.1" androidx-work = "2.8.1"
coil = "2.4.0" coil = "2.4.0"
hilt = "2.48.1" hilt = "2.48.1"
jellyfin = "1.4.4" jellyfin = "1.4.5"
kotlin = "1.9.10" kotlin = "1.9.10"
kotlinx-serialization = "1.6.0" kotlinx-serialization = "1.6.0"
ksp = "1.9.10-1.0.13" ksp = "1.9.10-1.0.13"

View file

@ -27,11 +27,11 @@ import dev.jdtech.jellyfin.utils.bif.BifUtil
import kotlinx.coroutines.DelicateCoroutinesApi import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
@ -60,8 +60,8 @@ constructor(
) )
val uiState = _uiState.asStateFlow() val uiState = _uiState.asStateFlow()
private val _navigateBack = MutableSharedFlow<Boolean>() private val eventsChannel = Channel<PlayerEvents>()
val navigateBack = _navigateBack.asSharedFlow() val eventsChannelFlow = eventsChannel.receiveAsFlow()
private val intros: MutableMap<UUID, Intro> = mutableMapOf() private val intros: MutableMap<UUID, Intro> = mutableMapOf()
@ -317,7 +317,7 @@ constructor(
} }
ExoPlayer.STATE_ENDED -> { ExoPlayer.STATE_ENDED -> {
stateString = "ExoPlayer.STATE_ENDED -" stateString = "ExoPlayer.STATE_ENDED -"
_navigateBack.tryEmit(true) eventsChannel.trySend(PlayerEvents.NavigateBack)
} }
} }
Timber.d("Changed player state to $stateString") Timber.d("Changed player state to $stateString")
@ -366,3 +366,7 @@ constructor(
} }
} }
} }
sealed interface PlayerEvents {
data object NavigateBack : PlayerEvents
}

View file

@ -15,4 +15,5 @@
<string name="player_controls_skip_forward">Saltar adelante</string> <string name="player_controls_skip_forward">Saltar adelante</string>
<string name="player_controls_progress">Barra de progreso</string> <string name="player_controls_progress">Barra de progreso</string>
<string name="player_trickplay">Avance</string> <string name="player_trickplay">Avance</string>
<string name="player_controls_picture_in_picture">Entrar en modo PIP</string>
</resources> </resources>

View file

@ -15,4 +15,5 @@
<string name="player_controls_play_pause">재생/일시정지</string> <string name="player_controls_play_pause">재생/일시정지</string>
<string name="player_controls_skip_forward">앞으로 건너뛰기</string> <string name="player_controls_skip_forward">앞으로 건너뛰기</string>
<string name="player_controls_progress">진행 표시줄</string> <string name="player_controls_progress">진행 표시줄</string>
<string name="player_controls_picture_in_picture">picture-in-picture 전환</string>
</resources> </resources>

View file

@ -9,4 +9,11 @@
<string name="player_controls_rewind">Tua lùi</string> <string name="player_controls_rewind">Tua lùi</string>
<string name="player_controls_fast_forward">Tua tớ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_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> </resources>

View file

@ -160,11 +160,4 @@ constructor(
putString(Constants.PREF_SORT_ORDER, value) putString(Constants.PREF_SORT_ORDER, value)
} }
} }
// Temp
var downloadsMigrated
get() = sharedPreferences.getBoolean("downloadsMigrated", false)
set(value) = sharedPreferences.edit {
putBoolean("downloadsMigrated", value)
}
} }

View file

@ -3,6 +3,7 @@
"labels": ["dependencies"], "labels": ["dependencies"],
"extends": [ "extends": [
"config:base", "config:base",
"schedule:weekly",
":semanticCommits" ":semanticCommits"
] ]
} }