play speed, played mark, swipe icon clicks

This commit is contained in:
Xilin Jia 2024-03-22 11:17:40 +00:00
parent 547aa1f5a9
commit 0ee4c7a276
54 changed files with 362 additions and 300 deletions

View File

@ -149,8 +149,8 @@ android {
// Version code schema (not used):
// "1.2.3-beta4" -> 1020304
// "1.2.3" -> 1020395
versionCode 3020111
versionName "4.3.0"
versionCode 3020112
versionName "4.3.1"
def commit = ""
try {

View File

@ -36,7 +36,7 @@ object ImageResourceUtils {
@JvmStatic
fun getFallbackImageLocation(playable: Playable): String? {
if (playable is FeedMedia) {
val item = playable.getItem()
val item = playable.item
return if (item?.feed != null) {
item.feed!!.imageUrl
} else {

View File

@ -26,8 +26,9 @@ object PlaybackSpeedUtils {
mediaType = media.getMediaType()
playbackSpeed = PlaybackPreferences.currentlyPlayingTemporaryPlaybackSpeed
if (playbackSpeed == FeedPreferences.SPEED_USE_GLOBAL && media is FeedMedia) {
val item = media.getItem()
// if (playbackSpeed == FeedPreferences.SPEED_USE_GLOBAL && media is FeedMedia) {
if (media is FeedMedia) {
val item = media.item
if (item != null) {
val feed = item.feed
if (feed?.preferences != null) {

View File

@ -65,13 +65,13 @@ object SynchronizationQueueSink {
if (!SynchronizationSettings.isProviderConnected) {
return
}
if (media.getItem()?.feed == null || media.getItem()!!.feed!!.isLocalFeed) {
if (media.item?.feed == null || media.item!!.feed!!.isLocalFeed) {
return
}
if (media.startPosition < 0 || (!completed && media.startPosition >= media.getPosition())) {
return
}
val action = EpisodeAction.Builder(media.getItem()!!, EpisodeAction.PLAY)
val action = EpisodeAction.Builder(media.item!!, EpisodeAction.PLAY)
.currentTimestamp()
.started(media.startPosition / 1000)
.position((if (completed) media.getDuration() else media.getPosition()) / 1000)

View File

@ -22,7 +22,7 @@ object PlayableUtils {
playable.setLastPlayedTime(timestamp)
if (playable is FeedMedia) {
val item = playable.getItem()
val item = playable.item
if (item != null && item.isNew) {
DBWriter.markItemPlayed(FeedItem.UNPLAYED, item.id)
}

View File

@ -330,7 +330,7 @@ abstract class PlaybackController(private val activity: FragmentActivity) {
val media = getMedia()
if (media is FeedMedia) {
media.setPosition(time)
DBWriter.setFeedItem(media.getItem())
DBWriter.setFeedItem(media.item)
EventBus.getDefault().post(PlaybackPositionEvent(time, media.getDuration()))
}
}

View File

@ -87,43 +87,43 @@ class PlaybackPreferences private constructor() : OnSharedPreferenceChangeListen
const val PLAYER_STATUS_OTHER: Int = 3
private var instance: PlaybackPreferences? = null
private var prefs: SharedPreferences? = null
private lateinit var prefs: SharedPreferences
@JvmStatic
fun init(context: Context) {
instance = PlaybackPreferences()
prefs = PreferenceManager.getDefaultSharedPreferences(context)
prefs?.registerOnSharedPreferenceChangeListener(instance)
prefs.registerOnSharedPreferenceChangeListener(instance)
}
@JvmStatic
val currentlyPlayingMediaType: Long
get() = prefs!!.getLong(PREF_CURRENTLY_PLAYING_MEDIA_TYPE, NO_MEDIA_PLAYING)
get() = prefs.getLong(PREF_CURRENTLY_PLAYING_MEDIA_TYPE, NO_MEDIA_PLAYING)
@JvmStatic
val currentlyPlayingFeedMediaId: Long
get() = prefs!!.getLong(PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, NO_MEDIA_PLAYING)
get() = prefs.getLong(PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, NO_MEDIA_PLAYING)
@JvmStatic
val currentEpisodeIsVideo: Boolean
get() = prefs!!.getBoolean(PREF_CURRENT_EPISODE_IS_VIDEO, false)
get() = prefs.getBoolean(PREF_CURRENT_EPISODE_IS_VIDEO, false)
@JvmStatic
val currentPlayerStatus: Int
get() = prefs!!.getInt(PREF_CURRENT_PLAYER_STATUS, PLAYER_STATUS_OTHER)
get() = prefs.getInt(PREF_CURRENT_PLAYER_STATUS, PLAYER_STATUS_OTHER)
@JvmStatic
var currentlyPlayingTemporaryPlaybackSpeed: Float
get() = prefs!!.getFloat(PREF_CURRENTLY_PLAYING_TEMPORARY_PLAYBACK_SPEED, FeedPreferences.SPEED_USE_GLOBAL)
get() = prefs.getFloat(PREF_CURRENTLY_PLAYING_TEMPORARY_PLAYBACK_SPEED, FeedPreferences.SPEED_USE_GLOBAL)
set(speed) {
val editor = prefs!!.edit()
val editor = prefs.edit()
editor.putFloat(PREF_CURRENTLY_PLAYING_TEMPORARY_PLAYBACK_SPEED, speed)
editor.apply()
}
@JvmStatic
fun writeNoMediaPlaying() {
val editor = prefs!!.edit()
val editor = prefs.edit()
editor.putLong(PREF_CURRENTLY_PLAYING_MEDIA_TYPE, NO_MEDIA_PLAYING)
editor.putLong(PREF_CURRENTLY_PLAYING_FEED_ID, NO_MEDIA_PLAYING)
editor.putLong(PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, NO_MEDIA_PLAYING)
@ -134,7 +134,7 @@ class PlaybackPreferences private constructor() : OnSharedPreferenceChangeListen
@JvmStatic
fun writeMediaPlaying(playable: Playable?, playerStatus: PlayerStatus) {
Log.d(TAG, "Writing playback preferences")
val editor = prefs!!.edit()
val editor = prefs.edit()
if (playable == null) {
writeNoMediaPlaying()
@ -142,7 +142,7 @@ class PlaybackPreferences private constructor() : OnSharedPreferenceChangeListen
editor.putLong(PREF_CURRENTLY_PLAYING_MEDIA_TYPE, playable.getPlayableType().toLong())
editor.putBoolean(PREF_CURRENT_EPISODE_IS_VIDEO, playable.getMediaType() == MediaType.VIDEO)
if (playable is FeedMedia) {
val itemId = playable.getItem()?.feed?.id
val itemId = playable.item?.feed?.id
if (itemId != null) editor.putLong(PREF_CURRENTLY_PLAYING_FEED_ID, itemId)
editor.putLong(PREF_CURRENTLY_PLAYING_FEEDMEDIA_ID, playable.id)
} else {
@ -160,14 +160,14 @@ class PlaybackPreferences private constructor() : OnSharedPreferenceChangeListen
fun writePlayerStatus(playerStatus: PlayerStatus) {
Log.d(TAG, "Writing player status playback preferences")
val editor = prefs!!.edit()
val editor = prefs.edit()
editor.putInt(PREF_CURRENT_PLAYER_STATUS, getCurrentPlayerStatusAsInt(playerStatus))
editor.apply()
}
@JvmStatic
fun clearCurrentlyPlayingTemporaryPlaybackSpeed() {
val editor = prefs!!.edit()
val editor = prefs.edit()
editor.remove(PREF_CURRENTLY_PLAYING_TEMPORARY_PLAYBACK_SPEED)
editor.apply()
}

View File

@ -31,7 +31,7 @@ object PreferenceUpgrader {
private const val PREF_CONFIGURED_VERSION = "version_code"
private const val PREF_NAME = "app_version"
private var prefs: SharedPreferences? = null
private lateinit var prefs: SharedPreferences
fun checkUpgrades(context: Context) {
prefs = PreferenceManager.getDefaultSharedPreferences(context)
@ -60,15 +60,15 @@ object PreferenceUpgrader {
} // else 0 or special negative values, no change needed
}
if (oldVersion < 1070197) {
if (prefs!!.getBoolean("prefMobileUpdate", false)) {
prefs!!.edit().putString("prefMobileUpdateAllowed", "everything").apply()
if (prefs.getBoolean("prefMobileUpdate", false)) {
prefs.edit().putString("prefMobileUpdateAllowed", "everything").apply()
}
}
if (oldVersion < 1070300) {
if (prefs!!.getBoolean("prefEnableAutoDownloadOnMobile", false)) {
if (prefs.getBoolean("prefEnableAutoDownloadOnMobile", false)) {
isAllowMobileAutoDownload = true
}
when (prefs!!.getString("prefMobileUpdateAllowed", "images")) {
when (prefs.getString("prefMobileUpdateAllowed", "images")) {
"everything" -> {
isAllowMobileFeedRefresh = true
isAllowMobileEpisodeDownload = true
@ -81,27 +81,27 @@ object PreferenceUpgrader {
if (oldVersion < 1070400) {
val theme = theme
if (theme == UserPreferences.ThemePreference.LIGHT) {
prefs!!.edit().putString(UserPreferences.PREF_THEME, "system").apply()
prefs.edit().putString(UserPreferences.PREF_THEME, "system").apply()
}
isQueueLocked = false
isStreamOverDownload = false
if (!prefs!!.contains(UserPreferences.PREF_ENQUEUE_LOCATION)) {
if (!prefs.contains(UserPreferences.PREF_ENQUEUE_LOCATION)) {
val keyOldPrefEnqueueFront = "prefQueueAddToFront"
val enqueueAtFront = prefs!!.getBoolean(keyOldPrefEnqueueFront, false)
val enqueueAtFront = prefs.getBoolean(keyOldPrefEnqueueFront, false)
val enqueueLocation = if (enqueueAtFront) EnqueueLocation.FRONT else EnqueueLocation.BACK
UserPreferences.enqueueLocation = enqueueLocation
}
}
if (oldVersion < 2010300) {
// Migrate hardware button preferences
if (prefs!!.getBoolean("prefHardwareForwardButtonSkips", false)) {
prefs!!.edit().putString(UserPreferences.PREF_HARDWARE_FORWARD_BUTTON,
if (prefs.getBoolean("prefHardwareForwardButtonSkips", false)) {
prefs.edit().putString(UserPreferences.PREF_HARDWARE_FORWARD_BUTTON,
KeyEvent.KEYCODE_MEDIA_NEXT.toString()).apply()
}
if (prefs!!.getBoolean("prefHardwarePreviousButtonRestarts", false)) {
prefs!!.edit().putString(UserPreferences.PREF_HARDWARE_PREVIOUS_BUTTON,
if (prefs.getBoolean("prefHardwarePreviousButtonRestarts", false)) {
prefs.edit().putString(UserPreferences.PREF_HARDWARE_PREVIOUS_BUTTON,
KeyEvent.KEYCODE_MEDIA_PREVIOUS.toString()).apply()
}
}
@ -111,14 +111,14 @@ object PreferenceUpgrader {
SwipeAction.REMOVE_FROM_QUEUE + "," + SwipeAction.REMOVE_FROM_QUEUE).apply()
}
if (oldVersion < 2050000) {
prefs!!.edit().putBoolean(UserPreferences.PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS, true).apply()
prefs.edit().putBoolean(UserPreferences.PREF_PAUSE_PLAYBACK_FOR_FOCUS_LOSS, true).apply()
}
if (oldVersion < 2080000) {
// Migrate drawer feed counter setting to reflect removal of
// "unplayed and in inbox" (0), by changing it to "unplayed" (2)
val feedCounterSetting = prefs!!.getString(UserPreferences.PREF_DRAWER_FEED_COUNTER, "2")
val feedCounterSetting = prefs.getString(UserPreferences.PREF_DRAWER_FEED_COUNTER, "2")
if (feedCounterSetting == "0") {
prefs!!.edit().putString(UserPreferences.PREF_DRAWER_FEED_COUNTER, "2").apply()
prefs.edit().putString(UserPreferences.PREF_DRAWER_FEED_COUNTER, "2").apply()
}
val sleepTimerPreferences =
@ -128,28 +128,28 @@ object PreferenceUpgrader {
val unit = timeUnits[sleepTimerPreferences.getInt("LastTimeUnit", 1)]
setLastTimer(unit.toMinutes(value).toString())
if (prefs!!.getString(UserPreferences.PREF_EPISODE_CACHE_SIZE, "20")
if (prefs.getString(UserPreferences.PREF_EPISODE_CACHE_SIZE, "20")
== context.getString(R.string.pref_episode_cache_unlimited)) {
prefs!!.edit().putString(UserPreferences.PREF_EPISODE_CACHE_SIZE,
prefs.edit().putString(UserPreferences.PREF_EPISODE_CACHE_SIZE,
"" + UserPreferences.EPISODE_CACHE_SIZE_UNLIMITED).apply()
}
}
if (oldVersion < 3000007) {
if (prefs!!.getString("prefBackButtonBehavior", "") == "drawer") {
prefs!!.edit().putBoolean(UserPreferences.PREF_BACK_OPENS_DRAWER, true).apply()
if (prefs.getString("prefBackButtonBehavior", "") == "drawer") {
prefs.edit().putBoolean(UserPreferences.PREF_BACK_OPENS_DRAWER, true).apply()
}
}
if (oldVersion < 3010000) {
if (prefs!!.getString(UserPreferences.PREF_THEME, "system") == "2") {
prefs!!.edit()
if (prefs.getString(UserPreferences.PREF_THEME, "system") == "2") {
prefs.edit()
.putString(UserPreferences.PREF_THEME, "1")
.putBoolean(UserPreferences.PREF_THEME_BLACK, true)
.apply()
}
isAllowMobileSync = true
if (prefs!!.getString(UserPreferences.PREF_UPDATE_INTERVAL, ":")!!
if (prefs.getString(UserPreferences.PREF_UPDATE_INTERVAL, ":")!!
.contains(":")) { // Unset or "time of day"
prefs!!.edit().putString(UserPreferences.PREF_UPDATE_INTERVAL, "12").apply()
prefs.edit().putString(UserPreferences.PREF_UPDATE_INTERVAL, "12").apply()
}
}
if (oldVersion < 3020000) {
@ -161,12 +161,12 @@ object PreferenceUpgrader {
context.getSharedPreferences(AllEpisodesFragment.PREF_NAME, Context.MODE_PRIVATE)
val oldEpisodeSort = allEpisodesPreferences.getString(UserPreferences.PREF_SORT_ALL_EPISODES, "")
if (!StringUtils.isAllEmpty(oldEpisodeSort)) {
prefs!!.edit().putString(UserPreferences.PREF_SORT_ALL_EPISODES, oldEpisodeSort).apply()
prefs.edit().putString(UserPreferences.PREF_SORT_ALL_EPISODES, oldEpisodeSort).apply()
}
val oldEpisodeFilter = allEpisodesPreferences.getString("filter", "")
if (!StringUtils.isAllEmpty(oldEpisodeFilter)) {
prefs!!.edit().putString(UserPreferences.PREF_FILTER_ALL_EPISODES, oldEpisodeFilter).apply()
prefs.edit().putString(UserPreferences.PREF_FILTER_ALL_EPISODES, oldEpisodeFilter).apply()
}
}
}

View File

@ -49,8 +49,8 @@ object DownloadRequestCreator {
}
Log.d(TAG, "Requesting download media from url " + media.download_url)
val username = if ((media.getItem()?.feed?.preferences != null)) media.getItem()!!.feed!!.preferences!!.username else null
val password = if ((media.getItem()?.feed?.preferences != null)) media.getItem()!!.feed!!.preferences!!.password else null
val username = if ((media.item?.feed?.preferences != null)) media.item!!.feed!!.preferences!!.username else null
val password = if ((media.item?.feed?.preferences != null)) media.item!!.feed!!.preferences!!.password else null
return DownloadRequest.Builder(dest.toString(), media).withAuthentication(username, password)
}
@ -87,7 +87,7 @@ object DownloadRequestCreator {
}
private fun getMediafilePath(media: FeedMedia): String {
val title = media.getItem()?.feed?.title?:return ""
val title = media.item?.feed?.title?:return ""
val mediaPath = (MEDIA_DOWNLOADPATH
+ FileNameGenerator.generateFileName(title))
return UserPreferences.getDataFolder(mediaPath).toString() + "/"
@ -97,8 +97,8 @@ object DownloadRequestCreator {
var titleBaseFilename = ""
// Try to generate the filename by the item title
if (media.getItem()?.title != null) {
val title = media.getItem()!!.title!!
if (media.item?.title != null) {
val title = media.item!!.title!!
titleBaseFilename = FileNameGenerator.generateFileName(title)
}

View File

@ -8,6 +8,8 @@ import ac.mdiq.podcini.storage.model.feed.FeedItem
import ac.mdiq.podcini.storage.model.feed.FeedMedia
import ac.mdiq.podcini.net.download.serviceinterface.DownloadServiceInterface
import ac.mdiq.podcini.preferences.UserPreferences
import androidx.annotation.OptIn
import androidx.media3.common.util.UnstableApi
import io.reactivex.Observable
import io.reactivex.schedulers.Schedulers
import java.util.concurrent.Future
@ -36,7 +38,7 @@ class DownloadServiceInterfaceImpl : DownloadServiceInterface() {
ExistingWorkPolicy.KEEP, workRequest.build())
}
override fun cancel(context: Context, media: FeedMedia) {
@OptIn(UnstableApi::class) override fun cancel(context: Context, media: FeedMedia) {
// This needs to be done here, not in the worker. Reason: The worker might or might not be running.
DBWriter.deleteFeedMediaOfItem(context, media.id) // Remove partially downloaded file
val tag = WORK_TAG_EPISODE_URL + media.download_url
@ -48,7 +50,7 @@ class DownloadServiceInterfaceImpl : DownloadServiceInterface() {
{ workInfos: List<WorkInfo> ->
for (info in workInfos) {
if (info.tags.contains(WORK_DATA_WAS_QUEUED)) {
if (media.getItem() != null) DBWriter.removeQueueItem(context, false, media.getItem()!!)
if (media.item != null) DBWriter.removeQueueItem(context, false, media.item!!)
}
}
WorkManager.getInstance(context).cancelAllWorkByTag(tag)

View File

@ -31,19 +31,19 @@ class MediaDownloadedHandler(private val context: Context, var updatedStatus: Do
return
}
// media.setDownloaded modifies played state
val broadcastUnreadStateUpdate = media.getItem() != null && media.getItem()!!.isNew
val broadcastUnreadStateUpdate = media.item != null && media.item!!.isNew
media.setDownloaded(true)
media.file_url = request.destination
if (request.destination != null) media.size = File(request.destination).length()
media.checkEmbeddedPicture() // enforce check
// check if file has chapters
if (media.getItem() != null && !media.getItem()!!.hasChapters()) {
if (media.item != null && !media.item!!.hasChapters()) {
media.setChapters(ChapterUtils.loadChaptersFromMediaFile(media, context))
}
if (media.getItem()?.podcastIndexChapterUrl != null) {
ChapterUtils.loadChaptersFromUrl(media.getItem()!!.podcastIndexChapterUrl!!, false)
if (media.item?.podcastIndexChapterUrl != null) {
ChapterUtils.loadChaptersFromUrl(media.item!!.podcastIndexChapterUrl!!, false)
}
// Get duration
var durationStr: String? = null
@ -60,7 +60,7 @@ class MediaDownloadedHandler(private val context: Context, var updatedStatus: Do
Log.e(TAG, "Get duration failed", e)
}
val item = media.getItem()
val item = media.item
try {
DBWriter.setFeedMedia(media).get()

View File

@ -166,8 +166,8 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia
stream -> {
val streamurl = media!!.getStreamUrl()
if (streamurl != null) {
if (playable is FeedMedia && playable.getItem()?.feed?.preferences != null) {
val preferences = playable.getItem()!!.feed!!.preferences!!
if (playable is FeedMedia && playable.item?.feed?.preferences != null) {
val preferences = playable.item!!.feed!!.preferences!!
mediaPlayer?.setDataSource(
streamurl,
preferences.username,
@ -465,8 +465,8 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia
var volumeLeft = volumeLeft
var volumeRight = volumeRight
val playable = getPlayable()
if (playable is FeedMedia && playable.getItem()?.feed?.preferences != null) {
val preferences = playable.getItem()!!.feed!!.preferences!!
if (playable is FeedMedia && playable.item?.feed?.preferences != null) {
val preferences = playable.item!!.feed!!.preferences!!
val volumeAdaptionSetting = preferences.volumeAdaptionSetting
if (volumeAdaptionSetting != null) {
val adaptionFactor = volumeAdaptionSetting.adaptionFactor

View File

@ -70,6 +70,7 @@ import ac.mdiq.podcini.util.FeedItemUtil.hasAlmostEnded
import ac.mdiq.podcini.util.FeedUtil.shouldAutoDeleteItemsOnThatFeed
import ac.mdiq.podcini.util.IntentUtils.sendLocalBroadcast
import ac.mdiq.podcini.util.NetworkUtils.isStreamingAllowed
import ac.mdiq.podcini.util.event.MessageEvent
import ac.mdiq.podcini.util.event.settings.SpeedPresetChangedEvent
import ac.mdiq.podcini.util.event.settings.VolumeAdaptionChangedEvent
import android.Manifest
@ -408,7 +409,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
parentId == getString(R.string.current_playing_episode) -> {
val playable = createInstanceFromPreferences(this)
if (playable is FeedMedia) {
feedItems = listOf(playable.getItem())
feedItems = listOf(playable.item)
} else {
return null
}
@ -516,7 +517,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
return
}
val preferences = playable.getItem()?.feed?.preferences
val preferences = playable.item?.feed?.preferences
val skipIntro = preferences?.feedSkipIntro ?: 0
val context = applicationContext
@ -536,8 +537,8 @@ class PlaybackService : MediaBrowserServiceCompat() {
@SuppressLint("LaunchActivityFromNotification")
private fun displayStreamingNotAllowedNotification(originalIntent: Intent) {
if (EventBus.getDefault().hasSubscriberForEvent(ac.mdiq.podcini.util.event.MessageEvent::class.java)) {
EventBus.getDefault().post(ac.mdiq.podcini.util.event.MessageEvent(
if (EventBus.getDefault().hasSubscriberForEvent(MessageEvent::class.java)) {
EventBus.getDefault().post(MessageEvent(
getString(R.string.confirm_mobile_streaming_notification_message)))
return
}
@ -836,7 +837,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
if (newInfo.oldPlayerStatus != null && newInfo.oldPlayerStatus != PlayerStatus.SEEKING && autoEnable() && autoEnableByTime && !sleepTimerActive()) {
setSleepTimer(timerMillis())
EventBus.getDefault()
.post(ac.mdiq.podcini.util.event.MessageEvent(getString(R.string.sleep_timer_enabled_label),
.post(MessageEvent(getString(R.string.sleep_timer_enabled_label),
{ disableSleepTimer() }, getString(R.string.undo)))
}
loadQueueForMediaSession()
@ -915,7 +916,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
}
override fun ensureMediaInfoLoaded(media: Playable) {
if (media is FeedMedia && media.getItem() == null) {
if (media is FeedMedia && media.item == null) {
media.setItem(DBReader.getFeedItem(media.itemId))
}
}
@ -968,10 +969,10 @@ class PlaybackService : MediaBrowserServiceCompat() {
return null
}
Log.d(TAG, "getNextInQueue()")
if (currentMedia.getItem() == null) {
if (currentMedia.item == null) {
currentMedia.setItem(DBReader.getFeedItem(currentMedia.itemId))
}
val item = currentMedia.getItem()
val item = currentMedia.item
if (item == null) {
Log.w(TAG, "getNextInQueue() with FeedMedia object whose FeedItem is null")
writeNoMediaPlaying()
@ -1061,7 +1062,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
return
}
val media = playable
val item = media.getItem()
val item = media.item
val smartMarkAsPlayed = hasAlmostEnded(media)
if (!ended && smartMarkAsPlayed) {
Log.d(TAG, "smart mark as played")
@ -1133,7 +1134,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
val remainingTime = duration - currentPosition
val feedMedia = playable
val preferences = feedMedia.getItem()?.feed?.preferences
val preferences = feedMedia.item?.feed?.preferences
val skipEnd = preferences?.feedSkipEnding
if (skipEnd != null && skipEnd > 0 && skipEnd * 1000 < this.duration && (remainingTime - (skipEnd * 1000) > 0)
&& ((remainingTime - skipEnd * 1000) < (currentPlaybackSpeed * 1000))) {
@ -1144,7 +1145,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
val toast = Toast.makeText(context, skipMesg, Toast.LENGTH_LONG)
toast.show()
this.autoSkippedFeedMediaId = feedMedia.getItem()!!.identifyingValue
this.autoSkippedFeedMediaId = feedMedia.item!!.identifyingValue
mediaPlayer?.skip()
}
}
@ -1260,11 +1261,11 @@ class PlaybackService : MediaBrowserServiceCompat() {
var iconUri = p.getImageLocation()
if (p is FeedMedia) { // Don't use embedded cover etc, which Android can't load
val m = p
if (m.getItem() != null) {
val item = m.getItem()
if (item?.imageUrl != null) {
if (m.item != null) {
val item = m.item!!
if (item.imageUrl != null) {
iconUri = item.imageUrl
} else if (item?.feed != null) {
} else if (item.feed != null) {
iconUri = item.feed!!.imageUrl
}
}
@ -1535,7 +1536,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
fun speedPresetChanged(event: SpeedPresetChangedEvent) {
// TODO: speed
if (playable is FeedMedia) {
if ((playable as FeedMedia).getItem()?.feed?.id == event.feedId) {
if ((playable as FeedMedia).item?.feed?.id == event.feedId) {
if (event.speed == FeedPreferences.SPEED_USE_GLOBAL) {
setSpeed(getPlaybackSpeed(playable!!.getMediaType()))
} else {
@ -1549,9 +1550,9 @@ class PlaybackService : MediaBrowserServiceCompat() {
@Suppress("unused")
fun skipIntroEndingPresetChanged(event: ac.mdiq.podcini.util.event.settings.SkipIntroEndingChangedEvent) {
if (playable is FeedMedia) {
if ((playable as FeedMedia).getItem()?.feed?.id == event.feedId) {
if ((playable as FeedMedia).item?.feed?.id == event.feedId) {
if (event.skipEnding != 0) {
val feedPreferences = (playable as FeedMedia).getItem()?.feed?.preferences
val feedPreferences = (playable as FeedMedia).item?.feed?.preferences
if (feedPreferences != null) {
feedPreferences.feedSkipIntro = event.skipIntro
feedPreferences.feedSkipEnding = event.skipEnding
@ -1682,7 +1683,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
private fun addPlayableToQueue(playable: Playable?) {
if (playable is FeedMedia) {
val itemId = playable.getItem()?.id ?: return
val itemId = playable.item?.id ?: return
DBWriter.addQueueItem(this, false, true, itemId)
notifyChildrenChanged(getString(R.string.queue_label))
}

View File

@ -19,8 +19,8 @@ internal class PlaybackVolumeUpdater {
private fun updateFeedMediaVolumeIfNecessary(mediaPlayer: PlaybackServiceMediaPlayer, feedId: Long,
volumeAdaptionSetting: VolumeAdaptionSetting, feedMedia: FeedMedia
) {
if (feedMedia.getItem()?.feed?.id == feedId) {
val preferences = feedMedia.getItem()!!.feed!!.preferences
if (feedMedia.item?.feed?.id == feedId) {
val preferences = feedMedia.item!!.feed!!.preferences
if (preferences != null) preferences.volumeAdaptionSetting = volumeAdaptionSetting
if (mediaPlayer.playerStatus == PlayerStatus.PLAYING) {

View File

@ -1,31 +1,30 @@
package ac.mdiq.podcini.storage
import ac.mdiq.podcini.R
import android.content.Context
import android.text.TextUtils
import android.util.Log
import androidx.annotation.VisibleForTesting
import androidx.media3.common.util.UnstableApi
import ac.mdiq.podcini.net.sync.model.EpisodeAction
import ac.mdiq.podcini.net.sync.queue.SynchronizationQueueSink
import ac.mdiq.podcini.storage.DBReader.extractItemlistFromCursor
import ac.mdiq.podcini.storage.DBReader.getFeed
import ac.mdiq.podcini.storage.DBReader.getFeedItemList
import ac.mdiq.podcini.storage.DBReader.getFeedList
import ac.mdiq.podcini.storage.DBReader.loadAdditionalFeedItemListData
import ac.mdiq.podcini.net.sync.queue.SynchronizationQueueSink
import ac.mdiq.podcini.util.comparator.FeedItemPubdateComparator
import ac.mdiq.podcini.util.event.FeedItemEvent.Companion.updated
import ac.mdiq.podcini.storage.database.PodDBAdapter
import ac.mdiq.podcini.storage.database.PodDBAdapter.Companion.getInstance
import ac.mdiq.podcini.storage.database.mapper.FeedCursorMapper.convert
import ac.mdiq.podcini.storage.model.download.DownloadError
import ac.mdiq.podcini.storage.model.download.DownloadResult
import ac.mdiq.podcini.storage.model.feed.Feed
import ac.mdiq.podcini.storage.model.feed.FeedItem
import ac.mdiq.podcini.storage.model.feed.FeedMedia
import ac.mdiq.podcini.storage.model.feed.FeedPreferences.NewEpisodesAction
import ac.mdiq.podcini.net.sync.model.EpisodeAction
import ac.mdiq.podcini.storage.database.PodDBAdapter
import ac.mdiq.podcini.storage.database.PodDBAdapter.Companion.getInstance
import ac.mdiq.podcini.storage.database.mapper.FeedCursorMapper.convert
import ac.mdiq.podcini.preferences.UserPreferences.newEpisodesAction
import ac.mdiq.podcini.util.comparator.FeedItemPubdateComparator
import ac.mdiq.podcini.util.event.FeedItemEvent.Companion.updated
import ac.mdiq.podcini.util.event.FeedListUpdateEvent
import ac.mdiq.podcini.util.event.MessageEvent
import android.content.Context
import android.text.TextUtils
import android.util.Log
import androidx.annotation.VisibleForTesting
import androidx.media3.common.util.UnstableApi
import org.greenrobot.eventbus.EventBus
import java.util.*
import java.util.concurrent.*
@ -92,8 +91,8 @@ import java.util.concurrent.*
media.setDownloaded(false)
media.setFile_url(null)
DBWriter.setFeedMedia(media)
if (media.getItem() != null) EventBus.getDefault().post(updated(media.getItem()!!))
EventBus.getDefault().post(ac.mdiq.podcini.util.event.MessageEvent(context.getString(R.string.error_file_not_found)))
if (media.item != null) EventBus.getDefault().post(updated(media.item!!))
EventBus.getDefault().post(MessageEvent(context.getString(R.string.error_file_not_found)))
}
/**
@ -229,6 +228,7 @@ import java.util.concurrent.*
}
// get the most recent date now, before we start changing the list
// only used to add to IN_Box???
val priorMostRecent = savedFeed.mostRecentItem
var priorMostRecentDate: Date? = Date()
if (priorMostRecent != null) {
@ -275,11 +275,12 @@ import java.util.concurrent.*
oldItem.itemIdentifier = item.itemIdentifier
if (oldItem.isPlayed() && oldItem.media != null) {
val durs = oldItem.media!!.getDuration() / 1000
val action = EpisodeAction.Builder(oldItem, EpisodeAction.PLAY)
.currentTimestamp()
.started(oldItem.media!!.getDuration() / 1000)
.position(oldItem.media!!.getDuration() / 1000)
.total(oldItem.media!!.getDuration() / 1000)
.started(durs)
.position(durs)
.total(durs)
.build()
SynchronizationQueueSink.enqueueEpisodeActionIfSynchronizationIsActive(context, action)
}

View File

@ -93,7 +93,7 @@ import java.util.concurrent.TimeUnit
if (media != null) {
val result = deleteFeedMediaSynchronous(context, media)
val item = media.getItem()
val item = media.item
if (result && item != null && shouldDeleteRemoveFromQueue()) {
removeQueueItemSynchronous(context, false, item.id)
}
@ -140,7 +140,7 @@ import java.util.concurrent.TimeUnit
nm.cancel(R.id.notification_playing)
}
val item = media.getItem()
val item = media.item
if (item != null) {
if (localDelete) {
// Do full update of this feed to get rid of the item

View File

@ -65,7 +65,7 @@ class ItemEnqueuePositionCalculator(private val enqueueLocation: EnqueueLocation
if (currentPlaying !is FeedMedia) {
return -1
}
val curPlayingItemId = currentPlaying.getItem()!!.id
val curPlayingItemId = currentPlaying.item!!.id
for (i in curQueue.indices) {
if (curPlayingItemId == curQueue[i].id) {
return i

View File

@ -157,8 +157,8 @@ class PodDBAdapter private constructor() {
} else {
values.put(KEY_PLAYBACK_COMPLETION_DATE, 0)
}
if (media.getItem() != null) {
values.put(KEY_FEEDITEM, media.getItem()!!.id)
if (media.item != null) {
values.put(KEY_FEEDITEM, media.item!!.id)
}
if (media.id == 0L) {
media.id = db.insert(TABLE_NAME_FEED_MEDIA, null, values)

View File

@ -197,11 +197,11 @@ class FeedItem : FeedComponent, Serializable {
* try to return the title. If the title is not given, it will use the link
* of the entry.
*/
get() = if (itemIdentifier != null && itemIdentifier!!.isNotEmpty()) {
get() = if (!itemIdentifier.isNullOrEmpty()) {
itemIdentifier
} else if (title != null && title!!.isNotEmpty()) {
} else if (!title.isNullOrEmpty()) {
title
} else if (hasMedia() && media!!.download_url != null) {
} else if (media?.download_url != null) {
media!!.download_url
} else {
link
@ -233,7 +233,7 @@ class FeedItem : FeedComponent, Serializable {
@JvmName("setMediaFunction")
fun setMedia(media: FeedMedia?) {
this.media = media
if (media != null && media.getItem() !== this) {
if (media != null && media.item !== this) {
media.setItem(this)
}
}

View File

@ -26,7 +26,9 @@ class FeedMedia : FeedFile, Playable {
private set
@Volatile
private var item: FeedItem?
var item: FeedItem?
private set
private var playbackCompletionDate: Date? = null
var startPosition: Int = -1
private set
@ -75,7 +77,7 @@ class FeedMedia : FeedFile, Playable {
}
override fun getHumanReadableIdentifier(): String? {
return if (item != null && item!!.title != null) {
return if (item?.title != null) {
item!!.title
} else {
download_url
@ -98,7 +100,7 @@ class FeedMedia : FeedFile, Playable {
// getImageLocation() also loads embedded images, which we can not send to external devices
if (item!!.imageUrl != null) {
builder.setIconUri(Uri.parse(item!!.imageUrl))
} else if (item!!.feed != null && item!!.feed!!.imageUrl != null) {
} else if (item!!.feed?.imageUrl != null) {
builder.setIconUri(Uri.parse(item!!.feed!!.imageUrl))
}
}
@ -109,7 +111,7 @@ class FeedMedia : FeedFile, Playable {
* Uses mimetype to determine the type of media.
*/
override fun getMediaType(): MediaType {
return MediaType.fromMimeType(mime_type!!)
return MediaType.fromMimeType(mime_type)
}
fun updateFromOther(other: FeedMedia) {
@ -190,9 +192,9 @@ class FeedMedia : FeedFile, Playable {
return (CHECKED_ON_SIZE_BUT_UNKNOWN.toLong() == this.size)
}
fun getItem(): FeedItem? {
return item
}
// fun getItem(): FeedItem? {
// return item
// }
/**
* Sets the item object of this FeedMedia. If the given
@ -241,24 +243,23 @@ class FeedMedia : FeedFile, Playable {
dest.writeString(file_url)
dest.writeString(download_url)
dest.writeByte((if ((isDownloaded())) 1 else 0).toByte())
dest.writeLong(if ((playbackCompletionDate != null)) playbackCompletionDate!!.time else 0)
dest.writeLong(playbackCompletionDate?.time ?: 0)
dest.writeInt(playedDuration)
dest.writeLong(lastPlayedTime)
}
override fun writeToPreferences(prefEditor: SharedPreferences.Editor?) {
if (item != null && item!!.feed != null) {
prefEditor!!.putLong(PREF_FEED_ID, item!!.feed!!.id)
override fun writeToPreferences(prefEditor: SharedPreferences.Editor) {
if (item?.feed != null) {
prefEditor.putLong(PREF_FEED_ID, item!!.feed!!.id)
} else {
prefEditor!!.putLong(PREF_FEED_ID, 0L)
prefEditor.putLong(PREF_FEED_ID, 0L)
}
prefEditor.putLong(PREF_MEDIA_ID, id)
}
override fun getEpisodeTitle(): String {
if (item == null) {
return ""
}
if (item == null) return ""
return if (item!!.title != null) {
item!!.title!!
} else {
@ -295,11 +296,7 @@ class FeedMedia : FeedFile, Playable {
}
override fun getPubDate(): Date? {
return if (item?.getPubDate() != null) {
item!!.getPubDate()!!
} else {
null
}
return item?.getPubDate()
}
override fun localFileAvailable(): Boolean {
@ -334,12 +331,16 @@ class FeedMedia : FeedFile, Playable {
}
override fun getImageLocation(): String? {
return if (item != null) {
item!!.imageLocation
} else if (hasEmbeddedPicture()) {
FILENAME_PREFIX_EMBEDDED_COVER + getLocalMediaUrl()
} else {
null
return when {
item != null -> {
item!!.imageLocation
}
hasEmbeddedPicture() -> {
FILENAME_PREFIX_EMBEDDED_COVER + getLocalMediaUrl()
}
else -> {
null
}
}
}

View File

@ -10,9 +10,9 @@ enum class MediaType {
"application/x-flac"
))
fun fromMimeType(mimeType: String): MediaType {
fun fromMimeType(mimeType: String?): MediaType {
return when {
mimeType.isEmpty() -> {
mimeType.isNullOrEmpty() -> {
UNKNOWN
}
mimeType.startsWith("audio") -> {

View File

@ -17,7 +17,7 @@ interface Playable : Parcelable, Serializable {
* Implementations must NOT call commit() after they have written the values
* to the preferences file.
*/
fun writeToPreferences(prefEditor: SharedPreferences.Editor?)
fun writeToPreferences(prefEditor: SharedPreferences.Editor)
/**
* Returns the title of the episode that this playable represents

View File

@ -118,7 +118,7 @@ class RemoteMedia : Playable {
// return notes
// }
override fun writeToPreferences(prefEditor: SharedPreferences.Editor?) {
override fun writeToPreferences(prefEditor: SharedPreferences.Editor) {
//it seems pointless to do it, since the session should be kept by the remote device.
}
@ -157,7 +157,7 @@ class RemoteMedia : Playable {
}
override fun getMediaType(): MediaType {
return MediaType.fromMimeType(mimeType!!)
return MediaType.fromMimeType(mimeType)
}
override fun getStreamUrl(): String? {
@ -243,7 +243,7 @@ class RemoteMedia : Playable {
if (!TextUtils.equals(streamUrl, other.getStreamUrl())) {
return false
}
val fi = other.getItem()
val fi = other.item
if (fi == null || !TextUtils.equals(itemIdentifier, fi.itemIdentifier)) {
return false
}

View File

@ -24,6 +24,7 @@ import ac.mdiq.podcini.ui.statistics.StatisticsFragment
import ac.mdiq.podcini.ui.view.LockableBottomSheetBehavior
import ac.mdiq.podcini.util.event.EpisodeDownloadEvent
import ac.mdiq.podcini.util.event.FeedUpdateRunningEvent
import ac.mdiq.podcini.util.event.MessageEvent
import android.Manifest
import android.annotation.SuppressLint
import android.content.Context
@ -553,7 +554,7 @@ class MainActivity : CastEnabledActivity() {
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(event: ac.mdiq.podcini.util.event.MessageEvent) {
fun onEventMainThread(event: MessageEvent) {
Log.d(TAG, "onEvent($event)")
val snackbar = showSnackbarAbovePlayer(event.message, Snackbar.LENGTH_LONG)

View File

@ -60,6 +60,7 @@ import ac.mdiq.podcini.feed.parser.FeedHandler
import ac.mdiq.podcini.feed.parser.FeedHandlerResult
import ac.mdiq.podcini.feed.parser.UnsupportedFeedtypeException
import ac.mdiq.podcini.preferences.UserPreferences.isEnableAutodownload
import ac.mdiq.podcini.service.download.Downloader
import ac.mdiq.podcini.ui.common.ThemeUtils.getColorFromAttr
import ac.mdiq.podcini.ui.glide.FastBlurTransformation
import io.reactivex.Maybe
@ -89,7 +90,7 @@ class OnlineFeedViewActivity : AppCompatActivity() {
@Volatile
private var feeds: List<Feed>? = null
private var selectedDownloadUrl: String? = null
private var downloader: ac.mdiq.podcini.service.download.Downloader? = null
private var downloader: Downloader? = null
private var username: String? = null
private var password: String? = null
@ -468,7 +469,6 @@ class OnlineFeedViewActivity : AppCompatActivity() {
if (feed.download_url != null) alternateUrlsList.add(feed.download_url!!)
alternateUrlsTitleList.add(feed.title)
alternateUrlsList.addAll(alternateFeedUrls.keys)
for (url in alternateFeedUrls.keys) {
alternateUrlsTitleList.add(alternateFeedUrls[url])
@ -554,9 +554,8 @@ class OnlineFeedViewActivity : AppCompatActivity() {
private val feedId: Long
get() {
if (feeds == null) {
return 0
}
if (feeds == null) return 0
for (f in feeds!!) {
if (f.download_url == selectedDownloadUrl) {
return f.id

View File

@ -29,6 +29,7 @@ import ac.mdiq.podcini.preferences.UserPreferences.rewindSecs
import ac.mdiq.podcini.preferences.UserPreferences.setShowRemainTimeSetting
import ac.mdiq.podcini.preferences.UserPreferences.shouldShowRemainingTime
import ac.mdiq.podcini.ui.appstartintent.MainActivityStarter
import ac.mdiq.podcini.util.event.MessageEvent
import android.content.DialogInterface
import android.content.Intent
import android.graphics.PixelFormat
@ -498,7 +499,7 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(event: ac.mdiq.podcini.util.event.MessageEvent) {
fun onEventMainThread(event: MessageEvent) {
Log.d(TAG, "onEvent($event)")
val errorDialog = MaterialAlertDialogBuilder(this)
errorDialog.setMessage(event.message)
@ -530,7 +531,7 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
val hasWebsiteLink = getWebsiteLinkWithFallback(media) != null
menu.findItem(R.id.visit_website_item).setVisible(hasWebsiteLink)
val isItemAndHasLink = isFeedMedia && hasLinkToShare((media as FeedMedia).getItem())
val isItemAndHasLink = isFeedMedia && hasLinkToShare((media as FeedMedia).item)
val isItemHasDownloadLink = isFeedMedia && (media as FeedMedia?)?.download_url != null
menu.findItem(R.id.share_item).setVisible(hasWebsiteLink || isItemAndHasLink || isItemHasDownloadLink)
@ -780,7 +781,7 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
return media.getWebsiteLink()
}
media is FeedMedia -> {
return getLinkWithFallback(media.getItem())
return getLinkWithFallback(media.item)
}
else -> return null
}
@ -788,7 +789,7 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
private fun getFeedItem(playable: Playable?): FeedItem? {
return if (playable is FeedMedia) {
playable.getItem()
playable.item
} else {
null
}

View File

@ -109,7 +109,7 @@ class DownloadLogAdapter(private val context: Activity) : BaseAdapter() {
Log.e(TAG, "Could not find feed media for feed id: " + status.feedfileId)
return@OnClickListener
}
if (media.getItem() != null) DownloadActionButton(media.getItem()!!).onClick(context)
if (media.item != null) DownloadActionButton(media.item!!).onClick(context)
(context as MainActivity).showSnackbarAbovePlayer(
R.string.status_downloading_label, Toast.LENGTH_SHORT)
})

View File

@ -70,7 +70,6 @@ open class VariableSpeedDialog : BottomSheetDialogFragment() {
savedInstanceState: Bundle?
): View? {
val binding = SpeedSelectDialogBinding.inflate(inflater)
// val root = View.inflate(context, R.layout.speed_select_dialog, null)
speedSeekBar = binding.speedSeekBar
speedSeekBar.setProgressChangedListener { multiplier: Float ->
controller?.setPlaybackSpeed(multiplier)

View File

@ -461,7 +461,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
val isFeedMedia = media is FeedMedia
toolbar.menu?.findItem(R.id.open_feed_item)?.setVisible(isFeedMedia)
if (media != null && isFeedMedia) {
FeedItemMenuHandler.onPrepareMenu(toolbar.menu, (media as FeedMedia).getItem())
FeedItemMenuHandler.onPrepareMenu(toolbar.menu, (media as FeedMedia).item)
}
if (controller != null) {
@ -474,7 +474,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
override fun onMenuItemClick(item: MenuItem): Boolean {
val media: Playable = controller?.getMedia() ?: return false
val feedItem: FeedItem? = if ((media is FeedMedia)) media.getItem() else null
val feedItem: FeedItem? = if ((media is FeedMedia)) media.item else null
if (feedItem != null && FeedItemMenuHandler.onMenuItemClicked(this, item.itemId, feedItem)) {
return true
}

View File

@ -157,7 +157,7 @@ class ChaptersFragment : AppCompatDialogFragment() {
}
adapter.setMedia(media)
(dialog as AlertDialog).getButton(DialogInterface.BUTTON_NEUTRAL).visibility = View.INVISIBLE
if ((media is FeedMedia) && !media.getItem()?.podcastIndexChapterUrl.isNullOrEmpty()) {
if ((media is FeedMedia) && !media.item?.podcastIndexChapterUrl.isNullOrEmpty()) {
(dialog as AlertDialog).getButton(DialogInterface.BUTTON_NEUTRAL).visibility = View.VISIBLE
}
val positionOfCurrentChapter = getCurrentChapter(media)

View File

@ -102,13 +102,13 @@ class CompletedDownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeLis
swipeActions = SwipeActions(this, TAG).attachTo(recyclerView)
swipeActions.setFilter(FeedItemFilter(FeedItemFilter.DOWNLOADED))
if (swipeActions.actions?.left != null) {
binding.leftActionIcon.setImageResource(swipeActions.actions!!.left!!.getActionIcon())
}
if (swipeActions.actions?.right != null) {
binding.rightActionIcon.setImageResource(swipeActions.actions!!.right!!.getActionIcon())
}
refreshSwipeTelltale()
binding.leftActionIcon.setOnClickListener({
swipeActions.showDialog()
})
binding.rightActionIcon.setOnClickListener({
swipeActions.showDialog()
})
val animator: RecyclerView.ItemAnimator? = recyclerView.itemAnimator
if (animator is SimpleItemAnimator) {
@ -299,6 +299,10 @@ class CompletedDownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeLis
@Subscribe(threadMode = ThreadMode.MAIN)
fun onSwipeActionsChanged(event: SwipeActionsChangedEvent?) {
refreshSwipeTelltale()
}
private fun refreshSwipeTelltale() {
if (swipeActions.actions?.left != null) {
binding.leftActionIcon.setImageResource(swipeActions.actions!!.left!!.getActionIcon())
}

View File

@ -138,7 +138,7 @@ class CoverFragment : Fragment() {
+ "\u00A0"
+ StringUtils.replace(StringUtils.stripToEmpty(pubDateStr), " ", "\u00A0"))
if (media is FeedMedia) {
val items = media.getItem()
val items = media.item
if (items != null) {
val openFeed: Intent = MainActivity.getIntentToOpenFeed(requireContext(), items.feedId)
viewBinding.txtvPodcastTitle.setOnClickListener { startActivity(openFeed) }
@ -189,7 +189,7 @@ class CoverFragment : Fragment() {
} else if (media is FeedMedia) {
val fm: FeedMedia? = (media as FeedMedia?)
// If an item has chapters but they are not loaded yet, still display the button.
chapterControlVisible = fm?.getItem() != null && fm.getItem()!!.hasChapters()
chapterControlVisible = fm?.item != null && fm.item!!.hasChapters()
}
val newVisibility = if (chapterControlVisible) View.VISIBLE else View.GONE
if (viewBinding.chapterButton.visibility != newVisibility) {

View File

@ -1,6 +1,27 @@
package ac.mdiq.podcini.ui.fragment
import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.EpisodesListFragmentBinding
import ac.mdiq.podcini.databinding.MultiSelectSpeedDialBinding
import ac.mdiq.podcini.net.download.FeedUpdateManager
import ac.mdiq.podcini.playback.event.PlaybackPositionEvent
import ac.mdiq.podcini.storage.model.feed.FeedItem
import ac.mdiq.podcini.storage.model.feed.FeedItemFilter
import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.ui.adapter.EpisodeItemListAdapter
import ac.mdiq.podcini.ui.adapter.SelectableAdapter
import ac.mdiq.podcini.ui.dialog.ConfirmationDialog
import ac.mdiq.podcini.ui.dialog.SwipeActionsDialog
import ac.mdiq.podcini.ui.fragment.actions.EpisodeMultiSelectActionHandler
import ac.mdiq.podcini.ui.fragment.swipeactions.SwipeActions
import ac.mdiq.podcini.ui.menuhandler.FeedItemMenuHandler
import ac.mdiq.podcini.ui.menuhandler.MenuItemUtils
import ac.mdiq.podcini.ui.view.EmptyViewHandler
import ac.mdiq.podcini.ui.view.EpisodeItemListRecyclerView
import ac.mdiq.podcini.ui.view.LiftOnScrollListener
import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder
import ac.mdiq.podcini.util.FeedItemUtil
import ac.mdiq.podcini.util.event.*
import android.content.DialogInterface
import android.os.Bundle
import android.util.Log
@ -18,26 +39,6 @@ import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.snackbar.Snackbar
import com.leinardi.android.speeddial.SpeedDialActionItem
import com.leinardi.android.speeddial.SpeedDialView
import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.EpisodesListFragmentBinding
import ac.mdiq.podcini.databinding.MultiSelectSpeedDialBinding
import ac.mdiq.podcini.ui.adapter.EpisodeItemListAdapter
import ac.mdiq.podcini.ui.adapter.SelectableAdapter
import ac.mdiq.podcini.ui.dialog.ConfirmationDialog
import ac.mdiq.podcini.ui.menuhandler.MenuItemUtils
import ac.mdiq.podcini.util.FeedItemUtil
import ac.mdiq.podcini.net.download.FeedUpdateManager
import ac.mdiq.podcini.util.event.*
import ac.mdiq.podcini.playback.event.PlaybackPositionEvent
import ac.mdiq.podcini.ui.fragment.actions.EpisodeMultiSelectActionHandler
import ac.mdiq.podcini.ui.fragment.swipeactions.SwipeActions
import ac.mdiq.podcini.ui.menuhandler.FeedItemMenuHandler
import ac.mdiq.podcini.storage.model.feed.FeedItem
import ac.mdiq.podcini.storage.model.feed.FeedItemFilter
import ac.mdiq.podcini.ui.view.EmptyViewHandler
import ac.mdiq.podcini.ui.view.EpisodeItemListRecyclerView
import ac.mdiq.podcini.ui.view.LiftOnScrollListener
import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder
import io.reactivex.Completable
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
@ -101,13 +102,13 @@ abstract class EpisodesListFragment : Fragment(), SelectableAdapter.OnSelectMode
swipeActions = SwipeActions(this, getFragmentTag()).attachTo(recyclerView)
swipeActions.setFilter(getFilter())
if (swipeActions.actions?.left != null) {
binding.leftActionIcon.setImageResource(swipeActions.actions!!.left!!.getActionIcon())
}
if (swipeActions.actions?.right != null) {
binding.rightActionIcon.setImageResource(swipeActions.actions!!.right!!.getActionIcon())
}
refreshSwipeTelltale()
binding.leftActionIcon.setOnClickListener({
swipeActions.showDialog()
})
binding.rightActionIcon.setOnClickListener({
swipeActions.showDialog()
})
val animator: RecyclerView.ItemAnimator? = recyclerView.itemAnimator
if (animator is SimpleItemAnimator) {
@ -389,6 +390,10 @@ abstract class EpisodesListFragment : Fragment(), SelectableAdapter.OnSelectMode
@Subscribe(threadMode = ThreadMode.MAIN)
fun onSwipeActionsChanged(event: SwipeActionsChangedEvent?) {
refreshSwipeTelltale()
}
private fun refreshSwipeTelltale() {
if (swipeActions.actions?.left != null) {
binding.leftActionIcon.setImageResource(swipeActions.actions!!.left!!.getActionIcon())
}

View File

@ -117,12 +117,13 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
binding.recyclerView.adapter = adapter
swipeActions = SwipeActions(this, TAG).attachTo(binding.recyclerView)
if (swipeActions.actions?.left != null) {
binding.header.leftActionIcon.setImageResource(swipeActions.actions!!.left!!.getActionIcon())
}
if (swipeActions.actions?.right != null) {
binding.header.rightActionIcon.setImageResource(swipeActions.actions!!.right!!.getActionIcon())
}
refreshSwipeTelltale()
binding.header.leftActionIcon.setOnClickListener({
swipeActions.showDialog()
})
binding.header.rightActionIcon.setOnClickListener({
swipeActions.showDialog()
})
binding.progressBar.visibility = View.VISIBLE
@ -409,12 +410,7 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
@Subscribe(threadMode = ThreadMode.MAIN)
fun onSwipeActionsChanged(event: SwipeActionsChangedEvent?) {
if (swipeActions.actions?.left != null) {
binding.header.leftActionIcon.setImageResource(swipeActions.actions!!.left!!.getActionIcon())
}
if (swipeActions.actions?.right != null) {
binding.header.rightActionIcon.setImageResource(swipeActions.actions!!.right!!.getActionIcon())
}
refreshSwipeTelltale()
}
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
@ -426,6 +422,15 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
binding.swipeRefresh.isRefreshing = event.isFeedUpdateRunning
}
private fun refreshSwipeTelltale() {
if (swipeActions.actions?.left != null) {
binding.header.leftActionIcon.setImageResource(swipeActions.actions!!.left!!.getActionIcon())
}
if (swipeActions.actions?.right != null) {
binding.header.rightActionIcon.setImageResource(swipeActions.actions!!.right!!.getActionIcon())
}
}
@UnstableApi private fun refreshHeaderView() {
setupHeaderView()
if (feed == null) {

View File

@ -93,7 +93,7 @@ class ItemDescriptionFragment : Fragment() {
return@create
}
if (media is FeedMedia) {
var item = media.getItem()
var item = media.item
if (item == null) {
item = DBReader.getFeedItem(media.itemId)
media.setItem(item)

View File

@ -45,6 +45,7 @@ import ac.mdiq.podcini.storage.model.feed.FeedItem
import ac.mdiq.podcini.storage.model.feed.FeedItemFilter
import ac.mdiq.podcini.storage.model.feed.SortOrder
import ac.mdiq.podcini.preferences.UserPreferences
import ac.mdiq.podcini.ui.dialog.SwipeActionsDialog
import ac.mdiq.podcini.ui.view.EmptyViewHandler
import ac.mdiq.podcini.ui.view.EpisodeItemListRecyclerView
import ac.mdiq.podcini.ui.view.LiftOnScrollListener
@ -122,13 +123,13 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda
swipeActions = QueueSwipeActions()
swipeActions.setFilter(FeedItemFilter(FeedItemFilter.QUEUED))
swipeActions.attachTo(recyclerView)
if (swipeActions.actions?.left != null) {
binding.leftActionIcon.setImageResource(swipeActions.actions!!.left!!.getActionIcon())
}
if (swipeActions.actions?.right != null) {
binding.rightActionIcon.setImageResource(swipeActions.actions!!.right!!.getActionIcon())
}
refreshSwipeTelltale()
binding.leftActionIcon.setOnClickListener({
swipeActions.showDialog()
})
binding.rightActionIcon.setOnClickListener({
swipeActions.showDialog()
})
recyclerAdapter = object : QueueRecyclerAdapter(activity as MainActivity, swipeActions) {
override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) {
@ -298,6 +299,10 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda
@Subscribe(threadMode = ThreadMode.MAIN)
fun onSwipeActionsChanged(event: SwipeActionsChangedEvent?) {
refreshSwipeTelltale()
}
private fun refreshSwipeTelltale() {
if (swipeActions.actions?.left != null) {
binding.leftActionIcon.setImageResource(swipeActions.actions!!.left!!.getActionIcon())
}

View File

@ -99,8 +99,8 @@ class FeedMultiSelectActionHandler(private val activity: MainActivity, private v
.setTitle(R.string.playback_speed)
.setView(viewBinding.root)
.setPositiveButton("OK") { _: DialogInterface?, _: Int ->
val newSpeed = if (viewBinding.useGlobalCheckbox.isChecked
) FeedPreferences.SPEED_USE_GLOBAL else viewBinding.seekBar.currentSpeed
val newSpeed = if (viewBinding.useGlobalCheckbox.isChecked) FeedPreferences.SPEED_USE_GLOBAL
else viewBinding.seekBar.currentSpeed
saveFeedPreferences { feedPreferences: FeedPreferences ->
feedPreferences.feedPlaybackSpeed = newSpeed
}

View File

@ -74,12 +74,13 @@ open class SwipeActions(dragDirs: Int, private val fragment: Fragment, private v
@UnstableApi override fun onSwiped(viewHolder: RecyclerView.ViewHolder, swipeDir: Int) {
if (actions != null && !actions!!.hasActions()) {
//open settings dialog if no prefs are set
SwipeActionsDialog(fragment.requireContext(), tag).show(object : SwipeActionsDialog.Callback {
override fun onCall() {
this@SwipeActions.reloadPreference()
EventBus.getDefault().post(SwipeActionsChangedEvent())
}
})
showDialog()
// SwipeActionsDialog(fragment.requireContext(), tag).show(object : SwipeActionsDialog.Callback {
// override fun onCall() {
// this@SwipeActions.reloadPreference()
// EventBus.getDefault().post(SwipeActionsChangedEvent())
// }
// })
return
}
@ -89,6 +90,15 @@ open class SwipeActions(dragDirs: Int, private val fragment: Fragment, private v
(if (swipeDir == ItemTouchHelper.RIGHT) actions!!.right else actions!!.left)?.performAction(item, fragment, filter!!)
}
fun showDialog() {
SwipeActionsDialog(fragment.requireContext(), tag).show(object : SwipeActionsDialog.Callback {
override fun onCall() {
this@SwipeActions.reloadPreference()
EventBus.getDefault().post(SwipeActionsChangedEvent())
}
})
}
@UnstableApi override fun onChildDraw(c: Canvas, recyclerView: RecyclerView,
viewHolder: RecyclerView.ViewHolder,
dx: Float, dy: Float, actionState: Int, isCurrentlyActive: Boolean
@ -206,7 +216,7 @@ open class SwipeActions(dragDirs: Int, private val fragment: Fragment, private v
}
fun hasActions(): Boolean {
return right != null && left != null && right!!.getId() != SwipeAction.NO_ACTION && left!!.getId() != SwipeAction.NO_ACTION
return right != null && left != null
}
}

View File

@ -54,26 +54,26 @@ class EpisodeItemViewHolder(private val activity: MainActivity, parent: ViewGrou
private val placeholder: TextView = binding.txtvPlaceholder
private val cover: ImageView = binding.imgvCover
private val title: TextView = binding.txtvTitle
private val pubDate: TextView
private val position: TextView
private val duration: TextView
private val size: TextView
private val pubDate: TextView = binding.txtvPubDate
private val position: TextView = binding.txtvPosition
private val duration: TextView = binding.txtvDuration
private val size: TextView = binding.size
@JvmField
val isInQueue: ImageView
private val isVideo: ImageView
val isFavorite: ImageView
private val progressBar: ProgressBar
val isInQueue: ImageView = binding.ivInPlaylist
private val isVideo: ImageView = binding.ivIsVideo
private val isFavorite: ImageView = binding.isFavorite
private val progressBar: ProgressBar = binding.progressBar
@JvmField
val secondaryActionButton: View
val secondaryActionButton: View = binding.secondaryActionButton.root
@JvmField
val secondaryActionIcon: ImageView
private val secondaryActionProgress: CircularProgressBar
private val separatorIcons: TextView
private val leftPadding: View
val secondaryActionIcon: ImageView = binding.secondaryActionButton.secondaryActionIcon
private val secondaryActionProgress: CircularProgressBar = binding.secondaryActionButton.secondaryActionProgress
private val separatorIcons: TextView = binding.separatorIcons
private val leftPadding: View = binding.leftPadding
@JvmField
val coverHolder: CardView
val coverHolder: CardView = binding.coverHolder
@JvmField
val infoCard: LinearLayout
val infoCard: LinearLayout = binding.infoCard
private var item: FeedItem? = null
@ -81,21 +81,6 @@ class EpisodeItemViewHolder(private val activity: MainActivity, parent: ViewGrou
if (Build.VERSION.SDK_INT >= 23) {
title.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL)
}
pubDate = binding.txtvPubDate
position = binding.txtvPosition
duration = binding.txtvDuration
progressBar = binding.progressBar
isInQueue = binding.ivInPlaylist
isVideo = binding.ivIsVideo
isFavorite = binding.isFavorite
size = binding.size
separatorIcons = binding.separatorIcons
secondaryActionProgress = binding.secondaryActionButton.secondaryActionProgress
secondaryActionButton = binding.secondaryActionButton.root
secondaryActionIcon = binding.secondaryActionButton.secondaryActionIcon
coverHolder = binding.coverHolder
infoCard = binding.infoCard
leftPadding = binding.leftPadding
itemView.tag = this
}
@ -103,16 +88,20 @@ class EpisodeItemViewHolder(private val activity: MainActivity, parent: ViewGrou
this.item = item
placeholder.text = item.feed?.title
title.text = item.title
container.alpha = if (item.isPlayed()) 0.75f else 1.0f
if (item.isPlayed()) {
leftPadding.contentDescription = item.title + ". " + activity.getString(R.string.is_played)
binding.playedMark.visibility = View.VISIBLE
binding.playedMark.alpha = 1.0f
} else {
leftPadding.contentDescription = item.title
binding.playedMark.visibility = View.GONE
}
pubDate.text = DateFormatter.formatAbbrev(activity, item.getPubDate())
pubDate.setContentDescription(DateFormatter.formatForAccessibility(item.getPubDate()))
isFavorite.visibility = if (item.isTagged(FeedItem.TAG_FAVORITE)) View.VISIBLE else View.GONE
isInQueue.visibility = if (item.isTagged(FeedItem.TAG_QUEUE)) View.VISIBLE else View.GONE
container.alpha = if (item.isPlayed()) 0.5f else 1.0f
container.alpha = if (item.isPlayed()) 0.75f else 1.0f
val actionButton: ItemActionButton = ItemActionButton.forItem(item)
actionButton.configure(secondaryActionButton, secondaryActionIcon, activity)

View File

@ -54,10 +54,10 @@ object ChapterUtils {
var chaptersFromDatabase: List<Chapter>? = null
var chaptersFromPodcastIndex: List<Chapter>? = null
if (playable is FeedMedia) {
if (playable.getItem() == null) {
if (playable.item == null) {
playable.setItem(DBReader.getFeedItem(playable.itemId))
}
val item = playable.getItem()
val item = playable.item
if (item != null) {
if (item.hasChapters()) {
chaptersFromDatabase = DBReader.loadChaptersOfFeedItem(item)

View File

@ -82,6 +82,17 @@
android:importantForAccessibility="no"
tools:src="@tools:sample/avatars" />
<ImageView
android:id="@+id/played_mark"
android:layout_width="20dp"
android:layout_height="20dp"
android:background="@color/background_green"
android:layout_alignParentEnd="true"
android:layout_alignParentBottom="true"
android:contentDescription="@string/is_played"
app:srcCompat="@drawable/ic_check"
android:importantForAccessibility="yes"/>
</RelativeLayout>
</androidx.cardview.widget.CardView>

View File

@ -20,6 +20,7 @@
<color name="seek_background_light">#90000000</color>
<color name="seek_background_dark">#905B5B5B</color>
<color name="navigation_bar_divider_light">#1F000000</color>
<color name="background_green">#00EE00</color>
<color name="accent_light">#0078C2</color>
<color name="accent_dark">#3D8BFF</color>

View File

@ -118,7 +118,7 @@ object CastUtils {
return false
}
val metadata = info.metadata
val fi = media.getItem()
val fi = media.item
if (fi == null || metadata == null || !TextUtils.equals(metadata.getString(KEY_EPISODE_IDENTIFIER), fi.itemIdentifier)) {
return false
}

View File

@ -71,8 +71,8 @@ object MediaInfoCreator {
return null
}
val metadata = MediaMetadata(MediaMetadata.MEDIA_TYPE_GENERIC)
checkNotNull(media.getItem()) { "item is null" }
val feedItem = media.getItem()
checkNotNull(media.item) { "item is null" }
val feedItem = media.item
if (feedItem != null) {
metadata.putString(MediaMetadata.KEY_TITLE, media.getEpisodeTitle())
val subtitle = media.getFeedTitle()
@ -86,7 +86,7 @@ object MediaInfoCreator {
metadata.addImage(WebImage(Uri.parse(url)))
}
val calendar = Calendar.getInstance()
if (media.getItem()?.getPubDate() != null) calendar.time = media.getItem()!!.getPubDate()!!
if (media.item?.getPubDate() != null) calendar.time = media.item!!.getPubDate()!!
metadata.putDate(MediaMetadata.KEY_RELEASE_DATE, calendar)
if (feed != null) {
if (!feed.author.isNullOrEmpty()) {

View File

@ -83,7 +83,7 @@ class PlaybackVolumeUpdaterTest {
val feedMedia = mockFeedMedia()
Mockito.`when`(mediaPlayer!!.getPlayable()).thenReturn(feedMedia)
Mockito.`when`(feedMedia.getItem()?.feed?.id).thenReturn(FEED_ID + 1)
Mockito.`when`(feedMedia.item?.feed?.id).thenReturn(FEED_ID + 1)
val playbackVolumeUpdater = PlaybackVolumeUpdater()
playbackVolumeUpdater.updateVolumeIfNecessary(mediaPlayer!!, FEED_ID, VolumeAdaptionSetting.OFF)
@ -100,7 +100,7 @@ class PlaybackVolumeUpdaterTest {
val feedMedia = mockFeedMedia()
Mockito.`when`(mediaPlayer!!.getPlayable()).thenReturn(feedMedia)
val feedPreferences: FeedPreferences = feedMedia.getItem()!!.feed!!.preferences!!
val feedPreferences: FeedPreferences = feedMedia.item!!.feed!!.preferences!!
playbackVolumeUpdater.updateVolumeIfNecessary(mediaPlayer!!, FEED_ID, VolumeAdaptionSetting.LIGHT_REDUCTION)
@ -119,7 +119,7 @@ class PlaybackVolumeUpdaterTest {
val feedMedia = mockFeedMedia()
Mockito.`when`(mediaPlayer!!.getPlayable()).thenReturn(feedMedia)
val feedPreferences: FeedPreferences = feedMedia.getItem()!!.feed!!.preferences!!
val feedPreferences: FeedPreferences = feedMedia.item!!.feed!!.preferences!!
playbackVolumeUpdater.updateVolumeIfNecessary(mediaPlayer!!, FEED_ID, VolumeAdaptionSetting.LIGHT_REDUCTION)
@ -138,7 +138,7 @@ class PlaybackVolumeUpdaterTest {
val feedMedia = mockFeedMedia()
Mockito.`when`(mediaPlayer!!.getPlayable()).thenReturn(feedMedia)
val feedPreferences: FeedPreferences = feedMedia.getItem()!!.feed!!.preferences!!
val feedPreferences: FeedPreferences = feedMedia.item!!.feed!!.preferences!!
playbackVolumeUpdater.updateVolumeIfNecessary(mediaPlayer!!, FEED_ID, VolumeAdaptionSetting.LIGHT_REDUCTION)
@ -157,7 +157,7 @@ class PlaybackVolumeUpdaterTest {
val feedMedia = mockFeedMedia()
Mockito.`when`(mediaPlayer!!.getPlayable()).thenReturn(feedMedia)
val feedPreferences: FeedPreferences = feedMedia.getItem()!!.feed!!.preferences!!
val feedPreferences: FeedPreferences = feedMedia.item!!.feed!!.preferences!!
playbackVolumeUpdater.updateVolumeIfNecessary(mediaPlayer!!, FEED_ID, VolumeAdaptionSetting.LIGHT_REDUCTION)
@ -176,7 +176,7 @@ class PlaybackVolumeUpdaterTest {
val feedMedia = mockFeedMedia()
Mockito.`when`(mediaPlayer!!.getPlayable()).thenReturn(feedMedia)
val feedPreferences: FeedPreferences = feedMedia.getItem()!!.feed!!.preferences!!
val feedPreferences: FeedPreferences = feedMedia.item!!.feed!!.preferences!!
playbackVolumeUpdater.updateVolumeIfNecessary(mediaPlayer!!, FEED_ID, VolumeAdaptionSetting.LIGHT_REDUCTION)
@ -195,7 +195,7 @@ class PlaybackVolumeUpdaterTest {
val feedMedia = mockFeedMedia()
Mockito.`when`(mediaPlayer!!.getPlayable()).thenReturn(feedMedia)
val feedPreferences: FeedPreferences = feedMedia.getItem()!!.feed!!.preferences!!
val feedPreferences: FeedPreferences = feedMedia.item!!.feed!!.preferences!!
playbackVolumeUpdater.updateVolumeIfNecessary(mediaPlayer!!, FEED_ID, VolumeAdaptionSetting.HEAVY_REDUCTION)
@ -212,7 +212,7 @@ class PlaybackVolumeUpdaterTest {
val feed = Mockito.mock(Feed::class.java)
val feedPreferences = Mockito.mock(FeedPreferences::class.java)
Mockito.`when`(feedMedia.getItem()).thenReturn(feedItem)
Mockito.`when`(feedMedia.item).thenReturn(feedItem)
Mockito.`when`(feedItem.feed).thenReturn(feed)
Mockito.`when`(feed.id).thenReturn(FEED_ID)
Mockito.`when`(feed.preferences).thenReturn(feedPreferences)

View File

@ -451,7 +451,7 @@ class DbReaderTest {
val m: FeedMedia = feed.items[i].media!!
m.setPlaybackCompletionDate(Date((i + 1).toLong()))
adapter.setFeedMediaPlaybackCompletionDate(m)
ids[ids.size - 1 - i] = m.getItem()!!.id
ids[ids.size - 1 - i] = m.item!!.id
}
adapter.close()

View File

@ -60,7 +60,7 @@ class DbWriterTest {
@Before
fun setUp() {
context = InstrumentationRegistry.getInstrumentation().targetContext
UserPreferences.init(context!!)
UserPreferences.init(context)
init(context)
val app = context as Application?
@ -69,14 +69,14 @@ class DbWriterTest {
DownloadServiceInterface.setImpl(DownloadServiceInterfaceStub())
// create new database
PodDBAdapter.init(context!!)
PodDBAdapter.init(context)
deleteDatabase()
val adapter = getInstance()
adapter.open()
adapter.close()
val prefEdit = PreferenceManager.getDefaultSharedPreferences(
context!!.applicationContext).edit()
context.applicationContext).edit()
prefEdit.putBoolean(UserPreferences.PREF_DELETE_REMOVES_FROM_QUEUE, true).commit()
}
@ -85,7 +85,7 @@ class DbWriterTest {
PodDBAdapter.tearDownTests()
DBWriter.tearDownTests()
val testDir = context!!.getExternalFilesDir(TEST_FOLDER)
val testDir = context.getExternalFilesDir(TEST_FOLDER)
Assert.assertNotNull(testDir)
for (f in testDir!!.listFiles()) {
f.delete()
@ -129,7 +129,7 @@ class DbWriterTest {
@Test
@Throws(Exception::class)
fun testDeleteFeedMediaOfItemFileExists() {
val dest = File(context!!.getExternalFilesDir(TEST_FOLDER), "testFile")
val dest = File(context.getExternalFilesDir(TEST_FOLDER), "testFile")
Assert.assertTrue(dest.createNewFile())
@ -151,7 +151,7 @@ class DbWriterTest {
Assert.assertTrue(media!!.id != 0L)
Assert.assertTrue(item.id != 0L)
deleteFeedMediaOfItem(context!!, media.id)[TIMEOUT, TimeUnit.SECONDS]
deleteFeedMediaOfItem(context, media.id)[TIMEOUT, TimeUnit.SECONDS]
media = getFeedMedia(media.id)
Assert.assertNotNull(media)
Assert.assertFalse(dest.exists())
@ -164,7 +164,7 @@ class DbWriterTest {
fun testDeleteFeedMediaOfItemRemoveFromQueue() {
Assert.assertTrue(shouldDeleteRemoveFromQueue())
val dest = File(context!!.getExternalFilesDir(TEST_FOLDER), "testFile")
val dest = File(context.getExternalFilesDir(TEST_FOLDER), "testFile")
Assert.assertTrue(dest.createNewFile())
@ -191,7 +191,7 @@ class DbWriterTest {
queue = getQueue().toMutableList()
Assert.assertTrue(queue.size != 0)
deleteFeedMediaOfItem(context!!, media.id)
deleteFeedMediaOfItem(context, media.id)
Awaitility.await().timeout(2, TimeUnit.SECONDS).until { !dest.exists() }
media = getFeedMedia(media.id)
Assert.assertNotNull(media)
@ -204,7 +204,7 @@ class DbWriterTest {
@Test
@Throws(Exception::class)
fun testDeleteFeed() {
val destFolder = context!!.getExternalFilesDir(TEST_FOLDER)
val destFolder = context.getExternalFilesDir(TEST_FOLDER)
Assert.assertNotNull(destFolder)
val feed = Feed("url", null, "title")
@ -236,7 +236,7 @@ class DbWriterTest {
Assert.assertTrue(item.media!!.id != 0L)
}
deleteFeed(context!!, feed.id)[TIMEOUT, TimeUnit.SECONDS]
deleteFeed(context, feed.id)[TIMEOUT, TimeUnit.SECONDS]
// check if files still exist
for (f in itemFiles) {
@ -262,7 +262,7 @@ class DbWriterTest {
@Test
@Throws(Exception::class)
fun testDeleteFeedNoItems() {
val destFolder = context!!.getExternalFilesDir(TEST_FOLDER)
val destFolder = context.getExternalFilesDir(TEST_FOLDER)
Assert.assertNotNull(destFolder)
val feed = Feed("url", null, "title")
@ -276,7 +276,7 @@ class DbWriterTest {
Assert.assertTrue(feed.id != 0L)
deleteFeed(context!!, feed.id)[TIMEOUT, TimeUnit.SECONDS]
deleteFeed(context, feed.id)[TIMEOUT, TimeUnit.SECONDS]
adapter = getInstance()
adapter.open()
@ -289,7 +289,7 @@ class DbWriterTest {
@Test
@Throws(Exception::class)
fun testDeleteFeedNoFeedMedia() {
val destFolder = context!!.getExternalFilesDir(TEST_FOLDER)
val destFolder = context.getExternalFilesDir(TEST_FOLDER)
Assert.assertNotNull(destFolder)
val feed = Feed("url", null, "title")
@ -313,7 +313,7 @@ class DbWriterTest {
Assert.assertTrue(item.id != 0L)
}
deleteFeed(context!!, feed.id)[TIMEOUT, TimeUnit.SECONDS]
deleteFeed(context, feed.id)[TIMEOUT, TimeUnit.SECONDS]
adapter = getInstance()
adapter.open()
@ -331,7 +331,7 @@ class DbWriterTest {
@Test
@Throws(Exception::class)
fun testDeleteFeedWithQueueItems() {
val destFolder = context!!.getExternalFilesDir(TEST_FOLDER)
val destFolder = context.getExternalFilesDir(TEST_FOLDER)
Assert.assertNotNull(destFolder)
val feed = Feed("url", null, "title")
@ -369,7 +369,7 @@ class DbWriterTest {
queueCursor.close()
adapter.close()
deleteFeed(context!!, feed.id)[TIMEOUT, TimeUnit.SECONDS]
deleteFeed(context, feed.id)[TIMEOUT, TimeUnit.SECONDS]
adapter.open()
var c = adapter.getFeedCursor(feed.id)
@ -392,7 +392,7 @@ class DbWriterTest {
@Test
@Throws(Exception::class)
fun testDeleteFeedNoDownloadedFiles() {
val destFolder = context!!.getExternalFilesDir(TEST_FOLDER)
val destFolder = context.getExternalFilesDir(TEST_FOLDER)
Assert.assertNotNull(destFolder)
val feed = Feed("url", null, "title")
@ -421,7 +421,7 @@ class DbWriterTest {
Assert.assertTrue(item.media!!.id != 0L)
}
deleteFeed(context!!, feed.id)[TIMEOUT, TimeUnit.SECONDS]
deleteFeed(context, feed.id)[TIMEOUT, TimeUnit.SECONDS]
adapter = getInstance()
adapter.open()
@ -459,7 +459,7 @@ class DbWriterTest {
adapter.close()
val itemsToDelete: List<FeedItem> = feed.items.subList(0, 2)
deleteFeedItems(context!!, itemsToDelete)[TIMEOUT, TimeUnit.SECONDS]
deleteFeedItems(context, itemsToDelete)[TIMEOUT, TimeUnit.SECONDS]
adapter = getInstance()
adapter.open()
@ -664,7 +664,7 @@ class DbWriterTest {
adapter.setQueue(feed.items.toList())
adapter.close()
removeQueueItem(context!!, false, item)[TIMEOUT, TimeUnit.SECONDS]
removeQueueItem(context, false, item)[TIMEOUT, TimeUnit.SECONDS]
adapter = getInstance()
adapter.open()
val queue = adapter.queueIDCursor
@ -700,21 +700,21 @@ class DbWriterTest {
// Use array rather than List to make codes more succinct
val itemIds = toItemIds(feed.items).toTypedArray<Long>()
removeQueueItem(context!!, false,
removeQueueItem(context, false,
itemIds[1], itemIds[3])[TIMEOUT, TimeUnit.SECONDS]
assertQueueByItemIds("Average case - 2 items removed successfully",
itemIds[0], itemIds[2])
removeQueueItem(context!!, false)[TIMEOUT, TimeUnit.SECONDS]
removeQueueItem(context, false)[TIMEOUT, TimeUnit.SECONDS]
assertQueueByItemIds("Boundary case - no items supplied. queue should see no change",
itemIds[0], itemIds[2])
removeQueueItem(context!!, false,
removeQueueItem(context, false,
itemIds[0], itemIds[4], -1L)[TIMEOUT, TimeUnit.SECONDS]
assertQueueByItemIds("Boundary case - items not in queue ignored",
itemIds[2])
removeQueueItem(context!!, false,
removeQueueItem(context, false,
itemIds[2], -1L)[TIMEOUT, TimeUnit.SECONDS]
assertQueueByItemIds("Boundary case - invalid itemIds ignored") // the queue is empty
}

View File

@ -92,9 +92,10 @@
* disabled drag actions when in multi-select mode (fixed crash bug)
* renewed PodcastIndex API keys
* added share notes menu option in episode view
* press on title area of an episode now opens the episode info faster and more economically - without horizontal swipe
* press on the icon of an episode opens the episode info the original way - with horizontal swipe
* https://github.com/XilinJia/Podcini/issues/20
* press on title area of an episode now opens the episode info faster and more economically - without horizontal swipe
* press on the icon of an episode opens the episode info the original way - with horizontal swipe
## 4.3.0
* added more info about feeds in the online search view
@ -104,4 +105,16 @@
* added swipe action telltales in all episode lists
* added NO_ACTION swipe action
* all default swipe actions are set to NO_ACTION
* cleaned up swipe preferences ui: statistics removed
* cleaned up swipe preferences ui: statistics removed
## 4.3.1
* titles of played episodes are made brighter
* icons of played episodes are marked with a check
* icons of swipe telltales are clickable for setting
* Straightened up play speed setting
* three places to set the play speed:
* global setting at the preference
* setting for a feed: either use global or customize
* setting at the player: set for current playing and save for global
* feed setting takes precedence when playing an episode

View File

@ -4,5 +4,6 @@ Version 4.2.7 brings several changes:
* disabled drag actions when in multi-select mode (fixed crash bug)
* renewed PodcastIndex API keys
* added share notes menu option in episode view
* press on title area of an episode now opens the episode info faster and more economically - without horizontal swipe
* press on the icon of an episode opens the episode info the original way - with horizontal swipe
* https://github.com/XilinJia/Podcini/issues/20
* press on title area of an episode now opens the episode info faster and more economically - without horizontal swipe
* press on the icon of an episode opens the episode info the original way - with horizontal swipe

View File

@ -0,0 +1,12 @@
Version 4.3.1 brings several changes:
* titles of played episodes are made brighter
* icons of played episodes are marked with a check
* icons of swipe telltales are clickable for setting
* Straightened up play speed setting
* three places to set the play speed:
* global setting at the preference
* setting for a feed: either use global or customize
* setting at the player: set for current playing and save for global
* feed setting takes precedence when playing an episode

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 KiB

After

Width:  |  Height:  |  Size: 148 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 148 KiB

After

Width:  |  Height:  |  Size: 159 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 159 KiB

After

Width:  |  Height:  |  Size: 148 KiB