diff --git a/README.md b/README.md
index ff3c03f3..9749c6bf 100644
--- a/README.md
+++ b/README.md
@@ -12,7 +12,7 @@ An open source podcast instrument, attuned to Puccini ![Puccini](./images/Puccin
[](https://f-droid.org/packages/ac.mdiq.podcini.R/)
[](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)
#### 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.
@@ -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 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"
* 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
diff --git a/app/build.gradle b/app/build.gradle
index 95f3e2f3..49903c48 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -19,8 +19,8 @@ composeCompiler {
android {
defaultConfig {
minSdk 24
- compileSdk 34
- targetSdk 34
+ compileSdk 35
+ targetSdk 35
kotlinOptions {
jvmTarget = '17'
@@ -31,8 +31,8 @@ android {
testApplicationId "ac.mdiq.podcini.tests"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
- versionCode 3020254
- versionName "6.7.1"
+ versionCode 3020255
+ versionName "6.7.2"
applicationId "ac.mdiq.podcini.R"
def commit = ""
@@ -174,12 +174,12 @@ dependencies {
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs_nio:2.1.2'
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
androidTestImplementation composeBom
- implementation 'androidx.compose.material:material:1.7.1'
- implementation 'androidx.compose.ui:ui-tooling-preview:1.7.1'
- debugImplementation 'androidx.compose.ui:ui-tooling:1.7.1'
+ implementation 'androidx.compose.material:material:1.7.2'
+ implementation 'androidx.compose.ui:ui-tooling-preview:1.7.2'
+ debugImplementation 'androidx.compose.ui:ui-tooling:1.7.2'
implementation 'androidx.activity:activity-compose:1.9.2'
implementation 'androidx.window:window:1.3.0'
@@ -187,7 +187,7 @@ dependencies {
implementation "androidx.core:core-ktx:1.13.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.appcompat:appcompat:1.7.0'
implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0'
@@ -205,7 +205,7 @@ dependencies {
implementation "androidx.work:work-runtime:2.9.1"
implementation "androidx.core:core-splashscreen: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"
diff --git a/app/src/main/kotlin/ac/mdiq/podcini/net/download/service/DownloadRequest.kt b/app/src/main/kotlin/ac/mdiq/podcini/net/download/service/DownloadRequest.kt
index 84b98ac8..608bff5d 100644
--- a/app/src/main/kotlin/ac/mdiq/podcini/net/download/service/DownloadRequest.kt
+++ b/app/src/main/kotlin/ac/mdiq/podcini/net/download/service/DownloadRequest.kt
@@ -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.
//
// see: https://stackoverflow.com/a/22926342
- dest.writeString(nonNullString(username))
- dest.writeString(nonNullString(password))
+ dest.writeString(username ?: "")
+ dest.writeString(password ?: "")
dest.writeByte(if ((mediaEnqueued)) 1.toByte() else 0)
dest.writeBundle(arguments)
dest.writeByte(if (initiatedByUser) 1.toByte() else 0)
@@ -183,10 +183,6 @@ class DownloadRequest private constructor(
companion object {
const val REQUEST_ARG_PAGE_NR: String = "page"
- private fun nonNullString(str: String?): String {
- return str ?: ""
- }
-
private fun nullIfEmpty(str: String?): String? {
return if (str.isNullOrEmpty()) null else str
}
diff --git a/app/src/main/kotlin/ac/mdiq/podcini/net/download/service/HttpDownloader.kt b/app/src/main/kotlin/ac/mdiq/podcini/net/download/service/HttpDownloader.kt
index e6238a1c..bde05cc1 100644
--- a/app/src/main/kotlin/ac/mdiq/podcini/net/download/service/HttpDownloader.kt
+++ b/app/src/main/kotlin/ac/mdiq/podcini/net/download/service/HttpDownloader.kt
@@ -138,6 +138,7 @@ class HttpDownloader(request: DownloadRequest) : Downloader(request) {
downloadRequest.progressPercent = progressPercent
}
} catch (e: IOException) { Log.e(TAG, Log.getStackTraceString(e)) }
+
if (cancelled) onCancelled()
else {
// check if size specified in the response header is the same as the size of the
diff --git a/app/src/main/kotlin/ac/mdiq/podcini/storage/database/Episodes.kt b/app/src/main/kotlin/ac/mdiq/podcini/storage/database/Episodes.kt
index 5b12ae15..6cbf6192 100644
--- a/app/src/main/kotlin/ac/mdiq/podcini/storage/database/Episodes.kt
+++ b/app/src/main/kotlin/ac/mdiq/podcini/storage/database/Episodes.kt
@@ -181,7 +181,7 @@ object Episodes {
@UnstableApi
fun deleteEpisodes(context: Context, episodes: List) : Job {
return runOnIOScope {
- val removedFromQueue: MutableList = ArrayList()
+ val removedFromQueue: MutableList = mutableListOf()
val queueItems = curQueue.episodes.toMutableList()
for (episode in episodes) {
if (queueItems.remove(episode)) removedFromQueue.add(episode)
diff --git a/app/src/main/kotlin/ac/mdiq/podcini/storage/database/Feeds.kt b/app/src/main/kotlin/ac/mdiq/podcini/storage/database/Feeds.kt
index a8268784..d323717a 100644
--- a/app/src/main/kotlin/ac/mdiq/podcini/storage/database/Feeds.kt
+++ b/app/src/main/kotlin/ac/mdiq/podcini/storage/database/Feeds.kt
@@ -200,7 +200,7 @@ object Feeds {
fun updateFeed(context: Context, newFeed: Feed, removeUnlistedItems: Boolean): Feed? {
Logd(TAG, "updateFeed called")
var resultFeed: Feed?
- val unlistedItems: MutableList = ArrayList()
+// val unlistedItems: MutableList = ArrayList()
// Look up feed in the feedslist
val savedFeed = searchFeedByIdentifyingValueOrID(newFeed, true)
@@ -212,7 +212,8 @@ object Feeds {
addNewFeedsSync(context, newFeed)
// Update with default values that are set in database
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: ExecutionException) { e.printStackTrace() }
return resultFeed
@@ -226,7 +227,7 @@ object Feeds {
savedFeed.updateFromOther(newFeed)
}
} 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
}
val priorMostRecent = savedFeed.mostRecentItem
@@ -240,9 +241,9 @@ object Feeds {
// Look for new or updated Items
for (idx in newFeed.episodes.indices) {
val episode = newFeed.episodes[idx]
- var oldItem = savedFeedAssistant.searchEpisodeByIdentifyingValue(episode)
+ var oldItem = savedFeedAssistant.getEpisodeByIdentifyingValue(episode)
if (!newFeed.isLocalFeed && oldItem == null) {
- oldItem = savedFeedAssistant.searchEpisodeGuessDuplicate(episode)
+ oldItem = savedFeedAssistant.guessDuplicate(episode)
if (oldItem != null) {
Logd(TAG, "Repaired duplicate: $oldItem, $episode")
addDownloadStatus(DownloadResult(savedFeed.id,
@@ -257,6 +258,7 @@ object Feeds {
${EpisodeAssistant.duplicateEpisodeDetails(episode)}
""".trimIndent()))
oldItem.identifier = episode.identifier
+ // queue for syncing with server
if (isProviderConnected && oldItem.isPlayed() && oldItem.media != null) {
val durs = oldItem.media!!.getDuration() / 1000
val action = EpisodeAction.Builder(oldItem, EpisodeAction.PLAY)
@@ -295,12 +297,13 @@ object Feeds {
}
savedFeedAssistant.clear()
+ val unlistedItems: MutableList = ArrayList()
// identify episodes to be removed
if (removeUnlistedItems) {
val it = savedFeed.episodes.toMutableList().iterator()
while (it.hasNext()) {
val feedItem = it.next()
- if (newFeedAssistant.searchEpisodeByIdentifyingValue(feedItem) == null) {
+ if (newFeedAssistant.getEpisodeByIdentifyingValue(feedItem) == null) {
unlistedItems.add(feedItem)
it.remove()
}
@@ -315,7 +318,7 @@ object Feeds {
resultFeed = savedFeed
try {
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: ExecutionException) { e.printStackTrace() }
return resultFeed
@@ -461,7 +464,7 @@ object Feeds {
}
// 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()
val tag: String = if (savedFeedId == 0L) "Saved feed" else "New feed"
@@ -538,10 +541,10 @@ object Feeds {
${EpisodeAssistant.duplicateEpisodeDetails(possibleDuplicate)}
""".trimIndent()))
}
- fun searchEpisodeByIdentifyingValue(item: Episode): Episode? {
+ fun getEpisodeByIdentifyingValue(item: Episode): Episode? {
return map[item.identifyingValue]
}
- fun searchEpisodeGuessDuplicate(item: Episode): Episode? {
+ fun guessDuplicate(item: Episode): Episode? {
var episode = map[item.identifier]
if (episode != null) return episode
val url = item.media?.getStreamUrl()
diff --git a/app/src/main/kotlin/ac/mdiq/podcini/storage/model/Feed.kt b/app/src/main/kotlin/ac/mdiq/podcini/storage/model/Feed.kt
index 9a5369e4..d3112fbd 100644
--- a/app/src/main/kotlin/ac/mdiq/podcini/storage/model/Feed.kt
+++ b/app/src/main/kotlin/ac/mdiq/podcini/storage/model/Feed.kt
@@ -84,6 +84,7 @@ class Feed : RealmObject {
var preferences: FeedPreferences? = null
+ // TODO: this might not be needed
var measures: FeedMeasures? = null
var hasVideoMedia: Boolean = false
@@ -126,15 +127,6 @@ 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
val mostRecentItem: Episode?
get() = realm.query(Episode::class).query("feedId == $id SORT(pubDate DESC)").first().find()
diff --git a/app/src/main/kotlin/ac/mdiq/podcini/storage/model/FeedMeasures.kt b/app/src/main/kotlin/ac/mdiq/podcini/storage/model/FeedMeasures.kt
index d8d8aafa..932e1083 100644
--- a/app/src/main/kotlin/ac/mdiq/podcini/storage/model/FeedMeasures.kt
+++ b/app/src/main/kotlin/ac/mdiq/podcini/storage/model/FeedMeasures.kt
@@ -3,6 +3,7 @@ package ac.mdiq.podcini.storage.model
import io.realm.kotlin.types.EmbeddedRealmObject
import io.realm.kotlin.types.annotations.Index
+// TODO: this might not be needed
class FeedMeasures : EmbeddedRealmObject {
@Index
var feedID: Long = 0L
diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedEpisodesFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedEpisodesFragment.kt
index 84bb5d84..e029a299 100644
--- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedEpisodesFragment.kt
+++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedEpisodesFragment.kt
@@ -97,7 +97,7 @@ import java.util.concurrent.Semaphore
_binding = FeedItemListFragmentBinding.inflate(inflater)
_dialBinding = MultiSelectSpeedDialBinding.bind(binding.root)
- binding.toolbar.inflateMenu(R.menu.feedlist)
+ binding.toolbar.inflateMenu(R.menu.feed_episodes)
binding.toolbar.setOnMenuItemClickListener(this)
binding.toolbar.setOnLongClickListener {
binding.recyclerView.scrollToPosition(5)
@@ -132,7 +132,7 @@ import java.util.concurrent.Semaphore
val iconTintManager: ToolbarIconTintManager = object : ToolbarIconTintManager(
requireContext(), binding.toolbar, binding.collapsingToolbar) {
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))
}
}
@@ -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.refresh_complete_item).setVisible(feed!!.isPaged)
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) {
@@ -285,8 +285,8 @@ import java.util.concurrent.Semaphore
}
when (item.itemId) {
R.id.visit_website_item -> if (feed!!.link != null) IntentUtils.openInBrowser(requireContext(), feed!!.link!!)
- R.id.share_item -> ShareUtils.shareFeedLink(requireContext(), feed!!)
- R.id.refresh_item -> FeedUpdateManager.runOnceOrAsk(requireContext(), feed)
+ R.id.share_feed -> ShareUtils.shareFeedLink(requireContext(), feed!!)
+ R.id.refresh_feed -> FeedUpdateManager.runOnceOrAsk(requireContext(), feed)
R.id.refresh_complete_item -> {
Thread {
try {
@@ -295,21 +295,14 @@ import java.util.concurrent.Semaphore
it.nextPageLink = it.downloadUrl
it.pageNr = 0
}
-// val feed_ = unmanaged(feed!!)
-// feed_.nextPageLink = feed_.downloadUrl
-// feed_.pageNr = 0
-// upsertBlk(feed_) {}
FeedUpdateManager.runOnce(requireContext(), feed_)
}
- } catch (e: ExecutionException) {
- throw RuntimeException(e)
- } catch (e: InterruptedException) {
- throw RuntimeException(e)
- }
+ } catch (e: ExecutionException) { throw RuntimeException(e)
+ } catch (e: InterruptedException) { throw RuntimeException(e) }
}.start()
}
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 -> {
RemoveFeedDialog.show(requireContext(), feed!!) {
(activity as MainActivity).loadFragment(UserPreferences.defaultPage, null)
diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedInfoFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedInfoFragment.kt
index 0c626244..d94d9d1f 100644
--- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedInfoFragment.kt
+++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/FeedInfoFragment.kt
@@ -6,11 +6,13 @@ import ac.mdiq.podcini.databinding.FeedinfoBinding
import ac.mdiq.podcini.net.feed.FeedUpdateManager.runOnce
import ac.mdiq.podcini.net.feed.discovery.CombinedSearcher
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.updateFeedDownloadURL
import ac.mdiq.podcini.storage.model.Feed
import ac.mdiq.podcini.storage.model.FeedFunding
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.StatisticsFragment
import ac.mdiq.podcini.ui.utils.ToolbarIconTintManager
@@ -248,6 +250,13 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
}
}.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
}
return true
@@ -255,10 +264,7 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
@UnstableApi private fun addLocalFolderResult(uri: Uri?) {
if (uri == null) return
- reconnectLocalFolder(uri)
- }
-
- @UnstableApi private fun reconnectLocalFolder(uri: Uri) {
+// reconnectLocalFolder(uri)
lifecycleScope.launch {
try {
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 fun cancelFlowEvents() {
eventSink?.cancel()
@@ -323,11 +350,8 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
runBlocking { updateFeedDownloadURL(original, updated).join() }
feed.downloadUrl = updated
runOnce(activityRef.get()!!, feed)
- } catch (e: ExecutionException) {
- throw RuntimeException(e)
- } catch (e: InterruptedException) {
- throw RuntimeException(e)
- }
+ } catch (e: ExecutionException) { throw RuntimeException(e)
+ } catch (e: InterruptedException) { throw RuntimeException(e) }
}
@UnstableApi private fun showConfirmAlertDialog(url: String) {
val activity = activityRef.get()
diff --git a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/OnlineFeedFragment.kt b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/OnlineFeedFragment.kt
index 46919d62..ad02ad14 100644
--- a/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/OnlineFeedFragment.kt
+++ b/app/src/main/kotlin/ac/mdiq/podcini/ui/fragment/OnlineFeedFragment.kt
@@ -5,9 +5,9 @@ import ac.mdiq.podcini.databinding.EditTextDialogBinding
import ac.mdiq.podcini.databinding.OnlineFeedviewFragmentBinding
import ac.mdiq.podcini.net.download.DownloadError
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.HttpDownloader
-import ac.mdiq.podcini.net.download.service.DownloadServiceInterface
import ac.mdiq.podcini.net.feed.FeedUrlNotFoundException
import ac.mdiq.podcini.net.feed.discovery.CombinedSearcher
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.getFeedfileName
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.utils.ThemeUtils.getColorFromAttr
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.exceptions.ExtractionException
import ac.mdiq.vista.extractor.playlist.PlaylistInfo
-import ac.mdiq.vista.extractor.stream.StreamInfo
import ac.mdiq.vista.extractor.stream.StreamInfoItem
import android.app.Dialog
import android.content.Context
@@ -239,7 +236,10 @@ class OnlineFeedFragment : Fragment() {
private fun htmlOrXml(url: String): String? {
val connection = URL(url).openConnection() as HttpURLConnection
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
Logd(TAG, "connection type: $type")
return when {
@@ -273,8 +273,7 @@ class OnlineFeedFragment : Fragment() {
var infoItems = playlistInfo.relatedItems
var nextPage = playlistInfo.nextPage
Logd(TAG, "infoItems: ${infoItems.size}")
- var i = 0
- while (infoItems.isNotEmpty() && i++ < 2) {
+ while (infoItems.isNotEmpty()) {
for (r in infoItems) {
Logd(TAG, "startFeedBuilding relatedItem: $r")
if (r.infoType != InfoItem.InfoType.STREAM) continue
@@ -283,18 +282,27 @@ class OnlineFeedFragment : Fragment() {
e.feedId = feed_.id
eList.add(e)
}
- if (nextPage == null) break
- val page = PlaylistInfo.getMoreItems(service, url, nextPage) ?: break
- nextPage = page.nextPage
- infoItems = page.items
- Logd(TAG, "more infoItems: ${infoItems.size}")
+ if (nextPage == null || eList.size > 500) break
+ try {
+ val page = PlaylistInfo.getMoreItems(service, url, nextPage) ?: break
+ nextPage = page.nextPage
+ infoItems = page.items
+ 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
withContext(Dispatchers.Main) { showFeedInformation(feed_, mapOf()) }
} else {
val channelInfo = ChannelInfo.getInfo(service, url)
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 {
val channelTabInfo = ChannelTabInfo.getInfo(service, channelInfo.tabs.first())
Logd(TAG, "startFeedBuilding result1: $channelTabInfo ${channelTabInfo.relatedItems.size}")
@@ -306,8 +314,7 @@ class OnlineFeedFragment : Fragment() {
var infoItems = channelTabInfo.relatedItems
var nextPage = channelTabInfo.nextPage
Logd(TAG, "infoItems: ${infoItems.size}")
- var i = 0
- while (infoItems.isNotEmpty() && i++ < 2) {
+ while (infoItems.isNotEmpty()) {
for (r in infoItems) {
Logd(TAG, "startFeedBuilding relatedItem: $r")
if (r.infoType != InfoItem.InfoType.STREAM) continue
@@ -316,17 +323,29 @@ class OnlineFeedFragment : Fragment() {
e.feedId = feed_.id
eList.add(e)
}
- if (nextPage == null) break
- val page = ChannelTabInfo.getMoreItems(service, channelInfo.tabs.first(), nextPage)
- nextPage = page.nextPage
- infoItems = page.items
- Logd(TAG, "more infoItems: ${infoItems.size}")
+ if (nextPage == null || eList.size > 200) break
+ try {
+ val page = ChannelTabInfo.getMoreItems(service, channelInfo.tabs.first(), nextPage)
+ nextPage = page.nextPage
+ infoItems = page.items
+ 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
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}") }
+ } catch (e: Throwable) {
+ Logd(TAG, "startFeedBuilding error ${e.message}")
+ withContext(Dispatchers.Main) { showErrorDialog(e.message, "") }
+ }
}
return
}
@@ -347,6 +366,7 @@ class OnlineFeedFragment : Fragment() {
"XML" -> {}
else -> {
Log.e(TAG, "unknown url type $urlType")
+ showErrorDialog("unknown url type $urlType", "")
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
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, "") }
+ }
}
}
diff --git a/app/src/main/res/menu/feedlist.xml b/app/src/main/res/menu/feed_episodes.xml
similarity index 94%
rename from app/src/main/res/menu/feedlist.xml
rename to app/src/main/res/menu/feed_episodes.xml
index 6a40a437..9b675b69 100644
--- a/app/src/main/res/menu/feedlist.xml
+++ b/app/src/main/res/menu/feed_episodes.xml
@@ -16,7 +16,7 @@
custom:showAsAction="always">
-
@@ -44,13 +44,13 @@
diff --git a/app/src/main/res/menu/feedinfo.xml b/app/src/main/res/menu/feedinfo.xml
index c3ca6623..f34db554 100644
--- a/app/src/main/res/menu/feedinfo.xml
+++ b/app/src/main/res/menu/feedinfo.xml
@@ -21,4 +21,12 @@
+ -
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index dd574b11..28f5066b 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -153,7 +153,7 @@
Newest unplayed
Oldest unplayed
- Add to queue...
+ Add to queue…
Remove from other queues
Nothing
@@ -363,7 +363,7 @@
Queue locked
Queue unlocked
If you lock the queue, you can no longer swipe or reorder episodes.
- Switch queue
+ Switch active queue
Open queue
Do not show again
Clear queue
diff --git a/changelog.md b/changelog.md
index 27700d88..3bffdef0 100644
--- a/changelog.md
+++ b/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
* ensured duplicate episodes are removed from secondary checking during refresh
diff --git a/fastlane/metadata/android/en-US/changelogs/3020255.txt b/fastlane/metadata/android/en-US/changelogs/3020255.txt
new file mode 100644
index 00000000..a4b08bd5
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/3020255.txt
@@ -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