From 8b747bf037002ae2d3be488d511c5d30e8f280fd Mon Sep 17 00:00:00 2001 From: Natanel Shitrit <65548905+Natanel-Shitrit@users.noreply.github.com> Date: Sat, 28 Oct 2023 13:38:35 +0300 Subject: [PATCH 01/16] refactor: remove `cleanUpOldDownloads` (#529) * Remove `cleanUpOldDownloads` * refactor: remove `downloadsMigrated` preference --------- Co-authored-by: Jarne Demeulemeester --- .../java/dev/jdtech/jellyfin/MainActivity.kt | 29 ------------------- .../dev/jdtech/jellyfin/AppPreferences.kt | 7 ----- 2 files changed, 36 deletions(-) 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 6df373d6..8a10177e 100644 --- a/app/phone/src/main/java/dev/jdtech/jellyfin/MainActivity.kt +++ b/app/phone/src/main/java/dev/jdtech/jellyfin/MainActivity.kt @@ -1,11 +1,9 @@ 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 -import androidx.lifecycle.lifecycleScope import androidx.navigation.NavController import androidx.navigation.NavGraph import androidx.navigation.fragment.NavHostFragment @@ -24,7 +22,6 @@ import dev.jdtech.jellyfin.database.ServerDatabaseDao import dev.jdtech.jellyfin.databinding.ActivityMainBinding import dev.jdtech.jellyfin.viewmodels.MainViewModel import dev.jdtech.jellyfin.work.SyncWorker -import kotlinx.coroutines.launch import javax.inject.Inject import dev.jdtech.jellyfin.core.R as CoreR @@ -46,7 +43,6 @@ class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) scheduleUserDataSync() - cleanUpOldDownloads() applyTheme() setupActivity() } @@ -134,31 +130,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/preferences/src/main/java/dev/jdtech/jellyfin/AppPreferences.kt b/preferences/src/main/java/dev/jdtech/jellyfin/AppPreferences.kt index 8879736d..6a97e827 100644 --- a/preferences/src/main/java/dev/jdtech/jellyfin/AppPreferences.kt +++ b/preferences/src/main/java/dev/jdtech/jellyfin/AppPreferences.kt @@ -144,11 +144,4 @@ constructor( putString(Constants.PREF_SORT_ORDER, value) } } - - // Temp - var downloadsMigrated - get() = sharedPreferences.getBoolean("downloadsMigrated", false) - set(value) = sharedPreferences.edit { - putBoolean("downloadsMigrated", value) - } } From 75d2b835dbf57ea5e2144803f78feb882fa7cbef Mon Sep 17 00:00:00 2001 From: Jarne Demeulemeester Date: Sat, 28 Oct 2023 15:15:51 +0200 Subject: [PATCH 02/16] refactor: generate kotlin code for dao --- .../dev/jdtech/jellyfin/di/DatabaseModule.kt | 2 +- data/build.gradle.kts | 1 + .../jellyfin/database/ServerDatabase.kt | 2 +- .../jellyfin/database/ServerDatabaseDao.kt | 150 +++++++++--------- 4 files changed, 78 insertions(+), 77 deletions(-) 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/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 } From b18b09eac1325e0d035da3c8be02363b0484c451 Mon Sep 17 00:00:00 2001 From: Jarne Demeulemeester Date: Sat, 28 Oct 2023 15:20:46 +0200 Subject: [PATCH 03/16] fix: delete userdata when deleting season or show --- core/src/main/java/dev/jdtech/jellyfin/utils/DownloaderImpl.kt | 2 ++ 1 file changed, 2 insertions(+) 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) } } } From 3087f301af6d6092d53051b29f6b8cf6c2d56280 Mon Sep 17 00:00:00 2001 From: Jarne Demeulemeester Date: Sat, 28 Oct 2023 15:32:54 +0200 Subject: [PATCH 04/16] chore: run renovate weekly Closes #513 --- renovate.json | 1 + 1 file changed, 1 insertion(+) 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" ] } From 38ed84c408ce1ce55e6b74bdec5ba306c41260f2 Mon Sep 17 00:00:00 2001 From: CodeName393 Date: Sat, 28 Oct 2023 07:57:44 +0000 Subject: [PATCH 05/16] chore(translate): (Korean) Currently translated at 99.4% (177 of 178 strings) Translation: Findroid/core Translate-URL: https://weblate.jdtech.dev/projects/findroid/core/ko/ --- core/src/main/res/values-ko/strings.xml | 3 +++ 1 file changed, 3 insertions(+) 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 From 6c3360f8e7e61971366a3b3a201c5387e3522d61 Mon Sep 17 00:00:00 2001 From: CodeName393 Date: Sat, 28 Oct 2023 08:00:23 +0000 Subject: [PATCH 06/16] chore(translate): (Korean) Currently translated at 100.0% (16 of 16 strings) Translation: Findroid/player:video Translate-URL: https://weblate.jdtech.dev/projects/findroid/playervideo/ko/ --- player/video/src/main/res/values-ko/strings.xml | 1 + 1 file changed, 1 insertion(+) 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 From 218b4f1af419e5d2a267c676a0084cc432f0eaf3 Mon Sep 17 00:00:00 2001 From: Jarne Demeulemeester Date: Mon, 6 Nov 2023 23:42:00 +0100 Subject: [PATCH 07/16] refactor: replace SharedFlows with Channels for sending events --- .../dev/jdtech/jellyfin/PlayerActivity.kt | 7 +++++-- .../jellyfin/fragments/AddServerFragment.kt | 7 ++++--- .../jellyfin/fragments/DownloadsFragment.kt | 19 +++++++++++------- .../fragments/EpisodeBottomSheetFragment.kt | 14 ++++++------- .../jellyfin/fragments/LoginFragment.kt | 7 ++++--- .../jellyfin/fragments/MovieFragment.kt | 14 ++++++------- .../jellyfin/fragments/SeasonFragment.kt | 7 +++++-- .../fragments/ServerAddressesFragment.kt | 7 ++++--- .../fragments/ServerSelectFragment.kt | 13 ++++++------ .../jdtech/jellyfin/fragments/ShowFragment.kt | 7 +++++-- .../jellyfin/fragments/UsersFragment.kt | 7 ++++--- .../jellyfin/viewmodels/AddServerViewModel.kt | 15 +++++++++----- .../jellyfin/viewmodels/DownloadsViewModel.kt | 15 +++++++++----- .../viewmodels/EpisodeBottomSheetViewModel.kt | 20 ++++++++++--------- .../jellyfin/viewmodels/LoginViewModel.kt | 17 ++++++++++------ .../jellyfin/viewmodels/MovieViewModel.kt | 20 ++++++++++--------- .../jellyfin/viewmodels/SeasonViewModel.kt | 14 ++++++++----- .../viewmodels/ServerAddressesViewModel.kt | 14 ++++++++----- .../viewmodels/ServerSelectViewModel.kt | 20 ++++++++++--------- .../jellyfin/viewmodels/ShowViewModel.kt | 14 ++++++++----- .../jellyfin/viewmodels/UsersViewModel.kt | 14 ++++++++----- .../viewmodels/PlayerActivityViewModel.kt | 14 ++++++++----- 22 files changed, 170 insertions(+), 116 deletions(-) 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/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 9321f676..a2ccb8a1 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/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 +} From 4f8ef331de2be78d92f25849e34f23f536db12f7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 6 Nov 2023 23:47:23 +0100 Subject: [PATCH 08/16] chore(deps): update aboutlibraries to v10.9.2 (#561) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index c2621cf6..9f1cab4b 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,5 +1,5 @@ [versions] -aboutlibraries = "10.9.1" +aboutlibraries = "10.9.2" android-plugin = "8.1.2" androidx-activity = "1.8.0" androidx-appcompat = "1.6.1" From 446e0dd648717cb83937b227a572f3885718c8d2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 6 Nov 2023 23:47:52 +0100 Subject: [PATCH 09/16] chore(deps): update androidx.navigation to v2.7.5 (#562) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 9f1cab4b..76577b2a 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -8,7 +8,7 @@ androidx-core = "1.12.0" androidx-hilt = "1.1.0-rc01" 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" From 4fcbc862cd7607c70eebdba171c4a7d4c255c856 Mon Sep 17 00:00:00 2001 From: Jarne Demeulemeester Date: Mon, 6 Nov 2023 23:49:12 +0100 Subject: [PATCH 10/16] chore(deps) update androidx.hilt to v1.1.0 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 76577b2a..a839ce8d 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -5,7 +5,7 @@ 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.5" From a4499f50c2ca334e1f9159f40c784f60dfd54224 Mon Sep 17 00:00:00 2001 From: Jarne Demeulemeester Date: Mon, 6 Nov 2023 23:49:28 +0100 Subject: [PATCH 11/16] chore(deps) update jellyfin to v1.4.5 --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index a839ce8d..0d18b922 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -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" From bc82b782567ad52eb9e06f4f4f0efd98f101f506 Mon Sep 17 00:00:00 2001 From: lzyhenniu <2998157572@qq.com> Date: Sat, 18 Nov 2023 01:54:40 +0000 Subject: [PATCH 12/16] chore(translate): (Chinese (Traditional)) Currently translated at 100.0% (178 of 178 strings) Translation: Findroid/core Translate-URL: https://weblate.jdtech.dev/projects/findroid/core/zh_Hant/ --- core/src/main/res/values-zh-rTW/strings.xml | 2 ++ 1 file changed, 2 insertions(+) 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 From 82dcbb4aaa324f94a525fbc78a4feb64877c38d5 Mon Sep 17 00:00:00 2001 From: Francisco Zorat Date: Thu, 23 Nov 2023 02:10:41 +0000 Subject: [PATCH 13/16] chore(translate): (Spanish (Latin America)) Currently translated at 100.0% (16 of 16 strings) Translation: Findroid/player:video Translate-URL: https://weblate.jdtech.dev/projects/findroid/playervideo/es_419/ --- player/video/src/main/res/values-b+es+419/strings.xml | 1 + 1 file changed, 1 insertion(+) 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 From 157b0e012a13090c284419a9941764bccdf5896d Mon Sep 17 00:00:00 2001 From: Zan Date: Thu, 7 Dec 2023 17:09:31 +0000 Subject: [PATCH 14/16] chore(translate): (Hungarian) Currently translated at 100.0% (178 of 178 strings) Translation: Findroid/core Translate-URL: https://weblate.jdtech.dev/projects/findroid/core/hu/ --- core/src/main/res/values-hu/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From e3df6e8d7e549a67cf55cb1c7ae1ea02c0db5085 Mon Sep 17 00:00:00 2001 From: Meguro Date: Sat, 9 Dec 2023 10:28:26 +0000 Subject: [PATCH 15/16] chore(translate): (Vietnamese) Currently translated at 100.0% (178 of 178 strings) Translation: Findroid/core Translate-URL: https://weblate.jdtech.dev/projects/findroid/core/vi/ --- core/src/main/res/values-vi/strings.xml | 37 +++++++++++++++++++++++++ 1 file changed, 37 insertions(+) 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 From 42650ee6c44a5219f5ed3c61e0d8a7ad694a2b08 Mon Sep 17 00:00:00 2001 From: Meguro Date: Sat, 9 Dec 2023 10:54:52 +0000 Subject: [PATCH 16/16] chore(translate): (Vietnamese) Currently translated at 100.0% (16 of 16 strings) Translation: Findroid/player:video Translate-URL: https://weblate.jdtech.dev/projects/findroid/playervideo/vi/ --- player/video/src/main/res/values-vi/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) 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