6.7.2 commit
This commit is contained in:
parent
daeee36985
commit
bea7ce74a7
|
@ -12,7 +12,7 @@ An open source podcast instrument, attuned to Puccini ![Puccini](./images/Puccin
|
||||||
[<img src="./images/external/getItf-droid.png" alt="F-Droid" height="50">](https://f-droid.org/packages/ac.mdiq.podcini.R/)
|
[<img src="./images/external/getItf-droid.png" alt="F-Droid" height="50">](https://f-droid.org/packages/ac.mdiq.podcini.R/)
|
||||||
[<img src="./images/external/amazon.png" alt="Amazon" height="40">](https://www.amazon.com/%E8%B4%BE%E8%A5%BF%E6%9E%97-Podcini-R/dp/B0D9WR8P13)
|
[<img src="./images/external/amazon.png" alt="Amazon" height="40">](https://www.amazon.com/%E8%B4%BE%E8%A5%BF%E6%9E%97-Podcini-R/dp/B0D9WR8P13)
|
||||||
|
|
||||||
#### Podcini.R 6.6 introduces the powerful feature of synthetic podcasts, enables the receiving/handling shared single media as well as playlist from Youtube and YT Music, for more see the Youtube section below or the changelogs.
|
#### Podcini.R 6.6 introduces the powerful feature of synthetic podcasts, enables the receiving/handling shared single media as well as playlist and podcast from Youtube and YT Music, for more see the Youtube section below or the changelogs.
|
||||||
That means finally: [Nessun dorma](https://www.youtube.com/watch?v=cWc7vYjgnTs)
|
That means finally: [Nessun dorma](https://www.youtube.com/watch?v=cWc7vYjgnTs)
|
||||||
#### Podcini.R version 6.5 as a major step forward brings YouTube channels in the app. They can be searched, received from share, subscribed and played from within Podcini. For more see the Youtube section below or the changelogs
|
#### Podcini.R version 6.5 as a major step forward brings YouTube channels in the app. They can be searched, received from share, subscribed and played from within Podcini. For more see the Youtube section below or the changelogs
|
||||||
#### For Podcini to show up on car's HUD with Android Auto, please read AnroidAuto.md for instructions.
|
#### For Podcini to show up on car's HUD with Android Auto, please read AnroidAuto.md for instructions.
|
||||||
|
@ -131,7 +131,7 @@ While podcast subscriptions' OPML files (from AntennaPod or any other sources) c
|
||||||
|
|
||||||
* Youtube channels can be searched in podcast search view, can also be shared from other apps (such as Youtube) to Podcini
|
* Youtube channels can be searched in podcast search view, can also be shared from other apps (such as Youtube) to Podcini
|
||||||
* Youtube channels can be subscribed as normal podcasts
|
* Youtube channels can be subscribed as normal podcasts
|
||||||
* Playlists on Youtube or Youtube Music can be shared to Podcini, and then can be subscribed in similar fashion as the channels
|
* Playlists and podcasts on Youtube or Youtube Music can be shared to Podcini, and then can be subscribed in similar fashion as the channels
|
||||||
* Single media from Youtube or Youtube Music can also be shared from other apps, can be accepted as including video or audio only, are added to synthetic podcasts such as "Youtube Syndicate"
|
* Single media from Youtube or Youtube Music can also be shared from other apps, can be accepted as including video or audio only, are added to synthetic podcasts such as "Youtube Syndicate"
|
||||||
* All the media from Youtube or Youtube Music can be played (only streamed) with video in fullscreen and in window modes or in audio only mode in the background
|
* All the media from Youtube or Youtube Music can be played (only streamed) with video in fullscreen and in window modes or in audio only mode in the background
|
||||||
* These media are played with the lowest video quality and highest audio quality
|
* These media are played with the lowest video quality and highest audio quality
|
||||||
|
|
|
@ -19,8 +19,8 @@ composeCompiler {
|
||||||
android {
|
android {
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdk 24
|
minSdk 24
|
||||||
compileSdk 34
|
compileSdk 35
|
||||||
targetSdk 34
|
targetSdk 35
|
||||||
|
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = '17'
|
jvmTarget = '17'
|
||||||
|
@ -31,8 +31,8 @@ android {
|
||||||
testApplicationId "ac.mdiq.podcini.tests"
|
testApplicationId "ac.mdiq.podcini.tests"
|
||||||
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
|
||||||
|
|
||||||
versionCode 3020254
|
versionCode 3020255
|
||||||
versionName "6.7.1"
|
versionName "6.7.2"
|
||||||
|
|
||||||
applicationId "ac.mdiq.podcini.R"
|
applicationId "ac.mdiq.podcini.R"
|
||||||
def commit = ""
|
def commit = ""
|
||||||
|
@ -174,12 +174,12 @@ dependencies {
|
||||||
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs_nio:2.1.2'
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs_nio:2.1.2'
|
||||||
implementation 'com.github.XilinJia.vistaguide:VistaGuide:lv0.24.2.6'
|
implementation 'com.github.XilinJia.vistaguide:VistaGuide:lv0.24.2.6'
|
||||||
|
|
||||||
def composeBom = platform('androidx.compose:compose-bom:2024.09.01')
|
def composeBom = platform('androidx.compose:compose-bom:2024.09.02')
|
||||||
implementation composeBom
|
implementation composeBom
|
||||||
androidTestImplementation composeBom
|
androidTestImplementation composeBom
|
||||||
implementation 'androidx.compose.material:material:1.7.1'
|
implementation 'androidx.compose.material:material:1.7.2'
|
||||||
implementation 'androidx.compose.ui:ui-tooling-preview:1.7.1'
|
implementation 'androidx.compose.ui:ui-tooling-preview:1.7.2'
|
||||||
debugImplementation 'androidx.compose.ui:ui-tooling:1.7.1'
|
debugImplementation 'androidx.compose.ui:ui-tooling:1.7.2'
|
||||||
|
|
||||||
implementation 'androidx.activity:activity-compose:1.9.2'
|
implementation 'androidx.activity:activity-compose:1.9.2'
|
||||||
implementation 'androidx.window:window:1.3.0'
|
implementation 'androidx.window:window:1.3.0'
|
||||||
|
@ -187,7 +187,7 @@ dependencies {
|
||||||
implementation "androidx.core:core-ktx:1.13.1"
|
implementation "androidx.core:core-ktx:1.13.1"
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1'
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.8.1'
|
||||||
|
|
||||||
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.8.5"
|
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.8.6"
|
||||||
implementation "androidx.annotation:annotation:1.8.2"
|
implementation "androidx.annotation:annotation:1.8.2"
|
||||||
implementation 'androidx.appcompat:appcompat:1.7.0'
|
implementation 'androidx.appcompat:appcompat:1.7.0'
|
||||||
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
|
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
|
||||||
|
@ -205,7 +205,7 @@ dependencies {
|
||||||
implementation "androidx.work:work-runtime:2.9.1"
|
implementation "androidx.work:work-runtime:2.9.1"
|
||||||
implementation "androidx.core:core-splashscreen:1.0.1"
|
implementation "androidx.core:core-splashscreen:1.0.1"
|
||||||
implementation 'androidx.documentfile:documentfile:1.0.1'
|
implementation 'androidx.documentfile:documentfile:1.0.1'
|
||||||
implementation 'androidx.webkit:webkit:1.11.0'
|
implementation 'androidx.webkit:webkit:1.12.0'
|
||||||
|
|
||||||
implementation "com.google.android.material:material:1.12.0"
|
implementation "com.google.android.material:material:1.12.0"
|
||||||
|
|
||||||
|
|
|
@ -71,8 +71,8 @@ class DownloadRequest private constructor(
|
||||||
// of them from a Parcel (from an Intent extra to submit a request to DownloadService) will fail.
|
// of them from a Parcel (from an Intent extra to submit a request to DownloadService) will fail.
|
||||||
//
|
//
|
||||||
// see: https://stackoverflow.com/a/22926342
|
// see: https://stackoverflow.com/a/22926342
|
||||||
dest.writeString(nonNullString(username))
|
dest.writeString(username ?: "")
|
||||||
dest.writeString(nonNullString(password))
|
dest.writeString(password ?: "")
|
||||||
dest.writeByte(if ((mediaEnqueued)) 1.toByte() else 0)
|
dest.writeByte(if ((mediaEnqueued)) 1.toByte() else 0)
|
||||||
dest.writeBundle(arguments)
|
dest.writeBundle(arguments)
|
||||||
dest.writeByte(if (initiatedByUser) 1.toByte() else 0)
|
dest.writeByte(if (initiatedByUser) 1.toByte() else 0)
|
||||||
|
@ -183,10 +183,6 @@ class DownloadRequest private constructor(
|
||||||
companion object {
|
companion object {
|
||||||
const val REQUEST_ARG_PAGE_NR: String = "page"
|
const val REQUEST_ARG_PAGE_NR: String = "page"
|
||||||
|
|
||||||
private fun nonNullString(str: String?): String {
|
|
||||||
return str ?: ""
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun nullIfEmpty(str: String?): String? {
|
private fun nullIfEmpty(str: String?): String? {
|
||||||
return if (str.isNullOrEmpty()) null else str
|
return if (str.isNullOrEmpty()) null else str
|
||||||
}
|
}
|
||||||
|
|
|
@ -138,6 +138,7 @@ class HttpDownloader(request: DownloadRequest) : Downloader(request) {
|
||||||
downloadRequest.progressPercent = progressPercent
|
downloadRequest.progressPercent = progressPercent
|
||||||
}
|
}
|
||||||
} catch (e: IOException) { Log.e(TAG, Log.getStackTraceString(e)) }
|
} catch (e: IOException) { Log.e(TAG, Log.getStackTraceString(e)) }
|
||||||
|
|
||||||
if (cancelled) onCancelled()
|
if (cancelled) onCancelled()
|
||||||
else {
|
else {
|
||||||
// check if size specified in the response header is the same as the size of the
|
// check if size specified in the response header is the same as the size of the
|
||||||
|
|
|
@ -181,7 +181,7 @@ object Episodes {
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
fun deleteEpisodes(context: Context, episodes: List<Episode>) : Job {
|
fun deleteEpisodes(context: Context, episodes: List<Episode>) : Job {
|
||||||
return runOnIOScope {
|
return runOnIOScope {
|
||||||
val removedFromQueue: MutableList<Episode> = ArrayList()
|
val removedFromQueue: MutableList<Episode> = mutableListOf()
|
||||||
val queueItems = curQueue.episodes.toMutableList()
|
val queueItems = curQueue.episodes.toMutableList()
|
||||||
for (episode in episodes) {
|
for (episode in episodes) {
|
||||||
if (queueItems.remove(episode)) removedFromQueue.add(episode)
|
if (queueItems.remove(episode)) removedFromQueue.add(episode)
|
||||||
|
|
|
@ -200,7 +200,7 @@ object Feeds {
|
||||||
fun updateFeed(context: Context, newFeed: Feed, removeUnlistedItems: Boolean): Feed? {
|
fun updateFeed(context: Context, newFeed: Feed, removeUnlistedItems: Boolean): Feed? {
|
||||||
Logd(TAG, "updateFeed called")
|
Logd(TAG, "updateFeed called")
|
||||||
var resultFeed: Feed?
|
var resultFeed: Feed?
|
||||||
val unlistedItems: MutableList<Episode> = ArrayList()
|
// val unlistedItems: MutableList<Episode> = ArrayList()
|
||||||
|
|
||||||
// Look up feed in the feedslist
|
// Look up feed in the feedslist
|
||||||
val savedFeed = searchFeedByIdentifyingValueOrID(newFeed, true)
|
val savedFeed = searchFeedByIdentifyingValueOrID(newFeed, true)
|
||||||
|
@ -212,7 +212,8 @@ object Feeds {
|
||||||
addNewFeedsSync(context, newFeed)
|
addNewFeedsSync(context, newFeed)
|
||||||
// Update with default values that are set in database
|
// Update with default values that are set in database
|
||||||
resultFeed = searchFeedByIdentifyingValueOrID(newFeed)
|
resultFeed = searchFeedByIdentifyingValueOrID(newFeed)
|
||||||
if (removeUnlistedItems) runBlocking { deleteEpisodes(context, unlistedItems).join() }
|
// TODO: This doesn't appear needed as unlistedItems is still empty
|
||||||
|
// if (removeUnlistedItems && unlistedItems.isNotEmpty()) runBlocking { deleteEpisodes(context, unlistedItems).join() }
|
||||||
} catch (e: InterruptedException) { e.printStackTrace()
|
} catch (e: InterruptedException) { e.printStackTrace()
|
||||||
} catch (e: ExecutionException) { e.printStackTrace() }
|
} catch (e: ExecutionException) { e.printStackTrace() }
|
||||||
return resultFeed
|
return resultFeed
|
||||||
|
@ -226,7 +227,7 @@ object Feeds {
|
||||||
savedFeed.updateFromOther(newFeed)
|
savedFeed.updateFromOther(newFeed)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Logd(TAG, "New feed has a higher page number.")
|
Logd(TAG, "New feed has a higher page number: ${newFeed.nextPageLink}")
|
||||||
savedFeed.nextPageLink = newFeed.nextPageLink
|
savedFeed.nextPageLink = newFeed.nextPageLink
|
||||||
}
|
}
|
||||||
val priorMostRecent = savedFeed.mostRecentItem
|
val priorMostRecent = savedFeed.mostRecentItem
|
||||||
|
@ -240,9 +241,9 @@ object Feeds {
|
||||||
// Look for new or updated Items
|
// Look for new or updated Items
|
||||||
for (idx in newFeed.episodes.indices) {
|
for (idx in newFeed.episodes.indices) {
|
||||||
val episode = newFeed.episodes[idx]
|
val episode = newFeed.episodes[idx]
|
||||||
var oldItem = savedFeedAssistant.searchEpisodeByIdentifyingValue(episode)
|
var oldItem = savedFeedAssistant.getEpisodeByIdentifyingValue(episode)
|
||||||
if (!newFeed.isLocalFeed && oldItem == null) {
|
if (!newFeed.isLocalFeed && oldItem == null) {
|
||||||
oldItem = savedFeedAssistant.searchEpisodeGuessDuplicate(episode)
|
oldItem = savedFeedAssistant.guessDuplicate(episode)
|
||||||
if (oldItem != null) {
|
if (oldItem != null) {
|
||||||
Logd(TAG, "Repaired duplicate: $oldItem, $episode")
|
Logd(TAG, "Repaired duplicate: $oldItem, $episode")
|
||||||
addDownloadStatus(DownloadResult(savedFeed.id,
|
addDownloadStatus(DownloadResult(savedFeed.id,
|
||||||
|
@ -257,6 +258,7 @@ object Feeds {
|
||||||
${EpisodeAssistant.duplicateEpisodeDetails(episode)}
|
${EpisodeAssistant.duplicateEpisodeDetails(episode)}
|
||||||
""".trimIndent()))
|
""".trimIndent()))
|
||||||
oldItem.identifier = episode.identifier
|
oldItem.identifier = episode.identifier
|
||||||
|
// queue for syncing with server
|
||||||
if (isProviderConnected && oldItem.isPlayed() && oldItem.media != null) {
|
if (isProviderConnected && oldItem.isPlayed() && oldItem.media != null) {
|
||||||
val durs = oldItem.media!!.getDuration() / 1000
|
val durs = oldItem.media!!.getDuration() / 1000
|
||||||
val action = EpisodeAction.Builder(oldItem, EpisodeAction.PLAY)
|
val action = EpisodeAction.Builder(oldItem, EpisodeAction.PLAY)
|
||||||
|
@ -295,12 +297,13 @@ object Feeds {
|
||||||
}
|
}
|
||||||
savedFeedAssistant.clear()
|
savedFeedAssistant.clear()
|
||||||
|
|
||||||
|
val unlistedItems: MutableList<Episode> = ArrayList()
|
||||||
// identify episodes to be removed
|
// identify episodes to be removed
|
||||||
if (removeUnlistedItems) {
|
if (removeUnlistedItems) {
|
||||||
val it = savedFeed.episodes.toMutableList().iterator()
|
val it = savedFeed.episodes.toMutableList().iterator()
|
||||||
while (it.hasNext()) {
|
while (it.hasNext()) {
|
||||||
val feedItem = it.next()
|
val feedItem = it.next()
|
||||||
if (newFeedAssistant.searchEpisodeByIdentifyingValue(feedItem) == null) {
|
if (newFeedAssistant.getEpisodeByIdentifyingValue(feedItem) == null) {
|
||||||
unlistedItems.add(feedItem)
|
unlistedItems.add(feedItem)
|
||||||
it.remove()
|
it.remove()
|
||||||
}
|
}
|
||||||
|
@ -315,7 +318,7 @@ object Feeds {
|
||||||
resultFeed = savedFeed
|
resultFeed = savedFeed
|
||||||
try {
|
try {
|
||||||
upsertBlk(savedFeed) {}
|
upsertBlk(savedFeed) {}
|
||||||
if (removeUnlistedItems) runBlocking { deleteEpisodes(context, unlistedItems).join() }
|
if (removeUnlistedItems && unlistedItems.isNotEmpty()) runBlocking { deleteEpisodes(context, unlistedItems).join() }
|
||||||
} catch (e: InterruptedException) { e.printStackTrace()
|
} catch (e: InterruptedException) { e.printStackTrace()
|
||||||
} catch (e: ExecutionException) { e.printStackTrace() }
|
} catch (e: ExecutionException) { e.printStackTrace() }
|
||||||
return resultFeed
|
return resultFeed
|
||||||
|
@ -461,7 +464,7 @@ object Feeds {
|
||||||
}
|
}
|
||||||
|
|
||||||
// savedFeedId == 0L means saved feed
|
// savedFeedId == 0L means saved feed
|
||||||
class FeedAssistant(val feed: Feed, val savedFeedId: Long = 0L) {
|
class FeedAssistant(val feed: Feed, private val savedFeedId: Long = 0L) {
|
||||||
val map = mutableMapOf<String, Episode>()
|
val map = mutableMapOf<String, Episode>()
|
||||||
val tag: String = if (savedFeedId == 0L) "Saved feed" else "New feed"
|
val tag: String = if (savedFeedId == 0L) "Saved feed" else "New feed"
|
||||||
|
|
||||||
|
@ -538,10 +541,10 @@ object Feeds {
|
||||||
${EpisodeAssistant.duplicateEpisodeDetails(possibleDuplicate)}
|
${EpisodeAssistant.duplicateEpisodeDetails(possibleDuplicate)}
|
||||||
""".trimIndent()))
|
""".trimIndent()))
|
||||||
}
|
}
|
||||||
fun searchEpisodeByIdentifyingValue(item: Episode): Episode? {
|
fun getEpisodeByIdentifyingValue(item: Episode): Episode? {
|
||||||
return map[item.identifyingValue]
|
return map[item.identifyingValue]
|
||||||
}
|
}
|
||||||
fun searchEpisodeGuessDuplicate(item: Episode): Episode? {
|
fun guessDuplicate(item: Episode): Episode? {
|
||||||
var episode = map[item.identifier]
|
var episode = map[item.identifier]
|
||||||
if (episode != null) return episode
|
if (episode != null) return episode
|
||||||
val url = item.media?.getStreamUrl()
|
val url = item.media?.getStreamUrl()
|
||||||
|
|
|
@ -84,6 +84,7 @@ class Feed : RealmObject {
|
||||||
|
|
||||||
var preferences: FeedPreferences? = null
|
var preferences: FeedPreferences? = null
|
||||||
|
|
||||||
|
// TODO: this might not be needed
|
||||||
var measures: FeedMeasures? = null
|
var measures: FeedMeasures? = null
|
||||||
|
|
||||||
var hasVideoMedia: Boolean = false
|
var hasVideoMedia: Boolean = false
|
||||||
|
@ -126,15 +127,6 @@ class Feed : RealmObject {
|
||||||
preferences?.sortOrderCode = value.code
|
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
|
@Ignore
|
||||||
val mostRecentItem: Episode?
|
val mostRecentItem: Episode?
|
||||||
get() = realm.query(Episode::class).query("feedId == $id SORT(pubDate DESC)").first().find()
|
get() = realm.query(Episode::class).query("feedId == $id SORT(pubDate DESC)").first().find()
|
||||||
|
|
|
@ -3,6 +3,7 @@ package ac.mdiq.podcini.storage.model
|
||||||
import io.realm.kotlin.types.EmbeddedRealmObject
|
import io.realm.kotlin.types.EmbeddedRealmObject
|
||||||
import io.realm.kotlin.types.annotations.Index
|
import io.realm.kotlin.types.annotations.Index
|
||||||
|
|
||||||
|
// TODO: this might not be needed
|
||||||
class FeedMeasures : EmbeddedRealmObject {
|
class FeedMeasures : EmbeddedRealmObject {
|
||||||
@Index
|
@Index
|
||||||
var feedID: Long = 0L
|
var feedID: Long = 0L
|
||||||
|
|
|
@ -97,7 +97,7 @@ import java.util.concurrent.Semaphore
|
||||||
_binding = FeedItemListFragmentBinding.inflate(inflater)
|
_binding = FeedItemListFragmentBinding.inflate(inflater)
|
||||||
_dialBinding = MultiSelectSpeedDialBinding.bind(binding.root)
|
_dialBinding = MultiSelectSpeedDialBinding.bind(binding.root)
|
||||||
|
|
||||||
binding.toolbar.inflateMenu(R.menu.feedlist)
|
binding.toolbar.inflateMenu(R.menu.feed_episodes)
|
||||||
binding.toolbar.setOnMenuItemClickListener(this)
|
binding.toolbar.setOnMenuItemClickListener(this)
|
||||||
binding.toolbar.setOnLongClickListener {
|
binding.toolbar.setOnLongClickListener {
|
||||||
binding.recyclerView.scrollToPosition(5)
|
binding.recyclerView.scrollToPosition(5)
|
||||||
|
@ -132,7 +132,7 @@ import java.util.concurrent.Semaphore
|
||||||
val iconTintManager: ToolbarIconTintManager = object : ToolbarIconTintManager(
|
val iconTintManager: ToolbarIconTintManager = object : ToolbarIconTintManager(
|
||||||
requireContext(), binding.toolbar, binding.collapsingToolbar) {
|
requireContext(), binding.toolbar, binding.collapsingToolbar) {
|
||||||
override fun doTint(themedContext: Context) {
|
override fun doTint(themedContext: Context) {
|
||||||
binding.toolbar.menu.findItem(R.id.refresh_item).setIcon(AppCompatResources.getDrawable(themedContext, R.drawable.ic_refresh))
|
binding.toolbar.menu.findItem(R.id.refresh_feed).setIcon(AppCompatResources.getDrawable(themedContext, R.drawable.ic_refresh))
|
||||||
binding.toolbar.menu.findItem(R.id.action_search).setIcon(AppCompatResources.getDrawable(themedContext, R.drawable.ic_search))
|
binding.toolbar.menu.findItem(R.id.action_search).setIcon(AppCompatResources.getDrawable(themedContext, R.drawable.ic_search))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -268,7 +268,7 @@ import java.util.concurrent.Semaphore
|
||||||
binding.toolbar.menu.findItem(R.id.visit_website_item).setVisible(feed!!.link != null)
|
binding.toolbar.menu.findItem(R.id.visit_website_item).setVisible(feed!!.link != null)
|
||||||
binding.toolbar.menu.findItem(R.id.refresh_complete_item).setVisible(feed!!.isPaged)
|
binding.toolbar.menu.findItem(R.id.refresh_complete_item).setVisible(feed!!.isPaged)
|
||||||
if (StringUtils.isBlank(feed!!.link)) binding.toolbar.menu.findItem(R.id.visit_website_item).setVisible(false)
|
if (StringUtils.isBlank(feed!!.link)) binding.toolbar.menu.findItem(R.id.visit_website_item).setVisible(false)
|
||||||
if (feed!!.isLocalFeed) binding.toolbar.menu.findItem(R.id.share_item).setVisible(false)
|
if (feed!!.isLocalFeed) binding.toolbar.menu.findItem(R.id.share_feed).setVisible(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onConfigurationChanged(newConfig: Configuration) {
|
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||||
|
@ -285,8 +285,8 @@ import java.util.concurrent.Semaphore
|
||||||
}
|
}
|
||||||
when (item.itemId) {
|
when (item.itemId) {
|
||||||
R.id.visit_website_item -> if (feed!!.link != null) IntentUtils.openInBrowser(requireContext(), feed!!.link!!)
|
R.id.visit_website_item -> if (feed!!.link != null) IntentUtils.openInBrowser(requireContext(), feed!!.link!!)
|
||||||
R.id.share_item -> ShareUtils.shareFeedLink(requireContext(), feed!!)
|
R.id.share_feed -> ShareUtils.shareFeedLink(requireContext(), feed!!)
|
||||||
R.id.refresh_item -> FeedUpdateManager.runOnceOrAsk(requireContext(), feed)
|
R.id.refresh_feed -> FeedUpdateManager.runOnceOrAsk(requireContext(), feed)
|
||||||
R.id.refresh_complete_item -> {
|
R.id.refresh_complete_item -> {
|
||||||
Thread {
|
Thread {
|
||||||
try {
|
try {
|
||||||
|
@ -295,21 +295,14 @@ import java.util.concurrent.Semaphore
|
||||||
it.nextPageLink = it.downloadUrl
|
it.nextPageLink = it.downloadUrl
|
||||||
it.pageNr = 0
|
it.pageNr = 0
|
||||||
}
|
}
|
||||||
// val feed_ = unmanaged(feed!!)
|
|
||||||
// feed_.nextPageLink = feed_.downloadUrl
|
|
||||||
// feed_.pageNr = 0
|
|
||||||
// upsertBlk(feed_) {}
|
|
||||||
FeedUpdateManager.runOnce(requireContext(), feed_)
|
FeedUpdateManager.runOnce(requireContext(), feed_)
|
||||||
}
|
}
|
||||||
} catch (e: ExecutionException) {
|
} catch (e: ExecutionException) { throw RuntimeException(e)
|
||||||
throw RuntimeException(e)
|
} catch (e: InterruptedException) { throw RuntimeException(e) }
|
||||||
} catch (e: InterruptedException) {
|
|
||||||
throw RuntimeException(e)
|
|
||||||
}
|
|
||||||
}.start()
|
}.start()
|
||||||
}
|
}
|
||||||
R.id.sort_items -> SingleFeedSortDialog(feed).show(childFragmentManager, "SortDialog")
|
R.id.sort_items -> SingleFeedSortDialog(feed).show(childFragmentManager, "SortDialog")
|
||||||
R.id.rename_item -> CustomFeedNameDialog(activity as Activity, feed!!).show()
|
R.id.rename_feed -> CustomFeedNameDialog(activity as Activity, feed!!).show()
|
||||||
R.id.remove_feed -> {
|
R.id.remove_feed -> {
|
||||||
RemoveFeedDialog.show(requireContext(), feed!!) {
|
RemoveFeedDialog.show(requireContext(), feed!!) {
|
||||||
(activity as MainActivity).loadFragment(UserPreferences.defaultPage, null)
|
(activity as MainActivity).loadFragment(UserPreferences.defaultPage, null)
|
||||||
|
|
|
@ -6,11 +6,13 @@ import ac.mdiq.podcini.databinding.FeedinfoBinding
|
||||||
import ac.mdiq.podcini.net.feed.FeedUpdateManager.runOnce
|
import ac.mdiq.podcini.net.feed.FeedUpdateManager.runOnce
|
||||||
import ac.mdiq.podcini.net.feed.discovery.CombinedSearcher
|
import ac.mdiq.podcini.net.feed.discovery.CombinedSearcher
|
||||||
import ac.mdiq.podcini.net.utils.HtmlToPlainText
|
import ac.mdiq.podcini.net.utils.HtmlToPlainText
|
||||||
|
import ac.mdiq.podcini.preferences.UserPreferences
|
||||||
import ac.mdiq.podcini.storage.database.Feeds.updateFeed
|
import ac.mdiq.podcini.storage.database.Feeds.updateFeed
|
||||||
import ac.mdiq.podcini.storage.database.Feeds.updateFeedDownloadURL
|
import ac.mdiq.podcini.storage.database.Feeds.updateFeedDownloadURL
|
||||||
import ac.mdiq.podcini.storage.model.Feed
|
import ac.mdiq.podcini.storage.model.Feed
|
||||||
import ac.mdiq.podcini.storage.model.FeedFunding
|
import ac.mdiq.podcini.storage.model.FeedFunding
|
||||||
import ac.mdiq.podcini.ui.activity.MainActivity
|
import ac.mdiq.podcini.ui.activity.MainActivity
|
||||||
|
import ac.mdiq.podcini.ui.dialog.RemoveFeedDialog
|
||||||
import ac.mdiq.podcini.ui.statistics.FeedStatisticsFragment
|
import ac.mdiq.podcini.ui.statistics.FeedStatisticsFragment
|
||||||
import ac.mdiq.podcini.ui.statistics.StatisticsFragment
|
import ac.mdiq.podcini.ui.statistics.StatisticsFragment
|
||||||
import ac.mdiq.podcini.ui.utils.ToolbarIconTintManager
|
import ac.mdiq.podcini.ui.utils.ToolbarIconTintManager
|
||||||
|
@ -248,6 +250,13 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
||||||
}
|
}
|
||||||
}.show()
|
}.show()
|
||||||
}
|
}
|
||||||
|
R.id.remove_feed -> {
|
||||||
|
RemoveFeedDialog.show(requireContext(), feed) {
|
||||||
|
(activity as MainActivity).loadFragment(UserPreferences.defaultPage, null)
|
||||||
|
// Make sure fragment is hidden before actually starting to delete
|
||||||
|
requireActivity().supportFragmentManager.executePendingTransactions()
|
||||||
|
}
|
||||||
|
}
|
||||||
else -> return false
|
else -> return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
@ -255,10 +264,7 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
||||||
|
|
||||||
@UnstableApi private fun addLocalFolderResult(uri: Uri?) {
|
@UnstableApi private fun addLocalFolderResult(uri: Uri?) {
|
||||||
if (uri == null) return
|
if (uri == null) return
|
||||||
reconnectLocalFolder(uri)
|
// reconnectLocalFolder(uri)
|
||||||
}
|
|
||||||
|
|
||||||
@UnstableApi private fun reconnectLocalFolder(uri: Uri) {
|
|
||||||
lifecycleScope.launch {
|
lifecycleScope.launch {
|
||||||
try {
|
try {
|
||||||
withContext(Dispatchers.IO) {
|
withContext(Dispatchers.IO) {
|
||||||
|
@ -279,6 +285,27 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @UnstableApi private fun reconnectLocalFolder(uri: Uri) {
|
||||||
|
// lifecycleScope.launch {
|
||||||
|
// try {
|
||||||
|
// withContext(Dispatchers.IO) {
|
||||||
|
// requireActivity().contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||||
|
// val documentFile = DocumentFile.fromTreeUri(requireContext(), uri)
|
||||||
|
// requireNotNull(documentFile) { "Unable to retrieve document tree" }
|
||||||
|
// feed.downloadUrl = Feed.PREFIX_LOCAL_FOLDER + uri.toString()
|
||||||
|
// updateFeed(requireContext(), feed, true)
|
||||||
|
// }
|
||||||
|
// withContext(Dispatchers.Main) {
|
||||||
|
// (activity as MainActivity).showSnackbarAbovePlayer(string.ok, Snackbar.LENGTH_SHORT)
|
||||||
|
// }
|
||||||
|
// } catch (e: Throwable) {
|
||||||
|
// withContext(Dispatchers.Main) {
|
||||||
|
// (activity as MainActivity).showSnackbarAbovePlayer(e.localizedMessage, Snackbar.LENGTH_LONG)
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
private var eventSink: Job? = null
|
private var eventSink: Job? = null
|
||||||
private fun cancelFlowEvents() {
|
private fun cancelFlowEvents() {
|
||||||
eventSink?.cancel()
|
eventSink?.cancel()
|
||||||
|
@ -323,11 +350,8 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
|
||||||
runBlocking { updateFeedDownloadURL(original, updated).join() }
|
runBlocking { updateFeedDownloadURL(original, updated).join() }
|
||||||
feed.downloadUrl = updated
|
feed.downloadUrl = updated
|
||||||
runOnce(activityRef.get()!!, feed)
|
runOnce(activityRef.get()!!, feed)
|
||||||
} catch (e: ExecutionException) {
|
} catch (e: ExecutionException) { throw RuntimeException(e)
|
||||||
throw RuntimeException(e)
|
} catch (e: InterruptedException) { throw RuntimeException(e) }
|
||||||
} catch (e: InterruptedException) {
|
|
||||||
throw RuntimeException(e)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@UnstableApi private fun showConfirmAlertDialog(url: String) {
|
@UnstableApi private fun showConfirmAlertDialog(url: String) {
|
||||||
val activity = activityRef.get()
|
val activity = activityRef.get()
|
||||||
|
|
|
@ -5,9 +5,9 @@ import ac.mdiq.podcini.databinding.EditTextDialogBinding
|
||||||
import ac.mdiq.podcini.databinding.OnlineFeedviewFragmentBinding
|
import ac.mdiq.podcini.databinding.OnlineFeedviewFragmentBinding
|
||||||
import ac.mdiq.podcini.net.download.DownloadError
|
import ac.mdiq.podcini.net.download.DownloadError
|
||||||
import ac.mdiq.podcini.net.download.service.DownloadRequestCreator.create
|
import ac.mdiq.podcini.net.download.service.DownloadRequestCreator.create
|
||||||
|
import ac.mdiq.podcini.net.download.service.DownloadServiceInterface
|
||||||
import ac.mdiq.podcini.net.download.service.Downloader
|
import ac.mdiq.podcini.net.download.service.Downloader
|
||||||
import ac.mdiq.podcini.net.download.service.HttpDownloader
|
import ac.mdiq.podcini.net.download.service.HttpDownloader
|
||||||
import ac.mdiq.podcini.net.download.service.DownloadServiceInterface
|
|
||||||
import ac.mdiq.podcini.net.feed.FeedUrlNotFoundException
|
import ac.mdiq.podcini.net.feed.FeedUrlNotFoundException
|
||||||
import ac.mdiq.podcini.net.feed.discovery.CombinedSearcher
|
import ac.mdiq.podcini.net.feed.discovery.CombinedSearcher
|
||||||
import ac.mdiq.podcini.net.feed.discovery.PodcastSearcherRegistry
|
import ac.mdiq.podcini.net.feed.discovery.PodcastSearcherRegistry
|
||||||
|
@ -24,8 +24,6 @@ import ac.mdiq.podcini.storage.model.*
|
||||||
import ac.mdiq.podcini.storage.utils.FilesUtils.feedfilePath
|
import ac.mdiq.podcini.storage.utils.FilesUtils.feedfilePath
|
||||||
import ac.mdiq.podcini.storage.utils.FilesUtils.getFeedfileName
|
import ac.mdiq.podcini.storage.utils.FilesUtils.getFeedfileName
|
||||||
import ac.mdiq.podcini.ui.activity.MainActivity
|
import ac.mdiq.podcini.ui.activity.MainActivity
|
||||||
import ac.mdiq.podcini.ui.adapter.EpisodesAdapter.EpisodeInfoFragment
|
|
||||||
import ac.mdiq.podcini.ui.adapter.EpisodesAdapter.EpisodeInfoFragment.Companion
|
|
||||||
import ac.mdiq.podcini.ui.dialog.AuthenticationDialog
|
import ac.mdiq.podcini.ui.dialog.AuthenticationDialog
|
||||||
import ac.mdiq.podcini.ui.utils.ThemeUtils.getColorFromAttr
|
import ac.mdiq.podcini.ui.utils.ThemeUtils.getColorFromAttr
|
||||||
import ac.mdiq.podcini.util.EventFlow
|
import ac.mdiq.podcini.util.EventFlow
|
||||||
|
@ -38,7 +36,6 @@ import ac.mdiq.vista.extractor.channel.ChannelInfo
|
||||||
import ac.mdiq.vista.extractor.channel.tabs.ChannelTabInfo
|
import ac.mdiq.vista.extractor.channel.tabs.ChannelTabInfo
|
||||||
import ac.mdiq.vista.extractor.exceptions.ExtractionException
|
import ac.mdiq.vista.extractor.exceptions.ExtractionException
|
||||||
import ac.mdiq.vista.extractor.playlist.PlaylistInfo
|
import ac.mdiq.vista.extractor.playlist.PlaylistInfo
|
||||||
import ac.mdiq.vista.extractor.stream.StreamInfo
|
|
||||||
import ac.mdiq.vista.extractor.stream.StreamInfoItem
|
import ac.mdiq.vista.extractor.stream.StreamInfoItem
|
||||||
import android.app.Dialog
|
import android.app.Dialog
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
|
@ -239,7 +236,10 @@ class OnlineFeedFragment : Fragment() {
|
||||||
private fun htmlOrXml(url: String): String? {
|
private fun htmlOrXml(url: String): String? {
|
||||||
val connection = URL(url).openConnection() as HttpURLConnection
|
val connection = URL(url).openConnection() as HttpURLConnection
|
||||||
var type: String? = null
|
var type: String? = null
|
||||||
try { type = connection.contentType } catch (e: IOException) { Log.e(TAG, "Error connecting to URL", e) } finally { connection.disconnect() }
|
try { type = connection.contentType } catch (e: IOException) {
|
||||||
|
Log.e(TAG, "Error connecting to URL", e)
|
||||||
|
showErrorDialog(e.message, "")
|
||||||
|
} finally { connection.disconnect() }
|
||||||
if (type == null) return null
|
if (type == null) return null
|
||||||
Logd(TAG, "connection type: $type")
|
Logd(TAG, "connection type: $type")
|
||||||
return when {
|
return when {
|
||||||
|
@ -273,8 +273,7 @@ class OnlineFeedFragment : Fragment() {
|
||||||
var infoItems = playlistInfo.relatedItems
|
var infoItems = playlistInfo.relatedItems
|
||||||
var nextPage = playlistInfo.nextPage
|
var nextPage = playlistInfo.nextPage
|
||||||
Logd(TAG, "infoItems: ${infoItems.size}")
|
Logd(TAG, "infoItems: ${infoItems.size}")
|
||||||
var i = 0
|
while (infoItems.isNotEmpty()) {
|
||||||
while (infoItems.isNotEmpty() && i++ < 2) {
|
|
||||||
for (r in infoItems) {
|
for (r in infoItems) {
|
||||||
Logd(TAG, "startFeedBuilding relatedItem: $r")
|
Logd(TAG, "startFeedBuilding relatedItem: $r")
|
||||||
if (r.infoType != InfoItem.InfoType.STREAM) continue
|
if (r.infoType != InfoItem.InfoType.STREAM) continue
|
||||||
|
@ -283,18 +282,27 @@ class OnlineFeedFragment : Fragment() {
|
||||||
e.feedId = feed_.id
|
e.feedId = feed_.id
|
||||||
eList.add(e)
|
eList.add(e)
|
||||||
}
|
}
|
||||||
if (nextPage == null) break
|
if (nextPage == null || eList.size > 500) break
|
||||||
|
try {
|
||||||
val page = PlaylistInfo.getMoreItems(service, url, nextPage) ?: break
|
val page = PlaylistInfo.getMoreItems(service, url, nextPage) ?: break
|
||||||
nextPage = page.nextPage
|
nextPage = page.nextPage
|
||||||
infoItems = page.items
|
infoItems = page.items
|
||||||
Logd(TAG, "more infoItems: ${infoItems.size}")
|
Logd(TAG, "more infoItems: ${infoItems.size}")
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Logd(TAG, "PlaylistInfo.getMoreItems error: ${e.message}")
|
||||||
|
withContext(Dispatchers.Main) { showErrorDialog(e.message, "") }
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
feed_.episodes = eList
|
feed_.episodes = eList
|
||||||
withContext(Dispatchers.Main) { showFeedInformation(feed_, mapOf()) }
|
withContext(Dispatchers.Main) { showFeedInformation(feed_, mapOf()) }
|
||||||
} else {
|
} else {
|
||||||
val channelInfo = ChannelInfo.getInfo(service, url)
|
val channelInfo = ChannelInfo.getInfo(service, url)
|
||||||
Logd(TAG, "startFeedBuilding result: $channelInfo ${channelInfo.tabs.size}")
|
Logd(TAG, "startFeedBuilding result: $channelInfo ${channelInfo.tabs.size}")
|
||||||
if (channelInfo.tabs.isEmpty()) return@launch
|
if (channelInfo.tabs.isEmpty()) {
|
||||||
|
withContext(Dispatchers.Main) { showErrorDialog("Channel is empty", "") }
|
||||||
|
return@launch
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
val channelTabInfo = ChannelTabInfo.getInfo(service, channelInfo.tabs.first())
|
val channelTabInfo = ChannelTabInfo.getInfo(service, channelInfo.tabs.first())
|
||||||
Logd(TAG, "startFeedBuilding result1: $channelTabInfo ${channelTabInfo.relatedItems.size}")
|
Logd(TAG, "startFeedBuilding result1: $channelTabInfo ${channelTabInfo.relatedItems.size}")
|
||||||
|
@ -306,8 +314,7 @@ class OnlineFeedFragment : Fragment() {
|
||||||
var infoItems = channelTabInfo.relatedItems
|
var infoItems = channelTabInfo.relatedItems
|
||||||
var nextPage = channelTabInfo.nextPage
|
var nextPage = channelTabInfo.nextPage
|
||||||
Logd(TAG, "infoItems: ${infoItems.size}")
|
Logd(TAG, "infoItems: ${infoItems.size}")
|
||||||
var i = 0
|
while (infoItems.isNotEmpty()) {
|
||||||
while (infoItems.isNotEmpty() && i++ < 2) {
|
|
||||||
for (r in infoItems) {
|
for (r in infoItems) {
|
||||||
Logd(TAG, "startFeedBuilding relatedItem: $r")
|
Logd(TAG, "startFeedBuilding relatedItem: $r")
|
||||||
if (r.infoType != InfoItem.InfoType.STREAM) continue
|
if (r.infoType != InfoItem.InfoType.STREAM) continue
|
||||||
|
@ -316,17 +323,29 @@ class OnlineFeedFragment : Fragment() {
|
||||||
e.feedId = feed_.id
|
e.feedId = feed_.id
|
||||||
eList.add(e)
|
eList.add(e)
|
||||||
}
|
}
|
||||||
if (nextPage == null) break
|
if (nextPage == null || eList.size > 200) break
|
||||||
|
try {
|
||||||
val page = ChannelTabInfo.getMoreItems(service, channelInfo.tabs.first(), nextPage)
|
val page = ChannelTabInfo.getMoreItems(service, channelInfo.tabs.first(), nextPage)
|
||||||
nextPage = page.nextPage
|
nextPage = page.nextPage
|
||||||
infoItems = page.items
|
infoItems = page.items
|
||||||
Logd(TAG, "more infoItems: ${infoItems.size}")
|
Logd(TAG, "more infoItems: ${infoItems.size}")
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Logd(TAG, "ChannelTabInfo.getMoreItems error: ${e.message}")
|
||||||
|
withContext(Dispatchers.Main) { showErrorDialog(e.message, "") }
|
||||||
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
feed_.episodes = eList
|
feed_.episodes = eList
|
||||||
withContext(Dispatchers.Main) { showFeedInformation(feed_, mapOf()) }
|
withContext(Dispatchers.Main) { showFeedInformation(feed_, mapOf()) }
|
||||||
} catch (e: Throwable) { Logd(TAG, "startFeedBuilding error1 ${e.message}") }
|
} catch (e: Throwable) {
|
||||||
|
Logd(TAG, "startFeedBuilding error1 ${e.message}")
|
||||||
|
withContext(Dispatchers.Main) { showErrorDialog(e.message, "") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
Logd(TAG, "startFeedBuilding error ${e.message}")
|
||||||
|
withContext(Dispatchers.Main) { showErrorDialog(e.message, "") }
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) { Logd(TAG, "startFeedBuilding error ${e.message}") }
|
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -347,6 +366,7 @@ class OnlineFeedFragment : Fragment() {
|
||||||
"XML" -> {}
|
"XML" -> {}
|
||||||
else -> {
|
else -> {
|
||||||
Log.e(TAG, "unknown url type $urlType")
|
Log.e(TAG, "unknown url type $urlType")
|
||||||
|
showErrorDialog("unknown url type $urlType", "")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -388,7 +408,10 @@ class OnlineFeedFragment : Fragment() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) { Log.e(TAG, Log.getStackTraceString(e)) }
|
} catch (e: Throwable) {
|
||||||
|
Log.e(TAG, Log.getStackTraceString(e))
|
||||||
|
withContext(Dispatchers.Main) { showErrorDialog(e.message, "") }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -429,7 +452,10 @@ class OnlineFeedFragment : Fragment() {
|
||||||
this@OnlineFeedFragment.feeds = feeds
|
this@OnlineFeedFragment.feeds = feeds
|
||||||
handleUpdatedFeedStatus()
|
handleUpdatedFeedStatus()
|
||||||
}
|
}
|
||||||
} catch (e: Throwable) { Log.e(TAG, Log.getStackTraceString(e)) }
|
} catch (e: Throwable) {
|
||||||
|
Log.e(TAG, Log.getStackTraceString(e))
|
||||||
|
withContext(Dispatchers.Main) { showErrorDialog(e.message, "") }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,7 +16,7 @@
|
||||||
custom:showAsAction="always">
|
custom:showAsAction="always">
|
||||||
</item>
|
</item>
|
||||||
<item
|
<item
|
||||||
android:id="@+id/refresh_item"
|
android:id="@+id/refresh_feed"
|
||||||
android:menuCategory="container"
|
android:menuCategory="container"
|
||||||
android:title="@string/refresh_label"
|
android:title="@string/refresh_label"
|
||||||
custom:showAsAction="never">
|
custom:showAsAction="never">
|
||||||
|
@ -44,13 +44,13 @@
|
||||||
</item>
|
</item>
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/share_item"
|
android:id="@+id/share_feed"
|
||||||
android:menuCategory="container"
|
android:menuCategory="container"
|
||||||
custom:showAsAction="never"
|
custom:showAsAction="never"
|
||||||
android:title="@string/share_label" />
|
android:title="@string/share_label" />
|
||||||
|
|
||||||
<item
|
<item
|
||||||
android:id="@+id/rename_item"
|
android:id="@+id/rename_feed"
|
||||||
android:menuCategory="container"
|
android:menuCategory="container"
|
||||||
android:title="@string/rename_feed_label"
|
android:title="@string/rename_feed_label"
|
||||||
custom:showAsAction="never" />
|
custom:showAsAction="never" />
|
|
@ -21,4 +21,12 @@
|
||||||
<item
|
<item
|
||||||
android:id="@+id/edit_feed_url_item"
|
android:id="@+id/edit_feed_url_item"
|
||||||
android:title="@string/edit_url_menu" />
|
android:title="@string/edit_url_menu" />
|
||||||
|
<item
|
||||||
|
android:id="@+id/remove_feed"
|
||||||
|
android:icon="@drawable/ic_delete"
|
||||||
|
android:menuCategory="container"
|
||||||
|
android:title="@string/remove_feed_label"
|
||||||
|
android:visible="true"
|
||||||
|
custom:showAsAction="collapseActionView">
|
||||||
|
</item>
|
||||||
</menu>
|
</menu>
|
|
@ -153,7 +153,7 @@
|
||||||
<string name="feed_auto_download_newer">Newest unplayed</string>
|
<string name="feed_auto_download_newer">Newest unplayed</string>
|
||||||
<string name="feed_auto_download_older">Oldest unplayed</string>
|
<string name="feed_auto_download_older">Oldest unplayed</string>
|
||||||
|
|
||||||
<string name="put_in_queue_label">Add to queue...</string>
|
<string name="put_in_queue_label">Add to queue…</string>
|
||||||
<string name="remove_from_other_queues">Remove from other queues</string>
|
<string name="remove_from_other_queues">Remove from other queues</string>
|
||||||
|
|
||||||
<string name="feed_new_episodes_action_nothing">Nothing</string>
|
<string name="feed_new_episodes_action_nothing">Nothing</string>
|
||||||
|
@ -363,7 +363,7 @@
|
||||||
<string name="queue_locked">Queue locked</string>
|
<string name="queue_locked">Queue locked</string>
|
||||||
<string name="queue_unlocked">Queue unlocked</string>
|
<string name="queue_unlocked">Queue unlocked</string>
|
||||||
<string name="queue_lock_warning">If you lock the queue, you can no longer swipe or reorder episodes.</string>
|
<string name="queue_lock_warning">If you lock the queue, you can no longer swipe or reorder episodes.</string>
|
||||||
<string name="switch_queue">Switch queue</string>
|
<string name="switch_queue">Switch active queue</string>
|
||||||
<string name="open_queue">Open queue</string>
|
<string name="open_queue">Open queue</string>
|
||||||
<string name="checkbox_do_not_show_again">Do not show again</string>
|
<string name="checkbox_do_not_show_again">Do not show again</string>
|
||||||
<string name="clear_queue_label">Clear queue</string>
|
<string name="clear_queue_label">Clear queue</string>
|
||||||
|
|
11
changelog.md
11
changelog.md
|
@ -1,3 +1,14 @@
|
||||||
|
# 6.7.2
|
||||||
|
|
||||||
|
* added menu item for removing feed in FeedInfo view
|
||||||
|
* menu item "Switch queue" is changed to "Switch active queue"
|
||||||
|
* Youtube and YT Music podcasts can be shared to Podcini
|
||||||
|
* initial max number of loaded items for Youtube and YT Music playlist and podcast is set 500
|
||||||
|
* initial max number of loaded items for Youtube channel is set 500
|
||||||
|
* added some error dialogs when handling shared links
|
||||||
|
* updated some dependencies including Compose
|
||||||
|
* compile and target SDK's are upped to 35
|
||||||
|
|
||||||
# 6.7.1
|
# 6.7.1
|
||||||
|
|
||||||
* ensured duplicate episodes are removed from secondary checking during refresh
|
* ensured duplicate episodes are removed from secondary checking during refresh
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
Version 6.7.2:
|
||||||
|
|
||||||
|
* added menu item for removing feed in FeedInfo view
|
||||||
|
* menu item "Switch queue" is changed to "Switch active queue"
|
||||||
|
* Youtube and YT Music podcasts can be shared to Podcini
|
||||||
|
* initial max number of loaded items for Youtube and YT Music playlist and podcast is set 500
|
||||||
|
* initial max number of loaded items for Youtube channel is set 500
|
||||||
|
* added some error dialogs when handling shared links
|
||||||
|
* updated some dependencies including Compose
|
||||||
|
* compile and target SDK's are upped to 35
|
Loading…
Reference in New Issue