6.0.6 commit

This commit is contained in:
Xilin Jia 2024-07-08 20:16:05 +01:00
parent 27f5f5e95a
commit 09328bf078
96 changed files with 311 additions and 359 deletions

View File

@ -125,8 +125,8 @@ android {
buildConfig true
}
defaultConfig {
versionCode 3020205
versionName "6.0.5"
versionCode 3020206
versionName "6.0.6"
applicationId "ac.mdiq.podcini.R"
def commit = ""
@ -265,7 +265,7 @@ dependencies {
testImplementation 'org.awaitility:awaitility:4.2.1'
testImplementation "junit:junit:4.13.2"
testImplementation 'org.mockito:mockito-inline:5.2.0'
testImplementation 'org.robolectric:robolectric:4.12'
testImplementation 'org.robolectric:robolectric:4.13'
testImplementation 'javax.inject:javax.inject:1'
playImplementation 'com.google.android.gms:play-services-base:18.5.0'

View File

@ -11,8 +11,8 @@ import ac.mdiq.podcini.storage.database.Episodes.getEpisode
import ac.mdiq.podcini.storage.database.Episodes.getEpisodes
import ac.mdiq.podcini.storage.database.Queues.clearQueue
import ac.mdiq.podcini.storage.database.Queues.getInQueueEpisodeIds
import ac.mdiq.podcini.storage.utils.EpisodeFilter.Companion.unfiltered
import ac.mdiq.podcini.storage.utils.SortOrder
import ac.mdiq.podcini.storage.model.EpisodeFilter.Companion.unfiltered
import ac.mdiq.podcini.storage.model.SortOrder
import ac.mdiq.podcini.ui.activity.MainActivity
import android.content.Context
import android.content.Intent

View File

@ -1,6 +1,6 @@
package de.test.podcini.service.playback
import ac.mdiq.podcini.storage.utils.MediaType
import ac.mdiq.podcini.storage.model.MediaType
import ac.mdiq.podcini.storage.model.Playable
import ac.mdiq.podcini.playback.base.MediaPlayerBase.MediaPlayerInfo
import ac.mdiq.podcini.playback.base.MediaPlayerCallback

View File

@ -1,6 +1,6 @@
package de.test.podcini.service.playback
import ac.mdiq.podcini.storage.utils.MediaType
import ac.mdiq.podcini.storage.model.MediaType
import ac.mdiq.podcini.storage.model.Playable
import ac.mdiq.podcini.playback.base.MediaPlayerBase.MediaPlayerInfo
import ac.mdiq.podcini.playback.base.MediaPlayerCallback

View File

@ -5,7 +5,7 @@ import ac.mdiq.podcini.playback.base.MediaPlayerBase.MediaPlayerInfo
import ac.mdiq.podcini.playback.base.PlayerStatus
import ac.mdiq.podcini.playback.service.LocalMediaPlayer
import ac.mdiq.podcini.storage.model.*
import ac.mdiq.podcini.storage.utils.VolumeAdaptionSetting
import ac.mdiq.podcini.storage.model.VolumeAdaptionSetting
import androidx.test.annotation.UiThreadTest
import androidx.test.filters.MediumTest
import androidx.test.platform.app.InstrumentationRegistry

View File

@ -1,5 +1,8 @@
package ac.mdiq.podcini.net.download
import ac.mdiq.podcini.storage.model.FeedPreferences.AutoDeleteAction
import ac.mdiq.podcini.storage.model.FeedPreferences.AutoDeleteAction.NEVER
/** Utility class for Download Errors. */
/** Get machine-readable code. */
enum class DownloadError(@JvmField val code: Int) {
@ -31,10 +34,7 @@ enum class DownloadError(@JvmField val code: Int) {
/** Return DownloadError from its associated code. */
@JvmStatic
fun fromCode(code: Int): DownloadError {
for (reason in entries) {
if (reason.code == code) return reason
}
throw IllegalArgumentException("unknown code: $code")
return enumValues<DownloadError>().firstOrNull { it.code == code } ?: throw IllegalArgumentException("unknown code: $code")
}
}
}

View File

@ -20,7 +20,7 @@ import ac.mdiq.podcini.storage.database.LogsAndStats
import ac.mdiq.podcini.storage.database.RealmDB.unmanaged
import ac.mdiq.podcini.storage.model.DownloadResult
import ac.mdiq.podcini.storage.model.FeedPreferences
import ac.mdiq.podcini.storage.utils.VolumeAdaptionSetting
import ac.mdiq.podcini.storage.model.VolumeAdaptionSetting
import ac.mdiq.podcini.ui.utils.NotificationUtils
import ac.mdiq.podcini.util.config.ClientConfigurator
import ac.mdiq.podcini.util.event.EventFlow

View File

@ -15,7 +15,7 @@ import ac.mdiq.podcini.storage.model.DownloadResult
import ac.mdiq.podcini.storage.model.Feed
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.model.EpisodeMedia
import ac.mdiq.podcini.storage.utils.MediaType
import ac.mdiq.podcini.storage.model.MediaType
import ac.mdiq.podcini.storage.utils.FastDocumentFile
import ac.mdiq.podcini.util.Logd
import android.content.Context

View File

@ -1,7 +1,7 @@
package ac.mdiq.podcini.net.feed.parser
import ac.mdiq.podcini.storage.model.Feed
import ac.mdiq.podcini.storage.utils.FeedFunding
import ac.mdiq.podcini.storage.model.FeedFunding
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.net.feed.parser.element.SyndElement
import ac.mdiq.podcini.net.feed.parser.namespace.Namespace

View File

@ -2,7 +2,7 @@ package ac.mdiq.podcini.net.feed.parser.media.id3
import android.util.Log
import ac.mdiq.podcini.storage.model.Chapter
import ac.mdiq.podcini.storage.utils.EmbeddedChapterImage.Companion.makeUrl
import ac.mdiq.podcini.storage.model.EmbeddedChapterImage.Companion.makeUrl
import ac.mdiq.podcini.net.feed.parser.media.id3.model.FrameHeader
import ac.mdiq.podcini.util.Logd
import org.apache.commons.io.input.CountingInputStream

View File

@ -8,7 +8,7 @@ import ac.mdiq.podcini.net.feed.parser.utils.MimeTypeUtils.getMimeType
import ac.mdiq.podcini.net.feed.parser.utils.MimeTypeUtils.isMediaFile
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.model.EpisodeMedia
import ac.mdiq.podcini.storage.utils.FeedFunding
import ac.mdiq.podcini.storage.model.FeedFunding
import ac.mdiq.podcini.util.Logd
import org.xml.sax.Attributes

View File

@ -1,7 +1,7 @@
package ac.mdiq.podcini.net.feed.parser.namespace
import ac.mdiq.podcini.net.feed.parser.HandlerState
import ac.mdiq.podcini.storage.utils.FeedFunding
import ac.mdiq.podcini.storage.model.FeedFunding
import ac.mdiq.podcini.net.feed.parser.element.SyndElement
import org.xml.sax.Attributes

View File

@ -28,9 +28,9 @@ import ac.mdiq.podcini.storage.database.Feeds.updateFeed
import ac.mdiq.podcini.storage.database.Queues.removeFromQueue
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.model.Feed
import ac.mdiq.podcini.storage.utils.EpisodeFilter
import ac.mdiq.podcini.storage.model.EpisodeFilter
import ac.mdiq.podcini.storage.utils.EpisodeUtil.hasAlmostEnded
import ac.mdiq.podcini.storage.utils.SortOrder
import ac.mdiq.podcini.storage.model.SortOrder
import ac.mdiq.podcini.ui.utils.NotificationUtils
import ac.mdiq.podcini.util.Logd
import ac.mdiq.podcini.util.event.*

View File

@ -29,6 +29,8 @@ class EpisodeAction private constructor(builder: Builder) {
*/
val position: Int
var playedDuration: Int
/**
* Returns the total length of the file in seconds.
*
@ -46,6 +48,7 @@ class EpisodeAction private constructor(builder: Builder) {
this.timestamp = builder.timestamp
this.started = builder.started
this.position = builder.position
this.playedDuration = builder.playedDuration
this.playState = builder.playState
this.isFavorite = builder.isFavorite
this.total = builder.total
@ -60,7 +63,7 @@ class EpisodeAction private constructor(builder: Builder) {
if (o !is EpisodeAction) return false
val that = o
return started == that.started && position == that.position && total == that.total && playState == that.playState && isFavorite == that.isFavorite && action != that.action && podcast == that.podcast && episode == that.episode && timestamp == that.timestamp && guid == that.guid
return started == that.started && position == that.position && playedDuration == that.playedDuration && total == that.total && playState == that.playState && isFavorite == that.isFavorite && action != that.action && podcast == that.podcast && episode == that.episode && timestamp == that.timestamp && guid == that.guid
}
override fun hashCode(): Int {
@ -71,6 +74,7 @@ class EpisodeAction private constructor(builder: Builder) {
result = 31 * result + (timestamp?.hashCode() ?: 0)
result = 31 * result + started
result = 31 * result + position
result = 31 * result + playedDuration
result = 31 * result + playState
result = 31 * result + total
return result
@ -94,6 +98,7 @@ class EpisodeAction private constructor(builder: Builder) {
if (this.action == Action.PLAY) {
obj.put("started", this.started)
obj.put("position", this.position)
obj.put("playedDuration", this.playedDuration)
obj.put("playState", this.playState)
obj.put("total", this.total)
obj.put("isFavorite", this.isFavorite)
@ -119,6 +124,7 @@ class EpisodeAction private constructor(builder: Builder) {
var timestamp: Date? = null
var started: Int = -1
var position: Int = -1
var playedDuration: Int = -1
var total: Int = -1
var playState: Int = 0
var isFavorite: Boolean = false
@ -152,6 +158,11 @@ class EpisodeAction private constructor(builder: Builder) {
return this
}
fun playedDuration(seconds: Int): Builder {
if (action == Action.PLAY) this.playedDuration = seconds
return this
}
fun total(seconds: Int): Builder {
if (action == Action.PLAY) this.total = seconds
return this
@ -217,11 +228,12 @@ class EpisodeAction private constructor(builder: Builder) {
if (action == Action.PLAY) {
val started = `object`.optInt("started", -1)
val position = `object`.optInt("position", -1)
val playedDuration = `object`.optInt("playedDuration", -1)
val total = `object`.optInt("total", -1)
val playState = `object`.optInt("playState", 0)
val isFavorite = `object`.optBoolean("isFavorite", false)
builder.playState(playState).isFavorite(isFavorite)
if (started >= 0 && position > 0 && total > 0) builder.started(started).position(position).total(total)
if (started >= 0 && position >= 0 && playedDuration >= 0 && total > 0) builder.started(started).position(position).playedDuration(playedDuration).total(total)
}
return builder.build()
}

View File

@ -11,9 +11,9 @@ import ac.mdiq.podcini.storage.database.Episodes.getEpisodeByGuidOrUrl
import ac.mdiq.podcini.storage.database.Episodes.getEpisodes
import ac.mdiq.podcini.storage.database.Episodes.persistEpisode
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.utils.EpisodeFilter
import ac.mdiq.podcini.storage.model.EpisodeFilter
import ac.mdiq.podcini.storage.utils.EpisodeUtil.hasAlmostEnded
import ac.mdiq.podcini.storage.utils.SortOrder
import ac.mdiq.podcini.storage.model.SortOrder
import ac.mdiq.podcini.util.Logd
import ac.mdiq.podcini.util.event.EventFlow
import ac.mdiq.podcini.util.event.FlowEvent
@ -256,8 +256,9 @@ import kotlin.math.min
val media = item.media ?: continue
val played = EpisodeAction.Builder(item, EpisodeAction.PLAY)
.timestamp(Date(media.getLastPlayedTime()))
.started(media.getPosition() / 1000)
.started(media.startPosition / 1000)
.position(media.getPosition() / 1000)
.playedDuration(media.playedDuration / 1000)
.total(media.getDuration() / 1000)
.isFavorite(item.isFavorite)
.playState(item.playState)
@ -327,7 +328,9 @@ import kotlin.math.min
var idRemove: Long? = null
Logd(TAG, "processEpisodeAction ${feedItem.media!!.getLastPlayedTime()} ${(action.timestamp?.time?:0L)} ${action.position} ${feedItem.title}")
if (feedItem.media!!.getLastPlayedTime() < (action.timestamp?.time?:0L)) {
feedItem.media!!.startPosition = action.started * 1000
feedItem.media!!.setPosition(action.position * 1000)
feedItem.media!!.playedDuration = action.playedDuration * 1000
feedItem.media!!.setLastPlayedTime(action.timestamp!!.time)
feedItem.isFavorite = action.isFavorite
feedItem.playState = action.playState

View File

@ -13,7 +13,7 @@ import ac.mdiq.podcini.playback.service.PlaybackService.Companion.isRunning
import ac.mdiq.podcini.playback.service.PlaybackService.LocalBinder
import ac.mdiq.podcini.preferences.UserPreferences.isSkipSilence
import ac.mdiq.podcini.storage.model.Playable
import ac.mdiq.podcini.storage.utils.MediaType
import ac.mdiq.podcini.storage.model.MediaType
import ac.mdiq.podcini.ui.activity.starter.MainActivityStarter
import ac.mdiq.podcini.ui.activity.starter.VideoPlayerActivityStarter
import ac.mdiq.podcini.util.Logd

View File

@ -6,7 +6,7 @@ import ac.mdiq.podcini.preferences.UserPreferences
import ac.mdiq.podcini.storage.model.EpisodeMedia
import ac.mdiq.podcini.storage.model.FeedPreferences
import ac.mdiq.podcini.storage.model.Playable
import ac.mdiq.podcini.storage.utils.MediaType
import ac.mdiq.podcini.storage.model.MediaType
import ac.mdiq.podcini.util.Logd
import android.content.Context
import android.media.AudioManager

View File

@ -2,7 +2,7 @@ package ac.mdiq.podcini.playback.base
import ac.mdiq.podcini.playback.base.MediaPlayerBase.MediaPlayerInfo
import ac.mdiq.podcini.storage.model.Playable
import ac.mdiq.podcini.storage.utils.MediaType
import ac.mdiq.podcini.storage.model.MediaType
interface MediaPlayerCallback {
fun statusChanged(newInfo: MediaPlayerInfo?)

View File

@ -13,7 +13,7 @@ import ac.mdiq.podcini.preferences.UserPreferences
import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope
import ac.mdiq.podcini.storage.model.EpisodeMedia
import ac.mdiq.podcini.storage.model.Playable
import ac.mdiq.podcini.storage.utils.MediaType
import ac.mdiq.podcini.storage.model.MediaType
import ac.mdiq.podcini.util.Logd
import ac.mdiq.podcini.util.config.ClientConfig
import ac.mdiq.podcini.util.event.EventFlow

View File

@ -57,8 +57,8 @@ import ac.mdiq.podcini.storage.model.FeedPreferences
import ac.mdiq.podcini.storage.model.FeedPreferences.AutoDeleteAction
import ac.mdiq.podcini.storage.model.Playable
import ac.mdiq.podcini.storage.utils.EpisodeUtil.hasAlmostEnded
import ac.mdiq.podcini.storage.utils.MediaType
import ac.mdiq.podcini.storage.utils.VolumeAdaptionSetting
import ac.mdiq.podcini.storage.model.MediaType
import ac.mdiq.podcini.storage.model.VolumeAdaptionSetting
import ac.mdiq.podcini.ui.utils.NotificationUtils
import ac.mdiq.podcini.ui.widget.WidgetUpdater.WidgetState
import ac.mdiq.podcini.util.IntentUtils.sendLocalBroadcast
@ -318,7 +318,7 @@ class PlaybackService : MediaSessionService() {
Logd(TAG, "onPostPlayback ended: $ended smartMarkAsPlayed: $smartMarkAsPlayed autoSkipped: $autoSkipped skipped: $skipped")
// only mark the item as played if we're not keeping it anyways
item = setPlayStateSync(Episode.PLAYED, ended || (skipped && smartMarkAsPlayed), item!!)
val action = item?.feed?.preferences?.currentAutoDelete
val action = item?.feed?.preferences?.autoDeleteAction
val shouldAutoDelete = (action == AutoDeleteAction.ALWAYS ||
(action == AutoDeleteAction.GLOBAL && item?.feed != null && shouldAutoDeleteItem(item!!.feed!!)))
if (playable is EpisodeMedia && shouldAutoDelete && (item?.isFavorite != true || !shouldFavoriteKeepEpisode())) {

View File

@ -2,8 +2,8 @@ package ac.mdiq.podcini.preferences
import ac.mdiq.podcini.storage.model.Feed
import ac.mdiq.podcini.storage.model.ProxyConfig
import ac.mdiq.podcini.storage.utils.SortOrder
import ac.mdiq.podcini.storage.utils.MediaType
import ac.mdiq.podcini.storage.model.SortOrder
import ac.mdiq.podcini.storage.model.MediaType
import ac.mdiq.podcini.util.Logd
import android.content.Context
import android.content.SharedPreferences

View File

@ -17,9 +17,9 @@ import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.model.Feed
import ac.mdiq.podcini.preferences.OpmlTransporter.*
import ac.mdiq.podcini.storage.utils.EpisodeFilter
import ac.mdiq.podcini.storage.model.EpisodeFilter
import ac.mdiq.podcini.storage.utils.EpisodeUtil.hasAlmostEnded
import ac.mdiq.podcini.storage.utils.SortOrder
import ac.mdiq.podcini.storage.model.SortOrder
import ac.mdiq.podcini.ui.activity.OpmlImportActivity
import ac.mdiq.podcini.ui.activity.PreferenceActivity
import ac.mdiq.podcini.util.Logd
@ -34,7 +34,6 @@ import android.os.Bundle
import android.os.ParcelFileDescriptor
import android.text.format.Formatter
import android.util.Log
import android.widget.Toast
import androidx.activity.result.ActivityResult
import androidx.activity.result.ActivityResultLauncher
import androidx.activity.result.contract.ActivityResultContracts
@ -281,7 +280,7 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() {
builder.setNegativeButton(R.string.no, null)
builder.setPositiveButton(R.string.confirm_label) { _: DialogInterface?, _: Int ->
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT)
intent.setType("application/octet-stream")
intent.setType("*/*")
intent.addCategory(Intent.CATEGORY_OPENABLE)
restoreProgressLauncher.launch(intent)
}
@ -711,7 +710,9 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() {
return null
}
var idRemove = 0L
feedItem.media!!.startPosition = action.started * 1000
feedItem.media!!.setPosition(action.position * 1000)
feedItem.media!!.playedDuration = action.playedDuration * 1000
feedItem.media!!.setLastPlayedTime(action.timestamp!!.time)
feedItem.isFavorite = action.isFavorite
feedItem.playState = action.playState
@ -743,8 +744,9 @@ class ImportExportPreferencesFragment : PreferenceFragmentCompat() {
val media = item.media ?: continue
val played = EpisodeAction.Builder(item, EpisodeAction.PLAY)
.timestamp(Date(media.getLastPlayedTime()))
.started(media.getPosition() / 1000)
.started(media.startPosition / 1000)
.position(media.getPosition() / 1000)
.playedDuration(media.playedDuration / 1000)
.total(media.getDuration() / 1000)
.isFavorite(item.isFavorite)
.playState(item.playState)

View File

@ -9,8 +9,8 @@ import ac.mdiq.podcini.storage.database.Episodes.getEpisodes
import ac.mdiq.podcini.storage.database.Episodes.getEpisodesCount
import ac.mdiq.podcini.storage.database.Queues.getInQueueEpisodeIds
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.utils.EpisodeFilter
import ac.mdiq.podcini.storage.utils.SortOrder
import ac.mdiq.podcini.storage.model.EpisodeFilter
import ac.mdiq.podcini.storage.model.SortOrder
import android.content.Context
import android.util.Log
import androidx.annotation.OptIn

View File

@ -11,8 +11,8 @@ import ac.mdiq.podcini.preferences.UserPreferences.isEnableAutodownloadOnBattery
import ac.mdiq.podcini.storage.database.Episodes.getEpisodes
import ac.mdiq.podcini.storage.database.Episodes.getEpisodesCount
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.utils.EpisodeFilter
import ac.mdiq.podcini.storage.utils.SortOrder
import ac.mdiq.podcini.storage.model.EpisodeFilter
import ac.mdiq.podcini.storage.model.SortOrder
import ac.mdiq.podcini.util.Logd
import android.content.Context
import android.content.Intent
@ -86,7 +86,7 @@ object AutoDownloads {
candidates.addAll(queue)
for (newItem in newItems) {
val feedPrefs = newItem.feed!!.preferences
if (feedPrefs!!.autoDownload && !candidates.contains(newItem) && feedPrefs.filter.shouldAutoDownload(newItem)) candidates.add(newItem)
if (feedPrefs!!.autoDownload && !candidates.contains(newItem) && feedPrefs.autoDownloadFilter.shouldAutoDownload(newItem)) candidates.add(newItem)
}
// filter items that are not auto downloadable
val it = candidates.iterator()

View File

@ -19,8 +19,8 @@ import ac.mdiq.podcini.storage.database.RealmDB.upsert
import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.model.EpisodeMedia
import ac.mdiq.podcini.storage.utils.EpisodeFilter
import ac.mdiq.podcini.storage.utils.SortOrder
import ac.mdiq.podcini.storage.model.EpisodeFilter
import ac.mdiq.podcini.storage.model.SortOrder
import ac.mdiq.podcini.util.IntentUtils.sendLocalBroadcast
import ac.mdiq.podcini.util.Logd
import ac.mdiq.podcini.util.event.EventFlow

View File

@ -1,5 +1,6 @@
package ac.mdiq.podcini.storage.database
import ac.mdiq.podcini.BuildConfig
import ac.mdiq.podcini.net.download.DownloadError
import ac.mdiq.podcini.net.sync.model.EpisodeAction
import ac.mdiq.podcini.net.sync.queue.SynchronizationQueueSink
@ -13,7 +14,7 @@ import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk
import ac.mdiq.podcini.storage.model.*
import ac.mdiq.podcini.storage.model.FeedPreferences.AutoDeleteAction
import ac.mdiq.podcini.storage.model.FeedPreferences.Companion.TAG_ROOT
import ac.mdiq.podcini.storage.utils.VolumeAdaptionSetting
import ac.mdiq.podcini.storage.model.VolumeAdaptionSetting
import ac.mdiq.podcini.util.Logd
import ac.mdiq.podcini.util.event.EventFlow
import ac.mdiq.podcini.util.event.FlowEvent
@ -164,7 +165,11 @@ object Feeds {
}
fun getFeed(feedId: Long, copy: Boolean = false, fromDB: Boolean = true): Feed? {
Logd(TAG, "getFeed called fromDB: $fromDB")
if (BuildConfig.DEBUG) {
val stackTrace = Thread.currentThread().stackTrace
val caller = if (stackTrace.size > 3) stackTrace[3] else null
Logd(TAG, "${caller?.className}.${caller?.methodName} getFeed called fromDB: $fromDB")
}
val f = if (fromDB) realm.query(Feed::class, "id == $feedId").first().find() else feedMap[feedId]
return if (f != null) {
if (copy) realm.copyFromRealm(f)

View File

@ -17,7 +17,7 @@ import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.model.EpisodeMedia
import ac.mdiq.podcini.storage.model.PlayQueue
import ac.mdiq.podcini.storage.model.Playable
import ac.mdiq.podcini.storage.utils.SortOrder
import ac.mdiq.podcini.storage.model.SortOrder
import ac.mdiq.podcini.util.Logd
import ac.mdiq.podcini.util.event.EventFlow
import ac.mdiq.podcini.util.event.FlowEvent

View File

@ -18,7 +18,7 @@ import kotlin.coroutines.ContinuationInterceptor
object RealmDB {
private val TAG: String = RealmDB::class.simpleName ?: "Anonymous"
private const val SCHEMA_VERSION_NUMBER = 6L
private const val SCHEMA_VERSION_NUMBER = 7L
private val ioScope = CoroutineScope(Dispatchers.IO)

View File

@ -1,6 +1,5 @@
package ac.mdiq.podcini.storage.utils
package ac.mdiq.podcini.storage.model
import ac.mdiq.podcini.storage.model.Playable
import java.util.regex.Pattern
class EmbeddedChapterImage(@JvmField val media: Playable, private val imageUrl: String) {

View File

@ -241,10 +241,10 @@ class Episode : RealmObject {
return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE)
}
override fun equals(o: Any?): Boolean {
if (this === o) return true
if (o !is Episode) return false
return id == o.id
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other !is Episode) return false
return id == other.id
}
override fun hashCode(): Int {

View File

@ -1,7 +1,6 @@
package ac.mdiq.podcini.storage.utils
package ac.mdiq.podcini.storage.model
import ac.mdiq.podcini.playback.base.InTheatre.curQueue
import ac.mdiq.podcini.storage.model.Episode
import java.io.Serializable
class EpisodeFilter(vararg properties: String) : Serializable {

View File

@ -2,7 +2,6 @@ package ac.mdiq.podcini.storage.model
import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk
import ac.mdiq.podcini.storage.utils.MediaMetadataRetrieverCompat
import ac.mdiq.podcini.storage.utils.MediaType
import ac.mdiq.podcini.util.Logd
import android.content.Context
import android.os.Parcel
@ -64,7 +63,6 @@ class EpisodeMedia: EmbeddedRealmObject, Playable {
}
var startPosition: Int = -1
private set
var playedDurationWhenStarted: Int = 0
private set
@ -343,10 +341,10 @@ class EpisodeMedia: EmbeddedRealmObject, Playable {
upsertBlk(episode!!) {}
}
override fun equals(o: Any?): Boolean {
if (o == null) return false
if (o is RemoteMedia) return o == this
return super.equals(o)
override fun equals(other: Any?): Boolean {
if (other == null) return false
if (other is RemoteMedia) return other == this
return super.equals(other)
}
override fun hashCode(): Int {

View File

@ -1,11 +1,7 @@
package ac.mdiq.podcini.storage.model
import ac.mdiq.podcini.storage.utils.FeedFunding.Companion.extractPaymentLinks
import ac.mdiq.podcini.storage.utils.EpisodeFilter
import ac.mdiq.podcini.storage.utils.FeedFunding
import ac.mdiq.podcini.storage.utils.SortOrder
import ac.mdiq.podcini.storage.utils.SortOrder.Companion.fromCode
import ac.mdiq.podcini.storage.utils.VolumeAdaptionSetting
import ac.mdiq.podcini.storage.model.FeedFunding.Companion.extractPaymentLinks
import ac.mdiq.podcini.storage.model.SortOrder.Companion.fromCode
import io.realm.kotlin.ext.realmListOf
import io.realm.kotlin.types.RealmList
import io.realm.kotlin.types.RealmObject
@ -178,11 +174,9 @@ class Feed : RealmObject {
this.identifier = feedIdentifier
this.imageUrl = imageUrl
this.isPaged = false
this.nextPageLink = nextPageLink
this.preferences?.filterString = ""
this.sortOrder = sortOrder
this.preferences?.sortOrderCode = sortOrder?.code ?: 0
this.lastUpdateFailed = lastUpdateFailed
}
/**

View File

@ -1,6 +1,5 @@
package ac.mdiq.podcini.storage.utils
package ac.mdiq.podcini.storage.model
import ac.mdiq.podcini.storage.model.Episode
import java.io.Serializable
import java.util.*
import java.util.regex.Pattern
@ -10,7 +9,7 @@ import java.util.regex.Pattern
// (we don't have to recreate it)
// 2. We don't know if we'll actually be asked to parse anything anyways.
class FeedEpisodesFilter(val includeFilterRaw: String? = "", val excludeFilterRaw: String? = "", val minimalDurationFilter: Int = -1) : Serializable {
class FeedAutoDownloadFilter(val includeFilterRaw: String? = "", val excludeFilterRaw: String? = "", val minimalDurationFilter: Int = -1) : Serializable {
/**
* Parses the text in to a list of single words or quoted strings.

View File

@ -1,4 +1,4 @@
package ac.mdiq.podcini.storage.utils
package ac.mdiq.podcini.storage.model
import org.apache.commons.lang3.StringUtils
@ -11,9 +11,9 @@ class FeedFunding(@JvmField var url: String?, @JvmField var content: String?) {
this.url = url
}
override fun equals(obj: Any?): Boolean {
if (obj == null || obj.javaClass != this.javaClass) return false
val funding = obj as FeedFunding
override fun equals(other: Any?): Boolean {
if (other == null || other.javaClass != this.javaClass) return false
val funding = other as FeedFunding
if (url == null && funding.url == null && content == null && funding.content == null) return true
if (url != null && url == funding.url && content != null && content == funding.content) return true

View File

@ -1,7 +1,6 @@
package ac.mdiq.podcini.storage.model
import ac.mdiq.podcini.storage.utils.FeedEpisodesFilter
import ac.mdiq.podcini.storage.utils.VolumeAdaptionSetting
import ac.mdiq.podcini.storage.model.VolumeAdaptionSetting.Companion.fromInteger
import io.realm.kotlin.ext.realmSetOf
import io.realm.kotlin.types.EmbeddedRealmObject
import io.realm.kotlin.types.RealmSet
@ -11,24 +10,42 @@ import io.realm.kotlin.types.annotations.Index
/**
* Contains preferences for a single feed.
*/
class FeedPreferences(@Index var feedID: Long,
var autoDownload: Boolean,
/**
* @return true if this feed should be refreshed when everything else is being refreshed
* if false the feed should only be refreshed if requested directly.
*/
var keepUpdated: Boolean,
@Ignore var currentAutoDelete: AutoDeleteAction,
@Ignore @JvmField var volumeAdaptionSetting: VolumeAdaptionSetting?,
var username: String?,
var password: String?,
@Ignore @JvmField var filter: FeedEpisodesFilter,
var playSpeed: Float,
var introSkip: Int,
var endingSkip: Int,
tags: RealmSet<String>) : EmbeddedRealmObject {
class FeedPreferences : EmbeddedRealmObject {
@Index var feedID: Long = 0L
var autoDownload: Boolean = false
/**
* @return true if this feed should be refreshed when everything else is being refreshed
* if false the feed should only be refreshed if requested directly.
*/
var keepUpdated: Boolean = true
var username: String? = null
var password: String? = null
var playSpeed: Float = SPEED_USE_GLOBAL
var introSkip: Int = 0
var endingSkip: Int = 0
@Ignore
var autoDeleteAction: AutoDeleteAction = AutoDeleteAction.GLOBAL
get() = AutoDeleteAction.fromCode(autoDelete)
set(value) {
field = value
autoDelete = field.code
}
var autoDelete: Int = 0
@Ignore
var volumeAdaptionSetting: VolumeAdaptionSetting = VolumeAdaptionSetting.OFF
get() = fromInteger(volumeAdaption)
set(value) {
field = value
volumeAdaption = field.toInteger()
}
var volumeAdaption: Int = 0
var tags: RealmSet<String> = realmSetOf()
@ -37,9 +54,19 @@ class FeedPreferences(@Index var feedID: Long,
val tagsAsString: String
get() = tags.joinToString(TAG_SEPARATOR)
/**
* Contains property strings. If such a property applies to a feed item, it is not shown in the feed list
*/
@Ignore
var autoDownloadFilter: FeedAutoDownloadFilter = FeedAutoDownloadFilter()
get() = FeedAutoDownloadFilter(autoDLInclude, autoDLExclude, autoDLMinDuration)
set(value) {
field = value
autoDLInclude = field.includeFilterRaw
autoDLExclude = field.excludeFilterRaw
autoDLMinDuration = field.minimalDurationFilter
}
var autoDLInclude: String? = ""
var autoDLExclude: String? = ""
var autoDLMinDuration: Int = -1
var filterString: String = ""
var sortOrderCode: Int = 0
@ -52,41 +79,34 @@ class FeedPreferences(@Index var feedID: Long,
companion object {
@JvmStatic
fun fromCode(code: Int): AutoDeleteAction {
for (action in entries) {
if (code == action.code) return action
}
return NEVER
return enumValues<AutoDeleteAction>().firstOrNull { it.code == code } ?: NEVER
}
}
}
constructor() : this(0L, false, true, AutoDeleteAction.GLOBAL, VolumeAdaptionSetting.OFF, null, null,
FeedEpisodesFilter(), SPEED_USE_GLOBAL, 0, 0, realmSetOf())
constructor() {}
constructor(feedID: Long, autoDownload: Boolean, autoDeleteAction: AutoDeleteAction,
volumeAdaptionSetting: VolumeAdaptionSetting?, username: String?, password: String?)
: this(feedID, autoDownload, true, autoDeleteAction, volumeAdaptionSetting, username, password,
FeedEpisodesFilter(), SPEED_USE_GLOBAL, 0, 0, realmSetOf()) {
volumeAdaptionSetting: VolumeAdaptionSetting?, username: String?, password: String?) {
this.feedID = feedID
this.autoDownload = autoDownload
this.autoDeleteAction = autoDeleteAction
if (volumeAdaptionSetting != null) this.volumeAdaptionSetting = volumeAdaptionSetting
this.username = username
this.password = password
this.autoDelete = autoDeleteAction.code
this.volumeAdaption = volumeAdaptionSetting?.toInteger() ?: 0
}
init {
this.tags.addAll(tags!!)
}
/**
* Compare another FeedPreferences with this one. The feedID, autoDownload and AutoDeleteAction attribute are excluded from the
* comparison.
*
* @return True if the two objects are different.
*/
fun compareWithOther(other: FeedPreferences?): Boolean {
if (other == null) return true
if (username != other.username) return true
if (password != other.password) return true
return false
}
@ -100,10 +120,6 @@ class FeedPreferences(@Index var feedID: Long,
this.password = other.password
}
fun getTags(): MutableSet<String> {
return tags
}
companion object {
const val SPEED_USE_GLOBAL: Float = -1f
const val TAG_ROOT: String = "#root"

View File

@ -1,4 +1,4 @@
package ac.mdiq.podcini.storage.utils
package ac.mdiq.podcini.storage.model
enum class MediaType {
AUDIO, VIDEO, FLASH, UNKNOWN;

View File

@ -1,6 +1,5 @@
package ac.mdiq.podcini.storage.model
import ac.mdiq.podcini.storage.utils.MediaType
import android.content.Context
import android.os.Parcelable
import java.io.Serializable
@ -65,9 +64,6 @@ interface Playable : Parcelable, Serializable {
* Returns last time (in ms) when this playable was played or 0
* if last played time is unknown.
*/
/**
* @param lastPlayedTime timestamp in ms
*/
fun getLastPlayedTime(): Long
/**

View File

@ -1,6 +1,5 @@
package ac.mdiq.podcini.storage.model
import ac.mdiq.podcini.storage.utils.MediaType
import android.content.Context
import android.os.Parcel
import android.os.Parcelable

View File

@ -1,4 +1,4 @@
package ac.mdiq.podcini.storage.utils
package ac.mdiq.podcini.storage.model
/**
* Provides sort orders to sort a list of episodes.
@ -56,11 +56,7 @@ enum class SortOrder(@JvmField val code: Int, @JvmField val scope: Scope) {
@JvmStatic
fun fromCode(code: Int): SortOrder? {
for (sortOrder in entries) {
if (sortOrder.code == code) return sortOrder
}
return null
// throw IllegalArgumentException("Unsupported code: $code")
return enumValues<SortOrder>().firstOrNull { it.code == code }
}
@JvmStatic

View File

@ -1,4 +1,4 @@
package ac.mdiq.podcini.storage.utils
package ac.mdiq.podcini.storage.model
enum class VolumeAdaptionSetting(private val value: Int, @JvmField val adaptionFactor: Float) {
OFF(0, 1.0f),
@ -15,10 +15,7 @@ enum class VolumeAdaptionSetting(private val value: Int, @JvmField val adaptionF
companion object {
@JvmStatic
fun fromInteger(value: Int): VolumeAdaptionSetting {
for (setting in entries) {
if (setting.value == value) return setting
}
throw IllegalArgumentException("Cannot map value to VolumeAdaptionSetting: $value")
return enumValues<VolumeAdaptionSetting>().firstOrNull { it.value == value } ?: throw IllegalArgumentException("Cannot map value to VolumeAdaptionSetting: $value")
}
}
}

View File

@ -9,6 +9,7 @@ import android.provider.DocumentsContract
* This queries the ContentResolver a single time with all the information.
*/
class FastDocumentFile(val name: String, val type: String, val uri: Uri, val length: Long, val lastModified: Long) {
companion object {
@JvmStatic
fun list(context: Context, folderUri: Uri?): List<FastDocumentFile> {

View File

@ -7,11 +7,11 @@ import ac.mdiq.podcini.preferences.UserPreferences
* Utility functions for handling storage errors
*/
object StorageUtils {
/**
* Get the number of free bytes that are available on the external storage.
*/
@JvmStatic
val freeSpaceAvailable: Long
/**
* Get the number of free bytes that are available on the external storage.
*/
get() {
val dataFolder = UserPreferences.getDataFolder(null)
return if (dataFolder != null) getFreeSpaceAvailable(dataFolder.absolutePath) else 0

View File

@ -3,11 +3,8 @@ package ac.mdiq.podcini.ui.actions.actionbutton
import ac.mdiq.podcini.net.download.serviceinterface.DownloadServiceInterface
import ac.mdiq.podcini.preferences.UserPreferences.isStreamOverDownload
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.utils.MediaType
import ac.mdiq.podcini.storage.model.MediaType
import ac.mdiq.podcini.playback.base.InTheatre.isCurrentlyPlaying
import ac.mdiq.podcini.storage.model.EpisodeMedia
import ac.mdiq.podcini.playback.base.InTheatre.curMedia
import ac.mdiq.podcini.util.Logd
import android.content.Context
import android.view.View
import android.widget.ImageView

View File

@ -8,7 +8,7 @@ import ac.mdiq.podcini.playback.base.InTheatre
import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.model.EpisodeMedia
import ac.mdiq.podcini.storage.utils.MediaType
import ac.mdiq.podcini.storage.model.MediaType
import ac.mdiq.podcini.util.Logd
import ac.mdiq.podcini.util.event.EventFlow
import ac.mdiq.podcini.util.event.FlowEvent

View File

@ -6,7 +6,7 @@ import ac.mdiq.podcini.playback.PlaybackController.Companion.playbackService
import ac.mdiq.podcini.playback.PlaybackServiceStarter
import ac.mdiq.podcini.playback.base.InTheatre
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.utils.MediaType
import ac.mdiq.podcini.storage.model.MediaType
import ac.mdiq.podcini.util.Logd
import ac.mdiq.podcini.util.event.EventFlow
import ac.mdiq.podcini.util.event.FlowEvent

View File

@ -6,7 +6,7 @@ import ac.mdiq.podcini.preferences.UsageStatistics
import ac.mdiq.podcini.preferences.UsageStatistics.logAction
import ac.mdiq.podcini.preferences.UserPreferences.isAllowMobileStreaming
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.utils.MediaType
import ac.mdiq.podcini.storage.model.MediaType
import ac.mdiq.podcini.storage.model.Playable
import ac.mdiq.podcini.storage.model.RemoteMedia
import ac.mdiq.podcini.net.utils.NetworkUtils.isStreamingAllowed

View File

@ -5,7 +5,7 @@ import androidx.fragment.app.Fragment
import ac.mdiq.podcini.R
import ac.mdiq.podcini.storage.database.Queues.addToQueue
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.utils.EpisodeFilter
import ac.mdiq.podcini.storage.model.EpisodeFilter
import androidx.annotation.OptIn
import androidx.media3.common.util.UnstableApi

View File

@ -6,7 +6,7 @@ import androidx.media3.common.util.UnstableApi
import ac.mdiq.podcini.R
import ac.mdiq.podcini.storage.database.Episodes.deleteMediaOfEpisode
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.utils.EpisodeFilter
import ac.mdiq.podcini.storage.model.EpisodeFilter
import ac.mdiq.podcini.ui.utils.LocalDeleteModal.showLocalFeedDeleteWarningIfNecessary
class DeleteSwipeAction : SwipeAction {

View File

@ -5,7 +5,7 @@ import androidx.fragment.app.Fragment
import ac.mdiq.podcini.R
import ac.mdiq.podcini.storage.database.Episodes.setFavorite
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.utils.EpisodeFilter
import ac.mdiq.podcini.storage.model.EpisodeFilter
import androidx.annotation.OptIn
import androidx.media3.common.util.UnstableApi

View File

@ -5,7 +5,7 @@ import androidx.fragment.app.Fragment
import androidx.media3.common.util.UnstableApi
import ac.mdiq.podcini.R
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.utils.EpisodeFilter
import ac.mdiq.podcini.storage.model.EpisodeFilter
class NoActionSwipeAction : SwipeAction {
override fun getId(): String {

View File

@ -3,7 +3,7 @@ package ac.mdiq.podcini.ui.actions.swipeactions
import ac.mdiq.podcini.R
import ac.mdiq.podcini.storage.database.Episodes.addToHistory
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.utils.EpisodeFilter
import ac.mdiq.podcini.storage.model.EpisodeFilter
import ac.mdiq.podcini.ui.activity.MainActivity
import android.content.Context
import androidx.annotation.OptIn

View File

@ -7,7 +7,7 @@ import ac.mdiq.podcini.storage.database.Queues.removeFromQueue
import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope
import ac.mdiq.podcini.storage.database.RealmDB.upsert
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.utils.EpisodeFilter
import ac.mdiq.podcini.storage.model.EpisodeFilter
import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.util.event.EventFlow
import ac.mdiq.podcini.util.event.FlowEvent

View File

@ -4,7 +4,7 @@ import android.content.Context
import androidx.fragment.app.Fragment
import ac.mdiq.podcini.R
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.utils.EpisodeFilter
import ac.mdiq.podcini.storage.model.EpisodeFilter
class ShowFirstSwipeDialogAction : SwipeAction {
override fun getId(): String {

View File

@ -5,7 +5,7 @@ import androidx.fragment.app.Fragment
import ac.mdiq.podcini.R
import ac.mdiq.podcini.ui.actions.actionbutton.DownloadActionButton
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.utils.EpisodeFilter
import ac.mdiq.podcini.storage.model.EpisodeFilter
class StartDownloadSwipeAction : SwipeAction {
override fun getId(): String {

View File

@ -5,7 +5,7 @@ import androidx.annotation.AttrRes
import androidx.annotation.DrawableRes
import androidx.fragment.app.Fragment
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.utils.EpisodeFilter
import ac.mdiq.podcini.storage.model.EpisodeFilter
interface SwipeAction {

View File

@ -1,7 +1,7 @@
package ac.mdiq.podcini.ui.actions.swipeactions
import ac.mdiq.podcini.R
import ac.mdiq.podcini.storage.utils.EpisodeFilter
import ac.mdiq.podcini.storage.model.EpisodeFilter
import ac.mdiq.podcini.ui.actions.swipeactions.SwipeAction.Companion.NO_ACTION
import ac.mdiq.podcini.ui.dialog.SwipeActionsDialog
import ac.mdiq.podcini.ui.fragment.AllEpisodesFragment

View File

@ -11,7 +11,7 @@ import ac.mdiq.podcini.storage.database.Episodes.setPlayStateSync
import ac.mdiq.podcini.storage.database.Queues.removeFromQueueSync
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.model.EpisodeMedia
import ac.mdiq.podcini.storage.utils.EpisodeFilter
import ac.mdiq.podcini.storage.model.EpisodeFilter
import ac.mdiq.podcini.storage.utils.EpisodeUtil
import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.util.Logd

View File

@ -89,6 +89,7 @@ abstract class DatesFilterDialog(private val context: Context, oldestDate: Long)
builder.setPositiveButton(android.R.string.ok) { dialog: DialogInterface?, which: Int ->
includeMarkedAsPlayed = binding.includeMarkedCheckbox.isChecked
if (includeMarkedAsPlayed) {
// TODO: what can be done with this?
// We do not know the date at which something was marked as played, so filtering does not make sense
timeFilterFrom = 0
timeFilterTo = Long.MAX_VALUE

View File

@ -3,7 +3,7 @@ package ac.mdiq.podcini.ui.dialog
import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.FilterDialogBinding
import ac.mdiq.podcini.databinding.FilterDialogRowBinding
import ac.mdiq.podcini.storage.utils.EpisodeFilter
import ac.mdiq.podcini.storage.model.EpisodeFilter
import ac.mdiq.podcini.util.Logd
import android.app.Dialog
import android.content.DialogInterface

View File

@ -4,7 +4,7 @@ import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.SortDialogBinding
import ac.mdiq.podcini.databinding.SortDialogItemActiveBinding
import ac.mdiq.podcini.databinding.SortDialogItemBinding
import ac.mdiq.podcini.storage.utils.SortOrder
import ac.mdiq.podcini.storage.model.SortOrder
import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle

View File

@ -15,7 +15,7 @@ import ac.mdiq.podcini.preferences.UserPreferences.videoPlaybackSpeed
import ac.mdiq.podcini.storage.database.Feeds.persistFeedPreferences
import ac.mdiq.podcini.storage.database.RealmDB.unmanaged
import ac.mdiq.podcini.storage.model.EpisodeMedia
import ac.mdiq.podcini.storage.utils.MediaType
import ac.mdiq.podcini.storage.model.MediaType
import ac.mdiq.podcini.ui.utils.ItemOffsetDecoration
import ac.mdiq.podcini.ui.view.PlaybackSpeedSeekBar
import ac.mdiq.podcini.util.Logd

View File

@ -7,7 +7,7 @@ import ac.mdiq.podcini.net.feed.discovery.*
import ac.mdiq.podcini.net.feed.FeedUpdateManager
import ac.mdiq.podcini.storage.database.Feeds.updateFeed
import ac.mdiq.podcini.storage.model.Feed
import ac.mdiq.podcini.storage.utils.SortOrder
import ac.mdiq.podcini.storage.model.SortOrder
import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.ui.activity.OpmlImportActivity
import ac.mdiq.podcini.util.Logd

View File

@ -6,8 +6,8 @@ import ac.mdiq.podcini.preferences.UserPreferences.prefFilterAllEpisodes
import ac.mdiq.podcini.storage.database.Episodes.getEpisodes
import ac.mdiq.podcini.storage.database.Episodes.getEpisodesCount
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.utils.EpisodeFilter
import ac.mdiq.podcini.storage.utils.SortOrder
import ac.mdiq.podcini.storage.model.EpisodeFilter
import ac.mdiq.podcini.storage.model.SortOrder
import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.ui.dialog.EpisodeFilterDialog
import ac.mdiq.podcini.ui.dialog.EpisodeSortDialog

View File

@ -28,7 +28,7 @@ import ac.mdiq.podcini.storage.model.EpisodeMedia
import ac.mdiq.podcini.storage.model.Playable
import ac.mdiq.podcini.storage.utils.ChapterUtils
import ac.mdiq.podcini.storage.utils.ImageResourceUtils
import ac.mdiq.podcini.storage.utils.MediaType
import ac.mdiq.podcini.storage.model.MediaType
import ac.mdiq.podcini.ui.actions.menuhandler.EpisodeMenuHandler
import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.ui.activity.VideoplayerActivity.Companion.videoMode

View File

@ -7,7 +7,7 @@ import ac.mdiq.podcini.net.feed.FeedUpdateManager
import ac.mdiq.podcini.playback.base.InTheatre.isCurMedia
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.model.EpisodeMedia
import ac.mdiq.podcini.storage.utils.EpisodeFilter
import ac.mdiq.podcini.storage.model.EpisodeFilter
import ac.mdiq.podcini.ui.actions.EpisodeMultiSelectHandler
import ac.mdiq.podcini.ui.actions.menuhandler.EpisodeMenuHandler
import ac.mdiq.podcini.ui.actions.menuhandler.MenuItemUtils

View File

@ -14,7 +14,7 @@ import ac.mdiq.podcini.playback.base.InTheatre.curMedia
import ac.mdiq.podcini.playback.PlaybackController.Companion.curPosition
import ac.mdiq.podcini.playback.PlaybackController.Companion.seekTo
import ac.mdiq.podcini.storage.model.Chapter
import ac.mdiq.podcini.storage.utils.EmbeddedChapterImage
import ac.mdiq.podcini.storage.model.EmbeddedChapterImage
import ac.mdiq.podcini.ui.view.CircularProgressBar
import ac.mdiq.podcini.util.Converter.getDurationStringLocalized
import ac.mdiq.podcini.util.Converter.getDurationStringLong

View File

@ -11,9 +11,9 @@ import ac.mdiq.podcini.storage.database.Episodes.getEpisodes
import ac.mdiq.podcini.storage.database.RealmDB.realm
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.model.EpisodeMedia
import ac.mdiq.podcini.storage.utils.EpisodeFilter
import ac.mdiq.podcini.storage.model.EpisodeFilter
import ac.mdiq.podcini.storage.utils.EpisodeUtil
import ac.mdiq.podcini.storage.utils.SortOrder
import ac.mdiq.podcini.storage.model.SortOrder
import ac.mdiq.podcini.ui.actions.EpisodeMultiSelectHandler
import ac.mdiq.podcini.ui.actions.actionbutton.DeleteActionButton
import ac.mdiq.podcini.ui.actions.menuhandler.EpisodeMenuHandler

View File

@ -15,7 +15,7 @@ import ac.mdiq.podcini.storage.database.RealmDB.unmanaged
import ac.mdiq.podcini.storage.database.RealmDB.upsert
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.model.EpisodeMedia
import ac.mdiq.podcini.storage.utils.MediaType
import ac.mdiq.podcini.storage.model.MediaType
import ac.mdiq.podcini.ui.actions.actionbutton.*
import ac.mdiq.podcini.ui.actions.menuhandler.EpisodeMenuHandler
import ac.mdiq.podcini.ui.activity.MainActivity

View File

@ -18,10 +18,10 @@ import ac.mdiq.podcini.storage.model.DownloadResult
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.model.EpisodeMedia
import ac.mdiq.podcini.storage.model.Feed
import ac.mdiq.podcini.storage.utils.EpisodeFilter
import ac.mdiq.podcini.storage.model.EpisodeFilter
import ac.mdiq.podcini.storage.utils.EpisodeUtil
import ac.mdiq.podcini.storage.utils.SortOrder
import ac.mdiq.podcini.storage.utils.SortOrder.Companion.fromCode
import ac.mdiq.podcini.storage.model.SortOrder
import ac.mdiq.podcini.storage.model.SortOrder.Companion.fromCode
import ac.mdiq.podcini.ui.actions.EpisodeMultiSelectHandler
import ac.mdiq.podcini.ui.actions.menuhandler.EpisodeMenuHandler
import ac.mdiq.podcini.ui.actions.menuhandler.MenuItemUtils

View File

@ -9,7 +9,7 @@ import ac.mdiq.podcini.net.utils.HtmlToPlainText
import ac.mdiq.podcini.storage.database.Feeds.updateFeed
import ac.mdiq.podcini.storage.database.Feeds.updateFeedDownloadURL
import ac.mdiq.podcini.storage.model.Feed
import ac.mdiq.podcini.storage.utils.FeedFunding
import ac.mdiq.podcini.storage.model.FeedFunding
import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.ui.statistics.FeedStatisticsFragment
import ac.mdiq.podcini.ui.statistics.StatisticsFragment

View File

@ -1,7 +1,7 @@
package ac.mdiq.podcini.ui.fragment
import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.EpisodeFilterDialogBinding
import ac.mdiq.podcini.databinding.AutodownloadFilterDialogBinding
import ac.mdiq.podcini.databinding.FeedPrefSkipDialogBinding
import ac.mdiq.podcini.databinding.FeedsettingsBinding
import ac.mdiq.podcini.databinding.PlaybackSpeedFeedSettingDialogBinding
@ -12,8 +12,8 @@ import ac.mdiq.podcini.storage.database.RealmDB.unmanaged
import ac.mdiq.podcini.storage.model.Feed
import ac.mdiq.podcini.storage.model.FeedPreferences
import ac.mdiq.podcini.storage.model.FeedPreferences.AutoDeleteAction
import ac.mdiq.podcini.storage.utils.FeedEpisodesFilter
import ac.mdiq.podcini.storage.utils.VolumeAdaptionSetting
import ac.mdiq.podcini.storage.model.FeedAutoDownloadFilter
import ac.mdiq.podcini.storage.model.VolumeAdaptionSetting
import ac.mdiq.podcini.ui.adapter.SimpleChipAdapter
import ac.mdiq.podcini.ui.dialog.AuthenticationDialog
import ac.mdiq.podcini.ui.dialog.TagSettingsDialog
@ -72,7 +72,6 @@ class FeedSettingsFragment : Fragment() {
class FeedSettingsPreferenceFragment : PreferenceFragmentCompat() {
private var feed: Feed? = null
private var feedPrefs: FeedPreferences? = null
private var notificationPermissionDenied: Boolean = false
private val requestPermissionLauncher = registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean ->
if (isGranted) return@registerForActivityResult
@ -95,34 +94,29 @@ class FeedSettingsFragment : Fragment() {
view.layoutAnimation = null
return view
}
@OptIn(UnstableApi::class) override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.feed_settings)
// To prevent displaying partially loaded data
findPreference<Preference>(PREF_SCREEN)!!.isVisible = false
if (feed != null) {
if (feed!!.preferences == null) {
feed!!.preferences = FeedPreferences(feed!!.id, false, AutoDeleteAction.GLOBAL, VolumeAdaptionSetting.OFF, "", "")
persistFeedPreferences(feed!!)
}
feedPrefs = feed!!.preferences
setupAutoDownloadGlobalPreference()
setupAutoDownloadPreference()
setupKeepUpdatedPreference()
setupAutoDeletePreference()
setupVolumeAdaptationPreferences()
setupAuthentificationPreference()
setupEpisodeFilterPreference()
setupAutoDownloadFilterPreference()
setupPlaybackSpeedPreference()
setupFeedAutoSkipPreference()
setupTags()
updateAutoDeleteSummary()
updateVolumeAdaptationValue()
updateAutoDownloadEnabled()
if (feed!!.isLocalFeed) {
findPreference<Preference>(PREF_AUTHENTICATION)!!.isVisible = false
findPreference<Preference>(PREF_CATEGORY_AUTO_DOWNLOAD)!!.isVisible = false
@ -130,23 +124,21 @@ class FeedSettingsFragment : Fragment() {
findPreference<Preference>(PREF_SCREEN)!!.isVisible = true
}
}
private fun setupFeedAutoSkipPreference() {
if (feedPrefs == null) return
findPreference<Preference>(PREF_AUTO_SKIP)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener {
object : FeedPreferenceSkipDialog(requireContext(), feedPrefs!!.introSkip, feedPrefs!!.endingSkip) {
@UnstableApi override fun onConfirmed(skipIntro: Int, skipEnding: Int) {
feedPrefs!!.introSkip = skipIntro
feedPrefs!!.endingSkip = skipEnding
persistFeedPreferences(feed!!)
// EventFlow.postEvent(FlowEvent.FeedListUpdateEvent(feedPrefs!!.feedID))
// EventFlow.postEvent(FlowEvent.SkipIntroEndingChangedEvent(feedPrefs!!.introSkip, feedPrefs!!.endingSkip, feed!!.id))
}
}.show()
if (feedPrefs != null) {
object : FeedPreferenceSkipDialog(requireContext(), feedPrefs!!.introSkip, feedPrefs!!.endingSkip) {
@UnstableApi
override fun onConfirmed(skipIntro: Int, skipEnding: Int) {
feedPrefs!!.introSkip = skipIntro
feedPrefs!!.endingSkip = skipEnding
persistFeedPreferences(feed!!)
}
}.show()
}
false
}
}
@UnstableApi private fun setupPlaybackSpeedPreference() {
val feedPlaybackSpeedPreference = findPreference<Preference>(PREF_FEED_PLAYBACK_SPEED)
feedPlaybackSpeedPreference!!.onPreferenceClickListener = Preference.OnPreferenceClickListener {
@ -173,8 +165,6 @@ class FeedSettingsFragment : Fragment() {
if (feedPrefs != null) {
feedPrefs!!.playSpeed = newSpeed
persistFeedPreferences(feed!!)
// EventFlow.postEvent(FlowEvent.FeedListUpdateEvent(feedPrefs!!.feedID))
// EventFlow.postEvent(FlowEvent.FeedPrefsChangeEvent(feedPrefs!!))
}
}
.setNegativeButton(R.string.cancel_label, null)
@ -182,64 +172,59 @@ class FeedSettingsFragment : Fragment() {
true
}
}
private fun setupEpisodeFilterPreference() {
if (feedPrefs == null) return
private fun setupAutoDownloadFilterPreference() {
findPreference<Preference>(PREF_EPISODE_FILTER)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener {
object : EpisodeFilterPrefDialog(requireContext(), feedPrefs!!.filter) {
@UnstableApi override fun onConfirmed(filter: FeedEpisodesFilter) {
if (feedPrefs != null) {
feedPrefs!!.filter = filter
persistFeedPreferences(feed!!)
// EventFlow.postEvent(FlowEvent.FeedListUpdateEvent(feedPrefs!!.feedID))
if (feedPrefs != null) {
object : AutoDownloadFilterPrefDialog(requireContext(), feedPrefs!!.autoDownloadFilter) {
@UnstableApi
override fun onConfirmed(filter: FeedAutoDownloadFilter) {
if (feedPrefs != null) {
feedPrefs!!.autoDownloadFilter = filter
persistFeedPreferences(feed!!)
}
}
}
}.show()
}.show()
}
false
}
}
private fun setupAuthentificationPreference() {
if (feedPrefs == null) return
findPreference<Preference>(PREF_AUTHENTICATION)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener {
object : AuthenticationDialog(requireContext(), R.string.authentication_label, true, feedPrefs!!.username, feedPrefs!!.password) {
@UnstableApi override fun onConfirmed(username: String, password: String) {
if (feedPrefs != null) {
feedPrefs!!.username = username
feedPrefs!!.password = password
persistFeedPreferences(feed!!)
// EventFlow.postEvent(FlowEvent.FeedListUpdateEvent(feedPrefs!!.feedID))
if (feedPrefs != null) {
object : AuthenticationDialog(requireContext(), R.string.authentication_label, true, feedPrefs!!.username, feedPrefs!!.password) {
@UnstableApi
override fun onConfirmed(username: String, password: String) {
if (feedPrefs != null) {
feedPrefs!!.username = username
feedPrefs!!.password = password
persistFeedPreferences(feed!!)
Thread({ runOnce(context, feed) }, "RefreshAfterCredentialChange").start()
}
}
Thread({ runOnce(context, feed) }, "RefreshAfterCredentialChange").start()
}
}.show()
}.show()
}
false
}
}
@UnstableApi private fun setupAutoDeletePreference() {
if (feedPrefs == null) return
findPreference<Preference>(PREF_AUTO_DELETE)!!.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any? ->
if (feedPrefs != null) {
when (newValue as String?) {
"global" -> feedPrefs!!.currentAutoDelete = AutoDeleteAction.GLOBAL
"always" -> feedPrefs!!.currentAutoDelete = AutoDeleteAction.ALWAYS
"never" -> feedPrefs!!.currentAutoDelete = AutoDeleteAction.NEVER
when (newValue as? String) {
"global" -> feedPrefs!!.autoDeleteAction = AutoDeleteAction.GLOBAL
"always" -> feedPrefs!!.autoDeleteAction = AutoDeleteAction.ALWAYS
"never" -> feedPrefs!!.autoDeleteAction = AutoDeleteAction.NEVER
else -> {}
}
persistFeedPreferences(feed!!)
// EventFlow.postEvent(FlowEvent.FeedListUpdateEvent(feedPrefs!!.feedID))
updateAutoDeleteSummary()
}
updateAutoDeleteSummary()
false
}
}
private fun updateAutoDeleteSummary() {
if (feedPrefs == null) return
val autoDeletePreference = findPreference<ListPreference>(PREF_AUTO_DELETE)
when (feedPrefs!!.currentAutoDelete) {
when (feedPrefs!!.autoDeleteAction) {
AutoDeleteAction.GLOBAL -> {
autoDeletePreference!!.setSummary(R.string.global_default)
autoDeletePreference.value = "global"
@ -254,11 +239,9 @@ class FeedSettingsFragment : Fragment() {
}
}
}
@UnstableApi private fun setupVolumeAdaptationPreferences() {
if (feedPrefs == null) return
val volumeAdaptationPreference = findPreference<ListPreference>("volumeReduction")
volumeAdaptationPreference!!.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any? ->
val volumeAdaptationPreference = findPreference<ListPreference>("volumeReduction") ?: return
volumeAdaptationPreference.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any? ->
if (feedPrefs != null) {
when (newValue as String?) {
"off" -> feedPrefs!!.volumeAdaptionSetting = VolumeAdaptionSetting.OFF
@ -270,48 +253,37 @@ class FeedSettingsFragment : Fragment() {
else -> {}
}
persistFeedPreferences(feed!!)
// EventFlow.postEvent(FlowEvent.FeedListUpdateEvent(feedPrefs!!.feedID))
updateVolumeAdaptationValue()
}
updateVolumeAdaptationValue()
// if (feed != null && feedPrefs!!.volumeAdaptionSetting != null)
// EventFlow.postEvent(FlowEvent.VolumeAdaptionChangedEvent(feedPrefs!!.volumeAdaptionSetting!!, feed!!.id))
false
}
}
private fun updateVolumeAdaptationValue() {
if (feedPrefs == null) return
val volumeAdaptationPreference = findPreference<ListPreference>("volumeReduction")
when (feedPrefs!!.volumeAdaptionSetting) {
VolumeAdaptionSetting.OFF -> volumeAdaptationPreference!!.value = "off"
VolumeAdaptionSetting.LIGHT_REDUCTION -> volumeAdaptationPreference!!.value = "light"
VolumeAdaptionSetting.HEAVY_REDUCTION -> volumeAdaptationPreference!!.value = "heavy"
VolumeAdaptionSetting.LIGHT_BOOST -> volumeAdaptationPreference!!.value = "light_boost"
VolumeAdaptionSetting.MEDIUM_BOOST -> volumeAdaptationPreference!!.value = "medium_boost"
VolumeAdaptionSetting.HEAVY_BOOST -> volumeAdaptationPreference!!.value = "heavy_boost"
val volumeAdaptationPreference = findPreference<ListPreference>("volumeReduction") ?: return
when (feedPrefs?.volumeAdaptionSetting) {
VolumeAdaptionSetting.OFF -> volumeAdaptationPreference.value = "off"
VolumeAdaptionSetting.LIGHT_REDUCTION -> volumeAdaptationPreference.value = "light"
VolumeAdaptionSetting.HEAVY_REDUCTION -> volumeAdaptationPreference.value = "heavy"
VolumeAdaptionSetting.LIGHT_BOOST -> volumeAdaptationPreference.value = "light_boost"
VolumeAdaptionSetting.MEDIUM_BOOST -> volumeAdaptationPreference.value = "medium_boost"
VolumeAdaptionSetting.HEAVY_BOOST -> volumeAdaptationPreference.value = "heavy_boost"
else -> {}
}
}
@OptIn(UnstableApi::class) private fun setupKeepUpdatedPreference() {
if (feedPrefs == null) return
val pref = findPreference<SwitchPreferenceCompat>("keepUpdated")
pref!!.isChecked = feedPrefs!!.keepUpdated
pref.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
val checked = newValue == true
if (feedPrefs != null) {
feedPrefs!!.keepUpdated = checked
persistFeedPreferences(feed!!)
// EventFlow.postEvent(FlowEvent.FeedListUpdateEvent(feedPrefs!!.feedID))
}
pref.isChecked = checked
false
}
}
private fun setupAutoDownloadGlobalPreference() {
if (!isEnableAutodownload) {
val autodl = findPreference<SwitchPreferenceCompat>("autoDownload")
@ -321,47 +293,38 @@ class FeedSettingsFragment : Fragment() {
findPreference<Preference>(PREF_EPISODE_FILTER)!!.isEnabled = false
}
}
@OptIn(UnstableApi::class) private fun setupAutoDownloadPreference() {
if (feedPrefs == null) return
val pref = findPreference<SwitchPreferenceCompat>("autoDownload")
pref!!.isEnabled = isEnableAutodownload
if (isEnableAutodownload) {
pref.isChecked = feedPrefs!!.autoDownload
} else {
if (isEnableAutodownload) pref.isChecked = feedPrefs!!.autoDownload
else {
pref.isChecked = false
pref.setSummary(R.string.auto_download_disabled_globally)
}
pref.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
val checked = newValue == true
if (feedPrefs != null) {
feedPrefs!!.autoDownload = checked
persistFeedPreferences(feed!!)
// EventFlow.postEvent(FlowEvent.FeedListUpdateEvent(feedPrefs!!.feedID))
updateAutoDownloadEnabled()
pref.isChecked = checked
}
updateAutoDownloadEnabled()
pref.isChecked = checked
false
}
}
private fun updateAutoDownloadEnabled() {
if (feed != null && feed!!.preferences != null) {
if (feed?.preferences != null) {
val enabled = feed!!.preferences!!.autoDownload && isEnableAutodownload
findPreference<Preference>(PREF_EPISODE_FILTER)!!.isEnabled = enabled
}
}
private fun setupTags() {
findPreference<Preference>(PREF_TAGS)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener {
if (feedPrefs != null) TagSettingsDialog.newInstance(listOf(feed!!))
.show(childFragmentManager, TagSettingsDialog.TAG)
if (feedPrefs != null) TagSettingsDialog.newInstance(listOf(feed!!)).show(childFragmentManager, TagSettingsDialog.TAG)
true
}
}
fun setFeed(feed_: Feed) {
feed = feed_
}
@ -372,7 +335,6 @@ class FeedSettingsFragment : Fragment() {
private val PREF_AUTHENTICATION: CharSequence = "authentication"
private val PREF_AUTO_DELETE: CharSequence = "autoDelete"
private val PREF_CATEGORY_AUTO_DOWNLOAD: CharSequence = "autoDownloadCategory"
// private val PREF_NEW_EPISODES_ACTION: CharSequence = "feedNewEpisodesAction"
private const val PREF_FEED_PLAYBACK_SPEED = "feedPlaybackSpeed"
private const val PREF_AUTO_SKIP = "feedAutoSkip"
private const val PREF_TAGS = "tags"
@ -388,52 +350,35 @@ class FeedSettingsFragment : Fragment() {
/**
* Displays a dialog with a username and password text field and an optional checkbox to save username and preferences.
*/
abstract class FeedPreferenceSkipDialog(context: Context, skipIntroInitialValue: Int, skipEndInitialValue: Int)
: MaterialAlertDialogBuilder(context) {
abstract class FeedPreferenceSkipDialog(context: Context, skipIntroInitialValue: Int, skipEndInitialValue: Int) : MaterialAlertDialogBuilder(context) {
init {
setTitle(R.string.pref_feed_skip)
val binding = FeedPrefSkipDialogBinding.bind(View.inflate(context, R.layout.feed_pref_skip_dialog, null))
setView(binding.root)
val etxtSkipIntro = binding.etxtSkipIntro
val etxtSkipEnd = binding.etxtSkipEnd
etxtSkipIntro.setText(skipIntroInitialValue.toString())
etxtSkipEnd.setText(skipEndInitialValue.toString())
setNegativeButton(R.string.cancel_label, null)
setPositiveButton(R.string.confirm_label) { _: DialogInterface?, _: Int ->
val skipIntro = try {
etxtSkipIntro.text.toString().toInt()
} catch (e: NumberFormatException) {
0
}
val skipEnding = try {
etxtSkipEnd.text.toString().toInt()
} catch (e: NumberFormatException) {
0
}
val skipIntro = try { etxtSkipIntro.text.toString().toInt() } catch (e: NumberFormatException) { 0 }
val skipEnding = try { etxtSkipEnd.text.toString().toInt() } catch (e: NumberFormatException) { 0 }
onConfirmed(skipIntro, skipEnding)
}
}
protected abstract fun onConfirmed(skipIntro: Int, skipEndig: Int)
protected abstract fun onConfirmed(skipIntro: Int, skipEnding: Int)
}
/**
* Displays a dialog with a text box for filtering episodes and two radio buttons for exclusion/inclusion
*/
abstract class EpisodeFilterPrefDialog(context: Context, filter: FeedEpisodesFilter) :
MaterialAlertDialogBuilder(context) {
private val binding = EpisodeFilterDialogBinding.inflate(LayoutInflater.from(context))
abstract class AutoDownloadFilterPrefDialog(context: Context, filter: FeedAutoDownloadFilter) : MaterialAlertDialogBuilder(context) {
private val binding = AutodownloadFilterDialogBinding.inflate(LayoutInflater.from(context))
private val termList: MutableList<String>
init {
setTitle(R.string.episode_filters_label)
setView(binding.root)
binding.durationCheckBox.setOnCheckedChangeListener { _: CompoundButton?, isChecked: Boolean ->
binding.episodeFilterDurationText.isEnabled = isChecked
}
@ -442,7 +387,6 @@ class FeedSettingsFragment : Fragment() {
// Store minimal duration in seconds, show in minutes
binding.episodeFilterDurationText.setText((filter.minimalDurationFilter / 60).toString())
} else binding.episodeFilterDurationText.isEnabled = false
if (filter.excludeOnly()) {
termList = filter.getExcludeFilter().toMutableList()
binding.excludeRadio.isChecked = true
@ -451,13 +395,11 @@ class FeedSettingsFragment : Fragment() {
binding.includeRadio.isChecked = true
}
setupWordsList()
setNegativeButton(R.string.cancel_label, null)
setPositiveButton(R.string.confirm_label) { dialog: DialogInterface, which: Int ->
this.onConfirmClick(dialog, which)
}
}
private fun setupWordsList() {
binding.termsRecycler.layoutManager = GridLayoutManager(context, 2)
binding.termsRecycler.addItemDecoration(ItemOffsetDecoration(context, 4))
@ -465,7 +407,6 @@ class FeedSettingsFragment : Fragment() {
override fun getChips(): List<String> {
return termList
}
override fun onRemoveClicked(position: Int) {
termList.removeAt(position)
notifyDataSetChanged()
@ -475,15 +416,12 @@ class FeedSettingsFragment : Fragment() {
binding.termsTextInput.setEndIconOnClickListener {
val newWord = binding.termsTextInput.editText!!.text.toString().replace("\"", "").trim { it <= ' ' }
if (newWord.isEmpty() || termList.contains(newWord)) return@setEndIconOnClickListener
termList.add(newWord)
binding.termsTextInput.editText!!.setText("")
adapter.notifyDataSetChanged()
}
}
protected abstract fun onConfirmed(filter: FeedEpisodesFilter)
protected abstract fun onConfirmed(filter: FeedAutoDownloadFilter)
private fun onConfirmClick(dialog: DialogInterface, which: Int) {
var minimalDuration = -1
if (binding.durationCheckBox.isChecked) {
@ -498,10 +436,8 @@ class FeedSettingsFragment : Fragment() {
var includeFilter = ""
if (binding.includeRadio.isChecked) includeFilter = toFilterString(termList)
else excludeFilter = toFilterString(termList)
onConfirmed(FeedEpisodesFilter(includeFilter, excludeFilter, minimalDuration))
onConfirmed(FeedAutoDownloadFilter(includeFilter, excludeFilter, minimalDuration))
}
private fun toFilterString(words: List<String>?): String {
val result = StringBuilder()
for (word in words!!) {

View File

@ -5,7 +5,7 @@ import ac.mdiq.podcini.storage.database.RealmDB.realm
import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.model.EpisodeMedia
import ac.mdiq.podcini.storage.utils.SortOrder
import ac.mdiq.podcini.storage.model.SortOrder
import ac.mdiq.podcini.ui.actions.menuhandler.MenuItemUtils
import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.ui.adapter.EpisodesAdapter

View File

@ -13,8 +13,8 @@ import ac.mdiq.podcini.storage.algorithms.AutoCleanups
import ac.mdiq.podcini.storage.database.Episodes.getEpisodesCount
import ac.mdiq.podcini.storage.database.Feeds.getFeedList
import ac.mdiq.podcini.storage.model.DatasetStats
import ac.mdiq.podcini.storage.utils.EpisodeFilter
import ac.mdiq.podcini.storage.utils.EpisodeFilter.Companion.unfiltered
import ac.mdiq.podcini.storage.model.EpisodeFilter
import ac.mdiq.podcini.storage.model.EpisodeFilter.Companion.unfiltered
import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.ui.activity.PreferenceActivity
import ac.mdiq.podcini.ui.activity.starter.MainActivityStarter

View File

@ -19,7 +19,7 @@ import ac.mdiq.podcini.storage.model.DownloadResult
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.model.Feed
import ac.mdiq.podcini.storage.model.FeedPreferences
import ac.mdiq.podcini.storage.utils.VolumeAdaptionSetting
import ac.mdiq.podcini.storage.model.VolumeAdaptionSetting
import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.ui.dialog.AuthenticationDialog
import ac.mdiq.podcini.ui.utils.ThemeUtils.getColorFromAttr

View File

@ -15,7 +15,7 @@ import ac.mdiq.podcini.storage.model.Chapter
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.model.EpisodeMedia
import ac.mdiq.podcini.storage.model.Playable
import ac.mdiq.podcini.storage.utils.EmbeddedChapterImage
import ac.mdiq.podcini.storage.model.EmbeddedChapterImage
import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.ui.utils.ShownotesCleaner
import ac.mdiq.podcini.ui.view.ShownotesWebView

View File

@ -17,9 +17,9 @@ import ac.mdiq.podcini.storage.database.RealmDB.runOnIOScope
import ac.mdiq.podcini.storage.database.RealmDB.upsert
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.model.EpisodeMedia
import ac.mdiq.podcini.storage.utils.EpisodeFilter
import ac.mdiq.podcini.storage.model.EpisodeFilter
import ac.mdiq.podcini.storage.utils.EpisodeUtil
import ac.mdiq.podcini.storage.utils.SortOrder
import ac.mdiq.podcini.storage.model.SortOrder
import ac.mdiq.podcini.ui.actions.EpisodeMultiSelectHandler
import ac.mdiq.podcini.ui.actions.menuhandler.EpisodeMenuHandler
import ac.mdiq.podcini.ui.actions.menuhandler.MenuItemUtils

View File

@ -56,7 +56,6 @@ import io.realm.kotlin.query.Sort
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.text.NumberFormat
@ -223,13 +222,13 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec
when (tagFilterIndex) {
1 -> feedListFiltered = feedList // All feeds
0 -> feedListFiltered = feedList.filter { // feeds without tag
val tags = it.preferences?.getTags()
val tags = it.preferences?.tags
tags.isNullOrEmpty() || (tags.size == 1 && tags.toList()[0] == "#root")
}
else -> { // feeds with the chosen tag
val tag = tags[tagFilterIndex]
feedListFiltered = feedList.filter {
it.preferences?.getTags()?.contains(tag) ?: false
it.preferences?.tags?.contains(tag) ?: false
}
}
}
@ -260,6 +259,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec
is FlowEvent.FeedListEvent, is FlowEvent.FeedsSortedEvent -> loadSubscriptions()
is FlowEvent.EpisodePlayedEvent -> loadSubscriptions()
is FlowEvent.FeedTagsChangedEvent -> loadSubscriptions()
// is FlowEvent.FeedPrefsChangeEvent -> onFeedPrefsChangeEvent(event)
else -> {}
}
}
@ -280,6 +280,10 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec
}
}
// private fun onFeedPrefsChangeEvent(event: FlowEvent.FeedPrefsChangeEvent) {
// val feed = getFeed(event.feed.id)
// }
@UnstableApi override fun onMenuItemClick(item: MenuItem): Boolean {
val itemId = item.itemId
when (itemId) {
@ -494,7 +498,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec
@UnstableApi override fun preferenceChanged(pos: Int) {
val autoDeleteAction: FeedPreferences.AutoDeleteAction = FeedPreferences.AutoDeleteAction.fromCode(pos)
saveFeedPreferences { feedPreferences: FeedPreferences ->
feedPreferences.currentAutoDelete = autoDeleteAction
feedPreferences.autoDeleteAction = autoDeleteAction
}
}
})

View File

@ -187,9 +187,6 @@ class StatisticsFragment : Fragment() {
}
}
/**
* Displays the 'playback statistics' screen
*/
class SubscriptionStatisticsFragment : Fragment() {
private var _binding: StatisticsFragmentBinding? = null
private val binding get() = _binding!!

View File

@ -11,7 +11,7 @@ import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.model.Episode.Companion.BUILDING
import ac.mdiq.podcini.storage.model.EpisodeMedia
import ac.mdiq.podcini.storage.model.Feed.Companion.PREFIX_GENERATIVE_COVER
import ac.mdiq.podcini.storage.utils.MediaType
import ac.mdiq.podcini.storage.model.MediaType
import ac.mdiq.podcini.storage.model.Playable
import ac.mdiq.podcini.ui.actions.actionbutton.EpisodeActionButton
import ac.mdiq.podcini.ui.actions.actionbutton.TTSActionButton
@ -22,7 +22,6 @@ import ac.mdiq.podcini.ui.view.CircularProgressBar
import ac.mdiq.podcini.util.Converter
import ac.mdiq.podcini.util.DateFormatter
import ac.mdiq.podcini.util.Logd
import ac.mdiq.podcini.util.event.FlowEvent
import android.text.Layout
import android.text.format.Formatter
import android.util.Log

View File

@ -8,7 +8,7 @@ import ac.mdiq.podcini.receiver.MediaButtonReceiver.Companion.createPendingInten
import ac.mdiq.podcini.receiver.PlayerWidget
import ac.mdiq.podcini.receiver.PlayerWidget.Companion.isEnabled
import ac.mdiq.podcini.receiver.PlayerWidget.Companion.prefs
import ac.mdiq.podcini.storage.utils.MediaType
import ac.mdiq.podcini.storage.model.MediaType
import ac.mdiq.podcini.storage.model.Playable
import ac.mdiq.podcini.ui.activity.starter.MainActivityStarter
import ac.mdiq.podcini.ui.activity.starter.PlaybackSpeedActivityStarter

View File

@ -5,7 +5,7 @@ import ac.mdiq.podcini.net.download.DownloadStatus
import ac.mdiq.podcini.storage.model.Feed
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.model.Playable
import ac.mdiq.podcini.storage.utils.SortOrder
import ac.mdiq.podcini.storage.model.SortOrder
import ac.mdiq.podcini.util.Logd
import android.content.Context
import android.view.KeyEvent

View File

@ -1,7 +1,7 @@
package ac.mdiq.podcini.util.sorting
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.utils.SortOrder
import ac.mdiq.podcini.storage.model.SortOrder
import java.util.*
/**

View File

@ -5,6 +5,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/autodownload_filter_dialog"
android:padding="16dp">
<LinearLayout
@ -91,9 +92,6 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/time_minutes" />
</LinearLayout>
</LinearLayout>
</ScrollView>

View File

@ -8,14 +8,6 @@
android:summary="@string/keep_updated_summary"
android:title="@string/keep_updated" />
<!-- <SwitchPreferenceCompat-->
<!-- android:defaultValue="false"-->
<!-- android:dependency="keepUpdated"-->
<!-- android:icon="@drawable/ic_notifications"-->
<!-- android:key="episodeNotification"-->
<!-- android:summary="@string/episode_notification_summary"-->
<!-- android:title="@string/episode_notification" />-->
<Preference
android:icon="@drawable/ic_key"
android:key="authentication"

View File

@ -1,10 +1,6 @@
package ac.mdiq.podcini.playback.cast
import ac.mdiq.podcini.storage.model.Feed
import ac.mdiq.podcini.storage.model.EpisodeMedia
import ac.mdiq.podcini.storage.utils.MediaType
import ac.mdiq.podcini.storage.model.Playable
import ac.mdiq.podcini.storage.model.RemoteMedia
import ac.mdiq.podcini.storage.model.*
import android.content.ContentResolver
import android.util.Log
import com.google.android.gms.cast.CastDevice

View File

@ -1,15 +1,15 @@
package ac.mdiq.podcini.feed
import ac.mdiq.podcini.util.Converter.durationStringShortToMs
import ac.mdiq.podcini.storage.utils.FeedEpisodesFilter
import ac.mdiq.podcini.storage.model.FeedAutoDownloadFilter
import ac.mdiq.podcini.storage.model.Episode
import org.junit.Assert
import org.junit.Test
class FeedEpisodesFilterTest {
class FeedAutoDownloadFilterTest {
@Test
fun testNullFilter() {
val filter = FeedEpisodesFilter()
val filter = FeedAutoDownloadFilter()
val item = Episode()
item.title = ("Hello world")
@ -23,7 +23,7 @@ class FeedEpisodesFilterTest {
@Test
fun testBasicIncludeFilter() {
val includeFilter = "Hello"
val filter = FeedEpisodesFilter(includeFilter, "")
val filter = FeedAutoDownloadFilter(includeFilter, "")
val item = Episode()
item.title = ("Hello world")
@ -41,7 +41,7 @@ class FeedEpisodesFilterTest {
@Test
fun testBasicExcludeFilter() {
val excludeFilter = "Hello"
val filter = FeedEpisodesFilter("", excludeFilter)
val filter = FeedAutoDownloadFilter("", excludeFilter)
val item = Episode()
item.title = ("Hello world")
@ -59,7 +59,7 @@ class FeedEpisodesFilterTest {
@Test
fun testComplexIncludeFilter() {
val includeFilter = "Hello \n\"Two words\""
val filter = FeedEpisodesFilter(includeFilter, "")
val filter = FeedAutoDownloadFilter(includeFilter, "")
val item = Episode()
item.title = ("hello world")
@ -81,7 +81,7 @@ class FeedEpisodesFilterTest {
@Test
fun testComplexExcludeFilter() {
val excludeFilter = "Hello \"Two words\""
val filter = FeedEpisodesFilter("", excludeFilter)
val filter = FeedAutoDownloadFilter("", excludeFilter)
val item = Episode()
item.title = ("hello world")
@ -104,7 +104,7 @@ class FeedEpisodesFilterTest {
fun testComboFilter() {
val includeFilter = "Hello world"
val excludeFilter = "dislike"
val filter = FeedEpisodesFilter(includeFilter, excludeFilter)
val filter = FeedAutoDownloadFilter(includeFilter, excludeFilter)
val download = Episode()
download.title = ("Hello everyone!")
@ -142,7 +142,7 @@ class FeedEpisodesFilterTest {
doNotDownload.setMedia(doNotDownloadMedia)
val minimalDurationFilter = 3 * 60
val filter = FeedEpisodesFilter("", "", minimalDurationFilter)
val filter = FeedAutoDownloadFilter("", "", minimalDurationFilter)
Assert.assertTrue(filter.hasMinimalDurationFilter())
Assert.assertTrue(filter.shouldAutoDownload(download))

View File

@ -2,7 +2,7 @@ package ac.mdiq.podcini.feed
import ac.mdiq.podcini.feed.FeedMother.anyFeed
import ac.mdiq.podcini.storage.model.Feed
import ac.mdiq.podcini.storage.utils.SortOrder
import ac.mdiq.podcini.storage.model.SortOrder
import junit.framework.TestCase.assertEquals
import org.junit.Assert
import org.junit.Before

View File

@ -1,7 +1,7 @@
package ac.mdiq.podcini.feed
import ac.mdiq.podcini.storage.utils.VolumeAdaptionSetting
import ac.mdiq.podcini.storage.utils.VolumeAdaptionSetting.Companion.fromInteger
import ac.mdiq.podcini.storage.model.VolumeAdaptionSetting
import ac.mdiq.podcini.storage.model.VolumeAdaptionSetting.Companion.fromInteger
import org.hamcrest.MatcherAssert
import org.hamcrest.Matchers
import org.junit.Assert

View File

@ -1,7 +1,7 @@
package ac.mdiq.podcini.feed.parser.element.namespace
import ac.mdiq.podcini.storage.model.Feed
import ac.mdiq.podcini.storage.utils.MediaType
import ac.mdiq.podcini.storage.model.MediaType
import ac.mdiq.podcini.feed.parser.element.namespace.FeedParserTestHelper.getFeedFile
import ac.mdiq.podcini.feed.parser.element.namespace.FeedParserTestHelper.runFeedParser
import junit.framework.TestCase.assertEquals

View File

@ -4,7 +4,7 @@ import ac.mdiq.podcini.net.feed.parser.media.id3.ChapterReader
import ac.mdiq.podcini.net.feed.parser.media.id3.ID3Reader
import ac.mdiq.podcini.net.feed.parser.media.id3.ID3ReaderException
import ac.mdiq.podcini.storage.model.Chapter
import ac.mdiq.podcini.storage.utils.EmbeddedChapterImage.Companion.makeUrl
import ac.mdiq.podcini.storage.model.EmbeddedChapterImage.Companion.makeUrl
import ac.mdiq.podcini.net.feed.parser.media.id3.model.FrameHeader
import org.apache.commons.io.input.CountingInputStream
import org.junit.Assert

View File

@ -5,7 +5,7 @@ import ac.mdiq.podcini.playback.base.MediaPlayerBase
import ac.mdiq.podcini.playback.base.PlayerStatus
import ac.mdiq.podcini.playback.service.PlaybackService
import ac.mdiq.podcini.storage.model.*
import ac.mdiq.podcini.storage.utils.VolumeAdaptionSetting
import ac.mdiq.podcini.storage.model.VolumeAdaptionSetting
import org.junit.Before
import org.junit.Test
import org.mockito.ArgumentMatchers

View File

@ -9,8 +9,8 @@ import ac.mdiq.podcini.storage.database.Feeds.getFeedList
import ac.mdiq.podcini.storage.database.Feeds.getFeedListDownloadUrls
import ac.mdiq.podcini.storage.database.Queues.getInQueueEpisodeIds
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.utils.EpisodeFilter
import ac.mdiq.podcini.storage.utils.SortOrder
import ac.mdiq.podcini.storage.model.EpisodeFilter
import ac.mdiq.podcini.storage.model.SortOrder
import ac.mdiq.podcini.ui.fragment.HistoryFragment.Companion.getHistory
import ac.mdiq.podcini.ui.fragment.HistoryFragment.Companion.getNumberOfCompleted
import ac.mdiq.podcini.ui.fragment.NavDrawerFragment.Companion.getDatasetStats

View File

@ -4,7 +4,7 @@ import ac.mdiq.podcini.util.sorting.EpisodesPermutors.getPermutor
import ac.mdiq.podcini.storage.model.Feed
import ac.mdiq.podcini.storage.model.Episode
import ac.mdiq.podcini.storage.model.EpisodeMedia
import ac.mdiq.podcini.storage.utils.SortOrder
import ac.mdiq.podcini.storage.model.SortOrder
import org.junit.Assert
import org.junit.Test
import java.util.*

View File

@ -1,3 +1,11 @@
## 6.0.6
* minor class re-structuring
* adjusted FeedPreferences to incoporate some previously ignored properties
* enabled selection of .json files when importing progress
* in wifi sync and episode progress export/import, changed start position and added played duration for episodes (available from 5.5.3),
this helps for the statistics view at the importer to correctly show imported progress without having to do "include marked played"
## 6.0.5
* fixed threading issue of downloading multiple episodes

View File

@ -0,0 +1,8 @@
Version 6.0.6 brings several changes:
* minor class re-structuring
* adjusted FeedPreferences to incoporate some previously ignored properties
* enabled selection of .json files when importing progress
* in wifi sync and episode progress export/import, changed start position and added played duration for episodes (available from 5.5.3),
this helps for the statistics view at the importer to correctly show imported progress without having to do "include marked played"