From bea7ce74a71ed03c756820b8996fbe1c3b8cfcc3 Mon Sep 17 00:00:00 2001 From: Xilin Jia <6257601+XilinJia@users.noreply.github.com> Date: Thu, 19 Sep 2024 22:43:15 +0100 Subject: [PATCH] 6.7.2 commit --- README.md | 4 +- app/build.gradle | 20 ++--- .../net/download/service/DownloadRequest.kt | 8 +- .../net/download/service/HttpDownloader.kt | 1 + .../mdiq/podcini/storage/database/Episodes.kt | 2 +- .../ac/mdiq/podcini/storage/database/Feeds.kt | 23 +++--- .../ac/mdiq/podcini/storage/model/Feed.kt | 10 +-- .../podcini/storage/model/FeedMeasures.kt | 1 + .../ui/fragment/FeedEpisodesFragment.kt | 23 ++---- .../podcini/ui/fragment/FeedInfoFragment.kt | 42 ++++++++--- .../podcini/ui/fragment/OnlineFeedFragment.kt | 74 +++++++++++++------ .../menu/{feedlist.xml => feed_episodes.xml} | 6 +- app/src/main/res/menu/feedinfo.xml | 8 ++ app/src/main/res/values/strings.xml | 4 +- changelog.md | 11 +++ .../android/en-US/changelogs/3020255.txt | 10 +++ 16 files changed, 156 insertions(+), 91 deletions(-) rename app/src/main/res/menu/{feedlist.xml => feed_episodes.xml} (94%) create mode 100644 fastlane/metadata/android/en-US/changelogs/3020255.txt 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 [F-Droid](https://f-droid.org/packages/ac.mdiq.podcini.R/) [Amazon](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