6.1.3 commit
This commit is contained in:
parent
3c2618a29a
commit
742aa3615d
|
@ -75,10 +75,11 @@ While podcast subscriptions' OPML files (from AntennaPod or any other sources) c
|
|||
* Sort dialog no longer dims the main view
|
||||
* download date can be used to sort both feeds and episodes
|
||||
* Subscriptions view has a filter based on feed preferences, in the same style as episodes filter
|
||||
* Subscriptions sorting is now bi-directional based on various explicit measures
|
||||
* Subscriptions sorting is now bi-directional based on various explicit measures, and sorting info is shown on every feed (List Layout only)
|
||||
* in Subscriptions view, click on cover image of a feed opens the FeedInfo view (not FeedEpisodes view)
|
||||
* in all episodes list views, click on an episode image brings up the FeedInfo view
|
||||
* in episode list view, if episode has no media, TTS button is shown for fetching transcript (if not exist) and then generating audio file from the transcript. TTS audio files are playable in the same way as local media (with speed setting, pause and rewind/forward)
|
||||
* Long-press filter button in FeedEpisode view enables/disables filters without changing filter settings
|
||||
* in Subscriptions view, click on cover image of a feed opens the FeedInfo view (not FeedEpisodes view)
|
||||
* History view shows time of last play, and allows filters and sorts
|
||||
* Multiple queues can be used: 5 queues are provided by default: Default queue, and Queues 1-4
|
||||
* all queue operations are on the curQueue, which can be set in all episodes list views
|
||||
|
@ -90,8 +91,9 @@ While podcast subscriptions' OPML files (from AntennaPod or any other sources) c
|
|||
### Podcast/Episode
|
||||
|
||||
* New share notes menu option on various episode views
|
||||
* Feed info view offers a link for direct search of feeds related to author
|
||||
* FeedInfo view offers a link for direct search of feeds related to author
|
||||
* FeedInfo view has button showing number of episodes to open the FeedEpisodes view
|
||||
* FeedInfo view has feed setting in the header
|
||||
* in EpisodeInfo view, "mark played/unplayed", "add to/remove from queue", and "favoraite/unfovorite" are at the action bar
|
||||
* New episode home view with two display modes: webpage or reader
|
||||
* In episode, in addition to "description" there is a new "transcript" field to save text (if any) fetched from the episode's website
|
||||
|
|
|
@ -126,8 +126,8 @@ android {
|
|||
buildConfig true
|
||||
}
|
||||
defaultConfig {
|
||||
versionCode 3020216
|
||||
versionName "6.1.2"
|
||||
versionCode 3020217
|
||||
versionName "6.1.3"
|
||||
|
||||
applicationId "ac.mdiq.podcini.R"
|
||||
def commit = ""
|
||||
|
@ -245,6 +245,8 @@ dependencies {
|
|||
|
||||
implementation "net.dankito.readability4j:readability4j:1.0.8"
|
||||
|
||||
// debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.14'
|
||||
|
||||
// Non-free dependencies:
|
||||
playImplementation 'com.google.android.play:core-ktx:1.8.1'
|
||||
compileOnly "com.google.android.wearable:wearable:2.9.0"
|
||||
|
|
|
@ -332,10 +332,9 @@ object FeedUpdateManager {
|
|||
if (isSuccessful) {
|
||||
downloadStatus = DownloadResult(feed.id, feed.getTextIdentifier()?:"", DownloadError.SUCCESS, isSuccessful, reasonDetailed?:"")
|
||||
return result
|
||||
} else {
|
||||
downloadStatus = DownloadResult(feed.id, feed.getTextIdentifier()?:"", reason?: DownloadError.ERROR_NOT_FOUND, isSuccessful, reasonDetailed?:"")
|
||||
return null
|
||||
}
|
||||
downloadStatus = DownloadResult(feed.id, feed.getTextIdentifier()?:"", reason?: DownloadError.ERROR_NOT_FOUND, isSuccessful, reasonDetailed?:"")
|
||||
return null
|
||||
}
|
||||
/**
|
||||
* Checks if the feed was parsed correctly.
|
||||
|
|
|
@ -8,7 +8,6 @@ import ac.mdiq.podcini.preferences.UserPreferences
|
|||
import ac.mdiq.podcini.preferences.UserPreferences.episodeCacheSize
|
||||
import ac.mdiq.podcini.preferences.UserPreferences.isEnableAutodownload
|
||||
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.database.Feeds.getFeedList
|
||||
import ac.mdiq.podcini.storage.database.RealmDB.realm
|
||||
|
@ -73,46 +72,46 @@ object AutoDownloads {
|
|||
@UnstableApi
|
||||
open fun autoDownloadEpisodeMedia(context: Context, feeds: List<Feed>? = null): Runnable? {
|
||||
return Runnable {
|
||||
// true if we should auto download based on network status
|
||||
// val networkShouldAutoDl = (isAutoDownloadAllowed)
|
||||
val networkShouldAutoDl = (isAutoDownloadAllowed && isEnableAutodownload)
|
||||
// true if we should auto download based on power status
|
||||
val powerShouldAutoDl = (deviceCharging(context) || isEnableAutodownloadOnBattery)
|
||||
Logd(TAG, "prepare autoDownloadUndownloadedItems $networkShouldAutoDl $powerShouldAutoDl")
|
||||
// we should only auto download if both network AND power are happy
|
||||
if (networkShouldAutoDl && powerShouldAutoDl) {
|
||||
Logd(TAG, "Performing auto-dl of undownloaded episodes")
|
||||
val queueItems = curQueue.episodes
|
||||
val newItems = getEpisodes(0, Int.MAX_VALUE, EpisodeFilter(EpisodeFilter.States.new.name), EpisodeSortOrder.DATE_NEW_OLD)
|
||||
Logd(TAG, "newItems: ${newItems.size}")
|
||||
val candidates: MutableList<Episode> = ArrayList(queueItems.size + newItems.size)
|
||||
candidates.addAll(queueItems)
|
||||
for (newItem in newItems) {
|
||||
val feedPrefs = newItem.feed!!.preferences
|
||||
if (feedPrefs!!.autoDownload && !candidates.contains(newItem) && feedPrefs.autoDownloadFilter!!.shouldAutoDownload(newItem)) candidates.add(newItem)
|
||||
}
|
||||
// filter items that are not auto downloadable
|
||||
val it = candidates.iterator()
|
||||
while (it.hasNext()) {
|
||||
val item = it.next()
|
||||
if (!item.isAutoDownloadEnabled || item.isDownloaded || item.media == null || isCurMedia(item.media) || item.feed?.isLocalFeed == true)
|
||||
it.remove()
|
||||
}
|
||||
val autoDownloadableEpisodes = candidates.size
|
||||
val downloadedEpisodes = getEpisodesCount(EpisodeFilter(EpisodeFilter.States.downloaded.name))
|
||||
val deletedEpisodes = AutoCleanups.build().makeRoomForEpisodes(context, autoDownloadableEpisodes)
|
||||
val cacheIsUnlimited = episodeCacheSize == UserPreferences.EPISODE_CACHE_SIZE_UNLIMITED
|
||||
val episodeCacheSize = episodeCacheSize
|
||||
val episodeSpaceLeft =
|
||||
if (cacheIsUnlimited || episodeCacheSize >= downloadedEpisodes + autoDownloadableEpisodes) autoDownloadableEpisodes
|
||||
else episodeCacheSize - (downloadedEpisodes - deletedEpisodes)
|
||||
val itemsToDownload: List<Episode> = candidates.subList(0, episodeSpaceLeft)
|
||||
if (itemsToDownload.isNotEmpty()) {
|
||||
Logd(TAG, "Enqueueing " + itemsToDownload.size + " items for download")
|
||||
for (episode in itemsToDownload) DownloadServiceInterface.get()?.download(context, episode)
|
||||
}
|
||||
}
|
||||
else Logd(TAG, "not auto downloaded networkShouldAutoDl: $networkShouldAutoDl powerShouldAutoDl $powerShouldAutoDl")
|
||||
// // true if we should auto download based on network status
|
||||
//// val networkShouldAutoDl = (isAutoDownloadAllowed)
|
||||
// val networkShouldAutoDl = (isAutoDownloadAllowed && isEnableAutodownload)
|
||||
// // true if we should auto download based on power status
|
||||
// val powerShouldAutoDl = (deviceCharging(context) || isEnableAutodownloadOnBattery)
|
||||
// Logd(TAG, "prepare autoDownloadUndownloadedItems $networkShouldAutoDl $powerShouldAutoDl")
|
||||
// // we should only auto download if both network AND power are happy
|
||||
// if (networkShouldAutoDl && powerShouldAutoDl) {
|
||||
// Logd(TAG, "Performing auto-dl of undownloaded episodes")
|
||||
// val queueItems = curQueue.episodes
|
||||
// val newItems = getEpisodes(0, Int.MAX_VALUE, EpisodeFilter(EpisodeFilter.States.new.name), EpisodeSortOrder.DATE_NEW_OLD)
|
||||
// Logd(TAG, "newItems: ${newItems.size}")
|
||||
// val candidates: MutableList<Episode> = ArrayList(queueItems.size + newItems.size)
|
||||
// candidates.addAll(queueItems)
|
||||
// for (newItem in newItems) {
|
||||
// val feedPrefs = newItem.feed!!.preferences
|
||||
// if (feedPrefs!!.autoDownload && !candidates.contains(newItem) && feedPrefs.autoDownloadFilter!!.shouldAutoDownload(newItem)) candidates.add(newItem)
|
||||
// }
|
||||
// // filter items that are not auto downloadable
|
||||
// val it = candidates.iterator()
|
||||
// while (it.hasNext()) {
|
||||
// val item = it.next()
|
||||
// if (!item.isAutoDownloadEnabled || item.isDownloaded || item.media == null || isCurMedia(item.media) || item.feed?.isLocalFeed == true)
|
||||
// it.remove()
|
||||
// }
|
||||
// val autoDownloadableEpisodes = candidates.size
|
||||
// val downloadedEpisodes = getEpisodesCount(EpisodeFilter(EpisodeFilter.States.downloaded.name))
|
||||
// val deletedEpisodes = AutoCleanups.build().makeRoomForEpisodes(context, autoDownloadableEpisodes)
|
||||
// val cacheIsUnlimited = episodeCacheSize == UserPreferences.EPISODE_CACHE_SIZE_UNLIMITED
|
||||
// val episodeCacheSize = episodeCacheSize
|
||||
// val episodeSpaceLeft =
|
||||
// if (cacheIsUnlimited || episodeCacheSize >= downloadedEpisodes + autoDownloadableEpisodes) autoDownloadableEpisodes
|
||||
// else episodeCacheSize - (downloadedEpisodes - deletedEpisodes)
|
||||
// val itemsToDownload: List<Episode> = candidates.subList(0, episodeSpaceLeft)
|
||||
// if (itemsToDownload.isNotEmpty()) {
|
||||
// Logd(TAG, "Enqueueing " + itemsToDownload.size + " items for download")
|
||||
// for (episode in itemsToDownload) DownloadServiceInterface.get()?.download(context, episode)
|
||||
// }
|
||||
// }
|
||||
// else Logd(TAG, "not auto downloaded networkShouldAutoDl: $networkShouldAutoDl powerShouldAutoDl $powerShouldAutoDl")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -150,7 +149,10 @@ object AutoDownloads {
|
|||
feeds.forEach { f ->
|
||||
if (f.preferences?.autoDownload == true && !f.isLocalFeed) {
|
||||
var episodes = mutableListOf<Episode>()
|
||||
val downloadedCount = getEpisodesCount(EpisodeFilter(EpisodeFilter.States.downloaded.name), f.id)
|
||||
val dlFilter =
|
||||
if (f.preferences?.countingPlayed == true) EpisodeFilter(EpisodeFilter.States.downloaded.name)
|
||||
else EpisodeFilter(EpisodeFilter.States.downloaded.name, EpisodeFilter.States.unplayed.name)
|
||||
val downloadedCount = getEpisodesCount(dlFilter, f.id)
|
||||
val allowedDLCount = (f.preferences?.autoDLMaxEpisodes?:0) - downloadedCount
|
||||
Logd(TAG, "autoDownloadEpisodeMedia ${f.preferences?.autoDLMaxEpisodes} downloadedCount: $downloadedCount allowedDLCount: $allowedDLCount")
|
||||
if (allowedDLCount > 0) {
|
||||
|
@ -198,7 +200,7 @@ object AutoDownloads {
|
|||
}
|
||||
}
|
||||
}
|
||||
// TODO: need to send an event
|
||||
// TODO: probably need to send an event
|
||||
}
|
||||
}
|
||||
if (candidates.isNotEmpty()) {
|
||||
|
|
|
@ -36,16 +36,12 @@ import kotlin.math.abs
|
|||
|
||||
object Feeds {
|
||||
private val TAG: String = Feeds::class.simpleName ?: "Anonymous"
|
||||
private val feedMap: MutableMap<Long, Feed> = mutableMapOf()
|
||||
private val tags: MutableList<String> = mutableListOf()
|
||||
|
||||
@Synchronized
|
||||
fun getFeedList(queryString: String = "", fromDB: Boolean = true): List<Feed> {
|
||||
if (fromDB) {
|
||||
return if (queryString.isEmpty()) realm.query(Feed::class).find()
|
||||
else realm.query(Feed::class, queryString).find()
|
||||
}
|
||||
return feedMap.values.toList()
|
||||
fun getFeedList(queryString: String = ""): List<Feed> {
|
||||
return if (queryString.isEmpty()) realm.query(Feed::class).find()
|
||||
else realm.query(Feed::class, queryString).find()
|
||||
}
|
||||
|
||||
fun getFeedCount(): Int {
|
||||
|
@ -56,26 +52,6 @@ object Feeds {
|
|||
return tags
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun updateFeedMap(feeds: List<Feed> = listOf(), wipe: Boolean = false) {
|
||||
Logd(TAG, "updateFeedMap called feeds: ${feeds.size} wipe: $wipe")
|
||||
when {
|
||||
feeds.isEmpty() -> {
|
||||
val feeds_ = realm.query(Feed::class).find()
|
||||
feedMap.clear()
|
||||
feedMap.putAll(feeds_.associateBy { it.id })
|
||||
}
|
||||
wipe -> {
|
||||
feedMap.clear()
|
||||
feedMap.putAll(feeds.associateBy { it.id })
|
||||
}
|
||||
else -> {
|
||||
for (f in feeds) feedMap[f.id] = f
|
||||
}
|
||||
}
|
||||
buildTags()
|
||||
}
|
||||
|
||||
fun buildTags() {
|
||||
val tagsSet = mutableSetOf<String>()
|
||||
val feedsCopy = getFeedList()
|
||||
|
@ -174,13 +150,13 @@ object Feeds {
|
|||
return result
|
||||
}
|
||||
|
||||
fun getFeed(feedId: Long, copy: Boolean = false, fromDB: Boolean = true): Feed? {
|
||||
fun getFeed(feedId: Long, copy: Boolean = false): Feed? {
|
||||
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")
|
||||
Logd(TAG, "${caller?.className}.${caller?.methodName} getFeed called")
|
||||
}
|
||||
val f = if (fromDB) realm.query(Feed::class, "id == $feedId").first().find() else feedMap[feedId]
|
||||
val f = realm.query(Feed::class, "id == $feedId").first().find()
|
||||
return if (f != null) {
|
||||
if (copy) realm.copyFromRealm(f)
|
||||
else f
|
||||
|
@ -247,7 +223,6 @@ object Feeds {
|
|||
// Look for new or updated Items
|
||||
for (idx in newFeed.episodes.indices) {
|
||||
val episode = newFeed.episodes[idx]
|
||||
|
||||
val possibleDuplicate = EpisodeAssistant.searchEpisodeGuessDuplicate(newFeed.episodes, episode)
|
||||
if (!newFeed.isLocalFeed && possibleDuplicate != null && episode !== possibleDuplicate) {
|
||||
// Canonical episode is the first one returned (usually oldest)
|
||||
|
@ -263,7 +238,6 @@ object Feeds {
|
|||
""".trimIndent()))
|
||||
continue
|
||||
}
|
||||
|
||||
var oldItem = EpisodeAssistant.searchEpisodeByIdentifyingValue(savedFeed.episodes, episode)
|
||||
if (!newFeed.isLocalFeed && oldItem == null) {
|
||||
oldItem = EpisodeAssistant.searchEpisodeGuessDuplicate(savedFeed.episodes, episode)
|
||||
|
@ -394,7 +368,6 @@ object Feeds {
|
|||
}
|
||||
copyToRealm(feed)
|
||||
}
|
||||
// updateFeedMap(feeds.toList())
|
||||
}
|
||||
for (feed in feeds) {
|
||||
if (!feed.isLocalFeed && feed.downloadUrl != null) SynchronizationQueueSink.enqueueFeedAddedIfSyncActive(context, feed.downloadUrl!!)
|
||||
|
@ -451,7 +424,7 @@ object Feeds {
|
|||
val feedToDelete = findLatest(feed_)
|
||||
if (feedToDelete != null) {
|
||||
delete(feedToDelete)
|
||||
feedMap.remove(feedId)
|
||||
// feedMap.remove(feedId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -155,12 +155,12 @@ object Queues {
|
|||
queue.episodeIds.add(insertPosition, episode.id)
|
||||
queue.episodes.add(insertPosition, episode)
|
||||
insertPosition++
|
||||
queue.update()
|
||||
if (queue.id == curQueue.id) queue.update()
|
||||
upsert(queue) {}
|
||||
|
||||
if (markAsUnplayed && episode.isNew) setPlayState(Episode.UNPLAYED, false, episode)
|
||||
|
||||
if (queue_?.id == curQueue.id) EventFlow.postEvent(FlowEvent.QueueEvent.added(episode, insertPosition))
|
||||
if (queue.id == curQueue.id) EventFlow.postEvent(FlowEvent.QueueEvent.added(episode, insertPosition))
|
||||
// if (performAutoDownload) autodownloadEpisodeMedia(context)
|
||||
}
|
||||
|
||||
|
|
|
@ -18,7 +18,7 @@ import kotlin.coroutines.ContinuationInterceptor
|
|||
object RealmDB {
|
||||
private val TAG: String = RealmDB::class.simpleName ?: "Anonymous"
|
||||
|
||||
private const val SCHEMA_VERSION_NUMBER = 10L
|
||||
private const val SCHEMA_VERSION_NUMBER = 11L
|
||||
|
||||
private val ioScope = CoroutineScope(Dispatchers.IO)
|
||||
|
||||
|
|
|
@ -51,7 +51,7 @@ class Episode : RealmObject {
|
|||
@Ignore
|
||||
var feed: Feed? = null
|
||||
get() {
|
||||
if (field == null && feedId != null) field = getFeed(feedId!!, fromDB = true)
|
||||
if (field == null && feedId != null) field = getFeed(feedId!!)
|
||||
return field
|
||||
}
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package ac.mdiq.podcini.storage.model
|
||||
|
||||
import ac.mdiq.podcini.storage.database.RealmDB.realm
|
||||
import ac.mdiq.podcini.storage.model.FeedFunding.Companion.extractPaymentLinks
|
||||
import ac.mdiq.podcini.storage.model.EpisodeSortOrder.Companion.fromCode
|
||||
import io.realm.kotlin.ext.realmListOf
|
||||
|
@ -132,29 +133,30 @@ class Feed : RealmObject {
|
|||
preferences?.sortOrderCode = value.code
|
||||
}
|
||||
|
||||
@Ignore
|
||||
var sortOrderAux: EpisodeSortOrder? = null
|
||||
get() = fromCode(preferences?.sortOrderAuxCode ?: 0)
|
||||
set(value) {
|
||||
if (value == null) return
|
||||
field = value
|
||||
preferences?.sortOrderAuxCode = value.code
|
||||
}
|
||||
// @Ignore
|
||||
// var sortOrderAux: EpisodeSortOrder? = null
|
||||
// get() = fromCode(preferences?.sortOrderAuxCode ?: 0)
|
||||
// set(value) {
|
||||
// if (value == null) return
|
||||
// field = value
|
||||
// preferences?.sortOrderAuxCode = value.code
|
||||
// }
|
||||
|
||||
@Ignore
|
||||
val mostRecentItem: Episode?
|
||||
get() {
|
||||
// we could sort, but we don't need to, a simple search is fine...
|
||||
var mostRecentDate = Date(0)
|
||||
var mostRecentItem: Episode? = null
|
||||
for (item in episodes) {
|
||||
val date = item.getPubDate()
|
||||
if (date != null && date.after(mostRecentDate)) {
|
||||
mostRecentDate = date
|
||||
mostRecentItem = item
|
||||
}
|
||||
}
|
||||
return mostRecentItem
|
||||
// // we could sort, but we don't need to, a simple search is fine...
|
||||
// var mostRecentDate = Date(0)
|
||||
// var mostRecentItem: Episode? = null
|
||||
// for (item in episodes) {
|
||||
// val date = item.getPubDate()
|
||||
// if (date != null && date.after(mostRecentDate)) {
|
||||
// mostRecentDate = date
|
||||
// mostRecentItem = item
|
||||
// }
|
||||
// }
|
||||
// return mostRecentItem
|
||||
return realm.query(Episode::class).query("feedId == $id SORT(pubDate DESC)").first().find()
|
||||
}
|
||||
|
||||
@Ignore
|
||||
|
@ -164,6 +166,9 @@ class Feed : RealmObject {
|
|||
this.eigenTitle = value
|
||||
}
|
||||
|
||||
@Ignore
|
||||
var sortInfo: String = ""
|
||||
|
||||
/**
|
||||
* This constructor is used for test purposes.
|
||||
*/
|
||||
|
|
|
@ -45,6 +45,13 @@ class FeedPreferences : EmbeddedRealmObject {
|
|||
}
|
||||
var volumeAdaption: Int = 0
|
||||
|
||||
var filterString: String = ""
|
||||
|
||||
var sortOrderCode: Int = 0 // in EpisodeSortOrder
|
||||
|
||||
// seems not too useful
|
||||
// var sortOrderAuxCode: Int = 0 // in EpisodeSortOrder
|
||||
|
||||
@Ignore
|
||||
val tagsAsString: String
|
||||
get() = tags.joinToString(TAG_SEPARATOR)
|
||||
|
@ -69,6 +76,8 @@ class FeedPreferences : EmbeddedRealmObject {
|
|||
|
||||
var autoDLMaxEpisodes: Int = 3
|
||||
|
||||
var countingPlayed: Boolean = true
|
||||
|
||||
@Ignore
|
||||
var autoDLPolicy: AutoDLPolicy = AutoDLPolicy.ONLY_NEW
|
||||
get() = AutoDLPolicy.fromCode(autoDLPolicyCode)
|
||||
|
@ -78,12 +87,6 @@ class FeedPreferences : EmbeddedRealmObject {
|
|||
}
|
||||
var autoDLPolicyCode: Int = 0
|
||||
|
||||
var filterString: String = ""
|
||||
|
||||
var sortOrderCode: Int = 0
|
||||
|
||||
var sortOrderAuxCode: Int = 0
|
||||
|
||||
enum class AutoDLPolicy(val code: Int) {
|
||||
ONLY_NEW(0),
|
||||
NEWER(1),
|
||||
|
|
|
@ -19,39 +19,23 @@ object EpisodesPermutors {
|
|||
var permutor: Permutor<Episode>? = null
|
||||
|
||||
when (sortOrder) {
|
||||
EpisodeSortOrder.EPISODE_TITLE_A_Z -> comparator = Comparator { f1: Episode?, f2: Episode? -> itemTitle(f1).compareTo(
|
||||
itemTitle(f2)) }
|
||||
EpisodeSortOrder.EPISODE_TITLE_Z_A -> comparator = Comparator { f1: Episode?, f2: Episode? -> itemTitle(f2).compareTo(
|
||||
itemTitle(f1)) }
|
||||
EpisodeSortOrder.DATE_OLD_NEW -> comparator = Comparator { f1: Episode?, f2: Episode? -> pubDate(f1).compareTo(
|
||||
pubDate(f2)) }
|
||||
EpisodeSortOrder.DATE_NEW_OLD -> comparator = Comparator { f1: Episode?, f2: Episode? -> pubDate(f2).compareTo(
|
||||
pubDate(f1)) }
|
||||
EpisodeSortOrder.DURATION_SHORT_LONG -> comparator = Comparator { f1: Episode?, f2: Episode? -> duration(f1).compareTo(
|
||||
duration(f2)) }
|
||||
EpisodeSortOrder.DURATION_LONG_SHORT -> comparator = Comparator { f1: Episode?, f2: Episode? -> duration(f2).compareTo(
|
||||
duration(f1)) }
|
||||
EpisodeSortOrder.EPISODE_FILENAME_A_Z -> comparator = Comparator { f1: Episode?, f2: Episode? -> itemLink(f1).compareTo(
|
||||
itemLink(f2)) }
|
||||
EpisodeSortOrder.EPISODE_FILENAME_Z_A -> comparator = Comparator { f1: Episode?, f2: Episode? -> itemLink(f2).compareTo(
|
||||
itemLink(f1)) }
|
||||
EpisodeSortOrder.PLAYED_DATE_OLD_NEW -> comparator = Comparator { f1: Episode?, f2: Episode? -> playDate(f1).compareTo(
|
||||
playDate(f2)) }
|
||||
EpisodeSortOrder.PLAYED_DATE_NEW_OLD -> comparator = Comparator { f1: Episode?, f2: Episode? -> playDate(f2).compareTo(
|
||||
playDate(f1)) }
|
||||
EpisodeSortOrder.COMPLETED_DATE_OLD_NEW -> comparator = Comparator { f1: Episode?, f2: Episode? -> completeDate(f1).compareTo(
|
||||
completeDate(f2)) }
|
||||
EpisodeSortOrder.COMPLETED_DATE_NEW_OLD -> comparator = Comparator { f1: Episode?, f2: Episode? -> completeDate(f2).compareTo(
|
||||
completeDate(f1)) }
|
||||
EpisodeSortOrder.DOWNLOAD_DATE_OLD_NEW -> comparator = Comparator { f1: Episode?, f2: Episode? -> downloadDate(f1).compareTo(
|
||||
downloadDate(f2)) }
|
||||
EpisodeSortOrder.DOWNLOAD_DATE_NEW_OLD -> comparator = Comparator { f1: Episode?, f2: Episode? -> downloadDate(f2).compareTo(
|
||||
downloadDate(f1)) }
|
||||
EpisodeSortOrder.EPISODE_TITLE_A_Z -> comparator = Comparator { f1: Episode?, f2: Episode? -> itemTitle(f1).compareTo(itemTitle(f2)) }
|
||||
EpisodeSortOrder.EPISODE_TITLE_Z_A -> comparator = Comparator { f1: Episode?, f2: Episode? -> itemTitle(f2).compareTo(itemTitle(f1)) }
|
||||
EpisodeSortOrder.DATE_OLD_NEW -> comparator = Comparator { f1: Episode?, f2: Episode? -> pubDate(f1).compareTo(pubDate(f2)) }
|
||||
EpisodeSortOrder.DATE_NEW_OLD -> comparator = Comparator { f1: Episode?, f2: Episode? -> pubDate(f2).compareTo(pubDate(f1)) }
|
||||
EpisodeSortOrder.DURATION_SHORT_LONG -> comparator = Comparator { f1: Episode?, f2: Episode? -> duration(f1).compareTo(duration(f2)) }
|
||||
EpisodeSortOrder.DURATION_LONG_SHORT -> comparator = Comparator { f1: Episode?, f2: Episode? -> duration(f2).compareTo(duration(f1)) }
|
||||
EpisodeSortOrder.EPISODE_FILENAME_A_Z -> comparator = Comparator { f1: Episode?, f2: Episode? -> itemLink(f1).compareTo(itemLink(f2)) }
|
||||
EpisodeSortOrder.EPISODE_FILENAME_Z_A -> comparator = Comparator { f1: Episode?, f2: Episode? -> itemLink(f2).compareTo(itemLink(f1)) }
|
||||
EpisodeSortOrder.PLAYED_DATE_OLD_NEW -> comparator = Comparator { f1: Episode?, f2: Episode? -> playDate(f1).compareTo(playDate(f2)) }
|
||||
EpisodeSortOrder.PLAYED_DATE_NEW_OLD -> comparator = Comparator { f1: Episode?, f2: Episode? -> playDate(f2).compareTo(playDate(f1)) }
|
||||
EpisodeSortOrder.COMPLETED_DATE_OLD_NEW -> comparator = Comparator { f1: Episode?, f2: Episode? -> completeDate(f1).compareTo(completeDate(f2)) }
|
||||
EpisodeSortOrder.COMPLETED_DATE_NEW_OLD -> comparator = Comparator { f1: Episode?, f2: Episode? -> completeDate(f2).compareTo(completeDate(f1)) }
|
||||
EpisodeSortOrder.DOWNLOAD_DATE_OLD_NEW -> comparator = Comparator { f1: Episode?, f2: Episode? -> downloadDate(f1).compareTo(downloadDate(f2)) }
|
||||
EpisodeSortOrder.DOWNLOAD_DATE_NEW_OLD -> comparator = Comparator { f1: Episode?, f2: Episode? -> downloadDate(f2).compareTo(downloadDate(f1)) }
|
||||
|
||||
EpisodeSortOrder.FEED_TITLE_A_Z -> comparator = Comparator { f1: Episode?, f2: Episode? -> feedTitle(f1).compareTo(
|
||||
feedTitle(f2)) }
|
||||
EpisodeSortOrder.FEED_TITLE_Z_A -> comparator = Comparator { f1: Episode?, f2: Episode? -> feedTitle(f2).compareTo(
|
||||
feedTitle(f1)) }
|
||||
EpisodeSortOrder.FEED_TITLE_A_Z -> comparator = Comparator { f1: Episode?, f2: Episode? -> feedTitle(f1).compareTo(feedTitle(f2)) }
|
||||
EpisodeSortOrder.FEED_TITLE_Z_A -> comparator = Comparator { f1: Episode?, f2: Episode? -> feedTitle(f2).compareTo(feedTitle(f1)) }
|
||||
EpisodeSortOrder.RANDOM -> permutor = object : Permutor<Episode> {
|
||||
override fun reorder(queue: MutableList<Episode>?) {
|
||||
if (!queue.isNullOrEmpty()) queue.shuffle()
|
||||
|
@ -67,10 +51,8 @@ object EpisodesPermutors {
|
|||
if (!queue.isNullOrEmpty()) smartShuffle(queue as MutableList<Episode?>, false)
|
||||
}
|
||||
}
|
||||
EpisodeSortOrder.SIZE_SMALL_LARGE -> comparator = Comparator { f1: Episode?, f2: Episode? -> size(f1).compareTo(
|
||||
size(f2)) }
|
||||
EpisodeSortOrder.SIZE_LARGE_SMALL -> comparator = Comparator { f1: Episode?, f2: Episode? -> size(f2).compareTo(
|
||||
size(f1)) }
|
||||
EpisodeSortOrder.SIZE_SMALL_LARGE -> comparator = Comparator { f1: Episode?, f2: Episode? -> size(f1).compareTo(size(f2)) }
|
||||
EpisodeSortOrder.SIZE_LARGE_SMALL -> comparator = Comparator { f1: Episode?, f2: Episode? -> size(f2).compareTo(size(f1)) }
|
||||
}
|
||||
if (comparator != null) {
|
||||
val comparator2: Comparator<Episode> = comparator
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
package ac.mdiq.podcini.ui.actions
|
||||
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.databinding.SwitchQueueDialogBinding
|
||||
import ac.mdiq.podcini.databinding.SelectQueueDialogBinding
|
||||
import ac.mdiq.podcini.net.download.serviceinterface.DownloadServiceInterface
|
||||
import ac.mdiq.podcini.playback.base.InTheatre.curQueue
|
||||
import ac.mdiq.podcini.storage.database.Episodes
|
||||
|
@ -22,9 +22,7 @@ import android.app.Activity
|
|||
import android.content.DialogInterface
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.AdapterView
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.RadioButton
|
||||
import androidx.annotation.PluralsRes
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
|
@ -43,7 +41,7 @@ class EpisodeMultiSelectHandler(private val activity: MainActivity, private val
|
|||
R.id.add_to_favorite_batch -> markFavorite(items, true)
|
||||
R.id.remove_favorite_batch -> markFavorite(items, false)
|
||||
R.id.add_to_queue_batch -> queueChecked(items)
|
||||
R.id.put_to_queue_batch -> putToQueue(items)
|
||||
R.id.put_in_queue_batch -> PutToQueueDialog(activity, items).show()
|
||||
R.id.remove_from_queue_batch -> removeFromQueueChecked(items)
|
||||
R.id.mark_read_batch -> {
|
||||
setPlayState(Episode.PLAYED, false, *items.toTypedArray())
|
||||
|
@ -114,33 +112,29 @@ class EpisodeMultiSelectHandler(private val activity: MainActivity, private val
|
|||
return checkedIds
|
||||
}
|
||||
|
||||
private fun putToQueue(items: List<Episode>) {
|
||||
PutToQueueDialog(activity as MainActivity, items).show()
|
||||
}
|
||||
|
||||
class PutToQueueDialog(activity: Activity, val items: List<Episode>) {
|
||||
private val activityRef: WeakReference<Activity> = WeakReference(activity)
|
||||
|
||||
fun show() {
|
||||
val activity = activityRef.get() ?: return
|
||||
val binding = SwitchQueueDialogBinding.inflate(LayoutInflater.from(activity))
|
||||
val binding = SelectQueueDialogBinding.inflate(LayoutInflater.from(activity))
|
||||
val queues = realm.query(PlayQueue::class).find()
|
||||
val queueNames = queues.map { it.name }.toTypedArray()
|
||||
val adaptor = ArrayAdapter(activity, android.R.layout.simple_spinner_item, queueNames)
|
||||
adaptor.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
val catSpinner = binding.queueSpinner
|
||||
catSpinner.setAdapter(adaptor)
|
||||
catSpinner.setSelection(adaptor.getPosition(curQueue.name))
|
||||
for (i in queues.indices) {
|
||||
val radioButton = RadioButton(activity)
|
||||
radioButton.text = queues[i].name
|
||||
radioButton.textSize = 20f
|
||||
radioButton.tag = i
|
||||
binding.radioGroup.addView(radioButton)
|
||||
}
|
||||
var toQueue: PlayQueue = curQueue
|
||||
catSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||
toQueue = unmanaged(queues[position])
|
||||
}
|
||||
override fun onNothingSelected(parent: AdapterView<*>?) {}
|
||||
binding.radioGroup.setOnCheckedChangeListener { group, checkedId ->
|
||||
val radioButton = group.findViewById<RadioButton>(checkedId)
|
||||
val selectedIndex = radioButton.tag as Int
|
||||
toQueue = unmanaged(queues[selectedIndex])
|
||||
}
|
||||
MaterialAlertDialogBuilder(activity)
|
||||
.setView(binding.root)
|
||||
.setTitle(R.string.switch_queue_label)
|
||||
.setTitle(R.string.put_in_queue_label)
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
|
||||
val queues = realm.query(PlayQueue::class).find()
|
||||
val toRemove = mutableSetOf<Long>()
|
||||
|
|
|
@ -7,6 +7,7 @@ import ac.mdiq.podcini.storage.model.Feed
|
|||
import ac.mdiq.podcini.ui.actions.menuhandler.EpisodeMenuHandler
|
||||
import ac.mdiq.podcini.ui.activity.MainActivity
|
||||
import ac.mdiq.podcini.ui.fragment.EpisodeInfoFragment
|
||||
import ac.mdiq.podcini.ui.fragment.FeedInfoFragment
|
||||
import ac.mdiq.podcini.ui.utils.ThemeUtils
|
||||
import ac.mdiq.podcini.ui.view.viewholder.EpisodeViewHolder
|
||||
import android.R.color
|
||||
|
@ -78,7 +79,8 @@ open class EpisodesAdapter(mainActivity: MainActivity)
|
|||
return EpisodeViewHolder(mainActivityRef.get()!!, parent)
|
||||
}
|
||||
|
||||
@UnstableApi override fun onBindViewHolder(holder: EpisodeViewHolder, pos: Int) {
|
||||
@UnstableApi
|
||||
override fun onBindViewHolder(holder: EpisodeViewHolder, pos: Int) {
|
||||
if (pos >= episodes.size || pos < 0) {
|
||||
beforeBindViewHolder(holder, pos)
|
||||
holder.bindDummy()
|
||||
|
@ -112,7 +114,7 @@ open class EpisodesAdapter(mainActivity: MainActivity)
|
|||
}
|
||||
holder.coverHolder.setOnClickListener {
|
||||
val activity: MainActivity? = mainActivityRef.get()
|
||||
if (!inActionMode()) activity?.loadChildFragment(EpisodeInfoFragment.newInstance(episodes[pos]))
|
||||
if (!inActionMode() && episodes[pos].feed != null) activity?.loadChildFragment(FeedInfoFragment.newInstance(episodes[pos].feed!!))
|
||||
else toggleSelection(holder.bindingAdapterPosition)
|
||||
}
|
||||
holder.itemView.setOnTouchListener(View.OnTouchListener { _: View?, e: MotionEvent ->
|
||||
|
@ -149,6 +151,11 @@ open class EpisodesAdapter(mainActivity: MainActivity)
|
|||
|
||||
protected open fun afterBindViewHolder(holder: EpisodeViewHolder, pos: Int) {}
|
||||
|
||||
override fun onViewDetachedFromWindow(holder: EpisodeViewHolder) {
|
||||
super.onViewDetachedFromWindow(holder)
|
||||
// visibleItemsPositions.remove(holder.adapterPosition)
|
||||
}
|
||||
|
||||
@UnstableApi override fun onViewRecycled(holder: EpisodeViewHolder) {
|
||||
super.onViewRecycled(holder)
|
||||
// Set all listeners to null. This is required to prevent leaking fragments that have set a listener.
|
||||
|
@ -160,6 +167,7 @@ open class EpisodesAdapter(mainActivity: MainActivity)
|
|||
holder.secondaryActionButton.setOnClickListener(null)
|
||||
holder.dragHandle.setOnTouchListener(null)
|
||||
holder.coverHolder.setOnTouchListener(null)
|
||||
holder.episode = null
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
package ac.mdiq.podcini.ui.dialog
|
||||
|
||||
import ac.mdiq.podcini.R
|
||||
import ac.mdiq.podcini.databinding.SwitchQueueDialogBinding
|
||||
import ac.mdiq.podcini.databinding.SelectQueueDialogBinding
|
||||
import ac.mdiq.podcini.playback.base.InTheatre.curQueue
|
||||
import ac.mdiq.podcini.storage.database.RealmDB.realm
|
||||
import ac.mdiq.podcini.storage.database.RealmDB.unmanaged
|
||||
import ac.mdiq.podcini.storage.database.RealmDB.upsertBlk
|
||||
import ac.mdiq.podcini.storage.model.Episode
|
||||
import ac.mdiq.podcini.storage.model.PlayQueue
|
||||
|
@ -11,10 +12,9 @@ import ac.mdiq.podcini.util.event.EventFlow
|
|||
import ac.mdiq.podcini.util.event.FlowEvent
|
||||
import android.app.Activity
|
||||
import android.content.DialogInterface
|
||||
import android.os.Debug
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.AdapterView
|
||||
import android.widget.ArrayAdapter
|
||||
import android.widget.RadioButton
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
|
@ -23,32 +23,36 @@ class SwitchQueueDialog(activity: Activity) {
|
|||
|
||||
fun show() {
|
||||
val activity = activityRef.get() ?: return
|
||||
val binding = SwitchQueueDialogBinding.inflate(LayoutInflater.from(activity))
|
||||
val binding = SelectQueueDialogBinding.inflate(LayoutInflater.from(activity))
|
||||
val queues = realm.query(PlayQueue::class).find()
|
||||
val queueNames = queues.map { it.name }.toTypedArray()
|
||||
val adaptor = ArrayAdapter(activity, android.R.layout.simple_spinner_item, queueNames)
|
||||
adaptor.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item)
|
||||
val catSpinner = binding.queueSpinner
|
||||
catSpinner.setAdapter(adaptor)
|
||||
catSpinner.setSelection(adaptor.getPosition(curQueue.name))
|
||||
var curQueue_: PlayQueue = curQueue
|
||||
catSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
|
||||
override fun onItemSelected(parent: AdapterView<*>?, view: View?, position: Int, id: Long) {
|
||||
curQueue_ = queues[position]
|
||||
}
|
||||
override fun onNothingSelected(parent: AdapterView<*>?) {}
|
||||
for (i in queues.indices) {
|
||||
val radioButton = RadioButton(activity)
|
||||
radioButton.text = queues[i].name
|
||||
radioButton.textSize = 20f
|
||||
radioButton.tag = i
|
||||
binding.radioGroup.addView(radioButton)
|
||||
if (queues[i].id == curQueue.id) binding.radioGroup.check(radioButton.id)
|
||||
}
|
||||
binding.radioGroup.setOnCheckedChangeListener { group, checkedId ->
|
||||
binding.radioGroup.check(checkedId)
|
||||
val radioButton = group.findViewById<RadioButton>(checkedId)
|
||||
val selectedIndex = radioButton.tag as Int
|
||||
curQueue_ = queues[selectedIndex]
|
||||
}
|
||||
MaterialAlertDialogBuilder(activity)
|
||||
.setView(binding.root)
|
||||
.setTitle(R.string.switch_queue_label)
|
||||
.setPositiveButton(android.R.string.ok) { _: DialogInterface?, _: Int ->
|
||||
val items = mutableListOf<Episode>()
|
||||
items.addAll(curQueue.episodes)
|
||||
items.addAll(curQueue_.episodes)
|
||||
curQueue = realm.copyFromRealm(curQueue_)
|
||||
curQueue.update()
|
||||
upsertBlk(curQueue) {}
|
||||
EventFlow.postEvent(FlowEvent.QueueEvent.switchQueue(items))
|
||||
if (curQueue_.id != curQueue.id) {
|
||||
val items = mutableListOf<Episode>()
|
||||
items.addAll(curQueue.episodes)
|
||||
items.addAll(curQueue_.episodes)
|
||||
curQueue = unmanaged(curQueue_)
|
||||
curQueue.update()
|
||||
upsertBlk(curQueue) {}
|
||||
EventFlow.postEvent(FlowEvent.QueueEvent.switchQueue(items))
|
||||
}
|
||||
}
|
||||
.setNegativeButton(R.string.cancel_label, null)
|
||||
.show()
|
||||
|
|
|
@ -245,7 +245,9 @@ import java.util.*
|
|||
val item = event.episode
|
||||
val pos: Int = EpisodeUtil.indexOfItemWithId(episodes, item.id)
|
||||
if (pos >= 0) {
|
||||
episodes[pos] = item
|
||||
episodes[pos] = unmanaged(episodes[pos])
|
||||
episodes[pos].isFavorite = item.isFavorite
|
||||
// episodes[pos] = item
|
||||
adapter.notifyItemChangedCompat(pos)
|
||||
}
|
||||
}
|
||||
|
@ -283,7 +285,7 @@ import java.util.*
|
|||
var i = 0
|
||||
val size: Int = event.episodes.size
|
||||
while (i < size) {
|
||||
val item: Episode = event.episodes[i]
|
||||
val item: Episode = event.episodes[i++]
|
||||
val pos = EpisodeUtil.indexOfItemWithId(episodes, item.id)
|
||||
if (pos >= 0) {
|
||||
episodes.removeAt(pos)
|
||||
|
@ -295,7 +297,6 @@ import java.util.*
|
|||
// adapter.notifyItemRemoved(pos)
|
||||
}
|
||||
}
|
||||
i++
|
||||
}
|
||||
// have to do this as adapter.notifyItemRemoved(pos) when pos == 0 causes crash
|
||||
if (size > 0) {
|
||||
|
|
|
@ -336,7 +336,7 @@ import java.util.concurrent.Semaphore
|
|||
var i = 0
|
||||
val size: Int = event.episodes.size
|
||||
while (i < size) {
|
||||
val item = event.episodes[i]
|
||||
val item = event.episodes[i++]
|
||||
if (item.feedId != feed!!.id) continue
|
||||
val pos: Int = EpisodeUtil.indexOfItemWithId(episodes, item.id)
|
||||
if (pos >= 0) {
|
||||
|
@ -344,7 +344,6 @@ import java.util.concurrent.Semaphore
|
|||
episodes[pos] = item
|
||||
adapter.notifyItemChangedCompat(pos)
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -353,16 +352,10 @@ import java.util.concurrent.Semaphore
|
|||
var i = 0
|
||||
val size: Int = event.episodes.size
|
||||
while (i < size) {
|
||||
val item = event.episodes[i]
|
||||
val item = event.episodes[i++]
|
||||
if (item.feedId != feed!!.id) continue
|
||||
val pos: Int = EpisodeUtil.indexOfItemWithId(episodes, item.id)
|
||||
if (pos >= 0) {
|
||||
// episodes[pos] = item
|
||||
adapter.notifyItemChangedCompat(pos)
|
||||
// episodes[pos].playState = item.playState
|
||||
// adapter.notifyItemChangedCompat(pos)
|
||||
}
|
||||
i++
|
||||
adapter.notifyDataSetChanged()
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -396,7 +389,9 @@ import java.util.concurrent.Semaphore
|
|||
val item = event.episode
|
||||
val pos: Int = EpisodeUtil.indexOfItemWithId(episodes, item.id)
|
||||
if (pos >= 0) {
|
||||
episodes[pos] = item
|
||||
episodes[pos] = unmanaged(episodes[pos])
|
||||
episodes[pos].isFavorite = item.isFavorite
|
||||
// episodes[pos] = item
|
||||
adapter.notifyItemChangedCompat(pos)
|
||||
}
|
||||
}
|
||||
|
@ -626,7 +621,7 @@ import java.util.concurrent.Semaphore
|
|||
lifecycleScope.launch {
|
||||
try {
|
||||
feed = withContext(Dispatchers.IO) {
|
||||
val feed_ = getFeed(feedID, fromDB = true)
|
||||
val feed_ = getFeed(feedID)
|
||||
if (feed_ != null) {
|
||||
Logd(TAG, "loadItems feed_.episodes.size: ${feed_.episodes.size}")
|
||||
episodes.clear()
|
||||
|
|
|
@ -116,6 +116,10 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
|||
val fragment: Fragment = FeedEpisodesFragment.newInstance(feed.id)
|
||||
(activity as MainActivity).loadChildFragment(fragment)
|
||||
}
|
||||
binding.header.butShowSettings.setOnClickListener {
|
||||
val fragment = FeedSettingsFragment.newInstance(feed)
|
||||
(activity as MainActivity).loadChildFragment(fragment, TransitionEffect.SLIDE)
|
||||
}
|
||||
|
||||
binding.btnvRelatedFeeds.setOnClickListener {
|
||||
val fragment = OnlineSearchFragment.newInstance(CombinedSearcher::class.java, "${binding.header.txtvAuthor.text} podcasts")
|
||||
|
|
|
@ -112,8 +112,8 @@ class FeedSettingsFragment : Fragment() {
|
|||
setupAuthentificationPreference()
|
||||
updateAutoDownloadPolicy()
|
||||
setupAutoDownloadPolicy()
|
||||
updateAutoDownloadCacheSize()
|
||||
setupAutoDownloadCacheSize()
|
||||
setupCountingPlayedPreference()
|
||||
setupAutoDownloadFilterPreference()
|
||||
setupPlaybackSpeedPreference()
|
||||
setupFeedAutoSkipPreference()
|
||||
|
@ -203,20 +203,30 @@ class FeedSettingsFragment : Fragment() {
|
|||
}
|
||||
}
|
||||
@UnstableApi private fun setupAutoDownloadCacheSize() {
|
||||
val cachePref = findPreference<Preference>(Prefs.feedEpisodeCacheSize.name)
|
||||
cachePref!!.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any? ->
|
||||
val cachePref = findPreference<ListPreference>(Prefs.feedEpisodeCacheSize.name)
|
||||
cachePref!!.value = feedPrefs!!.autoDLMaxEpisodes.toString()
|
||||
cachePref.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any? ->
|
||||
if (feedPrefs != null) {
|
||||
feedPrefs!!.autoDLMaxEpisodes = newValue.toString().toInt()
|
||||
cachePref.value = feedPrefs!!.autoDLMaxEpisodes.toString()
|
||||
persistFeedPreferences(feed!!)
|
||||
updateAutoDownloadCacheSize()
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
private fun updateAutoDownloadCacheSize() {
|
||||
@OptIn(UnstableApi::class) private fun setupCountingPlayedPreference() {
|
||||
if (feedPrefs == null) return
|
||||
val cachePref = findPreference<ListPreference>(Prefs.feedEpisodeCacheSize.name)
|
||||
cachePref!!.value = feedPrefs!!.autoDLMaxEpisodes.toString()
|
||||
val pref = findPreference<SwitchPreferenceCompat>(Prefs.countingPlayed.name)
|
||||
pref!!.isChecked = feedPrefs!!.countingPlayed
|
||||
pref.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
|
||||
val checked = newValue == true
|
||||
if (feedPrefs != null) {
|
||||
feedPrefs!!.countingPlayed = checked
|
||||
persistFeedPreferences(feed!!)
|
||||
}
|
||||
pref.isChecked = checked
|
||||
false
|
||||
}
|
||||
}
|
||||
private fun setupAutoDownloadFilterPreference() {
|
||||
findPreference<Preference>(Prefs.episodeInclusiveFilter.name)!!.onPreferenceClickListener = Preference.OnPreferenceClickListener {
|
||||
|
@ -332,7 +342,7 @@ class FeedSettingsFragment : Fragment() {
|
|||
}
|
||||
@OptIn(UnstableApi::class) private fun setupKeepUpdatedPreference() {
|
||||
if (feedPrefs == null) return
|
||||
val pref = findPreference<SwitchPreferenceCompat>("keepUpdated")
|
||||
val pref = findPreference<SwitchPreferenceCompat>(Prefs.keepUpdated.name)
|
||||
pref!!.isChecked = feedPrefs!!.keepUpdated
|
||||
pref.onPreferenceChangeListener = Preference.OnPreferenceChangeListener { _: Preference?, newValue: Any ->
|
||||
val checked = newValue == true
|
||||
|
@ -351,9 +361,10 @@ class FeedSettingsFragment : Fragment() {
|
|||
autodl.isEnabled = false
|
||||
autodl.setSummary(R.string.auto_download_disabled_globally)
|
||||
findPreference<Preference>(Prefs.feedAutoDownloadPolicy.name)!!.isEnabled = false
|
||||
findPreference<Preference>(Prefs.feedEpisodeCacheSize.name)!!.isEnabled = false
|
||||
findPreference<Preference>(Prefs.countingPlayed.name)!!.isEnabled = false
|
||||
findPreference<Preference>(Prefs.episodeInclusiveFilter.name)!!.isEnabled = false
|
||||
findPreference<Preference>(Prefs.episodeExclusiveFilter.name)!!.isEnabled = false
|
||||
findPreference<Preference>(Prefs.feedEpisodeCacheSize.name)!!.isEnabled = false
|
||||
}
|
||||
}
|
||||
@OptIn(UnstableApi::class) private fun setupAutoDownloadPreference() {
|
||||
|
@ -380,9 +391,10 @@ class FeedSettingsFragment : Fragment() {
|
|||
if (feed?.preferences != null) {
|
||||
val enabled = feed!!.preferences!!.autoDownload && isEnableAutodownload
|
||||
findPreference<Preference>(Prefs.feedAutoDownloadPolicy.name)!!.isEnabled = enabled
|
||||
findPreference<Preference>(Prefs.feedEpisodeCacheSize.name)!!.isEnabled = enabled
|
||||
findPreference<Preference>(Prefs.countingPlayed.name)!!.isEnabled = enabled
|
||||
findPreference<Preference>(Prefs.episodeInclusiveFilter.name)!!.isEnabled = enabled
|
||||
findPreference<Preference>(Prefs.episodeExclusiveFilter.name)!!.isEnabled = enabled
|
||||
findPreference<Preference>(Prefs.feedEpisodeCacheSize.name)!!.isEnabled = enabled
|
||||
}
|
||||
}
|
||||
private fun setupTags() {
|
||||
|
@ -397,6 +409,7 @@ class FeedSettingsFragment : Fragment() {
|
|||
|
||||
private enum class Prefs {
|
||||
feedSettingsScreen,
|
||||
keepUpdated,
|
||||
authentication,
|
||||
autoDelete,
|
||||
feedPlaybackSpeed,
|
||||
|
@ -404,10 +417,11 @@ class FeedSettingsFragment : Fragment() {
|
|||
tags,
|
||||
autoDownloadCategory,
|
||||
autoDownload,
|
||||
feedAutoDownloadPolicy,
|
||||
feedEpisodeCacheSize,
|
||||
countingPlayed,
|
||||
episodeInclusiveFilter,
|
||||
episodeExclusiveFilter,
|
||||
feedEpisodeCacheSize,
|
||||
feedAutoDownloadPolicy
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
|
|
@ -306,6 +306,7 @@ import java.util.*
|
|||
val item = event.episode
|
||||
val pos: Int = EpisodeUtil.indexOfItemWithId(queueItems, item.id)
|
||||
if (pos >= 0) {
|
||||
queueItems[pos] = unmanaged(queueItems[pos])
|
||||
queueItems[pos].isFavorite = item.isFavorite
|
||||
adapter?.notifyItemChangedCompat(pos)
|
||||
}
|
||||
|
@ -320,14 +321,13 @@ import java.util.*
|
|||
var i = 0
|
||||
val size: Int = event.episodes.size
|
||||
while (i < size) {
|
||||
val item: Episode = event.episodes[i]
|
||||
val item: Episode = event.episodes[i++]
|
||||
val pos: Int = EpisodeUtil.indexOfItemWithId(queueItems, item.id)
|
||||
if (pos >= 0) {
|
||||
queueItems[pos] = item
|
||||
adapter?.notifyItemChangedCompat(pos)
|
||||
refreshInfoBar()
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -418,6 +418,7 @@ import java.util.*
|
|||
val keepSorted: Boolean = isQueueKeepSorted
|
||||
toolbar.menu?.findItem(R.id.queue_lock)?.setChecked(isQueueLocked)
|
||||
toolbar.menu?.findItem(R.id.queue_lock)?.setVisible(!keepSorted)
|
||||
toolbar.menu.findItem(R.id.switch_queue).setVisible(false)
|
||||
}
|
||||
|
||||
@UnstableApi override fun onMenuItemClick(item: MenuItem): Boolean {
|
||||
|
@ -437,7 +438,7 @@ import java.util.*
|
|||
conDialog.createNewDialog().show()
|
||||
}
|
||||
R.id.action_search -> (activity as MainActivity).loadChildFragment(SearchFragment.newInstance())
|
||||
R.id.switch_queue -> SwitchQueueDialog(activity as MainActivity).show()
|
||||
// R.id.switch_queue -> SwitchQueueDialog(activity as MainActivity).show()
|
||||
else -> return false
|
||||
}
|
||||
return true
|
||||
|
|
|
@ -278,13 +278,12 @@ import java.lang.ref.WeakReference
|
|||
var i = 0
|
||||
val size: Int = event.episodes.size
|
||||
while (i < size) {
|
||||
val item: Episode = event.episodes[i]
|
||||
val item: Episode = event.episodes[i++]
|
||||
val pos: Int = EpisodeUtil.indexOfItemWithId(results, item.id)
|
||||
if (pos >= 0) {
|
||||
results[pos] = item
|
||||
adapter.notifyItemChangedCompat(pos)
|
||||
}
|
||||
i++
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -14,8 +14,10 @@ import ac.mdiq.podcini.ui.actions.menuhandler.FeedMenuHandler
|
|||
import ac.mdiq.podcini.ui.actions.menuhandler.MenuItemUtils
|
||||
import ac.mdiq.podcini.ui.activity.MainActivity
|
||||
import ac.mdiq.podcini.ui.adapter.SelectableAdapter
|
||||
import ac.mdiq.podcini.ui.dialog.*
|
||||
import ac.mdiq.podcini.ui.fragment.FeedEpisodesFragment.FeedEpisodeFilterDialog
|
||||
import ac.mdiq.podcini.ui.dialog.FeedFilterDialog
|
||||
import ac.mdiq.podcini.ui.dialog.FeedSortDialog
|
||||
import ac.mdiq.podcini.ui.dialog.RemoveFeedDialog
|
||||
import ac.mdiq.podcini.ui.dialog.TagSettingsDialog
|
||||
import ac.mdiq.podcini.ui.utils.CoverLoader
|
||||
import ac.mdiq.podcini.ui.utils.EmptyViewHandler
|
||||
import ac.mdiq.podcini.ui.utils.LiftOnScrollListener
|
||||
|
@ -48,13 +50,13 @@ import com.google.android.material.snackbar.Snackbar
|
|||
import com.leinardi.android.speeddial.SpeedDialActionItem
|
||||
import com.leinardi.android.speeddial.SpeedDialView
|
||||
import io.realm.kotlin.query.RealmResults
|
||||
import io.realm.kotlin.query.Sort
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.text.NumberFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
/**
|
||||
|
@ -238,24 +240,6 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec
|
|||
adapter.setItems(feedListFiltered)
|
||||
}
|
||||
|
||||
// fun filterOnTag() {
|
||||
// when (tagFilterIndex) {
|
||||
// 1 -> feedListFiltered = feedList // All feeds
|
||||
// 0 -> feedListFiltered = feedList.filter { // feeds without tag
|
||||
// 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?.tags?.contains(tag) ?: false
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// binding.count.text = feedListFiltered.size.toString() + " / " + feedList.size.toString()
|
||||
// adapter.setItems(feedListFiltered)
|
||||
// }
|
||||
|
||||
private fun resetTags() {
|
||||
tags.clear()
|
||||
tags.add("Untagged")
|
||||
|
@ -368,17 +352,23 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec
|
|||
val tagsQueryStr = queryStringOfTags()
|
||||
val fQueryStr = if (tagsQueryStr.isEmpty()) FeedFilter(feedsFilter).queryString() else FeedFilter(feedsFilter).queryString() + " AND " + tagsQueryStr
|
||||
Logd(TAG, "sortFeeds() called $feedsFilter $fQueryStr")
|
||||
val feedIds = getFeedList(fQueryStr).map { id }
|
||||
val feedList_ = getFeedList(fQueryStr).toMutableList()
|
||||
val feeds_ = feedList_
|
||||
val feedOrder = feedOrderBy
|
||||
val dir = 1 - 2*feedOrderDir // get from 0, 1 to 1, -1
|
||||
val comparator: Comparator<Feed> = when (feedOrder) {
|
||||
FeedSortOrder.UNPLAYED_NEW_OLD.index -> {
|
||||
val queryString = "feedId IN $0 AND (playState == ${Episode.NEW} OR playState == ${Episode.UNPLAYED})"
|
||||
val episodes = realm.query(Episode::class).query(queryString, feedIds).find()
|
||||
val counterMap = counterMap(episodes)
|
||||
val queryString = "feedId == $0 AND (playState == ${Episode.NEW} OR playState == ${Episode.UNPLAYED})"
|
||||
val counterMap: MutableMap<Long, Long> = mutableMapOf()
|
||||
for (f in feeds_) {
|
||||
val c = realm.query(Episode::class).query(queryString, f.id).count().find()
|
||||
counterMap[f.id] = c
|
||||
f.sortInfo = c.toString() + " unplayed"
|
||||
}
|
||||
comparator(counterMap, dir)
|
||||
}
|
||||
FeedSortOrder.ALPHABETIC_A_Z.index -> {
|
||||
for (f in feeds_) f.sortInfo = ""
|
||||
Comparator { lhs: Feed, rhs: Feed ->
|
||||
val t1 = lhs.title
|
||||
val t2 = rhs.title
|
||||
|
@ -390,78 +380,83 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec
|
|||
}
|
||||
}
|
||||
FeedSortOrder.MOST_PLAYED.index -> {
|
||||
val queryString = "feedId IN $0 AND playState == ${Episode.PLAYED}"
|
||||
val episodes = realm.query(Episode::class).query(queryString, feedIds).find()
|
||||
val counterMap = counterMap(episodes)
|
||||
val queryString = "feedId == $0 AND playState == ${Episode.PLAYED}"
|
||||
val counterMap: MutableMap<Long, Long> = mutableMapOf()
|
||||
for (f in feeds_) {
|
||||
val c = realm.query(Episode::class).query(queryString, f.id).count().find()
|
||||
counterMap[f.id] = c
|
||||
f.sortInfo = c.toString() + " played"
|
||||
}
|
||||
comparator(counterMap, dir)
|
||||
}
|
||||
FeedSortOrder.LAST_UPDATED_NEW_OLD.index -> {
|
||||
val queryString = "feedId IN $0"
|
||||
val episodes = realm.query(Episode::class, queryString, feedIds).sort("pubDate", Sort.DESCENDING).find()
|
||||
val queryString = "feedId == $0 SORT(pubDate DESC)"
|
||||
val counterMap: MutableMap<Long, Long> = mutableMapOf()
|
||||
for (episode in episodes) {
|
||||
val feedId = episode.feedId ?: continue
|
||||
val pDateOld = counterMap[feedId] ?: 0
|
||||
if (pDateOld < episode.pubDate) counterMap[feedId] = episode.pubDate
|
||||
for (f in feeds_) {
|
||||
val d = realm.query(Episode::class).query(queryString, f.id).first().find()?.pubDate ?: 0L
|
||||
counterMap[f.id] = d
|
||||
val dateFormat = SimpleDateFormat("yy-MM-dd HH:mm", Locale.getDefault())
|
||||
f.sortInfo = "Updated on " + dateFormat.format(Date(d))
|
||||
}
|
||||
comparator(counterMap, dir)
|
||||
}
|
||||
FeedSortOrder.LAST_DOWNLOAD_NEW_OLD.index -> {
|
||||
val queryString = "feedId IN $0"
|
||||
val episodes = realm.query(Episode::class, queryString, feedIds).sort("media.downloadTime", Sort.DESCENDING).find()
|
||||
val queryString = "feedId == $0 SORT(media.downloadTime DESC)"
|
||||
val counterMap: MutableMap<Long, Long> = mutableMapOf()
|
||||
for (episode in episodes) {
|
||||
val feedId = episode.feedId ?: continue
|
||||
val pDownloadOld = counterMap[feedId] ?: 0
|
||||
if (pDownloadOld < (episode.media?.downloadTime?:0)) counterMap[feedId] = episode.media?.downloadTime ?: 0
|
||||
for (f in feeds_) {
|
||||
val d = realm.query(Episode::class).query(queryString, f.id).first().find()?.media?.downloadTime ?: 0L
|
||||
counterMap[f.id] = d
|
||||
val dateFormat = SimpleDateFormat("yy-MM-dd HH:mm", Locale.getDefault())
|
||||
f.sortInfo = "Downloaded on " + dateFormat.format(Date(d))
|
||||
}
|
||||
comparator(counterMap, dir)
|
||||
}
|
||||
FeedSortOrder.LAST_UPDATED_UNPLAYED_NEW_OLD.index -> {
|
||||
val queryString = "feedId IN $0 AND (playState == ${Episode.NEW} OR playState == ${Episode.UNPLAYED})"
|
||||
val episodes = realm.query(Episode::class).query(queryString, feedIds).find()
|
||||
val queryString = "feedId == $0 AND (playState == ${Episode.NEW} OR playState == ${Episode.UNPLAYED}) SORT(pubDate DESC)"
|
||||
val counterMap: MutableMap<Long, Long> = mutableMapOf()
|
||||
for (episode in episodes) {
|
||||
val feedId = episode.feedId ?: continue
|
||||
val pDateOld = counterMap[feedId] ?: 0
|
||||
if (pDateOld < episode.pubDate) counterMap[feedId] = episode.pubDate
|
||||
for (f in feeds_) {
|
||||
val d = realm.query(Episode::class).query(queryString, f.id).first().find()?.pubDate ?: 0L
|
||||
counterMap[f.id] = d
|
||||
val dateFormat = SimpleDateFormat("yy-MM-dd HH:mm", Locale.getDefault())
|
||||
f.sortInfo = "Unplayed since " + dateFormat.format(Date(d))
|
||||
}
|
||||
comparator(counterMap, dir)
|
||||
}
|
||||
FeedSortOrder.MOST_DOWNLOADED.index -> {
|
||||
val queryString = "feedId IN $0 AND media.downloaded == true"
|
||||
val episodes = realm.query(Episode::class).query(queryString, feedIds).find()
|
||||
val counterMap = counterMap(episodes)
|
||||
val queryString = "feedId == $0 AND media.downloaded == true"
|
||||
val counterMap: MutableMap<Long, Long> = mutableMapOf()
|
||||
for (f in feeds_) {
|
||||
val c = realm.query(Episode::class).query(queryString, f.id).count().find()
|
||||
counterMap[f.id] = c
|
||||
f.sortInfo = c.toString() + " downloaded"
|
||||
}
|
||||
comparator(counterMap, dir)
|
||||
}
|
||||
FeedSortOrder.MOST_DOWNLOADED_UNPLAYED.index -> {
|
||||
val queryString = "feedId IN $0 AND (playState == ${Episode.NEW} OR playState == ${Episode.UNPLAYED}) AND media.downloaded == true"
|
||||
val episodes = realm.query(Episode::class).query(queryString, feedIds).find()
|
||||
val counterMap = counterMap(episodes)
|
||||
val queryString = "feedId == $0 AND (playState == ${Episode.NEW} OR playState == ${Episode.UNPLAYED}) AND media.downloaded == true"
|
||||
val counterMap: MutableMap<Long, Long> = mutableMapOf()
|
||||
for (f in feeds_) {
|
||||
val c = realm.query(Episode::class).query(queryString, f.id).count().find()
|
||||
counterMap[f.id] = c
|
||||
f.sortInfo = c.toString() + " downloaded unplayed"
|
||||
}
|
||||
comparator(counterMap, dir)
|
||||
}
|
||||
// doing FEED_ORDER_NEW
|
||||
else -> {
|
||||
val queryString = "feedId IN $0 AND playState == ${Episode.NEW}"
|
||||
val episodes = realm.query(Episode::class).query(queryString, feedIds).find()
|
||||
val counterMap = counterMap(episodes)
|
||||
val queryString = "feedId == $0 AND playState == ${Episode.NEW}"
|
||||
val counterMap: MutableMap<Long, Long> = mutableMapOf()
|
||||
for (f in feeds_) {
|
||||
val c = realm.query(Episode::class).query(queryString, f.id).count().find()
|
||||
counterMap[f.id] = c
|
||||
f.sortInfo = c.toString() + " new"
|
||||
}
|
||||
comparator(counterMap, dir)
|
||||
}
|
||||
}
|
||||
val feedList_ = getFeedList(fQueryStr).toMutableList()
|
||||
synchronized(feedList_) { feedList = feedList_.sortedWith(comparator).toMutableList() }
|
||||
}
|
||||
|
||||
private fun counterMap(episodes: RealmResults<Episode>): Map<Long, Long> {
|
||||
val counterMap: MutableMap<Long, Long> = mutableMapOf()
|
||||
for (episode in episodes) {
|
||||
val feedId = episode.feedId ?: continue
|
||||
val count = counterMap[feedId] ?: 0
|
||||
counterMap[feedId] = count + 1
|
||||
}
|
||||
return counterMap
|
||||
}
|
||||
|
||||
private fun comparator(counterMap: Map<Long, Long>, dir: Int): Comparator<Feed> {
|
||||
return Comparator { lhs: Feed, rhs: Feed ->
|
||||
val counterLhs = counterMap[lhs.id]?:0
|
||||
|
@ -766,7 +761,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec
|
|||
|
||||
private inner class ViewHolderExpanded(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
val binding = SubscriptionItemBinding.bind(itemView)
|
||||
val count: TextView = binding.countLabel
|
||||
val count: TextView = binding.episodeCount
|
||||
val coverImage: ImageView = binding.coverImage
|
||||
val infoCard: LinearLayout = binding.infoCard
|
||||
val selectView: FrameLayout = binding.selectContainer
|
||||
|
@ -778,6 +773,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec
|
|||
selectView.background = drawable // Setting this in XML crashes API <= 21
|
||||
binding.titleLabel.text = feed.title
|
||||
binding.producerLabel.text = feed.author
|
||||
binding.sortInfo.text = feed.sortInfo
|
||||
coverImage.contentDescription = feed.title
|
||||
coverImage.setImageDrawable(null)
|
||||
|
||||
|
@ -809,7 +805,7 @@ class SubscriptionsFragment : Fragment(), Toolbar.OnMenuItemClickListener, Selec
|
|||
private inner class ViewHolderBrief(itemView: View) : RecyclerView.ViewHolder(itemView) {
|
||||
val binding = SubscriptionItemBriefBinding.bind(itemView)
|
||||
private val title = binding.titleLabel
|
||||
val count: TextView = binding.countLabel
|
||||
val count: TextView = binding.episodeCount
|
||||
|
||||
val coverImage: ImageView = binding.coverImage
|
||||
val selectView: FrameLayout = binding.selectContainer
|
||||
|
|
|
@ -35,6 +35,21 @@
|
|||
android:layout_height="wrap_content"
|
||||
android:text="@string/episodes_label"/>
|
||||
|
||||
<View
|
||||
android:layout_width="15dp"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/butShowSettings"
|
||||
android:layout_width="40dp"
|
||||
android:layout_height="40dp"
|
||||
android:background="?attr/selectableItemBackground"
|
||||
android:contentDescription="@string/show_feed_settings_label"
|
||||
android:scaleType="fitXY"
|
||||
android:padding="3dp"
|
||||
app:srcCompat="@drawable/ic_settings_white"
|
||||
tools:visibility="visible" />
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/select_queue_dialog"
|
||||
android:padding="16dp">
|
||||
|
||||
<RadioGroup
|
||||
android:id="@+id/radio_group"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="30dp">
|
||||
|
||||
</RadioGroup>
|
||||
|
||||
</LinearLayout>
|
|
@ -57,13 +57,29 @@
|
|||
android:lines="1"
|
||||
android:text="Author" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/countLabel"
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:lines="1"
|
||||
android:text="0 episodes" />
|
||||
android:orientation="horizontal">
|
||||
<TextView
|
||||
android:id="@+id/episodeCount"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:text="0 episodes" />
|
||||
|
||||
<View
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_weight="1"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/sortInfo"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:text="info" />
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
|
|
|
@ -64,7 +64,7 @@
|
|||
tools:text="@sample/episodes.json/data/title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/countLabel"
|
||||
android:id="@+id/episodeCount"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:text="3"
|
||||
|
|
|
@ -1,20 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:id="@+id/switch_queue_dialog"
|
||||
android:padding="16dp">
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/queue_spinner"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:dropDownWidth="200dp"
|
||||
android:padding="8dp"
|
||||
android:paddingBottom="20dp"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textSize="@dimen/text_size_micro"
|
||||
android:spinnerMode="dropdown"/>
|
||||
|
||||
</LinearLayout>
|
|
@ -38,9 +38,9 @@
|
|||
android:title="@string/add_to_queue_label" />
|
||||
|
||||
<item
|
||||
android:id="@+id/put_to_queue_batch"
|
||||
android:id="@+id/put_in_queue_batch"
|
||||
android:icon="@drawable/ic_playlist_play"
|
||||
android:title="@string/put_to_queue_label" />
|
||||
android:title="@string/put_in_queue_label" />
|
||||
|
||||
<item
|
||||
android:id="@+id/add_to_favorite_batch"
|
||||
|
|
|
@ -146,7 +146,7 @@
|
|||
<string name="feed_auto_download_newer">Newest unplayed</string>
|
||||
<string name="feed_auto_download_older">Oldest unplayed</string>
|
||||
|
||||
<string name="put_to_queue_label">Put to queue</string>
|
||||
<string name="put_in_queue_label">Put in queue</string>
|
||||
|
||||
<string name="feed_new_episodes_action_nothing">Nothing</string>
|
||||
<string name="episode_cleanup_never">Never</string>
|
||||
|
@ -488,6 +488,8 @@
|
|||
<string name="pref_automatic_download_on_battery_sum">Allow automatic download when the battery is not charging</string>
|
||||
<string name="pref_episode_cache_title">Episode cache</string>
|
||||
<string name="pref_episode_cache_summary">Total number of downloaded episodes cached on the device. Automatic download will be suspended if this number is reached.</string>
|
||||
<string name="pref_auto_download_counting_played_title">Counting played</string>
|
||||
<string name="pref_auto_download_counting_played_summary">Set if downloaded episodes already played count into the episode cache</string>
|
||||
<string name="pref_episode_cover_title">Use episode cover</string>
|
||||
<string name="pref_episode_cover_summary">Use the episode specific cover in lists whenever available. If unchecked, the app will always use the podcast cover image.</string>
|
||||
<string name="pref_show_remain_time_title">Show remaining time</string>
|
||||
|
|
|
@ -67,6 +67,10 @@
|
|||
android:title="@string/pref_episode_cache_title"
|
||||
android:summary="@string/pref_episode_cache_summary"
|
||||
android:entryValues="@array/feed_episode_cache_size_values"/>
|
||||
<SwitchPreferenceCompat
|
||||
android:key="countingPlayed"
|
||||
android:summary="@string/pref_auto_download_counting_played_summary"
|
||||
android:title="@string/pref_auto_download_counting_played_title" />
|
||||
<Preference
|
||||
android:key="episodeInclusiveFilter"
|
||||
android:summary="@string/episode_filters_description"
|
||||
|
|
13
changelog.md
13
changelog.md
|
@ -1,3 +1,16 @@
|
|||
# 6.1.3
|
||||
|
||||
* added feed setting in the header of FeedInfo view
|
||||
* in all episodes list views, click on an episode image brings up the FeedInfo view
|
||||
* added countingPlayed for auto download in feed setting, when set to false, downloaded episodes that have been played are not counted as downloaded to the limit of auto-download
|
||||
* fixed possible mal-function of feed sorting
|
||||
* improved feed sorting efficiency
|
||||
* improved feed update efficiency
|
||||
* in Subscriptions view added sorting info on every feed (List Layout only)
|
||||
* "Put to queue" text changed to "Put in queue"
|
||||
* in dialogs "Put in queue" and "Switch queue" the spinner is changed to lisst of radio buttons
|
||||
* likely fixed hang when switching queue sometimes
|
||||
|
||||
# 6.1.2
|
||||
|
||||
* fixed crash issue when setting the inclusive or exclusive filters in feed auto-download setting
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
|
||||
Version 6.1.3 brings several changes:
|
||||
|
||||
* added feed setting in the header of FeedInfo view
|
||||
* in all episodes list views, click on an episode image brings up the FeedInfo view
|
||||
* added countingPlayed for auto download in feed setting, when set to false, downloaded episodes that have been played are not counted as downloaded to the limit of auto-download
|
||||
* fixed possible mal-function of feed sorting
|
||||
* improved feed sorting efficiency
|
||||
* improved feed update efficiency
|
||||
* in Subscriptions view added sorting info on every feed (List Layout only)
|
||||
* "Put to queue" text changed to "Put in queue"
|
||||
* in dialogs "Put in queue" and "Switch queue" the spinner is changed to lisst of radio buttons
|
||||
* likely fixed hang when switching queue sometimes
|
Loading…
Reference in New Issue