From 20d0038eee896210df07c659aeaa64affffcb69b Mon Sep 17 00:00:00 2001 From: Xilin Jia <6257601+XilinJia@users.noreply.github.com> Date: Mon, 1 Apr 2024 22:15:50 +0000 Subject: [PATCH] 4.5.0 commit --- README.md | 13 ++++ app/build.gradle | 4 +- .../ac/test/podcini/ui/MainActivityTest.kt | 2 +- .../podcini/playback/PlaybackController.kt | 29 ++++++++- .../podcini/playback/base/PlayerStatus.kt | 1 + .../podcini/preferences/UserPreferences.kt | 40 +++++++++++- .../service/playback/PlaybackService.kt | 61 +++++++++++++++---- .../java/ac/mdiq/podcini/storage/DBReader.kt | 20 ++++-- .../podcini/storage/database/PodDBAdapter.kt | 21 +++++++ .../mdiq/podcini/storage/model/feed/Feed.kt | 15 +---- .../podcini/storage/model/feed/SortOrder.kt | 4 +- .../ui/dialog/EditFallbackSpeedDialog.kt | 44 +++++++++++++ .../ui/dialog/EditForwardSpeedDialog.kt | 45 ++++++++++++++ .../ui/dialog/EditUrlSettingsDialog.kt | 4 +- .../mdiq/podcini/ui/dialog/FeedSortDialog.kt | 3 +- .../podcini/ui/dialog/RenameItemDialog.kt | 6 +- .../podcini/ui/fragment/AddFeedFragment.kt | 6 +- .../ui/fragment/AudioPlayerFragment.kt | 23 ++++++- .../ui/fragment/ExternalPlayerFragment.kt | 22 ++++++- .../ui/fragment/OnlineFeedViewFragment.kt | 4 +- .../mdiq/podcini/ui/fragment/QueueFragment.kt | 1 + .../PlaybackPreferencesFragment.kt | 18 +++++- .../mdiq/podcini/util/event/FavoritesEvent.kt | 1 + .../mdiq/podcini/util/event/FeedItemEvent.kt | 1 + .../podcini/util/event/PlayerStatusEvent.kt | 1 + .../util/event/UnreadItemsUpdateEvent.kt | 1 + .../main/res/layout/audioplayer_fragment.xml | 14 +++++ app/src/main/res/layout/edit_text_dialog.xml | 4 +- .../res/layout/external_player_fragment.xml | 14 +++++ app/src/main/res/values/arrays.xml | 2 + app/src/main/res/values/strings.xml | 16 +++-- app/src/main/res/xml/preferences_playback.xml | 8 +++ changelog.md | 20 +++++- .../android/en-US/changelogs/3020120.txt | 18 ++++++ 34 files changed, 423 insertions(+), 63 deletions(-) create mode 100644 app/src/main/java/ac/mdiq/podcini/ui/dialog/EditFallbackSpeedDialog.kt create mode 100644 app/src/main/java/ac/mdiq/podcini/ui/dialog/EditForwardSpeedDialog.kt create mode 100644 fastlane/metadata/android/en-US/changelogs/3020120.txt diff --git a/README.md b/README.md index 0754c550..a8cc3634 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,19 @@ Other notable features and changes include: * setting for a feed: either use global or customized * setting at the player: set for current playing and save for global * customized feed setting takes precedence when playing an episode +* Added preference "Fast Forward Speed" under "Playback" in settings with default value of 0.0, dialog allows setting a float number (capped between 0.0 and 10.0) +* The "Skip to next episode" button on the player + * long-press moves to the next episode + * by default, single tap does nothing + * if the user customize "Fast Forward Speed" to a value greater than 0.1, it behaves in the following way: + * single tap during play, the set speed is used to play the current audio + * single tap again, the original play speed resumes + * single tap not during play has no effect +* Added preference "Fallback Speed" under "Playback" in settings with default value of 0.0, dialog allows setting a float number (capped between 0.0 and 1.5) +* the Play button on the player + * by default, it behaves the same as usual + * if the user customize "Fallback speed" to a value greater than 0.1, long-press the button during play enters the fallback mode and plays at the set fallback speed, single tap exits the fallback mode +* Subscriptions view has sorting by "Unread publication date" * Feed info view offers a link for direct search of feeds related to author * More info about feeds are shown in the online search view * Online feed info display is handled in similar ways as any local feed, and offers options to subscribe or view episodes diff --git a/app/build.gradle b/app/build.gradle index 06770956..2501affe 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -149,8 +149,8 @@ android { // Version code schema (not used): // "1.2.3-beta4" -> 1020304 // "1.2.3" -> 1020395 - versionCode 3020119 - versionName "4.4.3" + versionCode 3020120 + versionName "4.5.0" def commit = "" try { diff --git a/app/src/androidTest/java/ac/test/podcini/ui/MainActivityTest.kt b/app/src/androidTest/java/ac/test/podcini/ui/MainActivityTest.kt index c1aa6cf0..2bdd1220 100644 --- a/app/src/androidTest/java/ac/test/podcini/ui/MainActivityTest.kt +++ b/app/src/androidTest/java/ac/test/podcini/ui/MainActivityTest.kt @@ -57,7 +57,7 @@ class MainActivityTest { EspressoTestUtils.openNavDrawer() Espresso.onView(ViewMatchers.withText(R.string.add_feed_label)).perform(ViewActions.click()) Espresso.onView(ViewMatchers.withId(R.id.addViaUrlButton)).perform(ViewActions.scrollTo(), ViewActions.click()) - Espresso.onView(ViewMatchers.withId(R.id.urlEditText)).perform(ViewActions.replaceText(feed.download_url)) + Espresso.onView(ViewMatchers.withId(R.id.editText)).perform(ViewActions.replaceText(feed.download_url)) Espresso.onView(ViewMatchers.withText(R.string.confirm_label)) .perform(ViewActions.scrollTo(), ViewActions.click()) diff --git a/app/src/main/java/ac/mdiq/podcini/playback/PlaybackController.kt b/app/src/main/java/ac/mdiq/podcini/playback/PlaybackController.kt index 900fcde6..500561d1 100644 --- a/app/src/main/java/ac/mdiq/podcini/playback/PlaybackController.kt +++ b/app/src/main/java/ac/mdiq/podcini/playback/PlaybackController.kt @@ -221,8 +221,8 @@ abstract class PlaybackController(private val activity: FragmentActivity) { PlayerStatus.PREPARING -> if (playbackService != null) { updatePlayButtonShowsPlay(!playbackService!!.isStartWhenPrepared) } - PlayerStatus.PAUSED, PlayerStatus.PREPARED, PlayerStatus.STOPPED, PlayerStatus.INITIALIZED -> updatePlayButtonShowsPlay( - true) + PlayerStatus.FALLBACK, PlayerStatus.PAUSED, PlayerStatus.PREPARED, PlayerStatus.STOPPED, PlayerStatus.INITIALIZED -> + updatePlayButtonShowsPlay(true) else -> {} } } @@ -267,7 +267,8 @@ abstract class PlaybackController(private val activity: FragmentActivity) { return } when (status) { - PlayerStatus.PLAYING -> playbackService?.pause(true, false) + PlayerStatus.FALLBACK -> fallbackSpeed(1.0f) + PlayerStatus.PLAYING -> playbackService?.pause(abandonAudioFocus = true, reinit = false) PlayerStatus.PAUSED, PlayerStatus.PREPARED -> playbackService?.resume() PlayerStatus.PREPARING -> playbackService!!.isStartWhenPrepared = !playbackService!!.isStartWhenPrepared PlayerStatus.INITIALIZED -> { @@ -345,6 +346,28 @@ abstract class PlaybackController(private val activity: FragmentActivity) { } } + fun speedForward(speed: Float) { + if (playbackService != null) { + playbackService!!.speedForward(speed) + } + } + + fun fallbackSpeed(speed: Float) { + if (playbackService != null) { + when (status) { + PlayerStatus.PLAYING -> { + status = PlayerStatus.FALLBACK + playbackService!!.fallbackSpeed(speed) + } + PlayerStatus.FALLBACK -> { + status = PlayerStatus.PLAYING + playbackService!!.fallbackSpeed(speed) + } + else -> {} + } + } + } + fun setSkipSilence(skipSilence: Boolean) { playbackService?.skipSilence(skipSilence) } diff --git a/app/src/main/java/ac/mdiq/podcini/playback/base/PlayerStatus.kt b/app/src/main/java/ac/mdiq/podcini/playback/base/PlayerStatus.kt index d380bed6..1bb1796b 100644 --- a/app/src/main/java/ac/mdiq/podcini/playback/base/PlayerStatus.kt +++ b/app/src/main/java/ac/mdiq/podcini/playback/base/PlayerStatus.kt @@ -5,6 +5,7 @@ enum class PlayerStatus(private val statusValue: Int) { ERROR(-1), PREPARING(19), PAUSED(30), + FALLBACK(35), PLAYING(40), STOPPED(5), PREPARED(20), diff --git a/app/src/main/java/ac/mdiq/podcini/preferences/UserPreferences.kt b/app/src/main/java/ac/mdiq/podcini/preferences/UserPreferences.kt index 1f5fe9b5..f237481e 100644 --- a/app/src/main/java/ac/mdiq/podcini/preferences/UserPreferences.kt +++ b/app/src/main/java/ac/mdiq/podcini/preferences/UserPreferences.kt @@ -74,9 +74,11 @@ object UserPreferences { private const val PREF_AUTO_DELETE_LOCAL = "prefAutoDeleteLocal" const val PREF_SMART_MARK_AS_PLAYED_SECS: String = "prefSmartMarkAsPlayedSecs" private const val PREF_PLAYBACK_SPEED_ARRAY = "prefPlaybackSpeedArray" + private const val PREF_FALLBACK_SPEED = "prefFallbackSpeed" const val PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS: String = "prefPauseForFocusLoss" private const val PREF_TIME_RESPECTS_SPEED = "prefPlaybackTimeRespectsSpeed" const val PREF_STREAM_OVER_DOWNLOAD: String = "prefStreamOverDownload" + private const val PREF_SPEEDFORWRD_SPEED = "prefSpeedforwardSpeed" // Network private const val PREF_ENQUEUE_DOWNLOADED = "prefEnqueueDownloaded" @@ -127,6 +129,8 @@ object UserPreferences { const val FEED_ORDER_COUNTER: Int = 0 const val FEED_ORDER_ALPHABETICAL: Int = 1 const val FEED_ORDER_MOST_PLAYED: Int = 3 + const val FEED_ORDER_LAST_UPDATED: Int = 4 + const val FEED_ORDER_LAST_UNREAD_UPDATED: Int = 5 const val DEFAULT_PAGE_REMEMBER: String = "remember" private lateinit var context: Context @@ -175,7 +179,7 @@ object UserPreferences { return ArrayList(listOf(*TextUtils.split(hiddenItems, ","))) } set(items) { - val str = TextUtils.join(",", items!!) + val str = TextUtils.join(",", items) prefs.edit() .putString(PREF_HIDDEN_DRAWER_ITEMS, str) .apply() @@ -545,6 +549,40 @@ object UserPreferences { val isEnableAutodownloadWifiFilter: Boolean get() = Build.VERSION.SDK_INT < 29 && prefs.getBoolean(PREF_ENABLE_AUTODL_WIFI_FILTER, false) + @JvmStatic + var speedforwardSpeed: Float + get() { + try { + return prefs.getString(PREF_SPEEDFORWRD_SPEED, "0.00")!!.toFloat() + } catch (e: NumberFormatException) { + Log.e(TAG, Log.getStackTraceString(e)) + speedforwardSpeed = 0.0f + return 0.0f + } + } + set(speed) { + prefs.edit() + .putString(PREF_SPEEDFORWRD_SPEED, speed.toString()) + .apply() + } + + @JvmStatic + var fallbackSpeed: Float + get() { + try { + return prefs.getString(PREF_FALLBACK_SPEED, "0.00")!!.toFloat() + } catch (e: NumberFormatException) { + Log.e(TAG, Log.getStackTraceString(e)) + fallbackSpeed = 0.0f + return 0.0f + } + } + set(speed) { + prefs.edit() + .putString(PREF_FALLBACK_SPEED, speed.toString()) + .apply() + } + @JvmStatic var fastForwardSecs: Int get() = prefs.getInt(PREF_FAST_FORWARD_SECS, 30) diff --git a/app/src/main/java/ac/mdiq/podcini/service/playback/PlaybackService.kt b/app/src/main/java/ac/mdiq/podcini/service/playback/PlaybackService.kt index 24535a49..a6798ef2 100644 --- a/app/src/main/java/ac/mdiq/podcini/service/playback/PlaybackService.kt +++ b/app/src/main/java/ac/mdiq/podcini/service/playback/PlaybackService.kt @@ -138,6 +138,10 @@ class PlaybackService : MediaBrowserServiceCompat() { private var clickCount = 0 private val clickHandler = Handler(Looper.getMainLooper()) + private var isSpeedForward = false + private var normalSpeed = 1.0f + private var isFallbackSpeed = false + /** * Used for Lollipop notifications, Android Wear, and Android Auto. */ @@ -224,7 +228,7 @@ class PlaybackService : MediaBrowserServiceCompat() { var wasPlaying = false if (mediaPlayer != null) { media = mediaPlayer!!.getPlayable() - wasPlaying = mediaPlayer!!.playerStatus == PlayerStatus.PLAYING + wasPlaying = mediaPlayer!!.playerStatus == PlayerStatus.PLAYING || mediaPlayer!!.playerStatus == PlayerStatus.FALLBACK mediaPlayer!!.pause(true, false) mediaPlayer!!.shutdown() } @@ -242,7 +246,7 @@ class PlaybackService : MediaBrowserServiceCompat() { super.onDestroy() Log.d(TAG, "Service is about to be destroyed") - if (notificationBuilder.playerStatus == PlayerStatus.PLAYING) { + if (notificationBuilder.playerStatus == PlayerStatus.PLAYING || notificationBuilder.playerStatus == PlayerStatus.FALLBACK) { notificationBuilder.playerStatus = PlayerStatus.STOPPED val notificationManager = NotificationManagerCompat.from(this) if (Build.VERSION.SDK_INT >= 33 && ActivityCompat.checkSelfPermission(this, @@ -618,7 +622,7 @@ class PlaybackService : MediaBrowserServiceCompat() { status == PlayerStatus.PLAYING -> { mediaPlayer?.pause(!isPersistNotify, false) } - status == PlayerStatus.PAUSED || status == PlayerStatus.PREPARED -> { + status == PlayerStatus.FALLBACK || status == PlayerStatus.PAUSED || status == PlayerStatus.PREPARED -> { mediaPlayer?.resume() } status == PlayerStatus.PREPARING -> { @@ -678,7 +682,7 @@ class PlaybackService : MediaBrowserServiceCompat() { } } KeyEvent.KEYCODE_MEDIA_FAST_FORWARD -> { - if (this.status == PlayerStatus.PLAYING || this.status == PlayerStatus.PAUSED) { + if (this.status == PlayerStatus.FALLBACK || this.status == PlayerStatus.PLAYING || this.status == PlayerStatus.PAUSED) { mediaPlayer?.seekDelta(fastForwardSecs * 1000) return true } @@ -690,7 +694,7 @@ class PlaybackService : MediaBrowserServiceCompat() { // Handle remapped button as notification button which is not remapped again. return handleKeycode(hardwarePreviousButton, true) } - this.status == PlayerStatus.PLAYING || this.status == PlayerStatus.PAUSED -> { + this.status == PlayerStatus.FALLBACK || this.status == PlayerStatus.PLAYING || this.status == PlayerStatus.PAUSED -> { mediaPlayer?.seekTo(0) return true } @@ -698,14 +702,14 @@ class PlaybackService : MediaBrowserServiceCompat() { } } KeyEvent.KEYCODE_MEDIA_REWIND -> { - if (this.status == PlayerStatus.PLAYING || this.status == PlayerStatus.PAUSED) { + if (this.status == PlayerStatus.FALLBACK || this.status == PlayerStatus.PLAYING || this.status == PlayerStatus.PAUSED) { mediaPlayer?.seekDelta(-rewindSecs * 1000) return true } return false } KeyEvent.KEYCODE_MEDIA_STOP -> { - if (status == PlayerStatus.PLAYING) { + if (this.status == PlayerStatus.FALLBACK || status == PlayerStatus.PLAYING) { mediaPlayer?.pause(true, true) } @@ -714,7 +718,8 @@ class PlaybackService : MediaBrowserServiceCompat() { } else -> { Log.d(TAG, "Unhandled key code: $keycode") - if (info?.playable != null && info.playerStatus == PlayerStatus.PLAYING) { // only notify the user about an unknown key event if it is actually doing something + if (info?.playable != null && info.playerStatus == PlayerStatus.PLAYING) { + // only notify the user about an unknown key event if it is actually doing something val message = String.format(resources.getString(R.string.unknown_media_key), keycode) Toast.makeText(this, message, Toast.LENGTH_SHORT).show() } @@ -933,7 +938,7 @@ class PlaybackService : MediaBrowserServiceCompat() { @Subscribe(threadMode = ThreadMode.MAIN) @Suppress("unused") fun playerError(event: PlayerErrorEvent?) { - if (mediaPlayer?.playerStatus == PlayerStatus.PLAYING) { + if (mediaPlayer?.playerStatus == PlayerStatus.PLAYING || mediaPlayer?.playerStatus == PlayerStatus.FALLBACK) { mediaPlayer!!.pause(true, false) } stateManager.stopService() @@ -1176,6 +1181,7 @@ class PlaybackService : MediaBrowserServiceCompat() { val state = if (playerStatus != null) { when (playerStatus) { PlayerStatus.PLAYING -> PlaybackStateCompat.STATE_PLAYING + PlayerStatus.FALLBACK -> PlaybackStateCompat.STATE_PLAYING PlayerStatus.PREPARED, PlayerStatus.PAUSED -> PlaybackStateCompat.STATE_PAUSED PlayerStatus.STOPPED -> PlaybackStateCompat.STATE_STOPPED PlayerStatus.SEEKING -> PlaybackStateCompat.STATE_FAST_FORWARDING @@ -1401,7 +1407,7 @@ class PlaybackService : MediaBrowserServiceCompat() { private fun bluetoothNotifyChange(info: PSMPInfo?, whatChanged: String) { var isPlaying = false - if (info?.playerStatus == PlayerStatus.PLAYING) { + if (info?.playerStatus == PlayerStatus.PLAYING || info?.playerStatus == PlayerStatus.FALLBACK) { isPlaying = true } @@ -1505,7 +1511,7 @@ class PlaybackService : MediaBrowserServiceCompat() { */ private fun pauseIfPauseOnDisconnect() { Log.d(TAG, "pauseIfPauseOnDisconnect()") - transientPause = (mediaPlayer?.playerStatus == PlayerStatus.PLAYING) + transientPause = (mediaPlayer?.playerStatus == PlayerStatus.PLAYING || mediaPlayer?.playerStatus == PlayerStatus.FALLBACK) if (isPauseOnHeadsetDisconnect && !isCasting) { mediaPlayer?.pause(!isPersistNotify, false) } @@ -1598,6 +1604,8 @@ class PlaybackService : MediaBrowserServiceCompat() { fun pause(abandonAudioFocus: Boolean, reinit: Boolean) { mediaPlayer?.pause(abandonAudioFocus, reinit) + isSpeedForward = false + isFallbackSpeed = false } val pSMPInfo: PSMPInfo @@ -1610,6 +1618,9 @@ class PlaybackService : MediaBrowserServiceCompat() { get() = mediaPlayer?.getPlayable() fun setSpeed(speed: Float) { + isSpeedForward = false + isFallbackSpeed = false + currentlyPlayingTemporaryPlaybackSpeed = speed if (currentMediaType == MediaType.VIDEO) { videoPlaybackSpeed = speed @@ -1620,6 +1631,30 @@ class PlaybackService : MediaBrowserServiceCompat() { mediaPlayer?.setPlaybackParams(speed, isSkipSilence) } + fun speedForward(speed: Float) { + if (mediaPlayer == null || isFallbackSpeed) return + + if (!isSpeedForward) { + normalSpeed = mediaPlayer!!.getPlaybackSpeed() + mediaPlayer!!.setPlaybackParams(speed, isSkipSilence) + } else { + mediaPlayer!!.setPlaybackParams(normalSpeed, isSkipSilence) + } + isSpeedForward = !isSpeedForward + } + + fun fallbackSpeed(speed: Float) { + if (mediaPlayer == null || isSpeedForward) return + + if (!isFallbackSpeed) { + normalSpeed = mediaPlayer!!.getPlaybackSpeed() + mediaPlayer!!.setPlaybackParams(speed, isSkipSilence) + } else { + mediaPlayer!!.setPlaybackParams(normalSpeed, isSkipSilence) + } + isFallbackSpeed = !isFallbackSpeed + } + fun skipSilence(skipSilence: Boolean) { mediaPlayer?.setPlaybackParams(currentPlaybackSpeed, skipSilence) } @@ -1800,13 +1835,13 @@ class PlaybackService : MediaBrowserServiceCompat() { override fun onFastForward() { Log.d(TAG, "onFastForward()") +// speedForward(2.5f) seekDelta(fastForwardSecs * 1000) } override fun onSkipToNext() { Log.d(TAG, "onSkipToNext()") - val uiModeManager = applicationContext - .getSystemService(UI_MODE_SERVICE) as UiModeManager + val uiModeManager = applicationContext.getSystemService(UI_MODE_SERVICE) as UiModeManager if (hardwareForwardButton == KeyEvent.KEYCODE_MEDIA_NEXT || uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_CAR) { mediaPlayer?.skip() diff --git a/app/src/main/java/ac/mdiq/podcini/storage/DBReader.kt b/app/src/main/java/ac/mdiq/podcini/storage/DBReader.kt index 607a34d1..3d6477ff 100644 --- a/app/src/main/java/ac/mdiq/podcini/storage/DBReader.kt +++ b/app/src/main/java/ac/mdiq/podcini/storage/DBReader.kt @@ -867,17 +867,17 @@ object DBReader { // getFeedList(adapter) // TODO: - if (false && subscriptionsFilter != null) { - feeds = subscriptionsFilter.filter(feeds, feedCounters as Map).toMutableList() - } +// if (false && subscriptionsFilter != null) { +// feeds = subscriptionsFilter.filter(feeds, feedCounters as Map).toMutableList() +// } val comparator: Comparator val feedOrder = feedOrder when (feedOrder) { UserPreferences.FEED_ORDER_COUNTER -> { comparator = Comparator { lhs: Feed, rhs: Feed -> - val counterLhs = (if (feedCounters.containsKey(lhs.id)) feedCounters[lhs.id] else 0)!!.toLong() - val counterRhs = (if (feedCounters.containsKey(rhs.id)) feedCounters[rhs.id] else 0)!!.toLong() + val counterLhs = (if (feedCounters.containsKey(lhs.id)) feedCounters[lhs.id]!! else 0).toLong() + val counterRhs = (if (feedCounters.containsKey(rhs.id)) feedCounters[rhs.id]!! else 0).toLong() when { counterLhs > counterRhs -> { // reverse natural order: podcast with most unplayed episodes first @@ -929,7 +929,7 @@ object DBReader { } } } - else -> { + UserPreferences.FEED_ORDER_LAST_UPDATED -> { val recentPubDates = adapter.mostRecentItemDates comparator = Comparator { lhs: Feed, rhs: Feed -> val dateLhs = if (recentPubDates.containsKey(lhs.id)) recentPubDates[lhs.id]!! else 0 @@ -937,6 +937,14 @@ object DBReader { dateRhs.compareTo(dateLhs) } } + else -> { + val recentUnreadPubDates = adapter.mostRecentUnreadItemDates + comparator = Comparator { lhs: Feed, rhs: Feed -> + val dateLhs = if (recentUnreadPubDates.containsKey(lhs.id)) recentUnreadPubDates[lhs.id]!! else 0 + val dateRhs = if (recentUnreadPubDates.containsKey(rhs.id)) recentUnreadPubDates[rhs.id]!! else 0 + dateRhs.compareTo(dateLhs) + } + } } feeds.sortWith(comparator) diff --git a/app/src/main/java/ac/mdiq/podcini/storage/database/PodDBAdapter.kt b/app/src/main/java/ac/mdiq/podcini/storage/database/PodDBAdapter.kt index d6a3804e..f8398b32 100644 --- a/app/src/main/java/ac/mdiq/podcini/storage/database/PodDBAdapter.kt +++ b/app/src/main/java/ac/mdiq/podcini/storage/database/PodDBAdapter.kt @@ -991,6 +991,27 @@ class PodDBAdapter private constructor() { return result } + val mostRecentUnreadItemDates: Map + get() { + val query = ("SELECT " + KEY_FEED + "," + + " MAX(" + TABLE_NAME_FEED_ITEMS + "." + KEY_PUBDATE + ") AS most_recent_pubdate" + + " FROM " + TABLE_NAME_FEED_ITEMS + + " WHERE " + KEY_READ + " = 0" + + " GROUP BY " + KEY_FEED) + + val c = db.rawQuery(query, null) + val result: MutableMap = HashMap() + if (c.moveToFirst()) { + do { + val feedId = c.getLong(0) + val date = c.getLong(1) + result[feedId] = date + } while (c.moveToNext()) + } + c.close() + return result + } + /** * Uses DatabaseUtils to escape a search query and removes ' at the * beginning and the end of the string returned by the escape method. diff --git a/app/src/main/java/ac/mdiq/podcini/storage/model/feed/Feed.kt b/app/src/main/java/ac/mdiq/podcini/storage/model/feed/Feed.kt index f40a1a92..4f326a1b 100644 --- a/app/src/main/java/ac/mdiq/podcini/storage/model/feed/Feed.kt +++ b/app/src/main/java/ac/mdiq/podcini/storage/model/feed/Feed.kt @@ -102,21 +102,12 @@ class Feed : FeedFile { var itemFilter: FeedItemFilter? = null private set - /** - * User-preferred sortOrder for display. - * Only those of scope [SortOrder.Scope.INTRA_FEED] is allowed. - */ var sortOrder: SortOrder? = null set(sortOrder) { - if (!(sortOrder != null && sortOrder.scope != SortOrder.Scope.INTRA_FEED)) { - Log.w("Feed sortOrder", "The specified sortOrder " + sortOrder - + " is invalid. Only those with INTRA_FEED scope are allowed.") + if (sortOrder == null) { + Log.w("Feed sortOrder", "The specified sortOrder $sortOrder is invalid.") + return } -// This looks suicidal: -// require(!(sortOrder != null && sortOrder.scope != SortOrder.Scope.INTRA_FEED)) { -// ("The specified sortOrder " + sortOrder -// + " is invalid. Only those with INTRA_FEED scope are allowed.") -// } field = sortOrder } diff --git a/app/src/main/java/ac/mdiq/podcini/storage/model/feed/SortOrder.kt b/app/src/main/java/ac/mdiq/podcini/storage/model/feed/SortOrder.kt index ff7c12ef..1e8c44c4 100644 --- a/app/src/main/java/ac/mdiq/podcini/storage/model/feed/SortOrder.kt +++ b/app/src/main/java/ac/mdiq/podcini/storage/model/feed/SortOrder.kt @@ -16,12 +16,14 @@ enum class SortOrder(@JvmField val code: Int, @JvmField val scope: Scope) { SIZE_LARGE_SMALL(10, Scope.INTRA_FEED), FEED_TITLE_A_Z(101, Scope.INTER_FEED), FEED_TITLE_Z_A(102, Scope.INTER_FEED), + RANDOM(103, Scope.INTER_FEED), SMART_SHUFFLE_OLD_NEW(104, Scope.INTER_FEED), SMART_SHUFFLE_NEW_OLD(105, Scope.INTER_FEED); enum class Scope { - INTRA_FEED, INTER_FEED + INTRA_FEED, + INTER_FEED } companion object { diff --git a/app/src/main/java/ac/mdiq/podcini/ui/dialog/EditFallbackSpeedDialog.kt b/app/src/main/java/ac/mdiq/podcini/ui/dialog/EditFallbackSpeedDialog.kt new file mode 100644 index 00000000..d437d589 --- /dev/null +++ b/app/src/main/java/ac/mdiq/podcini/ui/dialog/EditFallbackSpeedDialog.kt @@ -0,0 +1,44 @@ +package ac.mdiq.podcini.ui.dialog + +import ac.mdiq.podcini.R +import ac.mdiq.podcini.databinding.EditTextDialogBinding +import ac.mdiq.podcini.preferences.UserPreferences.fallbackSpeed +import ac.mdiq.podcini.preferences.UserPreferences.speedforwardSpeed +import android.app.Activity +import android.content.DialogInterface +import android.text.Editable +import android.text.InputType +import android.view.LayoutInflater +import androidx.media3.common.util.UnstableApi +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import java.lang.ref.WeakReference + +@UnstableApi + class EditFallbackSpeedDialog(activity: Activity) { + private val activityRef = WeakReference(activity) + + fun show() { + val activity = activityRef.get() ?: return + + val binding = EditTextDialogBinding.inflate(LayoutInflater.from(activity)) + binding.editText.inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL + binding.editText.text = Editable.Factory.getInstance().newEditable(fallbackSpeed.toString()) + MaterialAlertDialogBuilder(activity) + .setView(binding.root) + .setTitle(R.string.edit_fast_forward_speed) + .setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> + var speed = binding.editText.text.toString().toFloatOrNull() ?: 0.0f + when { + speed < 0.0f -> speed = 0.0f + speed > 1.5f -> speed = 1.5f + } + fallbackSpeed = String.format("%.1f", speed).toFloat() + } + .setNegativeButton(R.string.cancel_label, null) + .show() + } + + companion object { + const val TAG: String = "EditForwardSpeedDialog" + } +} diff --git a/app/src/main/java/ac/mdiq/podcini/ui/dialog/EditForwardSpeedDialog.kt b/app/src/main/java/ac/mdiq/podcini/ui/dialog/EditForwardSpeedDialog.kt new file mode 100644 index 00000000..bba502eb --- /dev/null +++ b/app/src/main/java/ac/mdiq/podcini/ui/dialog/EditForwardSpeedDialog.kt @@ -0,0 +1,45 @@ +package ac.mdiq.podcini.ui.dialog + +import ac.mdiq.podcini.R +import ac.mdiq.podcini.databinding.EditTextDialogBinding +import ac.mdiq.podcini.preferences.UserPreferences +import ac.mdiq.podcini.preferences.UserPreferences.speedforwardSpeed +import android.app.Activity +import android.content.DialogInterface +import android.text.Editable +import android.text.InputType +import android.view.LayoutInflater +import androidx.media3.common.util.UnstableApi +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import java.lang.ref.WeakReference + +@UnstableApi + class EditForwardSpeedDialog(activity: Activity) { + private val activityRef = WeakReference(activity) + + fun show() { + val activity = activityRef.get() ?: return + + val binding = EditTextDialogBinding.inflate(LayoutInflater.from(activity)) + binding.editText.inputType = InputType.TYPE_CLASS_NUMBER or InputType.TYPE_NUMBER_FLAG_DECIMAL + binding.editText.text = Editable.Factory.getInstance().newEditable(speedforwardSpeed.toString()) + + MaterialAlertDialogBuilder(activity) + .setView(binding.root) + .setTitle(R.string.edit_fast_forward_speed) + .setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> + var speed = binding.editText.text.toString().toFloatOrNull() ?: 0.0f + when { + speed < 0.0f -> speed = 0.0f + speed > 10.0f -> speed = 10.0f + } + speedforwardSpeed = String.format("%.1f", speed).toFloat() + } + .setNegativeButton(R.string.cancel_label, null) + .show() + } + + companion object { + const val TAG: String = "EditForwardSpeedDialog" + } +} diff --git a/app/src/main/java/ac/mdiq/podcini/ui/dialog/EditUrlSettingsDialog.kt b/app/src/main/java/ac/mdiq/podcini/ui/dialog/EditUrlSettingsDialog.kt index 9e6685fb..8a638ef7 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/dialog/EditUrlSettingsDialog.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/dialog/EditUrlSettingsDialog.kt @@ -25,12 +25,12 @@ import java.util.concurrent.ExecutionException val binding = EditTextDialogBinding.inflate(LayoutInflater.from(activity)) - binding.urlEditText.setText(feed.download_url) + binding.editText.setText(feed.download_url) MaterialAlertDialogBuilder(activity) .setView(binding.root) .setTitle(R.string.edit_url_menu) - .setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> showConfirmAlertDialog(binding.urlEditText.text.toString()) } + .setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> showConfirmAlertDialog(binding.editText.text.toString()) } .setNegativeButton(R.string.cancel_label, null) .show() } diff --git a/app/src/main/java/ac/mdiq/podcini/ui/dialog/FeedSortDialog.kt b/app/src/main/java/ac/mdiq/podcini/ui/dialog/FeedSortDialog.kt index f35e0590..6979123d 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/dialog/FeedSortDialog.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/dialog/FeedSortDialog.kt @@ -16,8 +16,7 @@ object FeedSortDialog { dialog.setNegativeButton(android.R.string.cancel) { d: DialogInterface, _: Int -> d.dismiss() } val selected = feedOrder - val entryValues = - listOf(*context.resources.getStringArray(R.array.nav_drawer_feed_order_values)) + val entryValues = listOf(*context.resources.getStringArray(R.array.nav_drawer_feed_order_values)) val selectedIndex = entryValues.indexOf("" + selected) val items = context.resources.getStringArray(R.array.nav_drawer_feed_order_options) diff --git a/app/src/main/java/ac/mdiq/podcini/ui/dialog/RenameItemDialog.kt b/app/src/main/java/ac/mdiq/podcini/ui/dialog/RenameItemDialog.kt index 37a98d7e..74ac97df 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/dialog/RenameItemDialog.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/dialog/RenameItemDialog.kt @@ -37,12 +37,12 @@ class RenameItemDialog { val binding = EditTextDialogBinding.inflate(LayoutInflater.from(activity)) val title = if (feed != null) feed!!.title else drawerItem!!.title - binding.urlEditText.setText(title) + binding.editText.setText(title) val dialog = MaterialAlertDialogBuilder(activity) .setView(binding.root) .setTitle(if (feed != null) R.string.rename_feed_label else R.string.rename_tag_label) .setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int -> - val newTitle = binding.urlEditText.text.toString() + val newTitle = binding.editText.text.toString() if (feed != null) { feed!!.setCustomTitle(newTitle) DBWriter.setFeedCustomTitle(feed!!) @@ -56,7 +56,7 @@ class RenameItemDialog { // To prevent cancelling the dialog on button click dialog.getButton(AlertDialog.BUTTON_NEUTRAL) - .setOnClickListener { binding.urlEditText.setText(title) } + .setOnClickListener { binding.editText.setText(title) } } private fun renameTag(title: String) { diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/AddFeedFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/AddFeedFragment.kt index f314c5cf..73d0351c 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/AddFeedFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/AddFeedFragment.kt @@ -117,18 +117,18 @@ class AddFeedFragment : Fragment() { val builder = MaterialAlertDialogBuilder(requireContext()) builder.setTitle(R.string.add_podcast_by_url) val dialogBinding = EditTextDialogBinding.inflate(layoutInflater) - dialogBinding.urlEditText.setHint(R.string.add_podcast_by_url_hint) + dialogBinding.editText.setHint(R.string.add_podcast_by_url_hint) val clipboard = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager val clipData: ClipData? = clipboard.primaryClip if (clipData != null && clipData.itemCount > 0 && clipData.getItemAt(0).text != null) { val clipboardContent: String = clipData.getItemAt(0).text.toString() if (clipboardContent.trim { it <= ' ' }.startsWith("http")) { - dialogBinding.urlEditText.setText(clipboardContent.trim { it <= ' ' }) + dialogBinding.editText.setText(clipboardContent.trim { it <= ' ' }) } } builder.setView(dialogBinding.root) - builder.setPositiveButton(R.string.confirm_label) { _: DialogInterface?, _: Int -> addUrl(dialogBinding.urlEditText.text.toString()) } + builder.setPositiveButton(R.string.confirm_label) { _: DialogInterface?, _: Int -> addUrl(dialogBinding.editText.text.toString()) } builder.setNegativeButton(R.string.cancel_label, null) builder.show() } diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt index 0c6efb5d..55887939 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt @@ -4,6 +4,7 @@ import ac.mdiq.podcini.R import ac.mdiq.podcini.databinding.AudioplayerFragmentBinding import ac.mdiq.podcini.feed.util.PlaybackSpeedUtils import ac.mdiq.podcini.playback.PlaybackController +import ac.mdiq.podcini.playback.base.PlayerStatus import ac.mdiq.podcini.playback.cast.CastEnabledActivity import ac.mdiq.podcini.playback.event.* import ac.mdiq.podcini.preferences.UserPreferences @@ -83,6 +84,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar private lateinit var butFF: ImageButton private lateinit var txtvFF: TextView private lateinit var butSkip: ImageButton + private lateinit var txtvSkip: TextView private lateinit var toolbar: MaterialToolbar private lateinit var playerFragment: View @@ -140,6 +142,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar butFF = binding.butFF txtvFF = binding.txtvFF butSkip = binding.butSkip + txtvSkip = binding.txtvSkip progressIndicator = binding.progLoading cardViewSeek = binding.cardViewSeek txtvSeek = binding.txtvSeek @@ -206,6 +209,13 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar controller?.init() controller?.playPause() } + butPlay.setOnLongClickListener { + if (controller != null && controller!!.status == PlayerStatus.PLAYING) { + val fallbackSpeed = UserPreferences.fallbackSpeed + if (fallbackSpeed > 0.1f) controller!!.fallbackSpeed(fallbackSpeed) + } + true + } butFF.setOnClickListener { if (controller != null) { val curr: Int = controller!!.position @@ -215,11 +225,18 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar butFF.setOnLongClickListener { SkipPreferenceDialog.showSkipPreference(requireContext(), SkipPreferenceDialog.SkipDirection.SKIP_FORWARD, txtvFF) - false + true } butSkip.setOnClickListener { + if (controller != null && controller!!.status == PlayerStatus.PLAYING) { + val speedForward = UserPreferences.speedforwardSpeed + if (speedForward > 0.1f) controller!!.speedForward(speedForward) + } + } + butSkip.setOnLongClickListener { activity?.sendBroadcast( MediaButtonReceiver.createIntent(requireContext(), KeyEvent.KEYCODE_MEDIA_NEXT)) + true } } @@ -323,7 +340,11 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar override fun onStart() { super.onStart() txtvRev.text = NumberFormat.getInstance().format(UserPreferences.rewindSecs.toLong()) + txtvRev.text = NumberFormat.getInstance().format(UserPreferences.rewindSecs.toLong()) txtvFF.text = NumberFormat.getInstance().format(UserPreferences.fastForwardSecs.toLong()) + if (UserPreferences.speedforwardSpeed > 0.1f) { + txtvSkip.text = NumberFormat.getInstance().format(UserPreferences.speedforwardSpeed) + } else txtvSkip.visibility = View.GONE } override fun onStop() { diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/ExternalPlayerFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/ExternalPlayerFragment.kt index d40ddd93..f21a8979 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/ExternalPlayerFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/ExternalPlayerFragment.kt @@ -69,6 +69,7 @@ class ExternalPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener { private lateinit var butFF: ImageButton private lateinit var txtvFF: TextView private lateinit var butSkip: ImageButton + private lateinit var txtvSkip: TextView private lateinit var txtvPosition: TextView private lateinit var txtvLength: TextView @@ -97,6 +98,7 @@ class ExternalPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener { butFF = binding.butFF txtvFF = binding.txtvFF butSkip = binding.butSkip + txtvSkip = binding.txtvSkip sbPosition = binding.sbPosition txtvPosition = binding.txtvPosition txtvLength = binding.txtvLength @@ -123,7 +125,6 @@ class ExternalPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener { controller = setupPlaybackController() controller!!.init() -// loadMediaInfo() EventBus.getDefault().register(this) return binding.root } @@ -169,6 +170,13 @@ class ExternalPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener { controller?.init() controller?.playPause() } + butPlay.setOnLongClickListener { + if (controller != null && controller!!.status == PlayerStatus.PLAYING) { + val fallbackSpeed = UserPreferences.fallbackSpeed + if (fallbackSpeed > 0.1f) controller!!.fallbackSpeed(fallbackSpeed) + } + true + } butFF.setOnClickListener { if (controller != null) { val curr: Int = controller!!.position @@ -178,11 +186,18 @@ class ExternalPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener { butFF.setOnLongClickListener { SkipPreferenceDialog.showSkipPreference(requireContext(), SkipPreferenceDialog.SkipDirection.SKIP_FORWARD, txtvFF) - false + true } butSkip.setOnClickListener { + if (controller != null && controller!!.status == PlayerStatus.PLAYING) { + val speedForward = UserPreferences.speedforwardSpeed + if (speedForward > 0.1f) controller!!.speedForward(speedForward) + } + } + butSkip.setOnLongClickListener { activity?.sendBroadcast( MediaButtonReceiver.createIntent(requireContext(), KeyEvent.KEYCODE_MEDIA_NEXT)) + true } } @@ -280,6 +295,9 @@ class ExternalPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener { super.onStart() txtvRev.text = NumberFormat.getInstance().format(UserPreferences.rewindSecs.toLong()) txtvFF.text = NumberFormat.getInstance().format(UserPreferences.fastForwardSecs.toLong()) + if (UserPreferences.speedforwardSpeed > 0.1f) { + txtvSkip.text = NumberFormat.getInstance().format(UserPreferences.speedforwardSpeed) + } else txtvSkip.visibility = View.GONE val media = controller?.getMedia() ?: return updatePlaybackSpeedButton(SpeedChangedEvent(PlaybackSpeedUtils.getCurrentPlaybackSpeed(media))) } diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/OnlineFeedViewFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/OnlineFeedViewFragment.kt index d5203bf9..c2d90b2e 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/OnlineFeedViewFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/OnlineFeedViewFragment.kt @@ -552,12 +552,12 @@ class OnlineFeedViewFragment : Fragment() { builder.setTitle(R.string.edit_url_menu) val dialogBinding = EditTextDialogBinding.inflate(layoutInflater) if (downloader != null) { - dialogBinding.urlEditText.setText(downloader!!.downloadRequest.source) + dialogBinding.editText.setText(downloader!!.downloadRequest.source) } builder.setView(dialogBinding.root) builder.setPositiveButton(R.string.confirm_label) { _: DialogInterface?, _: Int -> setLoadingLayout() - lookupUrlAndDownload(dialogBinding.urlEditText.text.toString()) + lookupUrlAndDownload(dialogBinding.editText.text.toString()) } builder.setNegativeButton(R.string.cancel_label) { dialog1: DialogInterface, _: Int -> dialog1.cancel() } builder.setOnCancelListener {} diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/QueueFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/QueueFragment.kt index 9b16a9d2..3bc6797f 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/QueueFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/QueueFragment.kt @@ -306,6 +306,7 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda @Subscribe(threadMode = ThreadMode.MAIN) fun onUnreadItemsChanged(event: UnreadItemsUpdateEvent?) { // Sent when playback position is reset + Log.d(TAG, "onUnreadItemsChanged() called with event = [$event]") loadItems(false) refreshToolbarState() } diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/preferences/PlaybackPreferencesFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/preferences/PlaybackPreferencesFragment.kt index 9286a3be..a4a2dec3 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/preferences/PlaybackPreferencesFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/preferences/PlaybackPreferencesFragment.kt @@ -5,13 +5,17 @@ import ac.mdiq.podcini.preferences.UsageStatistics import ac.mdiq.podcini.preferences.UsageStatistics.doNotAskAgain import ac.mdiq.podcini.preferences.UserPreferences import ac.mdiq.podcini.ui.activity.PreferenceActivity +import ac.mdiq.podcini.ui.dialog.EditFallbackSpeedDialog +import ac.mdiq.podcini.ui.dialog.EditForwardSpeedDialog import ac.mdiq.podcini.ui.dialog.SkipPreferenceDialog import ac.mdiq.podcini.ui.dialog.VariableSpeedDialog import ac.mdiq.podcini.util.event.UnreadItemsUpdateEvent import android.app.Activity import android.os.Build import android.os.Bundle +import androidx.annotation.OptIn import androidx.collection.ArrayMap +import androidx.media3.common.util.UnstableApi import androidx.preference.ListPreference import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat @@ -30,7 +34,7 @@ class PlaybackPreferencesFragment : PreferenceFragmentCompat() { (activity as PreferenceActivity).supportActionBar!!.setTitle(R.string.playback_pref) } - private fun setupPlaybackScreen() { + @OptIn(UnstableApi::class) private fun setupPlaybackScreen() { val activity: Activity? = activity findPreference(PREF_PLAYBACK_SPEED_LAUNCHER)!!.onPreferenceClickListener = @@ -43,6 +47,16 @@ class PlaybackPreferencesFragment : PreferenceFragmentCompat() { SkipPreferenceDialog.showSkipPreference(requireContext(), SkipPreferenceDialog.SkipDirection.SKIP_REWIND, null) true } + findPreference(PREF_PLAYBACK_SPEED_FORWARD_LAUNCHER)!!.onPreferenceClickListener = + Preference.OnPreferenceClickListener { + EditForwardSpeedDialog(requireActivity()).show() + true + } + findPreference(PREF_PLAYBACK_FALLBACK_SPEED_LAUNCHER)!!.onPreferenceClickListener = + Preference.OnPreferenceClickListener { + EditFallbackSpeedDialog(requireActivity()).show() + true + } findPreference(PREF_PLAYBACK_FAST_FORWARD_DELTA_LAUNCHER)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { SkipPreferenceDialog.showSkipPreference(requireContext(), SkipPreferenceDialog.SkipDirection.SKIP_FORWARD, null) @@ -120,6 +134,8 @@ class PlaybackPreferencesFragment : PreferenceFragmentCompat() { companion object { private const val PREF_PLAYBACK_SPEED_LAUNCHER = "prefPlaybackSpeedLauncher" private const val PREF_PLAYBACK_REWIND_DELTA_LAUNCHER = "prefPlaybackRewindDeltaLauncher" + private const val PREF_PLAYBACK_FALLBACK_SPEED_LAUNCHER = "prefPlaybackFallbackSpeedLauncher" + private const val PREF_PLAYBACK_SPEED_FORWARD_LAUNCHER = "prefPlaybackSpeedForwardLauncher" private const val PREF_PLAYBACK_FAST_FORWARD_DELTA_LAUNCHER = "prefPlaybackFastForwardDeltaLauncher" private const val PREF_PLAYBACK_PREFER_STREAMING = "prefStreamOverDownload" } diff --git a/app/src/main/java/ac/mdiq/podcini/util/event/FavoritesEvent.kt b/app/src/main/java/ac/mdiq/podcini/util/event/FavoritesEvent.kt index 72fdc9cb..fcebabfd 100644 --- a/app/src/main/java/ac/mdiq/podcini/util/event/FavoritesEvent.kt +++ b/app/src/main/java/ac/mdiq/podcini/util/event/FavoritesEvent.kt @@ -1,3 +1,4 @@ package ac.mdiq.podcini.util.event +//TODO: need to specify ids class FavoritesEvent diff --git a/app/src/main/java/ac/mdiq/podcini/util/event/FeedItemEvent.kt b/app/src/main/java/ac/mdiq/podcini/util/event/FeedItemEvent.kt index 8337ae37..fa9a0fd3 100644 --- a/app/src/main/java/ac/mdiq/podcini/util/event/FeedItemEvent.kt +++ b/app/src/main/java/ac/mdiq/podcini/util/event/FeedItemEvent.kt @@ -3,6 +3,7 @@ package ac.mdiq.podcini.util.event import ac.mdiq.podcini.storage.model.feed.FeedItem +// TODO: this appears not being posted class FeedItemEvent(@JvmField val items: List) { companion object { fun updated(items: List): FeedItemEvent { diff --git a/app/src/main/java/ac/mdiq/podcini/util/event/PlayerStatusEvent.kt b/app/src/main/java/ac/mdiq/podcini/util/event/PlayerStatusEvent.kt index d3cd85a9..dd93179d 100644 --- a/app/src/main/java/ac/mdiq/podcini/util/event/PlayerStatusEvent.kt +++ b/app/src/main/java/ac/mdiq/podcini/util/event/PlayerStatusEvent.kt @@ -1,3 +1,4 @@ package ac.mdiq.podcini.util.event +//TODO: need to be optimized class PlayerStatusEvent diff --git a/app/src/main/java/ac/mdiq/podcini/util/event/UnreadItemsUpdateEvent.kt b/app/src/main/java/ac/mdiq/podcini/util/event/UnreadItemsUpdateEvent.kt index 02712a2d..afbdd852 100644 --- a/app/src/main/java/ac/mdiq/podcini/util/event/UnreadItemsUpdateEvent.kt +++ b/app/src/main/java/ac/mdiq/podcini/util/event/UnreadItemsUpdateEvent.kt @@ -1,3 +1,4 @@ package ac.mdiq.podcini.util.event +// TODO: need to specify ids class UnreadItemsUpdateEvent diff --git a/app/src/main/res/layout/audioplayer_fragment.xml b/app/src/main/res/layout/audioplayer_fragment.xml index e5113d5a..7ad0c6cb 100644 --- a/app/src/main/res/layout/audioplayer_fragment.xml +++ b/app/src/main/res/layout/audioplayer_fragment.xml @@ -283,6 +283,20 @@ app:srcCompat="@drawable/ic_skip_48dp" tools:srcCompat="@drawable/ic_skip_48dp" /> + + diff --git a/app/src/main/res/layout/edit_text_dialog.xml b/app/src/main/res/layout/edit_text_dialog.xml index b442b92c..c9c13fcb 100644 --- a/app/src/main/res/layout/edit_text_dialog.xml +++ b/app/src/main/res/layout/edit_text_dialog.xml @@ -8,8 +8,8 @@ + android:id="@+id/editText" /> diff --git a/app/src/main/res/layout/external_player_fragment.xml b/app/src/main/res/layout/external_player_fragment.xml index 9be401ec..7c4d004e 100644 --- a/app/src/main/res/layout/external_player_fragment.xml +++ b/app/src/main/res/layout/external_player_fragment.xml @@ -208,6 +208,20 @@ tools:srcCompat="@drawable/ic_skip_48dp" app:tint="@color/medium_gray"/> + + diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 7d6847e4..104811f7 100644 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -181,6 +181,7 @@ @string/drawer_feed_order_unplayed_episodes @string/drawer_feed_order_alphabetical @string/drawer_feed_order_last_update + @string/drawer_feed_order_last_unread_update @string/drawer_feed_order_most_played @@ -188,6 +189,7 @@ 1 2 3 + 4 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 135ec766..9a3354e3 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -69,10 +69,11 @@ Open menu Close menu Drawer preferences - Sort by counter - Sort alphabetically - Sort by publication date - Sort by number of played episodes + Counter + Title + Publication date + Unread publication date + Number of played episodes Number of unplayed episodes Number of downloaded episodes @@ -445,7 +446,7 @@ Adapt app colors based on the wallpaper Set navigation drawer items Change which items appear in the navigation drawer - Set subscription order + Set subscription order by Change the order of your subscriptions Set subscription counter Change the information displayed by the subscription counter. Also affects the sorting of subscriptions if \'Subscription Order\' is set to \'Counter\'. @@ -475,6 +476,10 @@ Skipped first %d seconds Adjust media info to playback speed Displayed position and duration are adapted to playback speed + Fallback speed + Fast forward speed + Customize the speed to speed forward when the skip button is clicked + Customize the speed when the play button is long pressed Fast forward skip time Customize the number of seconds to jump forward when the fast forward button is clicked Rewind skip time @@ -724,6 +729,7 @@ {fa-spinner} Edit feed URL Changing the RSS address can easily break the playback state and episode listings of the podcast. We do NOT recommend changing it and will NOT provide support if anything goes wrong. This cannot be undone. The broken subscription CANNOT be repaired by simply changing the address back. We suggest creating a backup before continuing. + Edit fast forward speed Importing subscriptions from single-purpose apps… diff --git a/app/src/main/res/xml/preferences_playback.xml b/app/src/main/res/xml/preferences_playback.xml index 07f7d46e..4b98baae 100644 --- a/app/src/main/res/xml/preferences_playback.xml +++ b/app/src/main/res/xml/preferences_playback.xml @@ -43,11 +43,19 @@ android:key="prefPlaybackSpeedLauncher" android:summary="@string/pref_playback_speed_sum" android:title="@string/playback_speed"/> + +