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/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)
|
||||
#### 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
|
||||
|
|
|
@ -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"
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -181,7 +181,7 @@ object Episodes {
|
|||
@UnstableApi
|
||||
fun deleteEpisodes(context: Context, episodes: List<Episode>) : Job {
|
||||
return runOnIOScope {
|
||||
val removedFromQueue: MutableList<Episode> = ArrayList()
|
||||
val removedFromQueue: MutableList<Episode> = mutableListOf()
|
||||
val queueItems = curQueue.episodes.toMutableList()
|
||||
for (episode in episodes) {
|
||||
if (queueItems.remove(episode)) removedFromQueue.add(episode)
|
||||
|
|
|
@ -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<Episode> = ArrayList()
|
||||
// val unlistedItems: MutableList<Episode> = 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<Episode> = 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<String, Episode>()
|
||||
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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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, "") }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -16,7 +16,7 @@
|
|||
custom:showAsAction="always">
|
||||
</item>
|
||||
<item
|
||||
android:id="@+id/refresh_item"
|
||||
android:id="@+id/refresh_feed"
|
||||
android:menuCategory="container"
|
||||
android:title="@string/refresh_label"
|
||||
custom:showAsAction="never">
|
||||
|
@ -44,13 +44,13 @@
|
|||
</item>
|
||||
|
||||
<item
|
||||
android:id="@+id/share_item"
|
||||
android:id="@+id/share_feed"
|
||||
android:menuCategory="container"
|
||||
custom:showAsAction="never"
|
||||
android:title="@string/share_label" />
|
||||
|
||||
<item
|
||||
android:id="@+id/rename_item"
|
||||
android:id="@+id/rename_feed"
|
||||
android:menuCategory="container"
|
||||
android:title="@string/rename_feed_label"
|
||||
custom:showAsAction="never" />
|
|
@ -21,4 +21,12 @@
|
|||
<item
|
||||
android:id="@+id/edit_feed_url_item"
|
||||
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>
|
|
@ -153,7 +153,7 @@
|
|||
<string name="feed_auto_download_newer">Newest 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="feed_new_episodes_action_nothing">Nothing</string>
|
||||
|
@ -363,7 +363,7 @@
|
|||
<string name="queue_locked">Queue locked</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="switch_queue">Switch queue</string>
|
||||
<string name="switch_queue">Switch active queue</string>
|
||||
<string name="open_queue">Open queue</string>
|
||||
<string name="checkbox_do_not_show_again">Do not show again</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
|
||||
|
||||
* 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