4.4.1 commit

This commit is contained in:
Xilin Jia 2024-03-27 13:34:42 +00:00
parent c3f70fd1d4
commit e08d508c41
35 changed files with 377 additions and 295 deletions

View File

@ -149,8 +149,8 @@ android {
// Version code schema (not used): // Version code schema (not used):
// "1.2.3-beta4" -> 1020304 // "1.2.3-beta4" -> 1020304
// "1.2.3" -> 1020395 // "1.2.3" -> 1020395
versionCode 3020116 versionCode 3020117
versionName "4.4.0" versionName "4.4.1"
def commit = "" def commit = ""
try { try {

View File

@ -30,6 +30,9 @@
android:name="android.hardware.touchscreen" android:name="android.hardware.touchscreen"
android:required="false"/> android:required="false"/>
<!-- this is now taken out from application -->
<!-- android:usesCleartextTraffic="true"-->
<application <application
android:name="ac.mdiq.podcini.PodciniApp" android:name="ac.mdiq.podcini.PodciniApp"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
@ -38,7 +41,6 @@
android:backupAgent=".storage.backup.OpmlBackupAgent" android:backupAgent=".storage.backup.OpmlBackupAgent"
android:restoreAnyVersion="true" android:restoreAnyVersion="true"
android:theme="@style/Theme.Podcini.Splash" android:theme="@style/Theme.Podcini.Splash"
android:usesCleartextTraffic="true"
android:supportsRtl="true" android:supportsRtl="true"
android:logo="@mipmap/ic_launcher" android:logo="@mipmap/ic_launcher"
android:resizeableActivity="true" android:resizeableActivity="true"

View File

@ -3,17 +3,16 @@ package ac.mdiq.podcini.net.discovery
import io.reactivex.Single import io.reactivex.Single
object PodcastSearcherRegistry { object PodcastSearcherRegistry {
@Suppress("UNNECESSARY_SAFE_CALL")
@get:Synchronized @get:Synchronized
var searchProviders: MutableList<SearcherInfo> = mutableListOf() var searchProviders: MutableList<SearcherInfo> = mutableListOf()
get() { get() {
if (field.isEmpty()) { if (field.isEmpty()) {
field = ArrayList() field = ArrayList()
field?.add(SearcherInfo(CombinedSearcher(), 1.0f)) field.add(SearcherInfo(CombinedSearcher(), 1.0f))
field?.add(SearcherInfo(GpodnetPodcastSearcher(), 0.0f)) field.add(SearcherInfo(GpodnetPodcastSearcher(), 0.0f))
field?.add(SearcherInfo(FyydPodcastSearcher(), 1.0f)) field.add(SearcherInfo(FyydPodcastSearcher(), 1.0f))
field?.add(SearcherInfo(ItunesPodcastSearcher(), 1.0f)) field.add(SearcherInfo(ItunesPodcastSearcher(), 1.0f))
field?.add(SearcherInfo(PodcastIndexPodcastSearcher(), 1.0f)) field.add(SearcherInfo(PodcastIndexPodcastSearcher(), 1.0f))
} }
return field return field
} }

View File

@ -67,9 +67,7 @@ abstract class PlaybackController(private val activity: FragmentActivity) {
@Synchronized @Synchronized
private fun initServiceRunning() { private fun initServiceRunning() {
if (initialized) { if (initialized) return
return
}
initialized = true initialized = true
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
@ -171,7 +169,7 @@ abstract class PlaybackController(private val activity: FragmentActivity) {
private val statusUpdate: BroadcastReceiver = object : BroadcastReceiver() { private val statusUpdate: BroadcastReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) { override fun onReceive(context: Context, intent: Intent) {
// Log.d(TAG, "Received statusUpdate Intent.") Log.d(TAG, "Received statusUpdate Intent.")
if (playbackService != null) { if (playbackService != null) {
val info = playbackService!!.pSMPInfo val info = playbackService!!.pSMPInfo
status = info.playerStatus status = info.playerStatus
@ -271,7 +269,7 @@ abstract class PlaybackController(private val activity: FragmentActivity) {
PlaybackServiceStarter(activity, media!!) PlaybackServiceStarter(activity, media!!)
.callEvenIfRunning(true) .callEvenIfRunning(true)
.start() .start()
Log.w(TAG, "Play/Pause button was pressed, but playbackservice was null!") Log.w(TAG, "playbackservice was null, restarted!")
// return // return
} }
when (status) { when (status) {

View File

@ -84,88 +84,101 @@ class HttpDownloader(request: DownloadRequest) : Downloader(request) {
isGzip = TextUtils.equals(contentEncodingHeader.lowercase(Locale.getDefault()), "gzip") isGzip = TextUtils.equals(contentEncodingHeader.lowercase(Locale.getDefault()), "gzip")
} }
Log.d(TAG, "Response code is " + response.code) Log.d(TAG, "Response code is " + response.code)// check if size specified in the response header is the same as the size of the
if (!response.isSuccessful && response.code == HttpURLConnection.HTTP_NOT_MODIFIED) { // written file. This check cannot be made if compression was used
Log.d(TAG, "Feed '" + downloadRequest.source + "' not modified since last update, Download canceled") // Log.d(TAG,"buffer: $buffer")
onCancelled() when {
return !response.isSuccessful && response.code == HttpURLConnection.HTTP_NOT_MODIFIED -> {
} else if (!response.isSuccessful || response.body == null) { Log.d(TAG, "Feed '" + downloadRequest.source + "' not modified since last update, Download canceled")
callOnFailByResponseCode(response) onCancelled()
return
} else if (downloadRequest.feedfileType == FeedMedia.FEEDFILETYPE_FEEDMEDIA && isContentTypeTextAndSmallerThan100kb(response)) {
onFail(DownloadError.ERROR_FILE_TYPE, null)
return
}
checkIfRedirect(response)
connection = BufferedInputStream(responseBody!!.byteStream())
val contentRangeHeader = if ((fileExists)) response.header("Content-Range") else null
if (fileExists && response.code == HttpURLConnection.HTTP_PARTIAL && !contentRangeHeader.isNullOrEmpty()) {
val start = contentRangeHeader.substring("bytes ".length, contentRangeHeader.indexOf("-"))
downloadRequest.soFar = start.toLong()
Log.d(TAG, "Starting download at position " + downloadRequest.soFar)
out = RandomAccessFile(destination, "rw")
out.seek(downloadRequest.soFar)
} else {
var success = destination.delete()
success = success or destination.createNewFile()
if (!success) {
throw IOException("Unable to recreate partially downloaded file")
}
out = RandomAccessFile(destination, "rw")
}
val buffer = ByteArray(BUFFER_SIZE)
var count = 0
downloadRequest.setStatusMsg(R.string.download_running)
Log.d(TAG, "Getting size of download")
downloadRequest.size = responseBody.contentLength() + downloadRequest.soFar
Log.d(TAG, "Size is " + downloadRequest.size)
if (downloadRequest.size < 0) {
downloadRequest.size = DownloadResult.SIZE_UNKNOWN.toLong()
}
val freeSpace = freeSpaceAvailable
Log.d(TAG, "Free space is $freeSpace")
if (downloadRequest.size != DownloadResult.SIZE_UNKNOWN.toLong() && downloadRequest.size > freeSpace) {
onFail(DownloadError.ERROR_NOT_ENOUGH_SPACE, null)
return
}
Log.d(TAG, "Starting download")
try {
while (!cancelled && (connection.read(buffer).also { count = it }) != -1) {
// Log.d(TAG,"buffer: $buffer")
out.write(buffer, 0, count)
downloadRequest.soFar += count
val progressPercent = (100.0 * downloadRequest.soFar / downloadRequest.size).toInt()
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
// written file. This check cannot be made if compression was used
if (!isGzip && downloadRequest.size != DownloadResult.SIZE_UNKNOWN.toLong() && downloadRequest.soFar != downloadRequest.size) {
onFail(DownloadError.ERROR_IO_WRONG_SIZE, "Download completed but size: "
+ downloadRequest.soFar + " does not equal expected size " + downloadRequest.size)
return
} else if (downloadRequest.size > 0 && downloadRequest.soFar == 0L) {
onFail(DownloadError.ERROR_IO_ERROR, "Download completed, but nothing was read")
return return
} }
val lastModified = response.header("Last-Modified") !response.isSuccessful || response.body == null -> {
if (lastModified != null) { callOnFailByResponseCode(response)
downloadRequest.setLastModified(lastModified) return
} else { }
downloadRequest.setLastModified(response.header("ETag")) downloadRequest.feedfileType == FeedMedia.FEEDFILETYPE_FEEDMEDIA && isContentTypeTextAndSmallerThan100kb(response) -> {
onFail(DownloadError.ERROR_FILE_TYPE, null)
return
}
else -> {
checkIfRedirect(response)
connection = BufferedInputStream(responseBody!!.byteStream())
val contentRangeHeader = if ((fileExists)) response.header("Content-Range") else null
if (fileExists && response.code == HttpURLConnection.HTTP_PARTIAL && !contentRangeHeader.isNullOrEmpty()) {
val start = contentRangeHeader.substring("bytes ".length, contentRangeHeader.indexOf("-"))
downloadRequest.soFar = start.toLong()
Log.d(TAG, "Starting download at position " + downloadRequest.soFar)
out = RandomAccessFile(destination, "rw")
out.seek(downloadRequest.soFar)
} else {
var success = destination.delete()
success = success or destination.createNewFile()
if (!success) {
throw IOException("Unable to recreate partially downloaded file")
}
out = RandomAccessFile(destination, "rw")
}
val buffer = ByteArray(BUFFER_SIZE)
var count = 0
downloadRequest.setStatusMsg(R.string.download_running)
Log.d(TAG, "Getting size of download")
downloadRequest.size = responseBody.contentLength() + downloadRequest.soFar
Log.d(TAG, "Size is " + downloadRequest.size)
if (downloadRequest.size < 0) {
downloadRequest.size = DownloadResult.SIZE_UNKNOWN.toLong()
}
val freeSpace = freeSpaceAvailable
Log.d(TAG, "Free space is $freeSpace")
if (downloadRequest.size != DownloadResult.SIZE_UNKNOWN.toLong() && downloadRequest.size > freeSpace) {
onFail(DownloadError.ERROR_NOT_ENOUGH_SPACE, null)
return
}
Log.d(TAG, "Starting download")
try {
while (!cancelled && (connection.read(buffer).also { count = it }) != -1) {
// Log.d(TAG,"buffer: $buffer")
out.write(buffer, 0, count)
downloadRequest.soFar += count
val progressPercent = (100.0 * downloadRequest.soFar / downloadRequest.size).toInt()
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
// written file. This check cannot be made if compression was used
when {
!isGzip && downloadRequest.size != DownloadResult.SIZE_UNKNOWN.toLong() && downloadRequest.soFar != downloadRequest.size -> {
onFail(DownloadError.ERROR_IO_WRONG_SIZE, "Download completed but size: "
+ downloadRequest.soFar + " does not equal expected size " + downloadRequest.size)
return
}
downloadRequest.size > 0 && downloadRequest.soFar == 0L -> {
onFail(DownloadError.ERROR_IO_ERROR, "Download completed, but nothing was read")
return
}
else -> {
val lastModified = response.header("Last-Modified")
if (lastModified != null) {
downloadRequest.setLastModified(lastModified)
} else {
downloadRequest.setLastModified(response.header("ETag"))
}
onSuccess()
}
}
}
} }
onSuccess()
} }
} catch (e: IllegalArgumentException) { } catch (e: IllegalArgumentException) {
e.printStackTrace() e.printStackTrace()

View File

@ -139,12 +139,13 @@ class NewEpisodesNotification {
private fun loadIcon(context: Context, feed: Feed): Bitmap? { private fun loadIcon(context: Context, feed: Feed): Bitmap? {
val iconSize = (128 * context.resources.displayMetrics.density).toInt() val iconSize = (128 * context.resources.displayMetrics.density).toInt()
return try { return try {
Glide.with(context) if (!feed.imageUrl.isNullOrBlank()) Glide.with(context)
.asBitmap() .asBitmap()
.load(feed.imageUrl) .load(feed.imageUrl)
.apply(RequestOptions().centerCrop()) .apply(RequestOptions().centerCrop())
.submit(iconSize, iconSize) .submit(iconSize, iconSize)
.get() .get()
else null
} catch (tr: Throwable) { } catch (tr: Throwable) {
null null
} }

View File

@ -64,7 +64,7 @@ class PlaybackServiceNotificationBuilder(private val context: Context) {
val options = RequestOptions().centerCrop() val options = RequestOptions().centerCrop()
try { try {
var imgLoc = playable?.getImageLocation() var imgLoc = playable?.getImageLocation()
if (imgLoc != null) { if (!imgLoc.isNullOrBlank()) {
cachedIcon = Glide.with(context) cachedIcon = Glide.with(context)
.asBitmap() .asBitmap()
.load(imgLoc) .load(imgLoc)
@ -73,7 +73,7 @@ class PlaybackServiceNotificationBuilder(private val context: Context) {
.get() .get()
} else if (playable != null) { } else if (playable != null) {
imgLoc = ImageResourceUtils.getFallbackImageLocation(playable!!) imgLoc = ImageResourceUtils.getFallbackImageLocation(playable!!)
if (imgLoc != null) { if (!imgLoc.isNullOrBlank()) {
cachedIcon = Glide.with(context) cachedIcon = Glide.with(context)
.asBitmap() .asBitmap()
.load(imgLoc) .load(imgLoc)
@ -92,8 +92,7 @@ class PlaybackServiceNotificationBuilder(private val context: Context) {
private val defaultIcon: Bitmap? private val defaultIcon: Bitmap?
get() { get() {
if (Companion.defaultIcon == null) { if (Companion.defaultIcon == null) {
Companion.defaultIcon = getBitmap( Companion.defaultIcon = getBitmap(context, R.mipmap.ic_launcher)
context, R.mipmap.ic_launcher)
} }
return Companion.defaultIcon return Companion.defaultIcon
} }

View File

@ -20,7 +20,7 @@ import java.util.*
class RemoteMedia : Playable { class RemoteMedia : Playable {
private var itemIdentifier: String? = null private var itemIdentifier: String? = null
private val downloadUrl: String? = null private val downloadUrl: String? = null
private val imageUrl: String? = null private val imageUrl: String?
private val notes: String? = null private val notes: String? = null
private val streamUrl: String? private val streamUrl: String?
@ -52,6 +52,7 @@ class RemoteMedia : Playable {
this.episodeLink = episodeLink this.episodeLink = episodeLink
this.feedAuthor = feedAuthor this.feedAuthor = feedAuthor
this.imageLocation = imageUrl this.imageLocation = imageUrl
this.imageUrl = imageUrl
this.feedLink = feedLink this.feedLink = feedLink
this.mimeType = mimeType this.mimeType = mimeType
this.pubDate = pubDate this.pubDate = pubDate
@ -67,9 +68,11 @@ class RemoteMedia : Playable {
this.episodeLink = item.link this.episodeLink = item.link
this.feedAuthor = item.feed?.author this.feedAuthor = item.feed?.author
if (!item.imageUrl.isNullOrEmpty()) { if (!item.imageUrl.isNullOrEmpty()) {
this.imageLocation = item.imageUrl this.imageLocation = item.imageLocation
this.imageUrl = item.imageUrl
} else { } else {
this.imageLocation = item.feed?.imageUrl this.imageLocation = item.feed?.imageUrl
this.imageUrl = item.feed?.imageUrl
} }
this.feedLink = item.feed?.link this.feedLink = item.feed?.link
this.mimeType = item.media?.mime_type this.mimeType = item.media?.mime_type
@ -181,7 +184,7 @@ class RemoteMedia : Playable {
} }
override fun getImageLocation(): String? { override fun getImageLocation(): String? {
return imageUrl return imageLocation
} }
override fun describeContents(): Int { override fun describeContents(): Int {

View File

@ -176,12 +176,9 @@ class OnlineFeedViewActivity : AppCompatActivity() {
super.onStop() super.onStop()
isPaused = true isPaused = true
EventBus.getDefault().unregister(this) EventBus.getDefault().unregister(this)
if (downloader != null && !downloader!!.isFinished) { if (downloader != null && !downloader!!.isFinished) downloader!!.cancel()
downloader!!.cancel()
} if (dialog != null && dialog!!.isShowing) dialog!!.dismiss()
if (dialog != null && dialog!!.isShowing) {
dialog!!.dismiss()
}
} }
public override fun onDestroy() { public override fun onDestroy() {
@ -329,7 +326,6 @@ class OnlineFeedViewActivity : AppCompatActivity() {
.subscribeOn(Schedulers.computation()) .subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object : DisposableMaybeObserver<FeedHandlerResult?>() { .subscribeWith(object : DisposableMaybeObserver<FeedHandlerResult?>() {
@UnstableApi override fun onSuccess(result: FeedHandlerResult) { @UnstableApi override fun onSuccess(result: FeedHandlerResult) {
showFeedInformation(result.feed, result.alternateFeedUrls) showFeedInformation(result.feed, result.alternateFeedUrls)
} }
@ -398,7 +394,7 @@ class OnlineFeedViewActivity : AppCompatActivity() {
binding.episodeLabel.setOnClickListener { showEpisodes(feed.items)} binding.episodeLabel.setOnClickListener { showEpisodes(feed.items)}
if (StringUtils.isNotBlank(feed.imageUrl)) { if (!feed.imageUrl.isNullOrBlank()) {
Glide.with(this) Glide.with(this)
.load(feed.imageUrl) .load(feed.imageUrl)
.apply(RequestOptions() .apply(RequestOptions()
@ -499,40 +495,44 @@ class OnlineFeedViewActivity : AppCompatActivity() {
val dli = DownloadServiceInterface.get() val dli = DownloadServiceInterface.get()
if (dli == null || selectedDownloadUrl == null) return if (dli == null || selectedDownloadUrl == null) return
if (dli.isDownloadingEpisode(selectedDownloadUrl!!)) { when {
binding.subscribeButton.isEnabled = false dli.isDownloadingEpisode(selectedDownloadUrl!!) -> {
binding.subscribeButton.setText(R.string.subscribing_label) binding.subscribeButton.isEnabled = false
} else if (feedInFeedlist()) { binding.subscribeButton.setText(R.string.subscribing_label)
binding.subscribeButton.isEnabled = true
binding.subscribeButton.setText(R.string.open_podcast)
if (didPressSubscribe) {
didPressSubscribe = false
val feed1 = DBReader.getFeed(feedId)?: return
val feedPreferences = feed1.preferences
if (feedPreferences != null) {
if (isEnableAutodownload) {
val autoDownload = binding.autoDownloadCheckBox.isChecked
feedPreferences.autoDownload = autoDownload
val preferences = getSharedPreferences(PREFS, MODE_PRIVATE)
val editor = preferences.edit()
editor.putBoolean(PREF_LAST_AUTO_DOWNLOAD, autoDownload)
editor.apply()
}
if (username != null) {
feedPreferences.username = username
feedPreferences.password = password
}
DBWriter.setFeedPreferences(feedPreferences)
}
openFeed()
} }
} else { feedInFeedlist() -> {
binding.subscribeButton.isEnabled = true binding.subscribeButton.isEnabled = true
binding.subscribeButton.setText(R.string.subscribe_label) binding.subscribeButton.setText(R.string.open)
if (isEnableAutodownload) { if (didPressSubscribe) {
binding.autoDownloadCheckBox.visibility = View.VISIBLE didPressSubscribe = false
val feed1 = DBReader.getFeed(feedId)?: return
val feedPreferences = feed1.preferences
if (feedPreferences != null) {
if (isEnableAutodownload) {
val autoDownload = binding.autoDownloadCheckBox.isChecked
feedPreferences.autoDownload = autoDownload
val preferences = getSharedPreferences(PREFS, MODE_PRIVATE)
val editor = preferences.edit()
editor.putBoolean(PREF_LAST_AUTO_DOWNLOAD, autoDownload)
editor.apply()
}
if (username != null) {
feedPreferences.username = username
feedPreferences.password = password
}
DBWriter.setFeedPreferences(feedPreferences)
}
openFeed()
}
}
else -> {
binding.subscribeButton.isEnabled = true
binding.subscribeButton.setText(R.string.subscribe_label)
if (isEnableAutodownload) {
binding.autoDownloadCheckBox.visibility = View.VISIBLE
}
} }
} }
} }
@ -579,9 +579,7 @@ class OnlineFeedViewActivity : AppCompatActivity() {
setResult(RESULT_ERROR) setResult(RESULT_ERROR)
finish() finish()
} }
if (dialog != null && dialog!!.isShowing) { if (dialog != null && dialog!!.isShowing) dialog!!.dismiss()
dialog!!.dismiss()
}
dialog = builder.show() dialog = builder.show()
} }
} }
@ -615,17 +613,13 @@ class OnlineFeedViewActivity : AppCompatActivity() {
val urlsMap: Map<String, String> val urlsMap: Map<String, String>
try { try {
urlsMap = fd.findLinks(feedFile, baseUrl) urlsMap = fd.findLinks(feedFile, baseUrl)
if (urlsMap.isEmpty()) { if (urlsMap.isEmpty()) return false
return false
}
} catch (e: IOException) { } catch (e: IOException) {
e.printStackTrace() e.printStackTrace()
return false return false
} }
if (isPaused || isFinishing) { if (isPaused || isFinishing) return false
return false
}
val titles: MutableList<String?> = ArrayList() val titles: MutableList<String?> = ArrayList()
@ -657,9 +651,7 @@ class OnlineFeedViewActivity : AppCompatActivity() {
.setAdapter(adapter, onClickListener) .setAdapter(adapter, onClickListener)
runOnUiThread { runOnUiThread {
if (dialog != null && dialog!!.isShowing) { if (dialog != null && dialog!!.isShowing) dialog!!.dismiss()
dialog!!.dismiss()
}
dialog = ab.show() dialog = ab.show()
} }
return true return true

View File

@ -106,7 +106,7 @@ class SelectSubscriptionActivity : AppCompatActivity() {
private fun getBitmapFromUrl(feed: Feed) { private fun getBitmapFromUrl(feed: Feed) {
val iconSize = (128 * resources.displayMetrics.density).toInt() val iconSize = (128 * resources.displayMetrics.density).toInt()
Glide.with(this) if (!feed.imageUrl.isNullOrBlank()) Glide.with(this)
.asBitmap() .asBitmap()
.load(feed.imageUrl) .load(feed.imageUrl)
.apply(RequestOptions.overrideOf(iconSize, iconSize)) .apply(RequestOptions.overrideOf(iconSize, iconSize))

View File

@ -89,12 +89,15 @@ class ChaptersListAdapter(private val context: Context, private val callback: Ca
if (sc.imageUrl.isNullOrEmpty()) { if (sc.imageUrl.isNullOrEmpty()) {
Glide.with(context).clear(holder.image) Glide.with(context).clear(holder.image)
} else { } else {
if (media != null) Glide.with(context) if (media != null) {
.load(EmbeddedChapterImage.getModelFor(media!!, position)) val imgUrl = EmbeddedChapterImage.getModelFor(media!!,position)
.apply(RequestOptions() if (imgUrl != null) Glide.with(context)
.dontAnimate() .load(imgUrl)
.transform(FitCenter(), RoundedCorners((4 * context.resources.displayMetrics.density).toInt()))) .apply(RequestOptions()
.into(holder.image) .dontAnimate()
.transform(FitCenter(), RoundedCorners((4 * context.resources.displayMetrics.density).toInt())))
.into(holder.image)
}
} }
} else { } else {
holder.image.visibility = View.GONE holder.image.visibility = View.GONE

View File

@ -77,7 +77,7 @@ class CoverLoader(activity: MainActivity) {
.load(uri) .load(uri)
.apply(options) .apply(options)
if (fallbackUri != null) { if (!fallbackUri.isNullOrBlank()) {
builder = builder.error(Glide.with(imgvCover!!) builder = builder.error(Glide.with(imgvCover!!)
.`as`(Drawable::class.java) .`as`(Drawable::class.java)
.load(fallbackUri) .load(fallbackUri)

View File

@ -53,7 +53,7 @@ class FeedDiscoverAdapter(mainActivity: MainActivity) : BaseAdapter() {
val podcast: PodcastSearchResult? = getItem(position) val podcast: PodcastSearchResult? = getItem(position)
holder.imageView!!.contentDescription = podcast?.title holder.imageView!!.contentDescription = podcast?.title
Glide.with(mainActivityRef.get()!!) if (!podcast?.imageUrl.isNullOrBlank()) Glide.with(mainActivityRef.get()!!)
.load(podcast?.imageUrl) .load(podcast?.imageUrl)
.apply(RequestOptions() .apply(RequestOptions()
.placeholder(R.color.light_gray) .placeholder(R.color.light_gray)

View File

@ -77,7 +77,7 @@ open class HorizontalFeedListAdapter(mainActivity: MainActivity) :
false false
} }
Glide.with(mainActivityRef.get()!!) if (!podcast.imageUrl.isNullOrBlank()) Glide.with(mainActivityRef.get()!!)
.load(podcast.imageUrl) .load(podcast.imageUrl)
.apply(RequestOptions() .apply(RequestOptions()
.placeholder(R.color.light_gray) .placeholder(R.color.light_gray)

View File

@ -287,7 +287,7 @@ class NavListAdapter(private val itemAccess: ItemAccess, context: Activity) :
val feed = drawerItem.feed val feed = drawerItem.feed
val context = activity.get() ?: return val context = activity.get() ?: return
Glide.with(context) if (!feed.imageUrl.isNullOrBlank()) Glide.with(context)
.load(feed.imageUrl) .load(feed.imageUrl)
.apply(RequestOptions() .apply(RequestOptions()
.placeholder(R.color.light_gray) .placeholder(R.color.light_gray)

View File

@ -64,7 +64,7 @@ class OnlineFeedsAdapter(private val context: Context, objects: List<PodcastSear
} else viewHolder.updateView.visibility = View.INVISIBLE } else viewHolder.updateView.visibility = View.INVISIBLE
//Update the empty imageView with the image from the feed //Update the empty imageView with the image from the feed
Glide.with(context) if (!podcast.imageUrl.isNullOrBlank()) Glide.with(context)
.load(podcast.imageUrl) .load(podcast.imageUrl)
.apply(RequestOptions() .apply(RequestOptions()
.placeholder(R.color.light_gray) .placeholder(R.color.light_gray)

View File

@ -27,7 +27,7 @@ class SimpleIconListAdapter<T : SimpleIconListAdapter.ListItem>(private val cont
val binding = SimpleIconListItemBinding.bind(view!!) val binding = SimpleIconListItemBinding.bind(view!!)
binding.title.text = item.title binding.title.text = item.title
binding.subtitle.text = item.subtitle binding.subtitle.text = item.subtitle
Glide.with(context) if (item.imageUrl.isNotBlank()) Glide.with(context)
.load(item.imageUrl) .load(item.imageUrl)
.apply(RequestOptions() .apply(RequestOptions()
.diskCacheStrategy(DiskCacheStrategy.NONE) .diskCacheStrategy(DiskCacheStrategy.NONE)

View File

@ -8,6 +8,7 @@ import ac.mdiq.podcini.storage.DBTasks
import ac.mdiq.podcini.playback.PlaybackServiceStarter import ac.mdiq.podcini.playback.PlaybackServiceStarter
import ac.mdiq.podcini.storage.model.feed.FeedItem import ac.mdiq.podcini.storage.model.feed.FeedItem
import ac.mdiq.podcini.storage.model.playback.MediaType import ac.mdiq.podcini.storage.model.playback.MediaType
import android.util.Log
class PlayActionButton(item: FeedItem) : ItemActionButton(item) { class PlayActionButton(item: FeedItem) : ItemActionButton(item) {
override fun getLabel(): Int { override fun getLabel(): Int {
@ -18,6 +19,7 @@ class PlayActionButton(item: FeedItem) : ItemActionButton(item) {
} }
@UnstableApi override fun onClick(context: Context) { @UnstableApi override fun onClick(context: Context) {
val media = item.media ?: return val media = item.media ?: return
Log.d("PlayActionButton", "onClick called")
if (!media.fileExists()) { if (!media.fileExists()) {
DBTasks.notifyMissingFeedMediaFile(context, media) DBTasks.notifyMissingFeedMediaFile(context, media)
return return

View File

@ -121,8 +121,7 @@ class SwipeActionsDialog(private val context: Context, private val tag: String)
for (i in keys.indices) { for (i in keys.indices) {
val action = keys[i] val action = keys[i]
val item = SwipeactionsPickerItemBinding.inflate(LayoutInflater.from( val item = SwipeactionsPickerItemBinding.inflate(LayoutInflater.from(context))
context))
item.swipeActionLabel.text = action.getTitle(context) item.swipeActionLabel.text = action.getTitle(context)
val icon = DrawableCompat.wrap(AppCompatResources.getDrawable(context, action.getActionIcon())!!) val icon = DrawableCompat.wrap(AppCompatResources.getDrawable(context, action.getActionIcon())!!)

View File

@ -71,6 +71,8 @@ abstract class BaseEpisodesListFragment : Fragment(), SelectableAdapter.OnSelect
lateinit var listAdapter: EpisodeItemListAdapter lateinit var listAdapter: EpisodeItemListAdapter
protected lateinit var txtvInformation: TextView protected lateinit var txtvInformation: TextView
private var currentPlaying: EpisodeItemViewHolder? = null
@JvmField @JvmField
var episodes: MutableList<FeedItem> = ArrayList() var episodes: MutableList<FeedItem> = ArrayList()
@ -327,7 +329,7 @@ abstract class BaseEpisodesListFragment : Fragment(), SelectableAdapter.OnSelect
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(event: FeedItemEvent) { fun onEventMainThread(event: FeedItemEvent) {
Log.d(TAG, "onEventMainThread() called with: event = [$event]") Log.d(TAG, "onEventMainThread() called with FeedItemEvent event = [$event]")
for (item in event.items) { for (item in event.items) {
val pos: Int = FeedItemUtil.indexOfItemWithId(episodes, item.id) val pos: Int = FeedItemUtil.indexOfItemWithId(episodes, item.id)
if (pos >= 0) { if (pos >= 0) {
@ -344,11 +346,19 @@ abstract class BaseEpisodesListFragment : Fragment(), SelectableAdapter.OnSelect
@UnstableApi @Subscribe(threadMode = ThreadMode.MAIN) @UnstableApi @Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(event: PlaybackPositionEvent) { fun onEventMainThread(event: PlaybackPositionEvent) {
for (i in 0 until listAdapter.itemCount) { // Log.d(TAG, "onEventMainThread() called with PlaybackPositionEvent event = [$event]")
val holder: EpisodeItemViewHolder? = recyclerView.findViewHolderForAdapterPosition(i) as? EpisodeItemViewHolder if (currentPlaying != null && currentPlaying!!.isCurrentlyPlayingItem)
if (holder != null && holder.isCurrentlyPlayingItem) { currentPlaying!!.notifyPlaybackPositionUpdated(event)
holder.notifyPlaybackPositionUpdated(event) else {
break Log.d(TAG, "onEventMainThread() search list")
for (i in 0 until listAdapter.itemCount) {
val holder: EpisodeItemViewHolder? =
recyclerView.findViewHolderForAdapterPosition(i) as? EpisodeItemViewHolder
if (holder != null && holder.isCurrentlyPlayingItem) {
currentPlaying = holder
holder.notifyPlaybackPositionUpdated(event)
break
}
} }
} }
} }

View File

@ -70,7 +70,7 @@ class CompletedDownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeLis
private var disposable: Disposable? = null private var disposable: Disposable? = null
private var displayUpArrow = false private var displayUpArrow = false
private var currentPlaying: EpisodeItemViewHolder? = null
@UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, @UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
@ -272,12 +272,19 @@ class CompletedDownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeLis
@UnstableApi @Subscribe(threadMode = ThreadMode.MAIN) @UnstableApi @Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(event: PlaybackPositionEvent) { fun onEventMainThread(event: PlaybackPositionEvent) {
// if (event == null) return // Log.d(TAG, "onEventMainThread() called with PlaybackPositionEvent event = [$event]")
for (i in 0 until adapter.itemCount) { if (currentPlaying != null && currentPlaying!!.isCurrentlyPlayingItem)
val holder: EpisodeItemViewHolder? = recyclerView.findViewHolderForAdapterPosition(i) as? EpisodeItemViewHolder currentPlaying!!.notifyPlaybackPositionUpdated(event)
if (holder != null && holder.isCurrentlyPlayingItem) { else {
holder.notifyPlaybackPositionUpdated(event) Log.d(TAG, "onEventMainThread() search list")
break for (i in 0 until adapter.itemCount) {
val holder: EpisodeItemViewHolder? =
recyclerView.findViewHolderForAdapterPosition(i) as? EpisodeItemViewHolder
if (holder != null && holder.isCurrentlyPlayingItem) {
currentPlaying = holder
holder.notifyPlaybackPositionUpdated(event)
break
}
} }
} }
refreshInfoBar() refreshInfoBar()

View File

@ -35,6 +35,7 @@ import android.view.LayoutInflater
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Button
import android.widget.ImageView import android.widget.ImageView
import android.widget.ProgressBar import android.widget.ProgressBar
import android.widget.TextView import android.widget.TextView
@ -189,7 +190,7 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
val balloon: Balloon = Balloon.Builder(requireContext()) val balloon: Balloon = Balloon.Builder(requireContext())
.setArrowOrientation(ArrowOrientation.TOP) .setArrowOrientation(ArrowOrientation.TOP)
.setArrowOrientationRules(ArrowOrientationRules.ALIGN_FIXED) .setArrowOrientationRules(ArrowOrientationRules.ALIGN_FIXED)
.setArrowPosition(0.25f + (if ((isLocaleRtl xor offerStreaming)) 0f else 0.5f)) .setArrowPosition(0.25f + (if (isLocaleRtl xor offerStreaming) 0f else 0.5f))
.setWidthRatio(1.0f) .setWidthRatio(1.0f)
.setMarginLeft(8) .setMarginLeft(8)
.setMarginRight(8) .setMarginRight(8)
@ -199,10 +200,10 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
.setDismissWhenTouchOutside(true) .setDismissWhenTouchOutside(true)
.setLifecycleOwner(this) .setLifecycleOwner(this)
.build() .build()
val binding_ = PopupBubbleViewBinding.bind(balloon.getContentView()) val ballonView = balloon.getContentView()
val positiveButton = binding_.balloonButtonPositive val positiveButton = ballonView.findViewById(R.id.balloon_button_positive) as Button
val negativeButton = binding_.balloonButtonNegative val negativeButton = ballonView.findViewById(R.id.balloon_button_negative) as Button
val message: TextView = binding_.balloonMessage val message: TextView = ballonView.findViewById(R.id.balloon_message) as TextView
message.setText(if (offerStreaming) R.string.on_demand_config_stream_text message.setText(if (offerStreaming) R.string.on_demand_config_stream_text
else R.string.on_demand_config_download_text) else R.string.on_demand_config_download_text)
positiveButton.setOnClickListener { positiveButton.setOnClickListener {
@ -297,13 +298,15 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
RoundedCorners((8 * resources.displayMetrics.density).toInt())) RoundedCorners((8 * resources.displayMetrics.density).toInt()))
.dontAnimate() .dontAnimate()
Glide.with(this) val imgLocFB = ImageResourceUtils.getFallbackImageLocation(item!!)
.load(item!!.imageLocation) if (!item!!.imageLocation.isNullOrBlank() || !imgLocFB.isNullOrBlank())
.error(Glide.with(this) Glide.with(this)
.load(ImageResourceUtils.getFallbackImageLocation(item!!)) .load(item!!.imageLocation)
.apply(options)) .error(Glide.with(this)
.apply(options) .load(imgLocFB)
.into(imgvCover) .apply(options))
.apply(options)
.into(imgvCover)
updateButtons() updateButtons()
} }
@ -398,15 +401,9 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
@UnstableApi @Subscribe(sticky = true, threadMode = ThreadMode.MAIN) @UnstableApi @Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
fun onEventMainThread(event: EpisodeDownloadEvent) { fun onEventMainThread(event: EpisodeDownloadEvent) {
if (item == null || item!!.media == null) { if (item == null || item!!.media == null) return
return if (!event.urls.contains(item!!.media!!.download_url)) return
} if (itemsLoaded && activity != null) updateButtons()
if (!event.urls.contains(item!!.media!!.download_url)) {
return
}
if (itemsLoaded && activity != null) {
updateButtons()
}
} }
@UnstableApi @Subscribe(threadMode = ThreadMode.MAIN) @UnstableApi @Subscribe(threadMode = ThreadMode.MAIN)

View File

@ -1,15 +1,9 @@
package ac.mdiq.podcini.ui.fragment package ac.mdiq.podcini.ui.fragment
import ac.mdiq.podcini.R import ac.mdiq.podcini.R
import ac.mdiq.podcini.preferences.UserPreferences.allEpisodesSortOrder
import ac.mdiq.podcini.preferences.UserPreferences.prefFilterAllEpisodes
import ac.mdiq.podcini.storage.model.feed.FeedItem import ac.mdiq.podcini.storage.model.feed.FeedItem
import ac.mdiq.podcini.storage.model.feed.FeedItemFilter import ac.mdiq.podcini.storage.model.feed.FeedItemFilter
import ac.mdiq.podcini.storage.model.feed.SortOrder
import ac.mdiq.podcini.ui.dialog.AllEpisodesFilterDialog
import ac.mdiq.podcini.ui.dialog.AllEpisodesFilterDialog.AllEpisodesFilterChangedEvent import ac.mdiq.podcini.ui.dialog.AllEpisodesFilterDialog.AllEpisodesFilterChangedEvent
import ac.mdiq.podcini.ui.dialog.ItemSortDialog
import ac.mdiq.podcini.util.event.FeedListUpdateEvent
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
@ -18,8 +12,6 @@ import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.annotation.OptIn import androidx.annotation.OptIn
import androidx.media3.common.util.UnstableApi import androidx.media3.common.util.UnstableApi
import org.apache.commons.lang3.StringUtils
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.Subscribe
import kotlin.math.min import kotlin.math.min
@ -49,6 +41,7 @@ class EpisodesListFragment : BaseEpisodesListFragment() {
} }
override fun loadData(): List<FeedItem> { override fun loadData(): List<FeedItem> {
if (episodeList.isEmpty()) return listOf()
return episodeList.subList(0, min(episodeList.size-1, page * EPISODES_PER_PAGE)) return episodeList.subList(0, min(episodeList.size-1, page * EPISODES_PER_PAGE))
} }

View File

@ -76,6 +76,8 @@ class ExternalPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
private var showTimeLeft = false private var showTimeLeft = false
private var currentMedia: Playable? = null
private var controller: PlaybackController? = null private var controller: PlaybackController? = null
private var disposable: Disposable? = null private var disposable: Disposable? = null
@ -192,6 +194,7 @@ class ExternalPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
} }
override fun loadMediaInfo() { override fun loadMediaInfo() {
Log.d(TAG, "setupPlaybackController loadMediaInfo called")
this@ExternalPlayerFragment.loadMediaInfo() this@ExternalPlayerFragment.loadMediaInfo()
} }
@ -284,19 +287,24 @@ class ExternalPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
@UnstableApi @UnstableApi
private fun loadMediaInfo() { private fun loadMediaInfo() {
Log.d(TAG, "Loading media info") Log.d(TAG, "loadMediaInfo called")
if (controller == null) { if (controller == null) {
Log.w(TAG, "loadMediaInfo was called while PlaybackController was null!") Log.w(TAG, "loadMediaInfo was called while PlaybackController was null!")
return return
} }
val theMedia = controller?.getMedia()
disposable?.dispose() if (currentMedia == null || theMedia?.getIdentifier() != currentMedia?.getIdentifier()) {
disposable = Maybe.fromCallable<Playable?> { controller?.getMedia() } Log.d(TAG, "reloading media info")
.subscribeOn(Schedulers.io()) disposable?.dispose()
.observeOn(AndroidSchedulers.mainThread()) disposable = Maybe.fromCallable<Playable?> { theMedia }
.subscribe({ media: Playable? -> this.updateUi(media) }, .subscribeOn(Schedulers.io())
{ error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) }, .observeOn(AndroidSchedulers.mainThread())
{ (activity as MainActivity).setPlayerVisible(false) }) .subscribe({ media: Playable? ->
currentMedia = media
this.updateUi(media) },
{ error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) },
{ (activity as MainActivity).setPlayerVisible(false) })
}
} }
@OptIn(UnstableApi::class) override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) { @OptIn(UnstableApi::class) override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
@ -354,9 +362,8 @@ class ExternalPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
@UnstableApi @UnstableApi
private fun updateUi(media: Playable?) { private fun updateUi(media: Playable?) {
if (media == null) { if (media == null) return
return
}
episodeTitle.text = media.getEpisodeTitle() episodeTitle.text = media.getEpisodeTitle()
(activity as MainActivity).setPlayerVisible(true) (activity as MainActivity).setPlayerVisible(true)
onPositionObserverUpdate(PlaybackPositionEvent(media.getPosition(), media.getDuration())) onPositionObserverUpdate(PlaybackPositionEvent(media.getPosition(), media.getDuration()))
@ -367,13 +374,19 @@ class ExternalPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
.fitCenter() .fitCenter()
.dontAnimate() .dontAnimate()
Glide.with(this) val imgLoc = getEpisodeListImageLocation(media)
.load(getEpisodeListImageLocation(media)) val imgLocFB = getFallbackImageLocation(media)
.error(Glide.with(this) if (!imgLoc.isNullOrBlank())
.load(getFallbackImageLocation(media)) Glide.with(this)
.apply(options)) .load(imgLoc)
.apply(options) .apply(options)
.into(imgvCover) .into(imgvCover)
else if (!imgLocFB.isNullOrBlank())
Glide.with(this)
.load(imgLocFB)
.apply(options)
.into(imgvCover)
else imgvCover.setImageResource(R.mipmap.ic_launcher)
if (controller?.isPlayingVideoLocally == true) { if (controller?.isPlayingVideoLocally == true) {
(activity as MainActivity).bottomSheet.setLocked(true) (activity as MainActivity).bottomSheet.setLocked(true)

View File

@ -186,22 +186,24 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
Log.d(TAG, "Language is " + feed!!.language) Log.d(TAG, "Language is " + feed!!.language)
Log.d(TAG, "Author is " + feed!!.author) Log.d(TAG, "Author is " + feed!!.author)
Log.d(TAG, "URL is " + feed!!.download_url) Log.d(TAG, "URL is " + feed!!.download_url)
Glide.with(this) if (!feed?.imageUrl.isNullOrBlank()) {
.load(feed!!.imageUrl) Glide.with(this)
.apply(RequestOptions() .load(feed!!.imageUrl)
.placeholder(R.color.light_gray) .apply(RequestOptions()
.error(R.color.light_gray) .placeholder(R.color.light_gray)
.fitCenter() .error(R.color.light_gray)
.dontAnimate()) .fitCenter()
.into(imgvCover) .dontAnimate())
Glide.with(this) .into(imgvCover)
.load(feed!!.imageUrl) Glide.with(this)
.apply(RequestOptions() .load(feed!!.imageUrl)
.placeholder(R.color.image_readability_tint) .apply(RequestOptions()
.error(R.color.image_readability_tint) .placeholder(R.color.image_readability_tint)
.transform(FastBlurTransformation()) .error(R.color.image_readability_tint)
.dontAnimate()) .transform(FastBlurTransformation())
.into(imgvBackground) .dontAnimate())
.into(imgvBackground)
}
txtvTitle.text = feed!!.title txtvTitle.text = feed!!.title
txtvTitle.setMaxLines(6) txtvTitle.setMaxLines(6)

View File

@ -77,7 +77,9 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
private lateinit var adapter: FeedItemListAdapter private lateinit var adapter: FeedItemListAdapter
private lateinit var swipeActions: SwipeActions private lateinit var swipeActions: SwipeActions
private lateinit var nextPageLoader: MoreContentListFooterUtil private lateinit var nextPageLoader: MoreContentListFooterUtil
private var currentPlaying: EpisodeItemViewHolder? = null
private var displayUpArrow = false private var displayUpArrow = false
private var headerCreated = false private var headerCreated = false
private var feedID: Long = 0 private var feedID: Long = 0
@ -314,7 +316,7 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(event: FeedItemEvent) { fun onEventMainThread(event: FeedItemEvent) {
Log.d(TAG, "onEventMainThread() called with: event = [$event]") Log.d(TAG, "onEventMainThread() called with FeedItemEvent event = [$event]")
if (feed == null || feed!!.items.isEmpty()) { if (feed == null || feed!!.items.isEmpty()) {
return return
} }
@ -334,7 +336,7 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN) @Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
fun onEventMainThread(event: EpisodeDownloadEvent) { fun onEventMainThread(event: EpisodeDownloadEvent) {
Log.d(TAG, "onEventMainThread() called with: event = [$event]") Log.d(TAG, "onEventMainThread() called with EpisodeDownloadEvent event = [$event]")
if (feed == null || feed!!.items.isEmpty()) { if (feed == null || feed!!.items.isEmpty()) {
return return
} }
@ -348,13 +350,19 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
@UnstableApi @Subscribe(threadMode = ThreadMode.MAIN) @UnstableApi @Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(event: PlaybackPositionEvent) { fun onEventMainThread(event: PlaybackPositionEvent) {
Log.d(TAG, "onEventMainThread() called with: event = [$event]") // Log.d(TAG, "onEventMainThread() called with PlaybackPositionEvent event = [$event]")
for (i in 0 until adapter.itemCount) { if (currentPlaying != null && currentPlaying!!.isCurrentlyPlayingItem)
val holder: EpisodeItemViewHolder? = currentPlaying!!.notifyPlaybackPositionUpdated(event)
binding.recyclerView.findViewHolderForAdapterPosition(i) as? EpisodeItemViewHolder else {
if (holder != null && holder.isCurrentlyPlayingItem) { Log.d(TAG, "onEventMainThread() search list")
holder.notifyPlaybackPositionUpdated(event) for (i in 0 until adapter.itemCount) {
break val holder: EpisodeItemViewHolder? =
binding.recyclerView.findViewHolderForAdapterPosition(i) as? EpisodeItemViewHolder
if (holder != null && holder.isCurrentlyPlayingItem) {
currentPlaying = holder
holder.notifyPlaybackPositionUpdated(event)
break
}
} }
} }
} }
@ -521,24 +529,25 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
} }
private fun loadFeedImage() { private fun loadFeedImage() {
if (feed == null) return if (!feed?.imageUrl.isNullOrBlank()) {
Glide.with(this) Glide.with(this)
.load(feed!!.imageUrl) .load(feed!!.imageUrl)
.apply(RequestOptions() .apply(RequestOptions()
.placeholder(R.color.image_readability_tint) .placeholder(R.color.image_readability_tint)
.error(R.color.image_readability_tint) .error(R.color.image_readability_tint)
.transform(FastBlurTransformation()) .transform(FastBlurTransformation())
.dontAnimate()) .dontAnimate())
.into(binding.imgvBackground) .into(binding.imgvBackground)
Glide.with(this) Glide.with(this)
.load(feed!!.imageUrl) .load(feed!!.imageUrl)
.apply(RequestOptions() .apply(RequestOptions()
.placeholder(R.color.light_gray) .placeholder(R.color.light_gray)
.error(R.color.light_gray) .error(R.color.light_gray)
.fitCenter() .fitCenter()
.dontAnimate()) .dontAnimate())
.into(binding.header.imgvCover) .into(binding.header.imgvCover)
}
} }
@UnstableApi private fun loadItems() { @UnstableApi private fun loadItems() {
@ -657,7 +666,7 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
} }
companion object { companion object {
const val TAG: String = "ItemlistFragment" const val TAG: String = "FeedItemlistFragment"
private const val ARGUMENT_FEED_ID = "argument.ac.mdiq.podcini.feed_id" private const val ARGUMENT_FEED_ID = "argument.ac.mdiq.podcini.feed_id"
private const val KEY_UP_ARROW = "up_arrow" private const val KEY_UP_ARROW = "up_arrow"

View File

@ -98,8 +98,7 @@ class OnlineSearchFragment : Fragment() {
firstVisibleItem: Int, firstVisibleItem: Int,
visibleItemCount: Int, visibleItemCount: Int,
totalItemCount: Int totalItemCount: Int
) { ) {}
}
}) })
return binding.root return binding.root
} }

View File

@ -279,8 +279,9 @@ class PlayerDetailsFragment : Fragment() {
if (displayedChapterIndex == -1 || media!!.getChapters().isEmpty() || media!!.getChapters()[displayedChapterIndex].imageUrl.isNullOrEmpty()) { if (displayedChapterIndex == -1 || media!!.getChapters().isEmpty() || media!!.getChapters()[displayedChapterIndex].imageUrl.isNullOrEmpty()) {
cover.into(binding.imgvCover) cover.into(binding.imgvCover)
} else { } else {
Glide.with(this) val imgLoc = EmbeddedChapterImage.getModelFor(media!!, displayedChapterIndex)
.load(EmbeddedChapterImage.getModelFor(media!!, displayedChapterIndex)) if (imgLoc != null) Glide.with(this)
.load(imgLoc)
.apply(options) .apply(options)
.thumbnail(cover) .thumbnail(cover)
.error(cover) .error(cover)

View File

@ -81,6 +81,8 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda
private var queue: MutableList<FeedItem> = mutableListOf() private var queue: MutableList<FeedItem> = mutableListOf()
private var recyclerAdapter: QueueRecyclerAdapter? = null private var recyclerAdapter: QueueRecyclerAdapter? = null
private var currentPlaying: EpisodeItemViewHolder? = null
private var disposable: Disposable? = null private var disposable: Disposable? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -205,7 +207,7 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(event: QueueEvent) { fun onEventMainThread(event: QueueEvent) {
Log.d(TAG, "onEventMainThread() called with: event = [$event]") Log.d(TAG, "onEventMainThread() called with QueueEvent event = [$event]")
if (recyclerAdapter == null) { if (recyclerAdapter == null) {
loadItems(true) loadItems(true)
return return
@ -242,7 +244,7 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(event: FeedItemEvent) { fun onEventMainThread(event: FeedItemEvent) {
Log.d(TAG, "onEventMainThread() called with: event = [$event]") Log.d(TAG, "onEventMainThread() called with FeedItemEvent event = [$event]")
if (recyclerAdapter == null) { if (recyclerAdapter == null) {
loadItems(true) loadItems(true)
return return
@ -264,6 +266,7 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN) @Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
fun onEventMainThread(event: EpisodeDownloadEvent) { fun onEventMainThread(event: EpisodeDownloadEvent) {
Log.d(TAG, "onEventMainThread() called with EpisodeDownloadEvent event = [$event]")
for (downloadUrl in event.urls) { for (downloadUrl in event.urls) {
val pos: Int = FeedItemUtil.indexOfItemWithDownloadUrl(queue.toList(), downloadUrl) val pos: Int = FeedItemUtil.indexOfItemWithDownloadUrl(queue.toList(), downloadUrl)
if (pos >= 0) { if (pos >= 0) {
@ -274,12 +277,20 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda
@UnstableApi @Subscribe(threadMode = ThreadMode.MAIN) @UnstableApi @Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(event: PlaybackPositionEvent) { fun onEventMainThread(event: PlaybackPositionEvent) {
// Log.d(TAG, "onEventMainThread() called with PlaybackPositionEvent event = [$event]")
if (recyclerAdapter != null) { if (recyclerAdapter != null) {
for (i in 0 until recyclerAdapter!!.itemCount) { if (currentPlaying != null && currentPlaying!!.isCurrentlyPlayingItem)
val holder: EpisodeItemViewHolder? = recyclerView.findViewHolderForAdapterPosition(i) as? EpisodeItemViewHolder currentPlaying!!.notifyPlaybackPositionUpdated(event)
if (holder != null && holder.isCurrentlyPlayingItem) { else {
holder.notifyPlaybackPositionUpdated(event) Log.d(TAG, "onEventMainThread() search list")
break for (i in 0 until recyclerAdapter!!.itemCount) {
val holder: EpisodeItemViewHolder? =
recyclerView.findViewHolderForAdapterPosition(i) as? EpisodeItemViewHolder
if (holder != null && holder.isCurrentlyPlayingItem) {
currentPlaying = holder
holder.notifyPlaybackPositionUpdated(event)
break
}
} }
} }
} }
@ -287,6 +298,7 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
fun onPlayerStatusChanged(event: PlayerStatusEvent?) { fun onPlayerStatusChanged(event: PlayerStatusEvent?) {
Log.d(TAG, "onPlayerStatusChanged() called with event = [$event]")
loadItems(false) loadItems(false)
refreshToolbarState() refreshToolbarState()
} }

View File

@ -70,6 +70,7 @@ class SearchFragment : Fragment(), SelectableAdapter.OnSelectModeListener {
private lateinit var automaticSearchDebouncer: Handler private lateinit var automaticSearchDebouncer: Handler
private var results: MutableList<FeedItem> = mutableListOf() private var results: MutableList<FeedItem> = mutableListOf()
private var currentPlaying: EpisodeItemViewHolder? = null
private var disposable: Disposable? = null private var disposable: Disposable? = null
private var lastQueryChange: Long = 0 private var lastQueryChange: Long = 0
@ -291,12 +292,18 @@ class SearchFragment : Fragment(), SelectableAdapter.OnSelectModeListener {
@UnstableApi @Subscribe(threadMode = ThreadMode.MAIN) @UnstableApi @Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(event: PlaybackPositionEvent) { fun onEventMainThread(event: PlaybackPositionEvent) {
for (i in 0 until adapter.itemCount) { if (currentPlaying != null && currentPlaying!!.isCurrentlyPlayingItem)
val holder: EpisodeItemViewHolder? = currentPlaying!!.notifyPlaybackPositionUpdated(event)
recyclerView.findViewHolderForAdapterPosition(i) as? EpisodeItemViewHolder else {
if (holder != null && holder.isCurrentlyPlayingItem) { Log.d(FeedItemlistFragment.TAG, "onEventMainThread() search list")
holder.notifyPlaybackPositionUpdated(event) for (i in 0 until adapter.itemCount) {
break val holder: EpisodeItemViewHolder? =
recyclerView.findViewHolderForAdapterPosition(i) as? EpisodeItemViewHolder
if (holder != null && holder.isCurrentlyPlayingItem) {
currentPlaying = holder
holder.notifyPlaybackPositionUpdated(event)
break
}
} }
} }
} }

View File

@ -51,7 +51,7 @@ abstract class StatisticsListAdapter protected constructor(@JvmField protected v
} else { } else {
val holder = h as StatisticsHolder val holder = h as StatisticsHolder
val statsItem = statisticsData!![position - 1] val statsItem = statisticsData!![position - 1]
Glide.with(context) if (!statsItem.feed.imageUrl.isNullOrBlank()) Glide.with(context)
.load(statsItem.feed.imageUrl) .load(statsItem.feed.imageUrl)
.apply(RequestOptions() .apply(RequestOptions()
.placeholder(R.color.light_gray) .placeholder(R.color.light_gray)

View File

@ -120,12 +120,14 @@ class EpisodeItemViewHolder(private val activity: MainActivity, parent: ViewGrou
} }
if (coverHolder.visibility == View.VISIBLE) { if (coverHolder.visibility == View.VISIBLE) {
CoverLoader(activity) val imgLoc = ImageResourceUtils.getEpisodeListImageLocation(item)
.withUri(ImageResourceUtils.getEpisodeListImageLocation(item)) if (!imgLoc.isNullOrBlank()) CoverLoader(activity)
.withUri(imgLoc)
.withFallbackUri(item.feed?.imageUrl) .withFallbackUri(item.feed?.imageUrl)
.withPlaceholderView(placeholder) .withPlaceholderView(placeholder)
.withCoverView(cover) .withCoverView(cover)
.load() .load()
else cover.setImageResource(R.mipmap.ic_launcher)
} }
} }

View File

@ -187,6 +187,7 @@
<string name="filtered_label">Filtered</string> <string name="filtered_label">Filtered</string>
<string name="refresh_failed_msg">{fa-exclamation-circle} Last refresh failed. Tap to view details.</string> <string name="refresh_failed_msg">{fa-exclamation-circle} Last refresh failed. Tap to view details.</string>
<string name="open_podcast">Open podcast</string> <string name="open_podcast">Open podcast</string>
<string name="open">Open</string>
<string name="please_wait_for_data">Please wait until the data is loaded</string> <string name="please_wait_for_data">Please wait until the data is loaded</string>
<string name="updates_disabled_label">Updates disabled</string> <string name="updates_disabled_label">Updates disabled</string>
<plurals name="updated_feeds_batch_label"> <plurals name="updated_feeds_batch_label">

View File

@ -155,4 +155,13 @@
* revamped online feed view activity * revamped online feed view activity
* episodes (50 most recent) of any online feed can be viewed and played (streamed) directly without subscribing to the feed * episodes (50 most recent) of any online feed can be viewed and played (streamed) directly without subscribing to the feed
* bug fixes on passing Glide with null addresses * bug fixes on passing Glide with null addresses
* null safety enhancements in code * null safety enhancements in code
## 4.4.1
* fixed bug of app crash on stream episode customization
* disabled usesCleartextTraffic, connection to http sites appear OK, report if you find an issue
* enforced non-null load location for most Glide calls
* avoided redundant media loadings and ui updates when a new audio starts
* eliminated frequent list search during audio play, a serious energy waste
* icons in online episode list, when unavailable, are set to app logo

View File

@ -0,0 +1,9 @@
Version 4.4.1 brings several changes:
* fixed bug of app crash on stream episode customization
* disabled usesCleartextTraffic, connection to http sites appear OK, report if you find an issue
* enforced non-null load location for most Glide calls
* avoided redundant media loadings and ui updates when a new audio starts
* eliminated frequent list search during audio play, a serious energy waste
* icons in online episode list, when unavailable, are set to app logo