4.5.0 commit
This commit is contained in:
parent
725c15fbba
commit
20d0038eee
13
README.md
13
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
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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())
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -867,17 +867,17 @@ object DBReader {
|
|||
// getFeedList(adapter)
|
||||
|
||||
// TODO:
|
||||
if (false && subscriptionsFilter != null) {
|
||||
feeds = subscriptionsFilter.filter(feeds, feedCounters as Map<Long?, Int>).toMutableList()
|
||||
}
|
||||
// if (false && subscriptionsFilter != null) {
|
||||
// feeds = subscriptionsFilter.filter(feeds, feedCounters as Map<Long?, Int>).toMutableList()
|
||||
// }
|
||||
|
||||
val comparator: Comparator<Feed>
|
||||
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)
|
||||
|
|
|
@ -991,6 +991,27 @@ class PodDBAdapter private constructor() {
|
|||
return result
|
||||
}
|
||||
|
||||
val mostRecentUnreadItemDates: Map<Long, Long>
|
||||
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<Long, Long> = 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.
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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)))
|
||||
}
|
||||
|
|
|
@ -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 {}
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
|
|
@ -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<Preference>(PREF_PLAYBACK_SPEED_LAUNCHER)!!.onPreferenceClickListener =
|
||||
|
@ -43,6 +47,16 @@ class PlaybackPreferencesFragment : PreferenceFragmentCompat() {
|
|||
SkipPreferenceDialog.showSkipPreference(requireContext(), SkipPreferenceDialog.SkipDirection.SKIP_REWIND, null)
|
||||
true
|
||||
}
|
||||
findPreference<Preference>(PREF_PLAYBACK_SPEED_FORWARD_LAUNCHER)!!.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
EditForwardSpeedDialog(requireActivity()).show()
|
||||
true
|
||||
}
|
||||
findPreference<Preference>(PREF_PLAYBACK_FALLBACK_SPEED_LAUNCHER)!!.onPreferenceClickListener =
|
||||
Preference.OnPreferenceClickListener {
|
||||
EditFallbackSpeedDialog(requireActivity()).show()
|
||||
true
|
||||
}
|
||||
findPreference<Preference>(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"
|
||||
}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
package ac.mdiq.podcini.util.event
|
||||
|
||||
//TODO: need to specify ids
|
||||
class FavoritesEvent
|
||||
|
|
|
@ -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<FeedItem>) {
|
||||
companion object {
|
||||
fun updated(items: List<FeedItem>): FeedItemEvent {
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
package ac.mdiq.podcini.util.event
|
||||
|
||||
//TODO: need to be optimized
|
||||
class PlayerStatusEvent
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
package ac.mdiq.podcini.util.event
|
||||
|
||||
// TODO: need to specify ids
|
||||
class UnreadItemsUpdateEvent
|
||||
|
|
|
@ -283,6 +283,20 @@
|
|||
app:srcCompat="@drawable/ic_skip_48dp"
|
||||
tools:srcCompat="@drawable/ic_skip_48dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvSkip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/butSkip"
|
||||
android:layout_alignStart="@id/butSkip"
|
||||
android:layout_alignLeft="@id/butSkip"
|
||||
android:layout_alignEnd="@id/butSkip"
|
||||
android:layout_alignRight="@id/butSkip"
|
||||
android:clickable="false"
|
||||
android:gravity="center"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="12sp" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
|
|
@ -8,8 +8,8 @@
|
|||
<EditText
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:inputType="text"
|
||||
android:inputType="text|numberDecimal"
|
||||
android:ems="10"
|
||||
android:id="@+id/urlEditText" />
|
||||
android:id="@+id/editText" />
|
||||
|
||||
</LinearLayout>
|
||||
|
|
|
@ -208,6 +208,20 @@
|
|||
tools:srcCompat="@drawable/ic_skip_48dp"
|
||||
app:tint="@color/medium_gray"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvSkip"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@id/butSkip"
|
||||
android:layout_alignStart="@id/butSkip"
|
||||
android:layout_alignLeft="@id/butSkip"
|
||||
android:layout_alignEnd="@id/butSkip"
|
||||
android:layout_alignRight="@id/butSkip"
|
||||
android:clickable="false"
|
||||
android:gravity="center"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="12sp" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
|
|
@ -181,6 +181,7 @@
|
|||
<item>@string/drawer_feed_order_unplayed_episodes</item>
|
||||
<item>@string/drawer_feed_order_alphabetical</item>
|
||||
<item>@string/drawer_feed_order_last_update</item>
|
||||
<item>@string/drawer_feed_order_last_unread_update</item>
|
||||
<item>@string/drawer_feed_order_most_played</item>
|
||||
</string-array>
|
||||
<string-array name="nav_drawer_feed_order_values">
|
||||
|
@ -188,6 +189,7 @@
|
|||
<item>1</item>
|
||||
<item>2</item>
|
||||
<item>3</item>
|
||||
<item>4</item>
|
||||
</string-array>
|
||||
|
||||
<string-array name="nav_drawer_feed_counter_options">
|
||||
|
|
|
@ -69,10 +69,11 @@
|
|||
<string name="drawer_open">Open menu</string>
|
||||
<string name="drawer_close">Close menu</string>
|
||||
<string name="drawer_preferences">Drawer preferences</string>
|
||||
<string name="drawer_feed_order_unplayed_episodes">Sort by counter</string>
|
||||
<string name="drawer_feed_order_alphabetical">Sort alphabetically</string>
|
||||
<string name="drawer_feed_order_last_update">Sort by publication date</string>
|
||||
<string name="drawer_feed_order_most_played">Sort by number of played episodes</string>
|
||||
<string name="drawer_feed_order_unplayed_episodes">Counter</string>
|
||||
<string name="drawer_feed_order_alphabetical">Title</string>
|
||||
<string name="drawer_feed_order_last_update">Publication date</string>
|
||||
<string name="drawer_feed_order_last_unread_update">Unread publication date</string>
|
||||
<string name="drawer_feed_order_most_played">Number of played episodes</string>
|
||||
|
||||
<string name="drawer_feed_counter_unplayed">Number of unplayed episodes</string>
|
||||
<string name="drawer_feed_counter_downloaded">Number of downloaded episodes</string>
|
||||
|
@ -445,7 +446,7 @@
|
|||
<string name="pref_tinted_theme_message">Adapt app colors based on the wallpaper</string>
|
||||
<string name="pref_nav_drawer_items_title">Set navigation drawer items</string>
|
||||
<string name="pref_nav_drawer_items_sum">Change which items appear in the navigation drawer</string>
|
||||
<string name="pref_nav_drawer_feed_order_title">Set subscription order</string>
|
||||
<string name="pref_nav_drawer_feed_order_title">Set subscription order by</string>
|
||||
<string name="pref_nav_drawer_feed_order_sum">Change the order of your subscriptions</string>
|
||||
<string name="pref_nav_drawer_feed_counter_title">Set subscription counter</string>
|
||||
<string name="pref_nav_drawer_feed_counter_sum">Change the information displayed by the subscription counter. Also affects the sorting of subscriptions if \'Subscription Order\' is set to \'Counter\'.</string>
|
||||
|
@ -475,6 +476,10 @@
|
|||
<string name="pref_feed_skip_intro_toast">Skipped first %d seconds</string>
|
||||
<string name="pref_playback_time_respects_speed_title">Adjust media info to playback speed</string>
|
||||
<string name="pref_playback_time_respects_speed_sum">Displayed position and duration are adapted to playback speed</string>
|
||||
<string name="pref_fallback_speed">Fallback speed</string>
|
||||
<string name="pref_speed_forward">Fast forward speed</string>
|
||||
<string name="pref_speed_forward_sum">Customize the speed to speed forward when the skip button is clicked</string>
|
||||
<string name="pref_fallback_speed_sum">Customize the speed when the play button is long pressed</string>
|
||||
<string name="pref_fast_forward">Fast forward skip time</string>
|
||||
<string name="pref_fast_forward_sum">Customize the number of seconds to jump forward when the fast forward button is clicked</string>
|
||||
<string name="pref_rewind">Rewind skip time</string>
|
||||
|
@ -724,6 +729,7 @@
|
|||
<string name="wait_icon" translatable="false">{fa-spinner}</string>
|
||||
<string name="edit_url_menu">Edit feed URL</string>
|
||||
<string name="edit_url_confirmation_msg">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.</string>
|
||||
<string name="edit_fast_forward_speed">Edit fast forward speed</string>
|
||||
|
||||
<!-- PodciniSP -->
|
||||
<string name="sp_apps_importing_feeds_msg">Importing subscriptions from single-purpose apps…</string>
|
||||
|
|
|
@ -43,11 +43,19 @@
|
|||
android:key="prefPlaybackSpeedLauncher"
|
||||
android:summary="@string/pref_playback_speed_sum"
|
||||
android:title="@string/playback_speed"/>
|
||||
<Preference
|
||||
android:key="prefPlaybackFallbackSpeedLauncher"
|
||||
android:summary="@string/pref_fallback_speed_sum"
|
||||
android:title="@string/pref_fallback_speed"/>
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="prefPlaybackTimeRespectsSpeed"
|
||||
android:summary="@string/pref_playback_time_respects_speed_sum"
|
||||
android:title="@string/pref_playback_time_respects_speed_title"/>
|
||||
<Preference
|
||||
android:key="prefPlaybackSpeedForwardLauncher"
|
||||
android:summary="@string/pref_speed_forward_sum"
|
||||
android:title="@string/pref_speed_forward"/>
|
||||
<SwitchPreferenceCompat
|
||||
android:defaultValue="false"
|
||||
android:key="prefStreamOverDownload"
|
||||
|
|
20
changelog.md
20
changelog.md
|
@ -180,4 +180,22 @@
|
|||
* online episodes list view now better handles icons
|
||||
* online episodes list view goes back to the online feed view
|
||||
* the original online feed view activity is stripped and now only preserved for receiving shared feed
|
||||
* externally shared feed opens in the online feed view fragment
|
||||
* externally shared feed opens in the online feed view fragment
|
||||
|
||||
## 4.5.0
|
||||
|
||||
* fixed bug of sort order not stable in feed item list
|
||||
* in Subscriptions view added sorting of "Unread publication date"
|
||||
* 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)
|
||||
* 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)
|
||||
* added new ways to manipulate play speed
|
||||
* the "Skip to next episode" button
|
||||
* 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
|
||||
* the Play button
|
||||
* 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
|
|
@ -0,0 +1,18 @@
|
|||
|
||||
Version 4.5.0 brings several changes:
|
||||
|
||||
* fixed bug of sort order not stable in feed item list
|
||||
* in Subscriptions view added sorting of "Unread publication date"
|
||||
* 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)
|
||||
* 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)
|
||||
* added new ways to manipulate play speed
|
||||
* the "Skip to next episode" button
|
||||
* 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
|
||||
* the Play button
|
||||
* 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
|
Loading…
Reference in New Issue