diff --git a/app/phone/src/main/java/dev/jdtech/jellyfin/MainActivity.kt b/app/phone/src/main/java/dev/jdtech/jellyfin/MainActivity.kt index 5e808192..adc6a5f8 100644 --- a/app/phone/src/main/java/dev/jdtech/jellyfin/MainActivity.kt +++ b/app/phone/src/main/java/dev/jdtech/jellyfin/MainActivity.kt @@ -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() .setConstraints( diff --git a/app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt b/app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt index 46330153..5d5c2a80 100644 --- a/app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt +++ b/app/phone/src/main/java/dev/jdtech/jellyfin/PlayerActivity.kt @@ -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() + } } } } diff --git a/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/AddServerFragment.kt b/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/AddServerFragment.kt index c6b0627a..ef1dab39 100644 --- a/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/AddServerFragment.kt +++ b/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/AddServerFragment.kt @@ -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() } } } diff --git a/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/DownloadsFragment.kt b/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/DownloadsFragment.kt index 9887717d..1f154cef 100644 --- a/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/DownloadsFragment.kt +++ b/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/DownloadsFragment.kt @@ -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,14 +49,18 @@ class DownloadsFragment : Fragment() { viewLifecycleOwner.lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { launch { - viewModel.connectionError.collect { - Snackbar.make(binding.root, CoreR.string.no_server_connection, Snackbar.LENGTH_INDEFINITE) - .setTextMaxLines(2) - .setAction(CoreR.string.offline_mode) { - appPreferences.offlineMode = true - activity?.restart() + 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) { + appPreferences.offlineMode = true + activity?.restart() + } + .show() } - .show() + } } } launch { diff --git a/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/EpisodeBottomSheetFragment.kt b/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/EpisodeBottomSheetFragment.kt index b230f2e9..3ec17bb1 100644 --- a/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/EpisodeBottomSheetFragment.kt +++ b/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/EpisodeBottomSheetFragment.kt @@ -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,14 +123,11 @@ class EpisodeBottomSheetFragment : BottomSheetDialogFragment() { } launch { - viewModel.downloadError.collect { uiText -> - createErrorDialog(uiText) - } - } - - launch { - viewModel.navigateBack.collect { - if (it) findNavController().navigateUp() + viewModel.eventsChannelFlow.collect { event -> + when (event) { + is EpisodeBottomSheetEvent.NavigateBack -> findNavController().navigateUp() + is EpisodeBottomSheetEvent.DownloadError -> createErrorDialog(event.uiText) + } } } } diff --git a/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/LoginFragment.kt b/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/LoginFragment.kt index 39330a1b..a98864a6 100644 --- a/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/LoginFragment.kt +++ b/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/LoginFragment.kt @@ -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() } } } diff --git a/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/MovieFragment.kt b/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/MovieFragment.kt index 321fa077..781e240d 100644 --- a/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/MovieFragment.kt +++ b/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/MovieFragment.kt @@ -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,14 +119,11 @@ class MovieFragment : Fragment() { } launch { - viewModel.downloadError.collect { uiText -> - createErrorDialog(uiText) - } - } - - launch { - viewModel.navigateBack.collect { - if (it) findNavController().navigateUp() + viewModel.eventsChannelFlow.collect { event -> + when (event) { + is MovieEvent.NavigateBack -> findNavController().navigateUp() + is MovieEvent.DownloadError -> createErrorDialog(event.uiText) + } } } } diff --git a/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/SeasonFragment.kt b/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/SeasonFragment.kt index 5ab629e8..66cff353 100644 --- a/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/SeasonFragment.kt +++ b/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/SeasonFragment.kt @@ -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() + } } } } diff --git a/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/ServerAddressesFragment.kt b/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/ServerAddressesFragment.kt index c753e5d0..02b19b1a 100644 --- a/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/ServerAddressesFragment.kt +++ b/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/ServerAddressesFragment.kt @@ -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() } } } diff --git a/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/ServerSelectFragment.kt b/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/ServerSelectFragment.kt index eb212f14..a9a7fc07 100644 --- a/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/ServerSelectFragment.kt +++ b/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/ServerSelectFragment.kt @@ -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,13 +62,11 @@ class ServerSelectFragment : Fragment() { } } launch { - viewModel.navigateToMain.collect { - if (it) navigateToMainActivity() - } - } - launch { - viewModel.navigateToLogin.collect { - if (it) navigateToLoginFragment() + viewModel.eventsChannelFlow.collect { event -> + when (event) { + is ServerSelectEvent.NavigateToHome -> navigateToMainActivity() + is ServerSelectEvent.NavigateToLogin -> navigateToLoginFragment() + } } } } diff --git a/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/ShowFragment.kt b/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/ShowFragment.kt index 2be8b0ba..940a10e6 100644 --- a/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/ShowFragment.kt +++ b/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/ShowFragment.kt @@ -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() + } } } } diff --git a/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/UsersFragment.kt b/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/UsersFragment.kt index 2923653c..52fa5773 100644 --- a/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/UsersFragment.kt +++ b/app/phone/src/main/java/dev/jdtech/jellyfin/fragments/UsersFragment.kt @@ -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() } } } diff --git a/core/src/main/java/dev/jdtech/jellyfin/di/DatabaseModule.kt b/core/src/main/java/dev/jdtech/jellyfin/di/DatabaseModule.kt index 88c92dcc..6e811696 100644 --- a/core/src/main/java/dev/jdtech/jellyfin/di/DatabaseModule.kt +++ b/core/src/main/java/dev/jdtech/jellyfin/di/DatabaseModule.kt @@ -25,6 +25,6 @@ object DatabaseModule { .fallbackToDestructiveMigration() .allowMainThreadQueries() .build() - .serverDatabaseDao + .getServerDatabaseDao() } } diff --git a/core/src/main/java/dev/jdtech/jellyfin/utils/DownloaderImpl.kt b/core/src/main/java/dev/jdtech/jellyfin/utils/DownloaderImpl.kt index e4dac531..0b670493 100644 --- a/core/src/main/java/dev/jdtech/jellyfin/utils/DownloaderImpl.kt +++ b/core/src/main/java/dev/jdtech/jellyfin/utils/DownloaderImpl.kt @@ -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) } } } diff --git a/core/src/main/java/dev/jdtech/jellyfin/viewmodels/AddServerViewModel.kt b/core/src/main/java/dev/jdtech/jellyfin/viewmodels/AddServerViewModel.kt index e9dbf8c2..eb6361bd 100644 --- a/core/src/main/java/dev/jdtech/jellyfin/viewmodels/AddServerViewModel.kt +++ b/core/src/main/java/dev/jdtech/jellyfin/viewmodels/AddServerViewModel.kt @@ -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.Normal) val uiState = _uiState.asStateFlow() - private val _navigateToLogin = MutableSharedFlow() - val navigateToLogin = _navigateToLogin.asSharedFlow() private val _discoveredServersState = MutableStateFlow(DiscoveredServersState.Loading) val discoveredServersState = _discoveredServersState.asStateFlow() + private val eventsChannel = Channel() + val eventsChannelFlow = eventsChannel.receiveAsFlow() + private val discoveredServers = mutableListOf() 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 +} diff --git a/core/src/main/java/dev/jdtech/jellyfin/viewmodels/DownloadsViewModel.kt b/core/src/main/java/dev/jdtech/jellyfin/viewmodels/DownloadsViewModel.kt index 4242c639..f20421b6 100644 --- a/core/src/main/java/dev/jdtech/jellyfin/viewmodels/DownloadsViewModel.kt +++ b/core/src/main/java/dev/jdtech/jellyfin/viewmodels/DownloadsViewModel.kt @@ -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.Loading) val uiState = _uiState.asStateFlow() - private val _connectionError = MutableSharedFlow() - val connectionError = _connectionError.asSharedFlow() + + private val eventsChannel = Channel() + val eventsChannelFlow = eventsChannel.receiveAsFlow() sealed class UiState { data class Normal(val sections: List) : 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 +} diff --git a/core/src/main/java/dev/jdtech/jellyfin/viewmodels/EpisodeBottomSheetViewModel.kt b/core/src/main/java/dev/jdtech/jellyfin/viewmodels/EpisodeBottomSheetViewModel.kt index 8f55a558..9e49a04b 100644 --- a/core/src/main/java/dev/jdtech/jellyfin/viewmodels/EpisodeBottomSheetViewModel.kt +++ b/core/src/main/java/dev/jdtech/jellyfin/viewmodels/EpisodeBottomSheetViewModel.kt @@ -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() - val downloadError = _downloadError.asSharedFlow() - - private val _navigateBack = MutableSharedFlow() - val navigateBack = _navigateBack.asSharedFlow() + private val eventsChannel = Channel() + 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 +} diff --git a/core/src/main/java/dev/jdtech/jellyfin/viewmodels/LoginViewModel.kt b/core/src/main/java/dev/jdtech/jellyfin/viewmodels/LoginViewModel.kt index a5055eb5..81f7fcea 100644 --- a/core/src/main/java/dev/jdtech/jellyfin/viewmodels/LoginViewModel.kt +++ b/core/src/main/java/dev/jdtech/jellyfin/viewmodels/LoginViewModel.kt @@ -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.Disabled) val quickConnectUiState = _quickConnectUiState.asStateFlow() - private val _navigateToMain = MutableSharedFlow() - val navigateToMain = _navigateToMain.asSharedFlow() + + private val eventsChannel = Channel() + 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 +} diff --git a/core/src/main/java/dev/jdtech/jellyfin/viewmodels/MovieViewModel.kt b/core/src/main/java/dev/jdtech/jellyfin/viewmodels/MovieViewModel.kt index aa8e2dbf..0dd9c9d9 100644 --- a/core/src/main/java/dev/jdtech/jellyfin/viewmodels/MovieViewModel.kt +++ b/core/src/main/java/dev/jdtech/jellyfin/viewmodels/MovieViewModel.kt @@ -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() - val downloadError = _downloadError.asSharedFlow() - - private val _navigateBack = MutableSharedFlow() - val navigateBack = _navigateBack.asSharedFlow() + private val eventsChannel = Channel() + 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 +} diff --git a/core/src/main/java/dev/jdtech/jellyfin/viewmodels/SeasonViewModel.kt b/core/src/main/java/dev/jdtech/jellyfin/viewmodels/SeasonViewModel.kt index 2874bd70..ed270cad 100644 --- a/core/src/main/java/dev/jdtech/jellyfin/viewmodels/SeasonViewModel.kt +++ b/core/src/main/java/dev/jdtech/jellyfin/viewmodels/SeasonViewModel.kt @@ -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.Loading) val uiState = _uiState.asStateFlow() - private val _navigateBack = MutableSharedFlow() - val navigateBack = _navigateBack.asSharedFlow() + private val eventsChannel = Channel() + val eventsChannelFlow = eventsChannel.receiveAsFlow() sealed class UiState { data class Normal(val episodes: List) : 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 +} diff --git a/core/src/main/java/dev/jdtech/jellyfin/viewmodels/ServerAddressesViewModel.kt b/core/src/main/java/dev/jdtech/jellyfin/viewmodels/ServerAddressesViewModel.kt index 925962ad..4d2bc18c 100644 --- a/core/src/main/java/dev/jdtech/jellyfin/viewmodels/ServerAddressesViewModel.kt +++ b/core/src/main/java/dev/jdtech/jellyfin/viewmodels/ServerAddressesViewModel.kt @@ -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() - val navigateToMain = _navigateToMain.asSharedFlow() + private val eventsChannel = Channel() + 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 +} diff --git a/core/src/main/java/dev/jdtech/jellyfin/viewmodels/ServerSelectViewModel.kt b/core/src/main/java/dev/jdtech/jellyfin/viewmodels/ServerSelectViewModel.kt index e098be1d..974758a6 100644 --- a/core/src/main/java/dev/jdtech/jellyfin/viewmodels/ServerSelectViewModel.kt +++ b/core/src/main/java/dev/jdtech/jellyfin/viewmodels/ServerSelectViewModel.kt @@ -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.Loading) val uiState = _uiState.asStateFlow() - private val _navigateToMain = MutableSharedFlow() - val navigateToMain = _navigateToMain.asSharedFlow() - - private val _navigateToLogin = MutableSharedFlow() - val navigateToLogin = _navigateToLogin.asSharedFlow() + private val eventsChannel = Channel() + val eventsChannelFlow = eventsChannel.receiveAsFlow() sealed class UiState { data class Normal(val servers: List) : 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 +} diff --git a/core/src/main/java/dev/jdtech/jellyfin/viewmodels/ShowViewModel.kt b/core/src/main/java/dev/jdtech/jellyfin/viewmodels/ShowViewModel.kt index bc9e0532..f2836935 100644 --- a/core/src/main/java/dev/jdtech/jellyfin/viewmodels/ShowViewModel.kt +++ b/core/src/main/java/dev/jdtech/jellyfin/viewmodels/ShowViewModel.kt @@ -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.Loading) val uiState = _uiState.asStateFlow() - private val _navigateBack = MutableSharedFlow() - val navigateBack = _navigateBack.asSharedFlow() + private val eventsChannel = Channel() + 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 +} diff --git a/core/src/main/java/dev/jdtech/jellyfin/viewmodels/UsersViewModel.kt b/core/src/main/java/dev/jdtech/jellyfin/viewmodels/UsersViewModel.kt index 13ad4f62..26317828 100644 --- a/core/src/main/java/dev/jdtech/jellyfin/viewmodels/UsersViewModel.kt +++ b/core/src/main/java/dev/jdtech/jellyfin/viewmodels/UsersViewModel.kt @@ -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() - val navigateToMain = _navigateToMain.asSharedFlow() + private val eventsChannel = Channel() + 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 +} diff --git a/core/src/main/res/values-hu/strings.xml b/core/src/main/res/values-hu/strings.xml index 31bfc65c..90cd8212 100644 --- a/core/src/main/res/values-hu/strings.xml +++ b/core/src/main/res/values-hu/strings.xml @@ -20,7 +20,7 @@ Törlés Mégsem Kezdőlap - Én médiám + Könyvtáraid Kedvencek Beállítások Összes megtekintése diff --git a/core/src/main/res/values-ko/strings.xml b/core/src/main/res/values-ko/strings.xml index 72d5527e..a7b8ef59 100644 --- a/core/src/main/res/values-ko/strings.xml +++ b/core/src/main/res/values-ko/strings.xml @@ -173,4 +173,7 @@ 서버 주소 제 임시 CC + Picture-in-picture 홈 제스쳐 + 영상 재생 중 홈 버튼 또는 제스쳐를 이용해 picture-in-picture 변경 + Picture-in-picture 기능 \ No newline at end of file diff --git a/core/src/main/res/values-vi/strings.xml b/core/src/main/res/values-vi/strings.xml index 059b1bb4..ac4e9737 100644 --- a/core/src/main/res/values-vi/strings.xml +++ b/core/src/main/res/values-vi/strings.xml @@ -139,4 +139,41 @@ Yêu cầu phần mở rộng \"Intro Skipper\" của ConfusedPolarBear đã được cài đặt trên máy chủ Vuốt ngang để tua tới hoặc tua lui Cử chỉ tua + Biểu tượng Chế độ Ngoại tuyến + Trực tuyến trở lại + Kích thước + Lỗi trong khi tải về + Chế độ Ngoại tuyến + 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 + Nội + 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 + Bằng việc sử dụng Findroid, bạn đồng ý với Chính sách Bảo mật rằng chúng tôi không thu thập bất cứ dữ liệu nào + Ngoại + Đang chuẩn bị tải về + Hủy tải về + Âm thanh + Phụ đề + Phụ đề chi tiết + tạm + Hiện thị thêm thông tin + Video + Tua mượt mà + Hiển thị thêm các thông tin về âm thanh, video và phụ đề + Yêu cầu phần mở rộng Jellyscrub của nicknsy đã được cài đặt trên máy chủ + %1$s (%2$d MB trống) + Chủ đề màu tối (AMOLED) + Sử dụng chủ đề cho màn hình AMOLED với nền màu đen tuyệt đối + Xoá địa chỉ máy chủ + Dấu hiệu đã tải về + S%1$d:E%2$d-%3$d - %4$s + Bạn có chắc chắn muốn xoá địa chỉ máy chủ %1$s không + %1$d-%2$d. %3$s + Bạn có chắc là muốn hủy tải về không? + Dừng tải về + Hình trong hình + 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 + Cử chỉ về màn hình chính khi đang hình trong hình + Ngôn ngữ của ứng dụng + Chọn vị trí để lưu trữ + Vị trí để lưu trữ hiện không có sẵn \ No newline at end of file diff --git a/core/src/main/res/values-zh-rTW/strings.xml b/core/src/main/res/values-zh-rTW/strings.xml index 0baa30f9..241c6f5b 100644 --- a/core/src/main/res/values-zh-rTW/strings.xml +++ b/core/src/main/res/values-zh-rTW/strings.xml @@ -174,4 +174,6 @@ 影片播放時使用主頁按鈕或手勢進入畫中畫 刪除伺服器位址 您確定要刪除伺服器位址嗎%1$s + 跳轉預覽 + 臨時文件 \ No newline at end of file diff --git a/data/build.gradle.kts b/data/build.gradle.kts index 9e28054d..de7d78d7 100644 --- a/data/build.gradle.kts +++ b/data/build.gradle.kts @@ -21,6 +21,7 @@ android { ksp { arg("room.schemaLocation", "$projectDir/schemas") + arg("room.generateKotlin", "true") } } diff --git a/data/src/main/java/dev/jdtech/jellyfin/database/ServerDatabase.kt b/data/src/main/java/dev/jdtech/jellyfin/database/ServerDatabase.kt index b0240609..1d84c004 100644 --- a/data/src/main/java/dev/jdtech/jellyfin/database/ServerDatabase.kt +++ b/data/src/main/java/dev/jdtech/jellyfin/database/ServerDatabase.kt @@ -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 } diff --git a/data/src/main/java/dev/jdtech/jellyfin/database/ServerDatabaseDao.kt b/data/src/main/java/dev/jdtech/jellyfin/database/ServerDatabaseDao.kt index 285a0c7a..727328e4 100644 --- a/data/src/main/java/dev/jdtech/jellyfin/database/ServerDatabaseDao.kt +++ b/data/src/main/java/dev/jdtech/jellyfin/database/ServerDatabaseDao.kt @@ -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 + fun getAllServersSync(): List @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> + fun getMoviesAndSources(): Map> @Query("SELECT * FROM sources WHERE itemId = :itemId") - abstract fun getSources(itemId: UUID): List + fun getSources(itemId: UUID): List @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 + fun getMediaStreamsBySourceId(sourceId: String): List @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 + fun getMovies(): List @Query("SELECT * FROM movies WHERE serverId = :serverId ORDER BY name ASC") - abstract fun getMoviesByServerId(serverId: String): List + fun getMoviesByServerId(serverId: String): List @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 + fun getShows(): List @Query("SELECT * FROM shows WHERE serverId = :serverId ORDER BY name ASC") - abstract fun getShowsByServerId(serverId: String): List + fun getShowsByServerId(serverId: String): List @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 + fun getSeasonsByShowId(seriesId: UUID): List @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 + fun getEpisodesByShowId(seriesId: UUID): List @Query("SELECT * FROM episodes WHERE seasonId = :seasonId ORDER BY indexNumber ASC") - abstract fun getEpisodesBySeasonId(seasonId: UUID): List + fun getEpisodesBySeasonId(seasonId: UUID): List @Query("SELECT * FROM episodes WHERE serverId = :serverId ORDER BY seriesName ASC, parentIndexNumber ASC, indexNumber ASC") - abstract fun getEpisodesByServerId(serverId: String): List + fun getEpisodesByServerId(serverId: String): List @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 + fun getEpisodeResumeItems(serverId: String): List @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 + fun getSeasons(): List @Query("SELECT * FROM episodes") - abstract fun getEpisodes(): List + fun getEpisodes(): List @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 + fun searchMovies(serverId: String, name: String): List @Query("SELECT * FROM shows WHERE serverId = :serverId AND name LIKE '%' || :name || '%'") - abstract fun searchShows(serverId: String, name: String): List + fun searchShows(serverId: String, name: String): List @Query("SELECT * FROM episodes WHERE serverId = :serverId AND name LIKE '%' || :name || '%'") - abstract fun searchEpisodes(serverId: String, name: String): List + fun searchEpisodes(serverId: String, name: String): List } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c2621cf6..0d18b922 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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" diff --git a/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt b/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt index a7a8cfa3..4ef91f28 100644 --- a/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt +++ b/player/video/src/main/java/dev/jdtech/jellyfin/viewmodels/PlayerActivityViewModel.kt @@ -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() - val navigateBack = _navigateBack.asSharedFlow() + private val eventsChannel = Channel() + val eventsChannelFlow = eventsChannel.receiveAsFlow() private val intros: MutableMap = 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 +} diff --git a/player/video/src/main/res/values-b+es+419/strings.xml b/player/video/src/main/res/values-b+es+419/strings.xml index 72a9ca23..b76a8749 100644 --- a/player/video/src/main/res/values-b+es+419/strings.xml +++ b/player/video/src/main/res/values-b+es+419/strings.xml @@ -15,4 +15,5 @@ Saltar adelante Barra de progreso Avance + Entrar en modo PIP \ No newline at end of file diff --git a/player/video/src/main/res/values-ko/strings.xml b/player/video/src/main/res/values-ko/strings.xml index 32c10352..8993e36d 100644 --- a/player/video/src/main/res/values-ko/strings.xml +++ b/player/video/src/main/res/values-ko/strings.xml @@ -15,4 +15,5 @@ 재생/일시정지 앞으로 건너뛰기 진행 표시줄 + picture-in-picture 전환 \ No newline at end of file diff --git a/player/video/src/main/res/values-vi/strings.xml b/player/video/src/main/res/values-vi/strings.xml index 5b62e92c..f3061da1 100644 --- a/player/video/src/main/res/values-vi/strings.xml +++ b/player/video/src/main/res/values-vi/strings.xml @@ -9,4 +9,11 @@ Tua lùi Tua tới Bỏ qua đoạn mở đầu + Bỏ qua / Trở về + Khoá trình phát + Phát / Tạm dừng + Vào chế độ Hình trong Hình + Tiến tới + Tua mượt mà + Thanh tiến trình \ No newline at end of file diff --git a/preferences/src/main/java/dev/jdtech/jellyfin/AppPreferences.kt b/preferences/src/main/java/dev/jdtech/jellyfin/AppPreferences.kt index fbb8f4b1..fa72eda5 100644 --- a/preferences/src/main/java/dev/jdtech/jellyfin/AppPreferences.kt +++ b/preferences/src/main/java/dev/jdtech/jellyfin/AppPreferences.kt @@ -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) - } } diff --git a/renovate.json b/renovate.json index 3022bdb2..c4141782 100644 --- a/renovate.json +++ b/renovate.json @@ -3,6 +3,7 @@ "labels": ["dependencies"], "extends": [ "config:base", + "schedule:weekly", ":semanticCommits" ] }