From 6c86c2ee484eef146617857b7ec14f6ba95230cd Mon Sep 17 00:00:00 2001 From: Xilin Jia <6257601+XilinJia@users.noreply.github.com> Date: Thu, 31 Oct 2024 08:52:45 +0100 Subject: [PATCH] 6.13.2 commit --- app/build.gradle | 11 ++- .../service/DownloadServiceInterfaceImpl.kt | 7 +- .../ac/mdiq/podcini/net/sync/SyncService.kt | 7 +- .../playback/service/PlaybackService.kt | 83 +++++++++---------- .../podcini/preferences/UserPreferences.kt | 3 +- .../fragments/PlaybackPreferencesFragment.kt | 44 +++++----- .../mdiq/podcini/storage/database/Episodes.kt | 55 ++++-------- .../ac/mdiq/podcini/storage/database/Feeds.kt | 2 +- .../mdiq/podcini/storage/database/Queues.kt | 36 ++++---- .../podcini/storage/model/EpisodeFilter.kt | 6 ++ .../mdiq/podcini/storage/utils/EpisodeUtil.kt | 11 ++- .../podcini/ui/actions/EpisodeActionButton.kt | 6 +- .../mdiq/podcini/ui/actions/SwipeActions.kt | 54 +++++++----- .../ac/mdiq/podcini/ui/compose/EpisodesVM.kt | 64 ++++++++++---- .../ui/fragment/AudioPlayerFragment.kt | 7 +- .../ui/fragment/EpisodeInfoFragment.kt | 5 +- .../podcini/ui/fragment/QueuesFragment.kt | 7 +- app/src/main/res/values-ar/strings.xml | 4 +- app/src/main/res/values-ca/strings.xml | 4 +- app/src/main/res/values-cs/strings.xml | 4 +- app/src/main/res/values-da/strings.xml | 4 +- app/src/main/res/values-de/strings.xml | 4 +- app/src/main/res/values-es/strings.xml | 4 +- app/src/main/res/values-eu/strings.xml | 4 +- app/src/main/res/values-fa/strings.xml | 4 +- app/src/main/res/values-fi/strings.xml | 4 +- app/src/main/res/values-fr/strings.xml | 4 +- app/src/main/res/values-gl/strings.xml | 4 +- app/src/main/res/values-in/strings.xml | 4 +- app/src/main/res/values-it/strings.xml | 4 +- app/src/main/res/values-iw/strings.xml | 4 +- app/src/main/res/values-ko/strings.xml | 4 +- app/src/main/res/values-nb/strings.xml | 4 +- app/src/main/res/values-nl/strings.xml | 4 +- app/src/main/res/values-pl/strings.xml | 4 +- app/src/main/res/values-pt-rBR/strings.xml | 4 +- app/src/main/res/values-pt/strings.xml | 4 +- app/src/main/res/values-ro/strings.xml | 4 +- app/src/main/res/values-ru/strings.xml | 4 +- app/src/main/res/values-sk/strings.xml | 4 +- app/src/main/res/values-sv/strings.xml | 4 +- app/src/main/res/values-tr/strings.xml | 4 +- app/src/main/res/values-uk/strings.xml | 4 +- app/src/main/res/values-zh-rCN/strings.xml | 4 +- app/src/main/res/values/strings.xml | 7 +- .../main/res/xml/preferences_downloads.xml | 4 +- app/src/main/res/xml/preferences_playback.xml | 14 ++-- changelog.md | 11 +++ .../android/en-US/changelogs/3020289.txt | 10 +++ 49 files changed, 302 insertions(+), 260 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/3020289.txt diff --git a/app/build.gradle b/app/build.gradle index 6f38231e..78166d4d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -31,8 +31,8 @@ android { // testApplicationId "ac.mdiq.podcini.tests" // testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" - versionCode 3020288 - versionName "6.13.1" + versionCode 3020289 + versionName "6.13.2" applicationId "ac.mdiq.podcini.R" def commit = "" @@ -167,6 +167,13 @@ android { androidResources { additionalParameters "--no-version-vectors" } + + dependenciesInfo { + // Disables dependency metadata when building APKs. + includeInApk = false + // Disables dependency metadata when building Android App Bundles. + includeInBundle = false + } } dependencies { diff --git a/app/src/main/kotlin/ac/mdiq/podcini/net/download/service/DownloadServiceInterfaceImpl.kt b/app/src/main/kotlin/ac/mdiq/podcini/net/download/service/DownloadServiceInterfaceImpl.kt index e696bb1b..82469796 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/net/download/service/DownloadServiceInterfaceImpl.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/net/download/service/DownloadServiceInterfaceImpl.kt @@ -7,12 +7,15 @@ import ac.mdiq.podcini.net.sync.SynchronizationSettings.isProviderConnected import ac.mdiq.podcini.net.sync.model.EpisodeAction import ac.mdiq.podcini.net.sync.queue.SynchronizationQueueSink import ac.mdiq.podcini.net.utils.NetworkUtils.isAllowMobileEpisodeDownload +import ac.mdiq.podcini.playback.base.InTheatre.curQueue import ac.mdiq.podcini.preferences.UserPreferences import ac.mdiq.podcini.preferences.UserPreferences.appPrefs import ac.mdiq.podcini.storage.database.Episodes import ac.mdiq.podcini.storage.database.LogsAndStats import ac.mdiq.podcini.storage.database.Queues +import ac.mdiq.podcini.storage.database.Queues.removeFromQueueSync import ac.mdiq.podcini.storage.database.RealmDB.realm +import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk import ac.mdiq.podcini.storage.model.DownloadResult import ac.mdiq.podcini.storage.model.Episode @@ -70,7 +73,7 @@ class DownloadServiceInterfaceImpl : DownloadServiceInterface() { WorkManager.getInstance(context).enqueueUniqueWork(item.media!!.downloadUrl!!, ExistingWorkPolicy.KEEP, workRequest.build()) } - @OptIn(UnstableApi::class) override fun cancel(context: Context, media: EpisodeMedia) { + override fun cancel(context: Context, media: EpisodeMedia) { Logd(TAG, "starting cancel") // This needs to be done here, not in the worker. Reason: The worker might or might not be running. val item_ = media.episodeOrFetch() @@ -84,7 +87,7 @@ class DownloadServiceInterfaceImpl : DownloadServiceInterface() { workInfoList.forEach { workInfo -> if (workInfo.tags.contains(WORK_DATA_WAS_QUEUED)) { val item_ = media.episodeOrFetch() - if (item_ != null) Queues.removeFromQueue(item_) + if (item_ != null) runOnIOScope { removeFromQueueSync(curQueue, item_) } } } WorkManager.getInstance(context).cancelAllWorkByTag(tag) diff --git a/app/src/main/kotlin/ac/mdiq/podcini/net/sync/SyncService.kt b/app/src/main/kotlin/ac/mdiq/podcini/net/sync/SyncService.kt index 5b363461..be097f52 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/net/sync/SyncService.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/net/sync/SyncService.kt @@ -18,6 +18,7 @@ import ac.mdiq.podcini.net.sync.queue.SynchronizationQueueStorage import ac.mdiq.podcini.net.utils.NetworkUtils.isAllowMobileFor import ac.mdiq.podcini.net.utils.NetworkUtils.setAllowMobileFor import ac.mdiq.podcini.net.utils.UrlChecker.containsUrl +import ac.mdiq.podcini.playback.base.InTheatre.curQueue import ac.mdiq.podcini.preferences.UserPreferences import ac.mdiq.podcini.preferences.UserPreferences.appPrefs import ac.mdiq.podcini.storage.database.Episodes.getEpisodeByGuidOrUrl @@ -26,7 +27,7 @@ import ac.mdiq.podcini.storage.database.Feeds.deleteFeedSync import ac.mdiq.podcini.storage.database.Feeds.getFeedList import ac.mdiq.podcini.storage.database.Feeds.getFeedListDownloadUrls import ac.mdiq.podcini.storage.database.Feeds.updateFeed -import ac.mdiq.podcini.storage.database.Queues.removeFromQueue +import ac.mdiq.podcini.storage.database.Queues.removeFromQueueSync import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope import ac.mdiq.podcini.storage.database.RealmDB.upsert import ac.mdiq.podcini.storage.model.Episode @@ -289,8 +290,10 @@ open class SyncService(context: Context, params: WorkerParameters) : Worker(cont // if (result.first != null) queueToBeRemoved.add(result.second) updatedItems.add(result.second) } - removeFromQueue(*updatedItems.toTypedArray()) +// removeFromQueue(*updatedItems.toTypedArray()) + runOnIOScope { + removeFromQueueSync(curQueue, *updatedItems.toTypedArray()) for (episode in updatedItems) { upsert(episode) {} } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/playback/service/PlaybackService.kt b/app/src/main/kotlin/ac/mdiq/podcini/playback/service/PlaybackService.kt index 74e8c49f..93d734a5 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/playback/service/PlaybackService.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/playback/service/PlaybackService.kt @@ -34,14 +34,16 @@ import ac.mdiq.podcini.preferences.UserPreferences.rewindSecs import ac.mdiq.podcini.receiver.MediaButtonReceiver import ac.mdiq.podcini.storage.database.Episodes.deleteMediaSync import ac.mdiq.podcini.storage.database.Episodes.getEpisodeByGuidOrUrl -import ac.mdiq.podcini.storage.database.Episodes.setCompletionDate +import ac.mdiq.podcini.storage.database.Episodes.prefDeleteRemovesFromQueue +import ac.mdiq.podcini.storage.database.Episodes.prefRemoveFromQueueMarkedPlayed import ac.mdiq.podcini.storage.database.Episodes.setPlayStateSync -import ac.mdiq.podcini.storage.database.Episodes.shouldDeleteRemoveFromQueue -import ac.mdiq.podcini.storage.database.Feeds.shouldAutoDeleteItem +import ac.mdiq.podcini.storage.database.Feeds.allowForAutoDelete +import ac.mdiq.podcini.storage.database.Queues.removeFromAllQueuesSync import ac.mdiq.podcini.storage.database.Queues.removeFromQueueSync import ac.mdiq.podcini.storage.database.RealmDB.realm import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope import ac.mdiq.podcini.storage.database.RealmDB.unmanaged +import ac.mdiq.podcini.storage.database.RealmDB.upsert import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk import ac.mdiq.podcini.storage.model.* import ac.mdiq.podcini.storage.model.CurrentState.Companion.NO_MEDIA_PLAYING @@ -218,7 +220,9 @@ class PlaybackService : MediaLibraryService() { // sound is about to change, eg. bluetooth -> speaker Log.d(TAG, "audioBecomingNoisy onReceive called with action: ${intent.action}") Logd(TAG, "Pausing playback because audio is becoming noisy") - pauseIfPauseOnDisconnect() +// pauseIfPauseOnDisconnect() + transientPause = (MediaPlayerBase.status == PlayerStatus.PLAYING || MediaPlayerBase.status == PlayerStatus.FALLBACK) + if (isPauseOnHeadsetDisconnect && !isCasting) mPlayer?.pause(!isPersistNotify, false) } } @@ -271,6 +275,9 @@ class PlaybackService : MediaLibraryService() { list } + val shouldSkipKeepEpisode by lazy { appPrefs.getBoolean(UserPreferences.Prefs.prefSkipKeepsEpisode.name, true) } + val shouldKeepSuperEpisode by lazy { appPrefs.getBoolean(UserPreferences.Prefs.prefFavoriteKeepsEpisode.name, true) } + private val mediaPlayerCallback: MediaPlayerCallback = object : MediaPlayerCallback { override fun statusChanged(newInfo: MediaPlayerInfo?) { currentMediaType = mPlayer?.mediaType ?: MediaType.UNKNOWN @@ -354,35 +361,31 @@ class PlaybackService : MediaLibraryService() { } } if (item != null) { -// fun shouldSkipKeepEpisode(): Boolean { -// return appPrefs.getBoolean(UserPreferences.Prefs.prefSkipKeepsEpisode.name, true) -// } -// fun shouldFavoriteKeepEpisode(): Boolean { -// return appPrefs.getBoolean(UserPreferences.Prefs.prefFavoriteKeepsEpisode.name, true) -// } runOnIOScope { - val shouldSkipKeepEpisode = appPrefs.getBoolean(UserPreferences.Prefs.prefSkipKeepsEpisode.name, true) - val shouldFavoriteKeepEpisode = appPrefs.getBoolean(UserPreferences.Prefs.prefFavoriteKeepsEpisode.name, true) if (ended || smartMarkAsPlayed || autoSkipped || (skipped && !shouldSkipKeepEpisode)) { Logd(TAG, "onPostPlayback ended: $ended smartMarkAsPlayed: $smartMarkAsPlayed autoSkipped: $autoSkipped skipped: $skipped") // only mark the item as played if we're not keeping it anyways - item = setPlayStateSync(PlayState.PLAYED.code, ended || (skipped && smartMarkAsPlayed), item!!) + item = setPlayStateSync(PlayState.PLAYED.code, item!!, ended || (skipped && smartMarkAsPlayed), false) + if (playable is EpisodeMedia && (ended || skipped || playingNext)) { + item = upsert(item!!) { it.media?.playbackCompletionDate = Date() } + EventFlow.postEvent(FlowEvent.HistoryEvent()) + } val action = item?.feed?.preferences?.autoDeleteAction val shouldAutoDelete = (action == AutoDeleteAction.ALWAYS || - (action == AutoDeleteAction.GLOBAL && item?.feed != null && shouldAutoDeleteItem(item!!.feed!!))) - if (playable is EpisodeMedia && shouldAutoDelete && (item?.isSUPER != true || !shouldFavoriteKeepEpisode)) { + (action == AutoDeleteAction.GLOBAL && item?.feed != null && allowForAutoDelete(item!!.feed!!))) + val isItemdeletable = (!shouldKeepSuperEpisode || (item?.isSUPER != true && item?.playState != PlayState.AGAIN.code && item?.playState != PlayState.FOREVER.code)) + if (playable is EpisodeMedia && shouldAutoDelete && isItemdeletable) { if (playable.localFileAvailable()) item = deleteMediaSync(this@PlaybackService, item!!) - if (shouldDeleteRemoveFromQueue()) removeFromQueueSync(null, item!!) - } + if (prefDeleteRemovesFromQueue) removeFromQueueSync(null, item!!) + } else if (prefRemoveFromQueueMarkedPlayed) removeFromAllQueuesSync(item!!) } - if (playable is EpisodeMedia && (ended || skipped || playingNext)) setCompletionDate(item!!) } } } override fun onPlaybackStart(playable: Playable, position: Int) { - Logd(TAG, "onPlaybackStart position: $position") val delayInterval = positionUpdateInterval(playable.getDuration()) + Logd(TAG, "onPlaybackStart position: $position delayInterval: $delayInterval") taskManager.startWidgetUpdater(delayInterval) // if (position != Playable.INVALID_TIME) playable.setPosition(position + (delayInterval/2).toInt()) if (position != Playable.INVALID_TIME) playable.setPosition(position) @@ -453,7 +456,7 @@ class PlaybackService : MediaLibraryService() { writeNoMediaPlaying() return null } -// EventFlow.postEvent(FlowEvent.PlayEvent(nextItem)) + EventFlow.postEvent(FlowEvent.PlayEvent(nextItem)) return if (nextItem.media == null) null else unmanaged(nextItem.media!!) } override fun findMedia(url: String): Playable? { @@ -1183,11 +1186,11 @@ class PlaybackService : MediaLibraryService() { } } - private fun pauseIfPauseOnDisconnect() { - Logd(TAG, "pauseIfPauseOnDisconnect()") - transientPause = (MediaPlayerBase.status == PlayerStatus.PLAYING || MediaPlayerBase.status == PlayerStatus.FALLBACK) - if (isPauseOnHeadsetDisconnect && !isCasting) mPlayer?.pause(!isPersistNotify, false) - } +// private fun pauseIfPauseOnDisconnect() { +// Logd(TAG, "pauseIfPauseOnDisconnect()") +// transientPause = (MediaPlayerBase.status == PlayerStatus.PLAYING || MediaPlayerBase.status == PlayerStatus.FALLBACK) +// if (isPauseOnHeadsetDisconnect && !isCasting) mPlayer?.pause(!isPersistNotify, false) +// } /** * @param bluetooth true if the event for unpausing came from bluetooth @@ -1318,9 +1321,6 @@ class PlaybackService : MediaLibraryService() { if (!isPositionSaverActive) { var positionSaver = Runnable { callback.positionSaverTick() } positionSaver = useMainThreadIfNecessary(positionSaver) -// val delayInterval = positionUpdateInterval(duration) -// positionSaverFuture = schedExecutor.scheduleWithFixedDelay( -// positionSaver, POSITION_SAVER_WAITING_INTERVAL.toLong(), POSITION_SAVER_WAITING_INTERVAL.toLong(), TimeUnit.MILLISECONDS) positionSaverFuture = schedExecutor.scheduleWithFixedDelay(positionSaver, delayInterval, delayInterval, TimeUnit.MILLISECONDS) Logd(TAG, "Started PositionSaver") } else Logd(TAG, "Call to startPositionSaver was ignored.") @@ -1493,7 +1493,6 @@ class PlaybackService : MediaLibraryService() { Logd(TAG, "Sleep timer expired") shakeListener?.pause() shakeListener = null - hasVibrated = false } } @@ -1547,7 +1546,6 @@ class PlaybackService : MediaLibraryService() { val gX = event.values[0] / SensorManager.GRAVITY_EARTH val gY = event.values[1] / SensorManager.GRAVITY_EARTH val gZ = event.values[2] / SensorManager.GRAVITY_EARTH - val gForce = sqrt((gX * gX + gY * gY + gZ * gZ).toDouble()) if (gForce > 2.25) { Logd(TAG, "Detected shake $gForce") @@ -1562,13 +1560,12 @@ class PlaybackService : MediaLibraryService() { private const val SCHED_EX_POOL_SIZE = 2 private const val SLEEP_TIMER_UPDATE_INTERVAL = 10000L // in millisoconds - const val POSITION_SAVER_WAITING_INTERVAL: Int = 5000 // in millisoconds -// const val WIDGET_UPDATER_NOTIFICATION_INTERVAL: Int = 5000 // in millisoconds + const val MIN_POSITION_SAVER_INTERVAL: Int = 5000 // in millisoconds const val NOTIFICATION_THRESHOLD: Long = 10000 // in millisoconds fun positionUpdateInterval(duration: Int): Long { - return if (prefAdaptiveProgressUpdate) max(POSITION_SAVER_WAITING_INTERVAL, duration/50).toLong() - else POSITION_SAVER_WAITING_INTERVAL.toLong() + return if (prefAdaptiveProgressUpdate) max(MIN_POSITION_SAVER_INTERVAL, duration/50).toLong() + else MIN_POSITION_SAVER_INTERVAL.toLong() } } } @@ -1622,23 +1619,17 @@ class PlaybackService : MediaLibraryService() { /** * @return `true` if notifications are persistent, `false` otherwise */ - val isPersistNotify: Boolean - get() = appPrefs.getBoolean(UserPreferences.Prefs.prefPersistNotify.name, true) + val isPersistNotify: Boolean by lazy { appPrefs.getBoolean(UserPreferences.Prefs.prefPersistNotify.name, true) } - val isPauseOnHeadsetDisconnect: Boolean - get() = appPrefs.getBoolean(UserPreferences.Prefs.prefPauseOnHeadsetDisconnect.name, true) + val isPauseOnHeadsetDisconnect: Boolean by lazy { appPrefs.getBoolean(UserPreferences.Prefs.prefPauseOnHeadsetDisconnect.name, true) } - val isUnpauseOnHeadsetReconnect: Boolean - get() = appPrefs.getBoolean(UserPreferences.Prefs.prefUnpauseOnHeadsetReconnect.name, true) + val isUnpauseOnHeadsetReconnect: Boolean by lazy { appPrefs.getBoolean(UserPreferences.Prefs.prefUnpauseOnHeadsetReconnect.name, true) } - val isUnpauseOnBluetoothReconnect: Boolean - get() = appPrefs.getBoolean(UserPreferences.Prefs.prefUnpauseOnBluetoothReconnect.name, false) + val isUnpauseOnBluetoothReconnect: Boolean by lazy { appPrefs.getBoolean(UserPreferences.Prefs.prefUnpauseOnBluetoothReconnect.name, false) } - val hardwareForwardButton: Int - get() = appPrefs.getString(UserPreferences.Prefs.prefHardwareForwardButton.name, KeyEvent.KEYCODE_MEDIA_FAST_FORWARD.toString())!!.toInt() + val hardwareForwardButton: Int by lazy { appPrefs.getString(UserPreferences.Prefs.prefHardwareForwardButton.name, KeyEvent.KEYCODE_MEDIA_FAST_FORWARD.toString())!!.toInt() } - val hardwarePreviousButton: Int - get() = appPrefs.getString(UserPreferences.Prefs.prefHardwarePreviousButton.name, KeyEvent.KEYCODE_MEDIA_REWIND.toString())!!.toInt() + val hardwarePreviousButton: Int by lazy { appPrefs.getString(UserPreferences.Prefs.prefHardwarePreviousButton.name, KeyEvent.KEYCODE_MEDIA_REWIND.toString())!!.toInt() } /** * Set to true to enable Continuous Playback diff --git a/app/src/main/kotlin/ac/mdiq/podcini/preferences/UserPreferences.kt b/app/src/main/kotlin/ac/mdiq/podcini/preferences/UserPreferences.kt index 90c56256..bdd5073c 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/preferences/UserPreferences.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/preferences/UserPreferences.kt @@ -311,7 +311,8 @@ object UserPreferences { prefFavoriteKeepsEpisode, prefAutoDelete, prefAutoDeleteLocal, - prefSmartMarkAsPlayedSecs, +// prefSmartMarkAsPlayedSecs, +// prefSmartMarkAsPlayedPercent, prefPlaybackSpeedArray, prefFallbackSpeed, prefPauseForFocusLoss, diff --git a/app/src/main/kotlin/ac/mdiq/podcini/preferences/fragments/PlaybackPreferencesFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/preferences/fragments/PlaybackPreferencesFragment.kt index 3b0051b3..010aaba5 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/preferences/fragments/PlaybackPreferencesFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/preferences/fragments/PlaybackPreferencesFragment.kt @@ -36,7 +36,7 @@ class PlaybackPreferencesFragment : PreferenceFragmentCompat() { addPreferencesFromResource(R.xml.preferences_playback) setupPlaybackScreen() - buildSmartMarkAsPlayedPreference() +// buildSmartMarkAsPlayedPreference() } override fun onStart() { @@ -111,27 +111,27 @@ class PlaybackPreferencesFragment : PreferenceFragmentCompat() { return findPreference(key) ?: throw IllegalArgumentException("Preference with key '$key' is not found") } - private fun buildSmartMarkAsPlayedPreference() { - val res = requireActivity().resources - - val pref = findPreference(UserPreferences.Prefs.prefSmartMarkAsPlayedSecs.name) - val values = res.getStringArray(R.array.smart_mark_as_played_values) - val entries = arrayOfNulls(values.size) - for (x in values.indices) { - if (x == 0) { - entries[x] = res.getString(R.string.pref_smart_mark_as_played_disabled) - } else { - var v = values[x].toInt() - if (v < 60) { - entries[x] = res.getQuantityString(R.plurals.time_seconds_quantified, v, v) - } else { - v /= 60 - entries[x] = res.getQuantityString(R.plurals.time_minutes_quantified, v, v) - } - } - } - pref!!.entries = entries - } +// private fun buildSmartMarkAsPlayedPreference() { +// val res = requireActivity().resources +// +// val pref = findPreference(UserPreferences.Prefs.prefSmartMarkAsPlayedSecs.name) +// val values = res.getStringArray(R.array.smart_mark_as_played_values) +// val entries = arrayOfNulls(values.size) +// for (x in values.indices) { +// if (x == 0) { +// entries[x] = res.getString(R.string.pref_smart_mark_as_played_disabled) +// } else { +// var v = values[x].toInt() +// if (v < 60) { +// entries[x] = res.getQuantityString(R.plurals.time_seconds_quantified, v, v) +// } else { +// v /= 60 +// entries[x] = res.getQuantityString(R.plurals.time_minutes_quantified, v, v) +// } +// } +// } +// pref!!.entries = entries +// } @UnstableApi class EditFallbackSpeedDialog(activity: Activity) { diff --git a/app/src/main/kotlin/ac/mdiq/podcini/storage/database/Episodes.kt b/app/src/main/kotlin/ac/mdiq/podcini/storage/database/Episodes.kt index 05f70aaf..2db7128e 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/storage/database/Episodes.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/storage/database/Episodes.kt @@ -96,21 +96,22 @@ object Episodes { } // @JvmStatic is needed because some Runnable blocks call this - @OptIn(UnstableApi::class) @JvmStatic + @JvmStatic fun deleteEpisodeMedia(context: Context, episode: Episode) : Job { Logd(TAG, "deleteMediaOfEpisode called ${episode.title}") return runOnIOScope { if (episode.media == null) return@runOnIOScope val episode_ = deleteMediaSync(context, episode) - if (shouldDeleteRemoveFromQueue()) removeFromQueueSync(null, episode_) + if (prefDeleteRemovesFromQueue) removeFromQueueSync(null, episode_) } } - fun shouldDeleteRemoveFromQueue(): Boolean { - return appPrefs.getBoolean(Prefs.prefDeleteRemovesFromQueue.name, false) - } + val prefDeleteRemovesFromQueue: Boolean + get() = appPrefs.getBoolean(Prefs.prefDeleteRemovesFromQueue.name, false) +// fun shouldDeleteRemoveFromQueue(): Boolean { +// return appPrefs.getBoolean(Prefs.prefDeleteRemovesFromQueue.name, false) +// } - @OptIn(UnstableApi::class) fun deleteMediaSync(context: Context, episode: Episode): Episode { Logd(TAG, "deleteMediaSync called") val media = episode.media ?: return episode @@ -176,6 +177,7 @@ object Episodes { } /** + * This is used when the episodes are not listed with the feed. * Remove the listed episodes and their EpisodeMedia entries. * Deleting media also removes the download log entries. */ @@ -210,31 +212,6 @@ object Episodes { } } - /** - * This method will set the playback completion date to the current date regardless of the current value. - * @param episode Episode that should be added to the playback history. - * @param date PlaybackCompletionDate for `media` - */ - fun setCompletionDate(episode: Episode, date: Date? = Date()) : Job { - Logd(TAG, "setCompletionDate called played: ${episode.playState}") - return runOnIOScope { - val episode_ = realm.query(Episode::class).query("id == $0", episode.id).first().find() - if (episode_ != null) { - upsert(episode_) { it.media?.playbackCompletionDate = date } - EventFlow.postEvent(FlowEvent.HistoryEvent()) - } - } - } - -// @JvmStatic -// fun setFavorite(episode: Episode, stat: Boolean?) : Job { -// Logd(TAG, "setFavorite called $stat") -// return runOnIOScope { -// val result = upsert(episode) { it.rating = if (stat ?: !it.isFavorite) Episode.Rating.FAVORITE.code else Episode.Rating.NEUTRAL.code } -// EventFlow.postEvent(FlowEvent.RatingEvent(result, result.rating)) -// } -// } - fun setRating(episode: Episode, rating: Int) : Job { Logd(TAG, "setRating called $rating") return runOnIOScope { @@ -254,13 +231,12 @@ object Episodes { Logd(TAG, "setPlayState called") return runOnIOScope { for (episode in episodes) { - setPlayStateSync(played, resetMediaPosition, episode) + setPlayStateSync(played, episode, resetMediaPosition) } } } - @OptIn(UnstableApi::class) - suspend fun setPlayStateSync(played: Int, resetMediaPosition: Boolean, episode: Episode) : Episode { + suspend fun setPlayStateSync(played: Int, episode: Episode, resetMediaPosition: Boolean, removeFromQueue: Boolean = true) : Episode { Logd(TAG, "setPlayStateSync called played: $played resetMediaPosition: $resetMediaPosition ${episode.title}") var episode_ = episode if (!episode.isManaged()) episode_ = realm.query(Episode::class).query("id == $0", episode.id).first().find() ?: episode @@ -273,15 +249,18 @@ object Episodes { if (resetMediaPosition) it.media?.setPosition(0) } Logd(TAG, "setPlayStateSync played0: ${result.playState}") - if (played == PlayState.PLAYED.code && shouldMarkedPlayedRemoveFromQueues()) removeFromAllQueuesSync(result) + if (removeFromQueue && played == PlayState.PLAYED.code && prefRemoveFromQueueMarkedPlayed) removeFromAllQueuesSync(result) Logd(TAG, "setPlayStateSync played1: ${result.playState}") EventFlow.postEvent(FlowEvent.EpisodePlayedEvent(result)) return result } - private fun shouldMarkedPlayedRemoveFromQueues(): Boolean { - return appPrefs.getBoolean(Prefs.prefRemoveFromQueueMarkedPlayed.name, true) - } + val prefRemoveFromQueueMarkedPlayed: Boolean + get() = appPrefs.getBoolean(Prefs.prefRemoveFromQueueMarkedPlayed.name, true) + +// fun shouldMarkedPlayedRemoveFromQueues(): Boolean { +// return appPrefs.getBoolean(Prefs.prefRemoveFromQueueMarkedPlayed.name, true) +// } fun episodeFromStreamInfoItem(item: StreamInfoItem): Episode { val e = Episode() diff --git a/app/src/main/kotlin/ac/mdiq/podcini/storage/database/Feeds.kt b/app/src/main/kotlin/ac/mdiq/podcini/storage/database/Feeds.kt index 157803a7..a1f2043e 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/storage/database/Feeds.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/storage/database/Feeds.kt @@ -435,7 +435,7 @@ object Feeds { } @JvmStatic - fun shouldAutoDeleteItem(feed: Feed): Boolean { + fun allowForAutoDelete(feed: Feed): Boolean { if (!isAutoDelete) return false return !feed.isLocalFeed || isAutoDeleteLocal } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/storage/database/Queues.kt b/app/src/main/kotlin/ac/mdiq/podcini/storage/database/Queues.kt index 5abdf758..fe35b670 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/storage/database/Queues.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/storage/database/Queues.kt @@ -6,7 +6,6 @@ import ac.mdiq.podcini.playback.base.InTheatre.curQueue import ac.mdiq.podcini.preferences.UserPreferences import ac.mdiq.podcini.preferences.UserPreferences.appPrefs import ac.mdiq.podcini.storage.database.Episodes.setPlayState -import ac.mdiq.podcini.storage.database.Episodes.setPlayStateSync import ac.mdiq.podcini.storage.database.RealmDB.realm import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope import ac.mdiq.podcini.storage.database.RealmDB.upsert @@ -14,14 +13,10 @@ import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk import ac.mdiq.podcini.storage.model.* import ac.mdiq.podcini.storage.utils.EpisodeUtil.indexOfItemWithId import ac.mdiq.podcini.storage.utils.EpisodesPermutors.getPermutor -import ac.mdiq.podcini.util.Logd import ac.mdiq.podcini.util.EventFlow import ac.mdiq.podcini.util.FlowEvent +import ac.mdiq.podcini.util.Logd import android.util.Log -import androidx.annotation.OptIn -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.setValue import androidx.media3.common.util.UnstableApi import kotlinx.coroutines.Job import java.util.* @@ -91,10 +86,9 @@ object Queues { /** * Appends Episode objects to the end of the queue. The 'read'-attribute of all episodes will be set to true. * If a Episode is already in the queue, the Episode will not change its position in the queue. - * @param markAsUnplayed true if the episodes should be marked as unplayed when enqueueing * @param episodes the Episode objects that should be added to the queue. */ - @UnstableApi @JvmStatic @Synchronized + @JvmStatic @Synchronized fun addToQueue(vararg episodes: Episode) : Job { Logd(TAG, "addToQueue( ... ) called") return runOnIOScope { @@ -130,7 +124,6 @@ object Queues { it.update() } for (event in events) EventFlow.postEvent(event) - setPlayState(PlayState.INQUEUE.code, false, *setInQueue.toTypedArray()) // if (performAutoDownload) autodownloadEpisodeMedia(context) } @@ -199,16 +192,15 @@ object Queues { } } - /** - * Removes a Episode object from the queue. - * @param episodes FeedItems that should be removed. - */ - @OptIn(UnstableApi::class) @JvmStatic - fun removeFromQueue(vararg episodes: Episode) : Job { - return runOnIOScope { removeFromQueueSync(curQueue, *episodes) } - } +// /** +// * Removes a Episode object from the queue. +// * @param episodes FeedItems that should be removed. +// */ +// @JvmStatic +// fun removeFromQueue(vararg episodes: Episode) : Job { +// return runOnIOScope { removeFromQueueSync(curQueue, *episodes) } +// } - @OptIn(UnstableApi::class) fun removeFromAllQueuesSync(vararg episodes: Episode) { Logd(TAG, "removeFromAllQueuesSync called ") val queues = realm.query(PlayQueue::class).find() @@ -239,7 +231,7 @@ object Queues { if (indexOfItemWithId(eList, episode.id) >= 0) { Logd(TAG, "removing from queue: ${episode.id} ${episode.title}") indicesToRemove.add(i) - if (episode.playState < PlayState.SKIPPED.code) setPlayState(PlayState.SKIPPED.code, false, episode) +// if (setState && episode.playState < PlayState.SKIPPED.code) setPlayState(PlayState.SKIPPED.code, false, episode) if (queue.id == curQueue.id) events.add(FlowEvent.QueueEvent.removed(episode)) } } @@ -316,9 +308,9 @@ object Queues { * false if the caller wants to avoid unexpected updates of the GUI. * @throws IndexOutOfBoundsException if (to < 0 || to >= queue.size()) || (from < 0 || from >= queue.size()) */ - fun moveInQueue(from: Int, to: Int, broadcastUpdate: Boolean) : Job { - return runOnIOScope { moveInQueueSync(from, to, broadcastUpdate) } - } +// fun moveInQueue(from: Int, to: Int, broadcastUpdate: Boolean) : Job { +// return runOnIOScope { moveInQueueSync(from, to, broadcastUpdate) } +// } /** * Changes the position of a Episode in the queue. diff --git a/app/src/main/kotlin/ac/mdiq/podcini/storage/model/EpisodeFilter.kt b/app/src/main/kotlin/ac/mdiq/podcini/storage/model/EpisodeFilter.kt index 8b0e1284..fb66e316 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/storage/model/EpisodeFilter.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/storage/model/EpisodeFilter.kt @@ -80,6 +80,8 @@ class EpisodeFilter(vararg properties_: String) : Serializable { if (properties.contains(States.inProgress.name)) stateQuerys.add(" playState == ${PlayState.INPROGRESS.code} ") if (properties.contains(States.skipped.name)) stateQuerys.add(" playState == ${PlayState.SKIPPED.code} ") if (properties.contains(States.played.name)) stateQuerys.add(" playState == ${PlayState.PLAYED.code} ") + if (properties.contains(States.again.name)) stateQuerys.add(" playState == ${PlayState.AGAIN.code} ") + if (properties.contains(States.forever.name)) stateQuerys.add(" playState == ${PlayState.FOREVER.code} ") if (properties.contains(States.ignored.name)) stateQuerys.add(" playState == ${PlayState.IGNORED.code} ") if (stateQuerys.isNotEmpty()) { val query = StringBuilder(" (" + stateQuerys[0]) @@ -147,6 +149,8 @@ class EpisodeFilter(vararg properties_: String) : Serializable { inProgress, skipped, played, + again, + forever, ignored, has_chapters, no_chapters, @@ -197,6 +201,8 @@ class EpisodeFilter(vararg properties_: String) : Serializable { ItemProperties(R.string.in_progress, States.inProgress.name), ItemProperties(R.string.skipped, States.skipped.name), ItemProperties(R.string.played, States.played.name), + ItemProperties(R.string.again, States.again.name), + ItemProperties(R.string.forever, States.forever.name), ItemProperties(R.string.ignored, States.ignored.name), ), OPINION(R.string.has_comments, ItemProperties(R.string.yes, States.has_comments.name), ItemProperties(R.string.no, States.no_comments.name)), diff --git a/app/src/main/kotlin/ac/mdiq/podcini/storage/utils/EpisodeUtil.kt b/app/src/main/kotlin/ac/mdiq/podcini/storage/utils/EpisodeUtil.kt index 1022f683..f475ff47 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/storage/utils/EpisodeUtil.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/storage/utils/EpisodeUtil.kt @@ -1,15 +1,14 @@ package ac.mdiq.podcini.storage.utils -import ac.mdiq.podcini.preferences.UserPreferences -import ac.mdiq.podcini.preferences.UserPreferences.Prefs.prefSmartMarkAsPlayedSecs -import ac.mdiq.podcini.preferences.UserPreferences.appPrefs import ac.mdiq.podcini.storage.model.Episode import ac.mdiq.podcini.storage.model.Playable object EpisodeUtil { private val TAG: String = EpisodeUtil::class.simpleName ?: "Anonymous" - val smartMarkAsPlayedSecs: Int - get() = appPrefs.getString(UserPreferences.Prefs.prefSmartMarkAsPlayedSecs.name, "30")!!.toInt() +// val smartMarkAsPlayedSecs: Int +// get() = appPrefs.getString(UserPreferences.Prefs.prefSmartMarkAsPlayedSecs.name, "30")!!.toInt() + + private val smartMarkAsPlayedPercent: Int = 95 @JvmStatic fun indexOfItemWithId(episodes: List, id: Long): Int { @@ -36,6 +35,6 @@ object EpisodeUtil { @JvmStatic fun hasAlmostEnded(media: Playable): Boolean { - return media.getDuration() > 0 && media.getPosition() >= media.getDuration() - smartMarkAsPlayedSecs * 1000 + return media.getDuration() > 0 && media.getPosition() >= media.getDuration() * smartMarkAsPlayedPercent * 0.01 } } diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/EpisodeActionButton.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/EpisodeActionButton.kt index 4e75e2bb..7ab1b860 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/EpisodeActionButton.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/EpisodeActionButton.kt @@ -250,7 +250,7 @@ class PlayActionButton(item: Episode) : EpisodeActionButton(item) { } else { PlaybackService.clearCurTempSpeed() PlaybackServiceStarter(context, media).callEvenIfRunning(true).start() - item = runBlocking { setPlayStateSync(PlayState.INPROGRESS.code, false, item) } + if (item.playState < PlayState.INPROGRESS.code) item = runBlocking { setPlayStateSync(PlayState.INPROGRESS.code, item, false) } EventFlow.postEvent(FlowEvent.PlayEvent(item)) } playVideoIfNeeded(context, media) @@ -425,7 +425,7 @@ class StreamActionButton(item: Episode) : EpisodeActionButton(item) { if (media !is EpisodeMedia || !InTheatre.isCurMedia(media)) PlaybackService.clearCurTempSpeed() PlaybackServiceStarter(context, media).shouldStreamThisTime(true).callEvenIfRunning(true).start() if (media is EpisodeMedia && media.episode != null) { - val item = runBlocking { setPlayStateSync(PlayState.INPROGRESS.code, false, media.episode!!) } + val item = runBlocking { setPlayStateSync(PlayState.INPROGRESS.code, media.episode!!, false) } EventFlow.postEvent(FlowEvent.PlayEvent(item)) } playVideoIfNeeded(context, media) @@ -591,7 +591,7 @@ class PlayLocalActionButton(item: Episode) : EpisodeActionButton(item) { } else { PlaybackService.clearCurTempSpeed() PlaybackServiceStarter(context, media).callEvenIfRunning(true).start() - item = runBlocking { setPlayStateSync(PlayState.INPROGRESS.code, false, item) } + item = runBlocking { setPlayStateSync(PlayState.INPROGRESS.code, item, false) } EventFlow.postEvent(FlowEvent.PlayEvent(item)) } if (media.getMediaType() == MediaType.VIDEO) context.startActivity(getPlayerActivityIntent(context, diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/SwipeActions.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/SwipeActions.kt index 89db227b..d2330f35 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/SwipeActions.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/actions/SwipeActions.kt @@ -1,23 +1,19 @@ package ac.mdiq.podcini.ui.actions -//import ac.mdiq.podcini.ui.dialog.SwipeActionsDialog import ac.mdiq.podcini.R import ac.mdiq.podcini.playback.base.InTheatre.curQueue -import ac.mdiq.podcini.storage.database.Episodes.deleteMediaSync import ac.mdiq.podcini.storage.database.Episodes.setPlayState -import ac.mdiq.podcini.storage.database.Episodes.shouldDeleteRemoveFromQueue -import ac.mdiq.podcini.storage.database.Feeds.shouldAutoDeleteItem +import ac.mdiq.podcini.storage.database.Episodes.setPlayStateSync import ac.mdiq.podcini.storage.database.Queues.addToQueue -import ac.mdiq.podcini.storage.database.Queues.removeFromQueue import ac.mdiq.podcini.storage.database.Queues.removeFromQueueSync import ac.mdiq.podcini.storage.database.RealmDB.realm import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope import ac.mdiq.podcini.storage.database.RealmDB.upsert +import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk import ac.mdiq.podcini.storage.model.Episode import ac.mdiq.podcini.storage.model.EpisodeFilter -import ac.mdiq.podcini.storage.model.EpisodeMedia import ac.mdiq.podcini.storage.model.PlayState -import ac.mdiq.podcini.storage.utils.EpisodeUtil +import ac.mdiq.podcini.storage.utils.EpisodeUtil.hasAlmostEnded import ac.mdiq.podcini.ui.actions.SwipeAction.ActionTypes import ac.mdiq.podcini.ui.actions.SwipeAction.ActionTypes.NO_ACTION import ac.mdiq.podcini.ui.activity.MainActivity @@ -56,10 +52,8 @@ import androidx.lifecycle.LifecycleOwner import androidx.media3.common.util.UnstableApi import com.google.android.material.snackbar.Snackbar import kotlinx.coroutines.Job -import kotlinx.coroutines.delay import kotlinx.coroutines.runBlocking import java.util.* -import kotlin.math.ceil open class SwipeActions(private val fragment: Fragment, private val tag: String) : DefaultLifecycleObserver { @@ -202,8 +196,15 @@ open class SwipeActions(private val fragment: Fragment, private val tag: String) return context.getString(R.string.delete_episode_label) } @UnstableApi - override fun performAction(item: Episode, fragment: Fragment, filter: EpisodeFilter) { + override fun performAction(item_: Episode, fragment: Fragment, filter: EpisodeFilter) { + var item = item_ if (!item.isDownloaded && item.feed?.isLocalFeed != true) return + val media = item.media + if (media != null) { + val almostEnded = hasAlmostEnded(media) + if (almostEnded && item.playState < PlayState.PLAYED.code) item = runBlocking { setPlayStateSync(PlayState.PLAYED.code, item, almostEnded, false) } + if (almostEnded) item = upsertBlk(item) { it.media?.playbackCompletionDate = Date() } + } deleteEpisodesWarnLocal(fragment.requireContext(), listOf(item)) } override fun willRemove(filter: EpisodeFilter, item: Episode): Boolean { @@ -314,9 +315,18 @@ open class SwipeActions(private val fragment: Fragment, private val tag: String) return context.getString(R.string.remove_from_queue_label) } @OptIn(UnstableApi::class) - override fun performAction(item: Episode, fragment: Fragment, filter: EpisodeFilter) { - val position: Int = curQueue.episodes.indexOf(item) - removeFromQueue(item) + override fun performAction(item_: Episode, fragment: Fragment, filter: EpisodeFilter) { + val position: Int = curQueue.episodes.indexOf(item_) + var item = item_ + val media = item.media + if (media != null) { + val almostEnded = hasAlmostEnded(media) + if (almostEnded && item.playState < PlayState.PLAYED.code) item = runBlocking { setPlayStateSync(PlayState.PLAYED.code, item, almostEnded, false) } + if (almostEnded) item = upsertBlk(item) { it.media?.playbackCompletionDate = Date() } + } + if (item.playState < PlayState.SKIPPED.code) item = runBlocking { setPlayStateSync(PlayState.SKIPPED.code, item, false, false) } +// removeFromQueue(item) + runOnIOScope { removeFromQueueSync(curQueue, item) } if (willRemove(filter, item)) { (fragment.requireActivity() as MainActivity).showSnackbarAbovePlayer(fragment.resources.getQuantityString(R.plurals.removed_from_queue_batch_label, 1, 1), Snackbar.LENGTH_LONG) .setAction(fragment.getString(R.string.undo)) { @@ -426,15 +436,15 @@ open class SwipeActions(private val fragment: Fragment, private val tag: String) } (fragment.view as? ViewGroup)?.addView(composeView) } - private fun delayedExecution(item: Episode, fragment: Fragment, duration: Float) = runBlocking { - delay(ceil((duration * 1.05f).toDouble()).toLong()) - val media: EpisodeMedia? = item.media - val shouldAutoDelete = if (item.feed == null) false else shouldAutoDeleteItem(item.feed!!) - if (media != null && EpisodeUtil.hasAlmostEnded(media) && shouldAutoDelete) { -// deleteMediaOfEpisode(fragment.requireContext(), item) - val item_ = deleteMediaSync(fragment.requireContext(), item) - if (shouldDeleteRemoveFromQueue()) removeFromQueueSync(null, item_) } - } +// private fun delayedExecution(item: Episode, fragment: Fragment, duration: Float) = runBlocking { +// delay(ceil((duration * 1.05f).toDouble()).toLong()) +// val media: EpisodeMedia? = item.media +// val shouldAutoDelete = if (item.feed == null) false else shouldAutoDeleteItem(item.feed!!) +// if (media != null && EpisodeUtil.hasAlmostEnded(media) && shouldAutoDelete) { +//// deleteMediaOfEpisode(fragment.requireContext(), item) +// val item_ = deleteMediaSync(fragment.requireContext(), item) +// if (prefDeleteRemovesFromQueue) removeFromQueueSync(null, item_) } +// } } class ShelveSwipeAction : SwipeAction { diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/EpisodesVM.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/EpisodesVM.kt index fecaaf9a..b1e20bd8 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/EpisodesVM.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/compose/EpisodesVM.kt @@ -12,19 +12,23 @@ import ac.mdiq.podcini.playback.base.InTheatre import ac.mdiq.podcini.playback.base.InTheatre.curQueue import ac.mdiq.podcini.playback.base.MediaPlayerBase.Companion.status import ac.mdiq.podcini.storage.database.Episodes +import ac.mdiq.podcini.storage.database.Episodes.deleteEpisodeMedia import ac.mdiq.podcini.storage.database.Episodes.deleteMediaSync import ac.mdiq.podcini.storage.database.Episodes.episodeFromStreamInfo +import ac.mdiq.podcini.storage.database.Episodes.prefDeleteRemovesFromQueue +import ac.mdiq.podcini.storage.database.Episodes.prefRemoveFromQueueMarkedPlayed +import ac.mdiq.podcini.storage.database.Episodes.setPlayState import ac.mdiq.podcini.storage.database.Episodes.setPlayStateSync -import ac.mdiq.podcini.storage.database.Episodes.shouldDeleteRemoveFromQueue import ac.mdiq.podcini.storage.database.Feeds.addToMiscSyndicate import ac.mdiq.podcini.storage.database.Feeds.addToYoutubeSyndicate -import ac.mdiq.podcini.storage.database.Feeds.shouldAutoDeleteItem +import ac.mdiq.podcini.storage.database.Feeds.allowForAutoDelete import ac.mdiq.podcini.storage.database.Queues import ac.mdiq.podcini.storage.database.Queues.addToQueueSync import ac.mdiq.podcini.storage.database.Queues.removeFromAllQueuesQuiet -import ac.mdiq.podcini.storage.database.Queues.removeFromQueue +import ac.mdiq.podcini.storage.database.Queues.removeFromAllQueuesSync import ac.mdiq.podcini.storage.database.Queues.removeFromQueueSync import ac.mdiq.podcini.storage.database.RealmDB.realm +import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope import ac.mdiq.podcini.storage.database.RealmDB.upsert import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk import ac.mdiq.podcini.storage.model.* @@ -40,7 +44,6 @@ import ac.mdiq.podcini.ui.actions.SwipeAction import ac.mdiq.podcini.ui.activity.MainActivity import ac.mdiq.podcini.ui.fragment.EpisodeInfoFragment import ac.mdiq.podcini.ui.fragment.FeedInfoFragment -import ac.mdiq.podcini.ui.utils.LocalDeleteModal import ac.mdiq.podcini.util.EventFlow import ac.mdiq.podcini.util.FlowEvent import ac.mdiq.podcini.util.Logd @@ -66,14 +69,12 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.text.BasicTextField import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.AddCircle -import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.filled.Edit import androidx.compose.material3.* import androidx.compose.material3.pulltorefresh.PullToRefreshBox import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.alpha import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.input.pointer.pointerInput @@ -261,13 +262,9 @@ fun PlayStateDialog(selected: List, onDismissRequest: () -> Unit) { Row(verticalAlignment = Alignment.CenterVertically, modifier = Modifier.padding(4.dp) .clickable { for (item in selected) { - var item_ = runBlocking { setPlayStateSync(state.code, false, item) } - val media: EpisodeMedia? = item_.media - val shouldAutoDelete = if (item_.feed == null) false else shouldAutoDeleteItem(item_.feed!!) - if (media != null && hasAlmostEnded(media) && shouldAutoDelete) { - item_ = deleteMediaSync(context, item_) - if (shouldDeleteRemoveFromQueue()) removeFromQueueSync(null, item_) - } + var media: EpisodeMedia? = item.media + val hasAlmostEnded = if (media != null) hasAlmostEnded(media) else false + var item_ = runBlocking { setPlayStateSync(state.code, item, hasAlmostEnded, false) } when (state) { PlayState.UNPLAYED -> { if (isProviderConnected && item_.feed?.isLocalFeed != true && item_.media != null) { @@ -276,6 +273,13 @@ fun PlayStateDialog(selected: List, onDismissRequest: () -> Unit) { } } PlayState.PLAYED -> { + if (hasAlmostEnded) item_ = upsertBlk(item_) { it.media?.playbackCompletionDate = Date() } + val shouldAutoDelete = if (item_.feed == null) false else allowForAutoDelete(item_.feed!!) + media = item_.media + if (media != null && hasAlmostEnded && shouldAutoDelete) { + item_ = deleteMediaSync(context, item_) + if (prefDeleteRemovesFromQueue) removeFromQueueSync(null, item_) + } else if (prefRemoveFromQueueMarkedPlayed) removeFromAllQueuesSync(item_) if (item_.feed?.isLocalFeed != true && (isProviderConnected || wifiSyncEnabledKey)) { val media_: EpisodeMedia? = item_.media // not all items have media, Gpodder only cares about those that do @@ -516,7 +520,20 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: MutableList, feed: isExpanded = false selectMode = false Logd(TAG, "ic_delete: ${selected.size}") - LocalDeleteModal.deleteEpisodesWarnLocal(activity, selected) + runOnIOScope { + for (item_ in selected) { + var item = item_ + if (!item.isDownloaded && item.feed?.isLocalFeed != true) continue + val media = item.media + if (media != null) { + val almostEnded = hasAlmostEnded(media) + if (almostEnded && item.playState < PlayState.PLAYED.code) item = setPlayStateSync(PlayState.PLAYED.code, item, almostEnded, false) + if (almostEnded) item = upsert(item) { it.media?.playbackCompletionDate = Date() } + deleteEpisodeMedia(activity, item) + } + } + } +// LocalDeleteModal.deleteEpisodesWarnLocal(activity, selected) }, verticalAlignment = Alignment.CenterVertically) { Icon(imageVector = ImageVector.vectorResource(id = R.drawable.ic_delete), "Delete media") Text(stringResource(id = R.string.delete_episode_label)) } }, @@ -527,9 +544,7 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: MutableList, feed: selectMode = false Logd(TAG, "ic_download: ${selected.size}") for (episode in selected) { - if (episode.media != null && episode.feed != null && !episode.feed!!.isLocalFeed) DownloadServiceInterface - .get() - ?.download(activity, episode) + if (episode.media != null && episode.feed != null && !episode.feed!!.isLocalFeed) DownloadServiceInterface.get()?.download(activity, episode) } }, verticalAlignment = Alignment.CenterVertically) { Icon(imageVector = ImageVector.vectorResource(id = R.drawable.ic_download), "Download") @@ -551,7 +566,20 @@ fun EpisodeLazyColumn(activity: MainActivity, vms: MutableList, feed: isExpanded = false selectMode = false Logd(TAG, "ic_playlist_remove: ${selected.size}") - removeFromQueue(*selected.toTypedArray()) + runOnIOScope { + for (item_ in selected) { + var item = item_ + val media = item.media + if (media != null) { + val almostEnded = hasAlmostEnded(media) + if (almostEnded && item.playState < PlayState.PLAYED.code) item = setPlayStateSync(PlayState.PLAYED.code, item, almostEnded, false) + if (almostEnded) item = upsert(item) { it.media?.playbackCompletionDate = Date() } + } + if (item.playState < PlayState.SKIPPED.code) setPlayState(PlayState.SKIPPED.code, false, item) + } + removeFromQueueSync(curQueue, *selected.toTypedArray()) +// removeFromQueue(*selected.toTypedArray()) + } }, verticalAlignment = Alignment.CenterVertically) { Icon(imageVector = ImageVector.vectorResource(id = R.drawable.ic_playlist_remove), "Remove from active queue") Text(stringResource(id = R.string.remove_from_queue_label)) } }, diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt index b82973ae..bac26485 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt @@ -217,8 +217,7 @@ class AudioPlayerFragment : Fragment() { })) Spacer(Modifier.weight(0.1f)) Column(horizontalAlignment = Alignment.CenterHorizontally) { - Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_playback_speed), tint = textColor, - contentDescription = "speed", + Icon(imageVector = ImageVector.vectorResource(R.drawable.ic_playback_speed), tint = textColor, contentDescription = "speed", modifier = Modifier.width(43.dp).height(43.dp).clickable(onClick = { VariableSpeedDialog.newInstance(booleanArrayOf(true, true, true), null)?.show(childFragmentManager, null) })) @@ -555,7 +554,6 @@ class AudioPlayerFragment : Fragment() { private fun updatePlaybackSpeedButton(event: FlowEvent.SpeedChangedEvent) { val speedStr: String = DecimalFormat("0.00").format(event.newSpeed.toDouble()) txtvPlaybackSpeed = speedStr -// binding.butPlaybackSpeed.setSpeed(event.newSpeed) TODO } @UnstableApi @@ -594,6 +592,7 @@ class AudioPlayerFragment : Fragment() { fun updateUi(media: Playable) { Logd(TAG, "updateUi called $media") titleText = media.getEpisodeTitle() + txtvPlaybackSpeed = DecimalFormat("0.00").format(curSpeedFB.toDouble()) if (prevMedia?.getIdentifier() != media.getIdentifier()) imgLoc = ImageResourceUtils.getEpisodeListImageLocation(media) if (isPlayingVideoLocally && (curMedia as? EpisodeMedia)?.episode?.feed?.preferences?.videoModePolicy != VideoMode.AUDIO_ONLY) { // (activity as MainActivity).bottomSheet.setLocked(true) @@ -803,8 +802,6 @@ class AudioPlayerFragment : Fragment() { fun loadMediaInfo() { Logd(TAG, "loadMediaInfo() curMedia: ${curMedia?.getIdentifier()}") val actMain = (activity as MainActivity) - var i = 0 -// while (curMedia == null && i++ < 6) runBlocking { delay(500) } if (curMedia == null) { if (actMain.isPlayerVisible()) actMain.setPlayerVisible(false) return diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/EpisodeInfoFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/EpisodeInfoFragment.kt index 474007d8..20a0eb37 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/EpisodeInfoFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/EpisodeInfoFragment.kt @@ -13,7 +13,6 @@ import ac.mdiq.podcini.playback.service.PlaybackService.Companion.seekTo import ac.mdiq.podcini.preferences.UsageStatistics import ac.mdiq.podcini.preferences.UserPreferences import ac.mdiq.podcini.storage.database.Queues.addToQueue -import ac.mdiq.podcini.storage.database.Queues.removeFromQueue import ac.mdiq.podcini.storage.database.RealmDB.realm import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope import ac.mdiq.podcini.storage.database.RealmDB.unmanaged @@ -222,10 +221,10 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener { })) if (episode?.media != null && !inQueue) { Spacer(modifier = Modifier.weight(0.2f)) - val inQueueIconRes = if (inQueue) R.drawable.ic_playlist_play else R.drawable.ic_playlist_remove + val inQueueIconRes = R.drawable.ic_playlist_remove Icon(imageVector = ImageVector.vectorResource(inQueueIconRes), tint = MaterialTheme.colorScheme.tertiary, contentDescription = "inQueue", modifier = Modifier.background(MaterialTheme.colorScheme.tertiaryContainer).width(24.dp).height(24.dp).clickable(onClick = { - if (inQueue) removeFromQueue(episode!!) else addToQueue(episode!!) + addToQueue(episode!!) })) } Spacer(modifier = Modifier.weight(0.2f)) diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/QueuesFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/QueuesFragment.kt index c7a1ef04..2cbfeb29 100644 --- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/QueuesFragment.kt +++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/QueuesFragment.kt @@ -14,7 +14,7 @@ import ac.mdiq.podcini.preferences.UserPreferences import ac.mdiq.podcini.preferences.UserPreferences.appPrefs import ac.mdiq.podcini.storage.database.Queues.clearQueue import ac.mdiq.podcini.storage.database.Queues.isQueueKeepSorted -import ac.mdiq.podcini.storage.database.Queues.moveInQueue +import ac.mdiq.podcini.storage.database.Queues.moveInQueueSync import ac.mdiq.podcini.storage.database.Queues.queueKeepSortedOrder import ac.mdiq.podcini.storage.database.RealmDB.realm import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope @@ -212,7 +212,10 @@ import kotlin.math.max else rightActionState.value.performAction(episode, this@QueuesFragment, swipeActions.filter ?: EpisodeFilter()) } EpisodeLazyColumn(activity as MainActivity, vms = vms, - isDraggable = dragDropEnabled, dragCB = { iFrom, iTo -> moveInQueue(iFrom, iTo, true) }, + isDraggable = dragDropEnabled, dragCB = { iFrom, iTo -> +// moveInQueue(iFrom, iTo, true) + runOnIOScope { moveInQueueSync(iFrom, iTo, true) } + }, leftSwipeCB = { leftCB(it) }, rightSwipeCB = { rightCB(it) }) } } diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml index 619b7220..f38359cd 100644 --- a/app/src/main/res/values-ar/strings.xml +++ b/app/src/main/res/values-ar/strings.xml @@ -401,8 +401,8 @@ علم بذكاء أنها انتهت ابقي الحلقات التي يتم تخطيها الاحتفاظ بالحلقات التي تم تخطيها - تعليم الحلقة كمفضلة يبقيها على الجهاز - الاحتفاظ بالحلقات المفضلة + + تشغيل تحكم سماعة الأذن, وقت التقدم, لائحة الاستماع تنزيلات diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 27d20916..d3f78eb5 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -351,8 +351,8 @@ Marcat intel·ligent d\'episodis reproduïts Mantenir episodis quan són omesos Mantenir episodis omesos - Mantenir els episodis quan s\'han marcat com a preferits - Mantenir episodis preferits + + Reproducció Controls d\'auriculars, Intervals d\'avançada, Cua Baixades diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index bcb81ce2..6b0d96eb 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -392,8 +392,8 @@ Chytré označování jako poslechnuté Neodstraňovat epizody při jejich přeskočení Ponechat přeskočené epizody - Ponechat epizody označené jako oblíbené. - Ponechat oblíbené epizody + + Přehrávání Ovládání tlačítky sluchátek, přeskakování, fronta Stahování diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 9908b345..8cf37d26 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -367,8 +367,8 @@ Smart markering som afspillet Behold afsnit når de bliver sprunget over Behold oversprungne afsnit - Behold afsnit, når de er markeret som favorit - Behold favoritafsnit + + Afspilning Hovedtelefonstyring, overspringsintervaller, kø Overførsler diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 82704745..45016479 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -371,8 +371,8 @@ Intelligentes \"als abgespielt markieren\" Episoden behalten, wenn sie übersprungen werden Übersprungene Episoden behalten - Episoden nicht löschen, wenn sie als Favorit markiert wurden - Favorisierte Episoden nicht löschen + + Wiedergabe Kopfhörersteuerung, Sprungintervall, Warteschlange Downloads diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index d25fc2c0..59dbe0ae 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -382,8 +382,8 @@ Marcado inteligente como reproducido Conservar los episodios cuando son saltados Conservar episodios saltados - Conservar los episodios cuando se marcan como favoritos - Conservar episodios favoritos + + Reproducción Control de auriculares, Saltar intervalos, Cola Descargas diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index 2a9c4f45..f99d801e 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -339,8 +339,8 @@ Erreproduzitutako saioak markatu nahiz bukatzeko segundo batzuk falta Saioak gorde jaustean Mantendu saltatutako saioak - Mantendu gogoko gisa markatutako saioak - Mantendu gogoko saioak + + Erreprodukzioa Aurikularren kontrolak, saltatu tarteak, ilara Deskargak diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index 6041945f..92987190 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -355,8 +355,8 @@ علامت گذاری هوشمند به پخش شده نگه داشتن قسمت‌ها هنگام پریدن از رویشان نگه‌داری قسمت‌های پریده - نگه داری قسمت‌ها هنکامی که علامت محبوب خورده‌اند - نگه داری قسمت‌های محبوب + + پخش کنترل هدفون ، رد کردن فواصل ، صف بارگیری‌ها diff --git a/app/src/main/res/values-fi/strings.xml b/app/src/main/res/values-fi/strings.xml index f7c8cdd8..ad65b884 100644 --- a/app/src/main/res/values-fi/strings.xml +++ b/app/src/main/res/values-fi/strings.xml @@ -346,8 +346,8 @@ Älykäs toistetuksi merkitseminen Säilytä jaksot, kun ne ohitetaan Säilytä ohitetut jaksot - Säilytä suosikeiksi merkityt jaksot - Säilytä suosikkijaksot + + Toisto Kuulokkeiden ohjaimet, ohitusaikavälit, jono Lataukset diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index cb054bd7..8124f72b 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -383,8 +383,8 @@ Marquer comme lu intelligemment Garder les épisodes quand ils sont passés Garder les épisodes passés - Garder les épisodes marqués comme favoris - Garder les épisodes favoris + + Lecture Contrôles du casque, durée des sauts, liste de lecture Téléchargements diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 94edf0b2..ed92575b 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -366,8 +366,8 @@ Marcado intelixente como escoitado Manter os episodios cando son omitidos Manter episodios omitidos - Manter episodios cando están marcados como favoritos - Manter episodios favoritos + + Reprodución Control de auriculares, Intervalos de salto, Cola Descargas diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 7c403883..c114249a 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -282,8 +282,8 @@ Hapus episode ketika pemutaran selesai Tandai episode sebagai telah diputar jika kurang dari beberapa detik waktu masih tersisa Simpan episode ketika dilewatkan - Simpan episode saat difavoritkan - Simpan episode favorit + + Pemutaran Kontrol headphone, Jangka waktu lewati, Antrean Waktu pembaharuan, Jaringan seluler, Unduh otomatis, Hapus otomatis diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 427aa598..d8e4f7f5 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -383,8 +383,8 @@ Marcatura intelligente Mantiene gli episodi nella coda quando vengono saltati Manteni gli episodi saltati - Mantiene gli episodi se sono segnati come preferiti - Mantieni episodi preferiti + + Riproduzione Controllo cuffie, salto intervalli, coda Download diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index 1c6ec95d..fe30da25 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -391,8 +391,8 @@ סימון חכם כנוגנו להשאיר פרקים למרות שדילגת עליהם להשאיר פרקים שדולגו - להשאיר פרקים כשהם מסומנים כמועדפים - להשאיר פרקים מועדפים + + ניגון שליטה דרך האוזניות, מרחקי דילוג, תור הורדות diff --git a/app/src/main/res/values-ko/strings.xml b/app/src/main/res/values-ko/strings.xml index 6ef4cc6d..8b846ecb 100644 --- a/app/src/main/res/values-ko/strings.xml +++ b/app/src/main/res/values-ko/strings.xml @@ -336,8 +336,8 @@ 똑똑하게 재생한 것으로 표시 에피소드를 넘겼을 경우에도 유지 넘긴 에피소드 유지 - 즐겨 찾기로 표시한 에피소드를 유지합니다 - 즐겨 찾기 에피소드 유지 + + 재생 헤드폰 조작, 구간 넘기기, 대기열 다운로드 diff --git a/app/src/main/res/values-nb/strings.xml b/app/src/main/res/values-nb/strings.xml index 188a280d..8e729357 100644 --- a/app/src/main/res/values-nb/strings.xml +++ b/app/src/main/res/values-nb/strings.xml @@ -343,8 +343,8 @@ Smart markering som avspilt Behold episoder når de hoppes over Behold episoder som er hoppet over - Behold episoder når de er merket som favoritt - Behold favorittepisoder + + Avspilling Hodetelefon-kontroller, spole-intervaller, kø Nedlastinger diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 393c257e..b509ba2b 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -344,8 +344,8 @@ Slim markeren als afgespeeld Bewaar afleveringen wanneer ze worden overgeslagen Overgeslagen afleveringen behouden - Afleveringen bewaren als ze als favoriet gemarkeerd zijn - Favoriete afleveringen bewaren + + Afspelen Onderbreken, afspelen, wachtrij Downloads diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 32015d22..92fb5afc 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -368,8 +368,8 @@ Oznacz odcinek jako odtworzony, jeśli do końca pozostało mniej niż określona ilość czasu Zachowuje pominięte odcinki w kolejce Zachowaj pominięte odcinki - Zachowaj odcinki gdy są oznaczone jako ulubione - Zachowaj ulubione odcinki + + Odtwarzanie Kontrola za pomocą słuchawek, Pomijanie, Kolejka Pobrane diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index dbd5703b..5269a4a4 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -363,8 +363,8 @@ Marcação inteligente quando reproduzido Mantém os episódios quando eles forem pulados Manter episódios ignorados - Manter os episódios quando eles estiverem marcados como favoritos - Manter episódios favoritos + + Reprodução Controles de fone de ouvido, intervalos para saltar, Fila Downloads diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 36b2e599..538cb24f 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -382,8 +382,8 @@ Marcar como reproduzido (inteligente) Manter episódios mesmo se tiverem sido ignorados Manter episódios ignorados - Manter episódios se forem assinalados como favoritos - Manter episódios favoritos + + Reprodução Controlo com auscultador, intervalos e fila Descargas diff --git a/app/src/main/res/values-ro/strings.xml b/app/src/main/res/values-ro/strings.xml index 5215cc01..6bc8e96a 100644 --- a/app/src/main/res/values-ro/strings.xml +++ b/app/src/main/res/values-ro/strings.xml @@ -379,8 +379,8 @@ Marchează inteligent ca fiind redate Păstrează episoade când acestea sunt sărite Păstrează episoadele sărite - Păstrează episoadele când sunt marcate ca favorite - Păstrează episoadele favorite + + Ascultare Controlul căștilor, Sari peste un interval de timp, Coadă Descărcări diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index ea407ef4..d2be4994 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -372,8 +372,8 @@ Отметка «Прослушанное» до окончания Сохранять выпуски, которые были пропущены Сохранять пропущенные выпуски - Хранить выпуски, добавленные в избранное - Хранить избранные выпуски + + Воспроизведение Кнопки гарнитуры, шаг перемотки, очередь Загрузки diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 71eb674b..b6fe7699 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -391,8 +391,8 @@ Inteligentné označovanie ako vypočuté Neodstraňovať epizódy pri ich preskočení Nemazať preskočené epizódy - Ponechať epizódy, ktoré sú označené ako obľúbené - Ponechať obľúbené epizódy + + Prehrávanie Ovládanie tlačidlami slúchadiel, preskakovanie, poradie Sťahovanie diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 4fbb3b5b..64ba180c 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -367,8 +367,8 @@ Smart markera som spelad Ta inte bort episoder när de hoppas över Behåll överhoppade episoder - Behåll episoder när de är favoritmarkerade - Behåll favoritepisoder + + Uppspelning Hörlurskontroller, Överhoppningsintervaller, Kö Nedladdningar diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index a983d178..f1f418be 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -353,8 +353,8 @@ Oynatıldı olarak işaretle Bölümler atlandığında tutmaya devam et Atlanan bölümleri sakla - Bölümler favori olarak işaretlendiğinde tut - Favori bölümleri tut + + Çalma Kulaklık kontrolleri, Atlama aralığı ve Kuyruk ayarları İndirilenler diff --git a/app/src/main/res/values-uk/strings.xml b/app/src/main/res/values-uk/strings.xml index 6c2c6d18..ef38f189 100644 --- a/app/src/main/res/values-uk/strings.xml +++ b/app/src/main/res/values-uk/strings.xml @@ -391,8 +391,8 @@ Розумне позначення прослуханих епізодів Зберігати пропущені епізоди при програванні Зберігати пропущені епізоди - Зберігати епізоди що помічені як улюблені - Зберігати улюблені епізоди + + Відтворення Керування навушниками, Пропуск інтервалів, Черга Завантаження diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index c786442b..50e8edfe 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -359,8 +359,8 @@ 智能标记为已播放 当剧集被跳过时保留它们 保留跳过的节目 - 标记节目为收藏时保留节目 - 保留收藏的节目 + + 播放 耳机控制、跳过间隔、排队 下载 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 194aa208..918dd22a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -323,6 +323,8 @@ In progress Skipped Played + Again + Forever Ignored Remove from favorites Visit website @@ -499,8 +501,9 @@ Smart mark as played Keep episodes when they are skipped Keep skipped episodes - Keep episodes when they are marked favorite - Keep favorite episodes + + Keep episodes when they are marked Super or set as Again or Forever. + Keep important episodes Playback Headphone controls, Skip intervals, Queue Downloads diff --git a/app/src/main/res/xml/preferences_downloads.xml b/app/src/main/res/xml/preferences_downloads.xml index 4685a5db..57cb9bd6 100644 --- a/app/src/main/res/xml/preferences_downloads.xml +++ b/app/src/main/res/xml/preferences_downloads.xml @@ -36,8 +36,8 @@ android:defaultValue="true" android:enabled="true" android:key="prefFavoriteKeepsEpisode" - android:summary="@string/pref_favorite_keeps_episodes_sum" - android:title="@string/pref_favorite_keeps_episodes_title"/> + android:summary="@string/pref_keeps_important_episodes_sum" + android:title="@string/pref_keeps_important_episodes_title"/> - + + + + + + +