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
import android.os.Bundle
import android.os.Environment
import android.view.View
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
@ -52,7 +51,6 @@ class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
scheduleUserDataSync()
cleanUpOldDownloads()
applyTheme()
setupActivity()
}
@ -148,31 +146,6 @@ class MainActivity : AppCompatActivity() {
}
}
/**
* Temp to remove old downloads, will be removed in a future version
*/
private fun cleanUpOldDownloads() {
if (appPreferences.downloadsMigrated) {
return
}
lifecycleScope.launch {
val oldDir = applicationContext.getExternalFilesDir(Environment.DIRECTORY_MOVIES)
if (oldDir == null) {
appPreferences.downloadsMigrated = true
return@launch
}
try {
for (file in oldDir.listFiles()!!) {
file.delete()
}
} catch (_: Exception) {}
appPreferences.downloadsMigrated = true
}
}
private fun scheduleUserDataSync() {
val syncWorkRequest = OneTimeWorkRequestBuilder<SyncWorker>()
.setConstraints(

View file

@ -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()
}
}
}
}

View file

@ -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()
}
}
}

View file

@ -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 {

View file

@ -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)
}
}
}
}

View file

@ -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()
}
}
}

View file

@ -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)
}
}
}
}

View file

@ -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()
}
}
}
}

View file

@ -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()
}
}
}

View file

@ -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()
}
}
}
}

View file

@ -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()
}
}
}
}

View file

@ -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()
}
}
}

View file

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

View file

@ -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)
}
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -20,7 +20,7 @@
<string name="remove">Törlés</string>
<string name="cancel">Mégsem</string>
<string name="title_home">Kezdőlap</string>
<string name="title_media">Én médiám</string>
<string name="title_media">Könyvtáraid</string>
<string name="title_favorite">Kedvencek</string>
<string name="title_settings">Beállítások</string>
<string name="view_all">Összes megtekintése</string>

View file

@ -173,4 +173,7 @@
<string name="remove_server_address">서버 주소 제</string>
<string name="temp">임시</string>
<string name="subtitle_chip_text">CC</string>
<string name="picture_in_picture_gesture">Picture-in-picture 홈 제스쳐</string>
<string name="picture_in_picture_gesture_summary">영상 재생 중 홈 버튼 또는 제스쳐를 이용해 picture-in-picture 변경</string>
<string name="picture_in_picture">Picture-in-picture 기능</string>
</resources>

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="player_gestures_seek_summary">Vuốt ngang để tua tới hoặc tua lui</string>
<string name="player_gestures_seek">Cử chỉ tua</string>
<string name="offline_mode_icon">Biểu tượng Chế độ Ngoại tuyến</string>
<string name="offline_mode_go_online">Trực tuyến trở lại</string>
<string name="size">Kích thước</string>
<string name="downloading_error">Lỗi trong khi tải về</string>
<string name="offline_mode">Chế độ Ngoại tuyến</string>
<string name="no_server_connection">Không có kết nối tới máy chủ Jellyfin, để xem ngoại tuyến xin hãy bật Chế độ Ngoại tuyến</string>
<string name="internal">Nội</string>
<string name="not_enough_storage">Nội dùng này yêu cầu %1$s bộ nhớ trống nhưng chỉ có %2$s là có sẵn</string>
<string name="privacy_policy_notice">Bằng việc sử dụng Findroid, bạn đồng ý với <a href="https://raw.githubusercontent.com/jarnedemeulemeester/findroid/main/PRIVACY">Chính sách Bảo mật</a> rằng chúng tôi không thu thập bất cứ dữ liệu nào</string>
<string name="external">Ngoại</string>
<string name="preparing_download">Đang chuẩn bị tải về</string>
<string name="cancel_download">Hủy tải về</string>
<string name="audio">Âm thanh</string>
<string name="subtitle">Phụ đề</string>
<string name="subtitle_chip_text">Phụ đề chi tiết</string>
<string name="temp">tạm</string>
<string name="extra_info">Hiện thị thêm thông tin</string>
<string name="video">Video</string>
<string name="pref_player_trick_play">Tua mượt mà</string>
<string name="extra_info_summary">Hiển thị thêm các thông tin về âm thanh, video và phụ đề</string>
<string name="pref_player_trick_play_summary">Yêu cầu phần mở rộng Jellyscrub của nicknsy đã được cài đặt trên máy chủ</string>
<string name="storage_name">%1$s (%2$d MB trống)</string>
<string name="amoled_theme">Chủ đề màu tối (AMOLED)</string>
<string name="amoled_theme_summary">Sử dụng chủ đề cho màn hình AMOLED với nền màu đen tuyệt đối</string>
<string name="remove_server_address">Xoá địa chỉ máy chủ</string>
<string name="downloaded_indicator">Dấu hiệu đã tải về</string>
<string name="episode_name_extended_with_end">S%1$d:E%2$d-%3$d - %4$s</string>
<string name="remove_server_address_dialog_text">Bạn có chắc chắn muốn xoá địa chỉ máy chủ %1$s không</string>
<string name="episode_name_with_end">%1$d-%2$d. %3$s</string>
<string name="cancel_download_message">Bạn có chắc là muốn hủy tải về không?</string>
<string name="stop_download">Dừng tải về</string>
<string name="picture_in_picture">Hình trong hình</string>
<string name="picture_in_picture_gesture_summary">Sử dùng phím hoặc cử chỉ về màn hình chính để vào chế độ Hình trong Hình khi video đang phát</string>
<string name="picture_in_picture_gesture">Cử chỉ về màn hình chính khi đang hình trong hình</string>
<string name="app_language">Ngôn ngữ của ứng dụng</string>
<string name="select_storage_location">Chọn vị trí để lưu trữ</string>
<string name="storage_unavailable">Vị trí để lưu trữ hiện không có sẵn</string>
</resources>

View file

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

View file

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

View file

@ -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
}

View file

@ -24,210 +24,210 @@ import dev.jdtech.jellyfin.models.User
import java.util.UUID
@Dao
abstract class ServerDatabaseDao {
interface ServerDatabaseDao {
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract fun insertServer(server: Server)
fun insertServer(server: Server)
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract fun insertServerAddress(address: ServerAddress)
fun insertServerAddress(address: ServerAddress)
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract fun insertUser(user: User)
fun insertUser(user: User)
@Update
abstract fun update(server: Server)
fun update(server: Server)
@Query("SELECT * FROM servers WHERE id = :id")
abstract fun get(id: String): Server?
fun get(id: String): Server?
@Query("SELECT * FROM users WHERE id = :id")
abstract fun getUser(id: UUID): User?
fun getUser(id: UUID): User?
@Transaction
@Query("SELECT * FROM servers WHERE id = :id")
abstract fun getServerWithAddresses(id: String): ServerWithAddresses
fun getServerWithAddresses(id: String): ServerWithAddresses
@Transaction
@Query("SELECT * FROM servers WHERE id = :id")
abstract fun getServerWithUsers(id: String): ServerWithUsers
fun getServerWithUsers(id: String): ServerWithUsers
@Transaction
@Query("SELECT * FROM servers WHERE id = :id")
abstract fun getServerWithAddressesAndUsers(id: String): ServerWithAddressesAndUsers?
fun getServerWithAddressesAndUsers(id: String): ServerWithAddressesAndUsers?
@Query("DELETE FROM servers")
abstract fun clear()
fun clear()
@Query("SELECT * FROM servers")
abstract fun getAllServersSync(): List<Server>
fun getAllServersSync(): List<Server>
@Query("SELECT COUNT(*) FROM servers")
abstract fun getServersCount(): Int
fun getServersCount(): Int
@Query("DELETE FROM servers WHERE id = :id")
abstract fun delete(id: String)
fun delete(id: String)
@Query("DELETE FROM users WHERE id = :id")
abstract fun deleteUser(id: UUID)
fun deleteUser(id: UUID)
@Query("DELETE FROM serverAddresses WHERE id = :id")
abstract fun deleteServerAddress(id: UUID)
fun deleteServerAddress(id: UUID)
@Query("UPDATE servers SET currentUserId = :userId WHERE id = :serverId")
abstract fun updateServerCurrentUser(serverId: String, userId: UUID)
fun updateServerCurrentUser(serverId: String, userId: UUID)
@Query("SELECT * FROM users WHERE id = (SELECT currentUserId FROM servers WHERE id = :serverId)")
abstract fun getServerCurrentUser(serverId: String): User?
fun getServerCurrentUser(serverId: String): User?
@Query("SELECT * FROM serverAddresses WHERE id = (SELECT currentServerAddressId FROM servers WHERE id = :serverId)")
abstract fun getServerCurrentAddress(serverId: String): ServerAddress?
fun getServerCurrentAddress(serverId: String): ServerAddress?
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract fun insertMovie(movie: FindroidMovieDto)
fun insertMovie(movie: FindroidMovieDto)
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract fun insertSource(source: FindroidSourceDto)
fun insertSource(source: FindroidSourceDto)
@Query("SELECT * FROM movies WHERE id = :id")
abstract fun getMovie(id: UUID): FindroidMovieDto
fun getMovie(id: UUID): FindroidMovieDto
@Query("SELECT * FROM movies JOIN sources ON movies.id = sources.itemId ORDER BY movies.name ASC")
abstract fun getMoviesAndSources(): Map<FindroidMovieDto, List<FindroidSourceDto>>
fun getMoviesAndSources(): Map<FindroidMovieDto, List<FindroidSourceDto>>
@Query("SELECT * FROM sources WHERE itemId = :itemId")
abstract fun getSources(itemId: UUID): List<FindroidSourceDto>
fun getSources(itemId: UUID): List<FindroidSourceDto>
@Query("SELECT * FROM sources WHERE downloadId = :downloadId")
abstract fun getSourceByDownloadId(downloadId: Long): FindroidSourceDto?
fun getSourceByDownloadId(downloadId: Long): FindroidSourceDto?
@Query("UPDATE sources SET downloadId = :downloadId WHERE id = :id")
abstract fun setSourceDownloadId(id: String, downloadId: Long)
fun setSourceDownloadId(id: String, downloadId: Long)
@Query("UPDATE sources SET path = :path WHERE id = :id")
abstract fun setSourcePath(id: String, path: String)
fun setSourcePath(id: String, path: String)
@Query("DELETE FROM sources WHERE id = :id")
abstract fun deleteSource(id: String)
fun deleteSource(id: String)
@Query("DELETE FROM movies WHERE id = :id")
abstract fun deleteMovie(id: UUID)
fun deleteMovie(id: UUID)
@Query("UPDATE userdata SET playbackPositionTicks = :playbackPositionTicks WHERE itemId = :itemId AND userid = :userId")
abstract fun setPlaybackPositionTicks(itemId: UUID, userId: UUID, playbackPositionTicks: Long)
fun setPlaybackPositionTicks(itemId: UUID, userId: UUID, playbackPositionTicks: Long)
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract fun insertMediaStream(mediaStream: FindroidMediaStreamDto)
fun insertMediaStream(mediaStream: FindroidMediaStreamDto)
@Query("SELECT * FROM mediastreams WHERE sourceId = :sourceId")
abstract fun getMediaStreamsBySourceId(sourceId: String): List<FindroidMediaStreamDto>
fun getMediaStreamsBySourceId(sourceId: String): List<FindroidMediaStreamDto>
@Query("SELECT * FROM mediastreams WHERE downloadId = :downloadId")
abstract fun getMediaStreamByDownloadId(downloadId: Long): FindroidMediaStreamDto?
fun getMediaStreamByDownloadId(downloadId: Long): FindroidMediaStreamDto?
@Query("UPDATE mediastreams SET downloadId = :downloadId WHERE id = :id")
abstract fun setMediaStreamDownloadId(id: UUID, downloadId: Long)
fun setMediaStreamDownloadId(id: UUID, downloadId: Long)
@Query("UPDATE mediastreams SET path = :path WHERE id = :id")
abstract fun setMediaStreamPath(id: UUID, path: String)
fun setMediaStreamPath(id: UUID, path: String)
@Query("DELETE FROM mediastreams WHERE id = :id")
abstract fun deleteMediaStream(id: UUID)
fun deleteMediaStream(id: UUID)
@Query("DELETE FROM mediastreams WHERE sourceId = :sourceId")
abstract fun deleteMediaStreamsBySourceId(sourceId: String)
fun deleteMediaStreamsBySourceId(sourceId: String)
@Query("UPDATE userdata SET played = :played WHERE userId = :userId AND itemId = :itemId")
abstract fun setPlayed(userId: UUID, itemId: UUID, played: Boolean)
fun setPlayed(userId: UUID, itemId: UUID, played: Boolean)
@Query("UPDATE userdata SET favorite = :favorite WHERE userId = :userId AND itemId = :itemId")
abstract fun setFavorite(userId: UUID, itemId: UUID, favorite: Boolean)
fun setFavorite(userId: UUID, itemId: UUID, favorite: Boolean)
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract fun insertTrickPlayManifest(trickPlayManifestDto: TrickPlayManifestDto)
fun insertTrickPlayManifest(trickPlayManifestDto: TrickPlayManifestDto)
@Query("SELECT * FROM trickPlayManifests WHERE itemId = :itemId")
abstract fun getTrickPlayManifest(itemId: UUID): TrickPlayManifestDto?
fun getTrickPlayManifest(itemId: UUID): TrickPlayManifestDto?
@Query("DELETE FROM trickPlayManifests WHERE itemId = :itemId")
abstract fun deleteTrickPlayManifest(itemId: UUID)
fun deleteTrickPlayManifest(itemId: UUID)
@Query("SELECT * FROM movies ORDER BY name ASC")
abstract fun getMovies(): List<FindroidMovieDto>
fun getMovies(): List<FindroidMovieDto>
@Query("SELECT * FROM movies WHERE serverId = :serverId ORDER BY name ASC")
abstract fun getMoviesByServerId(serverId: String): List<FindroidMovieDto>
fun getMoviesByServerId(serverId: String): List<FindroidMovieDto>
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract fun insertShow(show: FindroidShowDto)
fun insertShow(show: FindroidShowDto)
@Query("SELECT * FROM shows WHERE id = :id")
abstract fun getShow(id: UUID): FindroidShowDto
fun getShow(id: UUID): FindroidShowDto
@Query("SELECT * FROM shows ORDER BY name ASC")
abstract fun getShows(): List<FindroidShowDto>
fun getShows(): List<FindroidShowDto>
@Query("SELECT * FROM shows WHERE serverId = :serverId ORDER BY name ASC")
abstract fun getShowsByServerId(serverId: String): List<FindroidShowDto>
fun getShowsByServerId(serverId: String): List<FindroidShowDto>
@Query("DELETE FROM shows WHERE id = :id")
abstract fun deleteShow(id: UUID)
fun deleteShow(id: UUID)
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract fun insertSeason(show: FindroidSeasonDto)
fun insertSeason(show: FindroidSeasonDto)
@Query("SELECT * FROM seasons WHERE id = :id")
abstract fun getSeason(id: UUID): FindroidSeasonDto
fun getSeason(id: UUID): FindroidSeasonDto
@Query("SELECT * FROM seasons WHERE seriesId = :seriesId ORDER BY indexNumber ASC")
abstract fun getSeasonsByShowId(seriesId: UUID): List<FindroidSeasonDto>
fun getSeasonsByShowId(seriesId: UUID): List<FindroidSeasonDto>
@Query("DELETE FROM seasons WHERE id = :id")
abstract fun deleteSeason(id: UUID)
fun deleteSeason(id: UUID)
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract fun insertEpisode(episode: FindroidEpisodeDto)
fun insertEpisode(episode: FindroidEpisodeDto)
@Query("SELECT * FROM episodes WHERE id = :id")
abstract fun getEpisode(id: UUID): FindroidEpisodeDto
fun getEpisode(id: UUID): FindroidEpisodeDto
@Query("SELECT * FROM episodes WHERE seriesId = :seriesId ORDER BY parentIndexNumber ASC, indexNumber ASC")
abstract fun getEpisodesByShowId(seriesId: UUID): List<FindroidEpisodeDto>
fun getEpisodesByShowId(seriesId: UUID): List<FindroidEpisodeDto>
@Query("SELECT * FROM episodes WHERE seasonId = :seasonId ORDER BY indexNumber ASC")
abstract fun getEpisodesBySeasonId(seasonId: UUID): List<FindroidEpisodeDto>
fun getEpisodesBySeasonId(seasonId: UUID): List<FindroidEpisodeDto>
@Query("SELECT * FROM episodes WHERE serverId = :serverId ORDER BY seriesName ASC, parentIndexNumber ASC, indexNumber ASC")
abstract fun getEpisodesByServerId(serverId: String): List<FindroidEpisodeDto>
fun getEpisodesByServerId(serverId: String): List<FindroidEpisodeDto>
@Query("SELECT episodes.id, episodes.serverId, episodes.seasonId, episodes.seriesId, episodes.name, episodes.seriesName, episodes.overview, episodes.indexNumber, episodes.indexNumberEnd, episodes.parentIndexNumber, episodes.runtimeTicks, episodes.premiereDate, episodes.communityRating FROM episodes INNER JOIN userdata ON episodes.id = userdata.itemId WHERE serverId = :serverId AND playbackPositionTicks > 0 ORDER BY episodes.parentIndexNumber ASC, episodes.indexNumber ASC")
abstract fun getEpisodeResumeItems(serverId: String): List<FindroidEpisodeDto>
fun getEpisodeResumeItems(serverId: String): List<FindroidEpisodeDto>
@Query("DELETE FROM episodes WHERE id = :id")
abstract fun deleteEpisode(id: UUID)
fun deleteEpisode(id: UUID)
@Query("DELETE FROM episodes WHERE seasonId = :seasonId")
abstract fun deleteEpisodesBySeasonId(seasonId: UUID)
fun deleteEpisodesBySeasonId(seasonId: UUID)
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract fun insertIntro(intro: IntroDto)
fun insertIntro(intro: IntroDto)
@Query("SELECT * FROM intros WHERE itemId = :itemId")
abstract fun getIntro(itemId: UUID): IntroDto?
fun getIntro(itemId: UUID): IntroDto?
@Query("DELETE FROM intros WHERE itemId = :itemId")
abstract fun deleteIntro(itemId: UUID)
fun deleteIntro(itemId: UUID)
@Query("SELECT * FROM seasons")
abstract fun getSeasons(): List<FindroidSeasonDto>
fun getSeasons(): List<FindroidSeasonDto>
@Query("SELECT * FROM episodes")
abstract fun getEpisodes(): List<FindroidEpisodeDto>
fun getEpisodes(): List<FindroidEpisodeDto>
@Query("SELECT * FROM userdata WHERE itemId = :itemId AND userId = :userId")
abstract fun getUserData(itemId: UUID, userId: UUID): FindroidUserDataDto?
fun getUserData(itemId: UUID, userId: UUID): FindroidUserDataDto?
@Transaction
open fun getUserDataOrCreateNew(itemId: UUID, userId: UUID): FindroidUserDataDto {
fun getUserDataOrCreateNew(itemId: UUID, userId: UUID): FindroidUserDataDto {
var userData = getUserData(itemId, userId)
// Create user data when there is none
@ -246,23 +246,23 @@ abstract class ServerDatabaseDao {
}
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract fun insertUserData(userData: FindroidUserDataDto)
fun insertUserData(userData: FindroidUserDataDto)
@Query("DELETE FROM userdata WHERE itemId = :itemId")
abstract fun deleteUserData(itemId: UUID)
fun deleteUserData(itemId: UUID)
@Query("SELECT * FROM userdata WHERE userId = :userId AND itemId = :itemId AND toBeSynced = 1")
abstract fun getUserDataToBeSynced(userId: UUID, itemId: UUID): FindroidUserDataDto?
fun getUserDataToBeSynced(userId: UUID, itemId: UUID): FindroidUserDataDto?
@Query("UPDATE userdata SET toBeSynced = :toBeSynced WHERE itemId = :itemId AND userId = :userId")
abstract fun setUserDataToBeSynced(userId: UUID, itemId: UUID, toBeSynced: Boolean)
fun setUserDataToBeSynced(userId: UUID, itemId: UUID, toBeSynced: Boolean)
@Query("SELECT * FROM movies WHERE serverId = :serverId AND name LIKE '%' || :name || '%'")
abstract fun searchMovies(serverId: String, name: String): List<FindroidMovieDto>
fun searchMovies(serverId: String, name: String): List<FindroidMovieDto>
@Query("SELECT * FROM shows WHERE serverId = :serverId AND name LIKE '%' || :name || '%'")
abstract fun searchShows(serverId: String, name: String): List<FindroidShowDto>
fun searchShows(serverId: String, name: String): List<FindroidShowDto>
@Query("SELECT * FROM episodes WHERE serverId = :serverId AND name LIKE '%' || :name || '%'")
abstract fun searchEpisodes(serverId: String, name: String): List<FindroidEpisodeDto>
fun searchEpisodes(serverId: String, name: String): List<FindroidEpisodeDto>
}

View file

@ -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"

View file

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

View file

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

View file

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

View file

@ -9,4 +9,11 @@
<string name="player_controls_rewind">Tua lùi</string>
<string name="player_controls_fast_forward">Tua tới</string>
<string name="player_controls_skip_intro">Bỏ qua đoạn mở đầu</string>
<string name="player_controls_skip_back">Bỏ qua / Trở về</string>
<string name="player_controls_lock">Khoá trình phát</string>
<string name="player_controls_play_pause">Phát / Tạm dừng</string>
<string name="player_controls_picture_in_picture">Vào chế độ Hình trong Hình</string>
<string name="player_controls_skip_forward">Tiến tới</string>
<string name="player_trickplay">Tua mượt mà</string>
<string name="player_controls_progress">Thanh tiến trình</string>
</resources>

View file

@ -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)
}
}

View file

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