4.3.4 commit

This commit is contained in:
Xilin Jia 2024-03-25 15:18:29 +00:00
parent c5bf432283
commit cc4cdb281a
45 changed files with 286 additions and 207 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 3020114 versionCode 3020115
versionName "4.3.3" versionName "4.3.4"
def commit = "" def commit = ""
try { try {

View File

@ -62,12 +62,12 @@ class HttpDownloaderTest {
private fun download(url: String?, title: String, expectedResult: Boolean, deleteExisting: Boolean = true, private fun download(url: String?, title: String, expectedResult: Boolean, deleteExisting: Boolean = true,
username: String? = null, password: String? = null username: String? = null, password: String? = null
): ac.mdiq.podcini.service.download.Downloader { ): Downloader {
val feedFile: FeedFile = setupFeedFile(url, title, deleteExisting) val feedFile: FeedFile = setupFeedFile(url, title, deleteExisting)
val request = DownloadRequest( val request = DownloadRequest(
feedFile.getFile_url()!!, url!!, title, 0, feedFile.getTypeAsInt(), feedFile.getFile_url()!!, url!!, title, 0, feedFile.getTypeAsInt(),
username, password, null, false) username, password, null, false)
val downloader: ac.mdiq.podcini.service.download.Downloader = HttpDownloader(request) val downloader: Downloader = HttpDownloader(request)
downloader.call() downloader.call()
val status = downloader.result val status = downloader.result
Assert.assertNotNull(status) Assert.assertNotNull(status)
@ -101,7 +101,7 @@ class HttpDownloaderTest {
fun testCancel() { fun testCancel() {
val url = httpServer!!.baseUrl + "/delay/3" val url = httpServer!!.baseUrl + "/delay/3"
val feedFile = setupFeedFile(url, "delay", true) val feedFile = setupFeedFile(url, "delay", true)
val downloader: ac.mdiq.podcini.service.download.Downloader = HttpDownloader(DownloadRequest( val downloader: Downloader = HttpDownloader(DownloadRequest(
feedFile.getFile_url()!!, url, "delay", 0, feedFile.getFile_url()!!, url, "delay", 0,
feedFile.getTypeAsInt(), null, null, null, false)) feedFile.getTypeAsInt(), null, null, null, false))
val t: Thread = object : Thread() { val t: Thread = object : Thread() {

View File

@ -1,5 +1,6 @@
package ac.mdiq.podcini.feed.parser.namespace package ac.mdiq.podcini.feed.parser.namespace
import ac.mdiq.podcini.feed.parser.HandlerState
import android.util.Log import android.util.Log
import ac.mdiq.podcini.storage.model.feed.FeedFunding import ac.mdiq.podcini.storage.model.feed.FeedFunding
import ac.mdiq.podcini.storage.model.feed.FeedItem import ac.mdiq.podcini.storage.model.feed.FeedItem
@ -13,7 +14,7 @@ import ac.mdiq.podcini.feed.parser.util.SyndStringUtils.trimAllWhitespace
import org.xml.sax.Attributes import org.xml.sax.Attributes
class Atom : Namespace() { class Atom : Namespace() {
override fun handleElementStart(localName: String, state: ac.mdiq.podcini.feed.parser.HandlerState, attributes: Attributes): SyndElement { override fun handleElementStart(localName: String, state: HandlerState, attributes: Attributes): SyndElement {
if (ENTRY == localName) { if (ENTRY == localName) {
state.currentItem = FeedItem() state.currentItem = FeedItem()
state.items.add(state.currentItem!!) state.items.add(state.currentItem!!)
@ -88,7 +89,7 @@ class Atom : Namespace() {
return SyndElement(localName, this) return SyndElement(localName, this)
} }
override fun handleElementEnd(localName: String, state: ac.mdiq.podcini.feed.parser.HandlerState) { override fun handleElementEnd(localName: String, state: HandlerState) {
if (ENTRY == localName) { if (ENTRY == localName) {
if (state.currentItem != null && if (state.currentItem != null &&
state.tempObjects.containsKey(Itunes.DURATION)) { state.tempObjects.containsKey(Itunes.DURATION)) {

View File

@ -1,5 +1,6 @@
package ac.mdiq.podcini.feed.parser.namespace package ac.mdiq.podcini.feed.parser.namespace
import ac.mdiq.podcini.feed.parser.HandlerState
import android.util.Log import android.util.Log
import androidx.core.text.HtmlCompat import androidx.core.text.HtmlCompat
import ac.mdiq.podcini.feed.parser.element.SyndElement import ac.mdiq.podcini.feed.parser.element.SyndElement
@ -7,7 +8,7 @@ import ac.mdiq.podcini.feed.parser.util.DurationParser.inMillis
import org.xml.sax.Attributes import org.xml.sax.Attributes
class Itunes : Namespace() { class Itunes : Namespace() {
override fun handleElementStart(localName: String, state: ac.mdiq.podcini.feed.parser.HandlerState, override fun handleElementStart(localName: String, state: HandlerState,
attributes: Attributes): SyndElement { attributes: Attributes): SyndElement {
if (IMAGE == localName) { if (IMAGE == localName) {
val url: String? = attributes.getValue(IMAGE_HREF) val url: String? = attributes.getValue(IMAGE_HREF)
@ -25,7 +26,7 @@ class Itunes : Namespace() {
return SyndElement(localName, this) return SyndElement(localName, this)
} }
override fun handleElementEnd(localName: String, state: ac.mdiq.podcini.feed.parser.HandlerState) { override fun handleElementEnd(localName: String, state: HandlerState) {
if (state.contentBuf == null) { if (state.contentBuf == null) {
return return
} }

View File

@ -1,5 +1,6 @@
package ac.mdiq.podcini.feed.parser.namespace package ac.mdiq.podcini.feed.parser.namespace
import ac.mdiq.podcini.feed.parser.HandlerState
import android.util.Log import android.util.Log
import ac.mdiq.podcini.storage.model.feed.Chapter import ac.mdiq.podcini.storage.model.feed.Chapter
import ac.mdiq.podcini.feed.parser.element.SyndElement import ac.mdiq.podcini.feed.parser.element.SyndElement
@ -7,7 +8,7 @@ import ac.mdiq.podcini.feed.parser.util.DateUtils.parseTimeString
import org.xml.sax.Attributes import org.xml.sax.Attributes
class SimpleChapters : Namespace() { class SimpleChapters : Namespace() {
override fun handleElementStart(localName: String, state: ac.mdiq.podcini.feed.parser.HandlerState, attributes: Attributes): SyndElement { override fun handleElementStart(localName: String, state: HandlerState, attributes: Attributes): SyndElement {
val currentItem = state.currentItem val currentItem = state.currentItem
if (currentItem != null) { if (currentItem != null) {
if (localName == CHAPTERS) { if (localName == CHAPTERS) {
@ -29,7 +30,7 @@ class SimpleChapters : Namespace() {
return SyndElement(localName, this) return SyndElement(localName, this)
} }
override fun handleElementEnd(localName: String, state: ac.mdiq.podcini.feed.parser.HandlerState) { override fun handleElementEnd(localName: String, state: HandlerState) {
} }
companion object { companion object {

View File

@ -267,17 +267,20 @@ abstract class PlaybackController(private val activity: FragmentActivity) {
fun playPause() { fun playPause() {
if (media == null) return if (media == null) return
if (playbackService == null) { if (playbackService == null) {
PlaybackServiceStarter(activity, media!!).start() // PlaybackServiceStarter(activity, media!!).start()
PlaybackServiceStarter(activity, media!!)
.callEvenIfRunning(true)
.start()
Log.w(TAG, "Play/Pause button was pressed, but playbackservice was null!") Log.w(TAG, "Play/Pause button was pressed, but playbackservice was null!")
return // return
} }
when (status) { when (status) {
PlayerStatus.PLAYING -> playbackService!!.pause(true, false) PlayerStatus.PLAYING -> playbackService?.pause(true, false)
PlayerStatus.PAUSED, PlayerStatus.PREPARED -> playbackService!!.resume() PlayerStatus.PAUSED, PlayerStatus.PREPARED -> playbackService?.resume()
PlayerStatus.PREPARING -> playbackService!!.isStartWhenPrepared = !playbackService!!.isStartWhenPrepared PlayerStatus.PREPARING -> playbackService!!.isStartWhenPrepared = !playbackService!!.isStartWhenPrepared
PlayerStatus.INITIALIZED -> { PlayerStatus.INITIALIZED -> {
playbackService!!.isStartWhenPrepared = true if (playbackService != null) playbackService!!.isStartWhenPrepared = true
playbackService!!.prepare() playbackService?.prepare()
} }
else -> { else -> {
PlaybackServiceStarter(activity, media!!) PlaybackServiceStarter(activity, media!!)

View File

@ -1,29 +1,28 @@
package ac.mdiq.podcini.preferences package ac.mdiq.podcini.preferences
import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.ThemePreferenceBinding
import android.content.Context import android.content.Context
import android.util.AttributeSet import android.util.AttributeSet
import android.view.View
import androidx.cardview.widget.CardView import androidx.cardview.widget.CardView
import androidx.preference.Preference import androidx.preference.Preference
import androidx.preference.PreferenceViewHolder import androidx.preference.PreferenceViewHolder
import com.google.android.material.elevation.SurfaceColors import com.google.android.material.elevation.SurfaceColors
import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.ThemePreferenceBinding
class ThemePreference : Preference { class ThemePreference : Preference {
var viewBinding: ThemePreferenceBinding? = null var binding: ThemePreferenceBinding? = null
constructor(context: Context) : super(context!!) { constructor(context: Context) : super(context) {
layoutResource = R.layout.theme_preference layoutResource = R.layout.theme_preference
} }
constructor(context: Context, attrs: AttributeSet?) : super(context!!, attrs) { constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
layoutResource = R.layout.theme_preference layoutResource = R.layout.theme_preference
} }
override fun onBindViewHolder(holder: PreferenceViewHolder) { override fun onBindViewHolder(holder: PreferenceViewHolder) {
super.onBindViewHolder(holder) super.onBindViewHolder(holder)
viewBinding = ThemePreferenceBinding.bind(holder.itemView) binding = ThemePreferenceBinding.bind(holder.itemView)
updateUi() updateUi()
} }
@ -33,7 +32,7 @@ class ThemePreference : Preference {
val surfaceColorActive = SurfaceColors.getColorForElevation(context, 32 * density) val surfaceColorActive = SurfaceColors.getColorForElevation(context, 32 * density)
val activeTheme = UserPreferences.theme val activeTheme = UserPreferences.theme
card.setCardBackgroundColor(if (theme == activeTheme) surfaceColorActive else surfaceColor) card.setCardBackgroundColor(if (theme == activeTheme) surfaceColorActive else surfaceColor)
card.setOnClickListener { v: View? -> card.setOnClickListener {
UserPreferences.theme = theme UserPreferences.theme = theme
if (onPreferenceChangeListener != null) { if (onPreferenceChangeListener != null) {
onPreferenceChangeListener!!.onPreferenceChange(this, UserPreferences.theme) onPreferenceChangeListener!!.onPreferenceChange(this, UserPreferences.theme)
@ -43,8 +42,8 @@ class ThemePreference : Preference {
} }
fun updateUi() { fun updateUi() {
updateThemeCard(viewBinding!!.themeSystemCard, UserPreferences.ThemePreference.SYSTEM) updateThemeCard(binding!!.themeSystemCard, UserPreferences.ThemePreference.SYSTEM)
updateThemeCard(viewBinding!!.themeLightCard, UserPreferences.ThemePreference.LIGHT) updateThemeCard(binding!!.themeLightCard, UserPreferences.ThemePreference.LIGHT)
updateThemeCard(viewBinding!!.themeDarkCard, UserPreferences.ThemePreference.DARK) updateThemeCard(binding!!.themeDarkCard, UserPreferences.ThemePreference.DARK)
} }
} }

View File

@ -1,5 +1,6 @@
package ac.mdiq.podcini.service.download.handler package ac.mdiq.podcini.service.download.handler
import ac.mdiq.podcini.feed.parser.FeedHandlerResult
import android.util.Log import android.util.Log
import ac.mdiq.podcini.util.InvalidFeedException import ac.mdiq.podcini.util.InvalidFeedException
import ac.mdiq.podcini.storage.model.download.DownloadError import ac.mdiq.podcini.storage.model.download.DownloadError
@ -16,7 +17,7 @@ import java.util.*
import java.util.concurrent.Callable import java.util.concurrent.Callable
import javax.xml.parsers.ParserConfigurationException import javax.xml.parsers.ParserConfigurationException
class FeedParserTask(private val request: DownloadRequest) : Callable<ac.mdiq.podcini.feed.parser.FeedHandlerResult?> { class FeedParserTask(private val request: DownloadRequest) : Callable<FeedHandlerResult?> {
var downloadStatus: DownloadResult var downloadStatus: DownloadResult
private set private set
var isSuccessful: Boolean = true var isSuccessful: Boolean = true
@ -29,7 +30,7 @@ class FeedParserTask(private val request: DownloadRequest) : Callable<ac.mdiq.po
"Unknown error: Status not set") "Unknown error: Status not set")
} }
override fun call(): ac.mdiq.podcini.feed.parser.FeedHandlerResult? { override fun call(): FeedHandlerResult? {
val feed = Feed(request.source, request.lastModified) val feed = Feed(request.source, request.lastModified)
feed.file_url = request.destination feed.file_url = request.destination
feed.id = request.feedfileId feed.id = request.feedfileId
@ -42,7 +43,7 @@ class FeedParserTask(private val request: DownloadRequest) : Callable<ac.mdiq.po
var reasonDetailed: String? = null var reasonDetailed: String? = null
val feedHandler = ac.mdiq.podcini.feed.parser.FeedHandler() val feedHandler = ac.mdiq.podcini.feed.parser.FeedHandler()
var result: ac.mdiq.podcini.feed.parser.FeedHandlerResult? = null var result: FeedHandlerResult? = null
try { try {
result = feedHandler.parseFeed(feed) result = feedHandler.parseFeed(feed)
Log.d(TAG, feed.title + " parsed") Log.d(TAG, feed.title + " parsed")

View File

@ -25,6 +25,7 @@ import ac.mdiq.podcini.playback.base.PlaybackServiceMediaPlayer
import ac.mdiq.podcini.playback.base.PlayerStatus import ac.mdiq.podcini.playback.base.PlayerStatus
import ac.mdiq.podcini.playback.base.RewindAfterPauseUtils import ac.mdiq.podcini.playback.base.RewindAfterPauseUtils
import ac.mdiq.podcini.preferences.UserPreferences import ac.mdiq.podcini.preferences.UserPreferences
import ac.mdiq.podcini.util.event.PlayerErrorEvent
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import java.io.File import java.io.File
import java.io.IOException import java.io.IOException
@ -197,11 +198,11 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia
} catch (e: IOException) { } catch (e: IOException) {
e.printStackTrace() e.printStackTrace()
setPlayerStatus(PlayerStatus.ERROR, null) setPlayerStatus(PlayerStatus.ERROR, null)
EventBus.getDefault().postSticky(ac.mdiq.podcini.util.event.PlayerErrorEvent(e.localizedMessage ?: "")) EventBus.getDefault().postSticky(PlayerErrorEvent(e.localizedMessage ?: ""))
} catch (e: IllegalStateException) { } catch (e: IllegalStateException) {
e.printStackTrace() e.printStackTrace()
setPlayerStatus(PlayerStatus.ERROR, null) setPlayerStatus(PlayerStatus.ERROR, null)
EventBus.getDefault().postSticky(ac.mdiq.podcini.util.event.PlayerErrorEvent(e.localizedMessage ?: "")) EventBus.getDefault().postSticky(PlayerErrorEvent(e.localizedMessage ?: ""))
} }
} }
@ -727,7 +728,7 @@ class LocalPSMP(context: Context, callback: PSMPCallback) : PlaybackServiceMedia
} }
}) })
mp.setOnErrorListener(Consumer { message: String -> mp.setOnErrorListener(Consumer { message: String ->
EventBus.getDefault().postSticky(ac.mdiq.podcini.util.event.PlayerErrorEvent(message)) EventBus.getDefault().postSticky(PlayerErrorEvent(message))
}) })
} }

View File

@ -71,6 +71,8 @@ import ac.mdiq.podcini.util.FeedUtil.shouldAutoDeleteItemsOnThatFeed
import ac.mdiq.podcini.util.IntentUtils.sendLocalBroadcast import ac.mdiq.podcini.util.IntentUtils.sendLocalBroadcast
import ac.mdiq.podcini.util.NetworkUtils.isStreamingAllowed import ac.mdiq.podcini.util.NetworkUtils.isStreamingAllowed
import ac.mdiq.podcini.util.event.MessageEvent import ac.mdiq.podcini.util.event.MessageEvent
import ac.mdiq.podcini.util.event.PlayerErrorEvent
import ac.mdiq.podcini.util.event.settings.SkipIntroEndingChangedEvent
import ac.mdiq.podcini.util.event.settings.SpeedPresetChangedEvent import ac.mdiq.podcini.util.event.settings.SpeedPresetChangedEvent
import ac.mdiq.podcini.util.event.settings.VolumeAdaptionChangedEvent import ac.mdiq.podcini.util.event.settings.VolumeAdaptionChangedEvent
import android.Manifest import android.Manifest
@ -924,7 +926,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
@Suppress("unused") @Suppress("unused")
fun playerError(event: ac.mdiq.podcini.util.event.PlayerErrorEvent?) { fun playerError(event: PlayerErrorEvent?) {
if (mediaPlayer?.playerStatus == PlayerStatus.PLAYING) { if (mediaPlayer?.playerStatus == PlayerStatus.PLAYING) {
mediaPlayer!!.pause(true, false) mediaPlayer!!.pause(true, false)
} }
@ -1548,7 +1550,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
@Suppress("unused") @Suppress("unused")
fun skipIntroEndingPresetChanged(event: ac.mdiq.podcini.util.event.settings.SkipIntroEndingChangedEvent) { fun skipIntroEndingPresetChanged(event: SkipIntroEndingChangedEvent) {
if (playable is FeedMedia) { if (playable is FeedMedia) {
if ((playable as FeedMedia).item?.feed?.id == event.feedId) { if ((playable as FeedMedia).item?.feed?.id == event.feedId) {
if (event.skipEnding != 0) { if (event.skipEnding != 0) {

View File

@ -310,7 +310,7 @@ object DBReader {
*/ */
@JvmStatic @JvmStatic
fun getEpisodes(offset: Int, limit: Int, filter: FeedItemFilter?, sortOrder: SortOrder?): List<FeedItem> { fun getEpisodes(offset: Int, limit: Int, filter: FeedItemFilter?, sortOrder: SortOrder?): List<FeedItem> {
Log.d(TAG, "getRecentlyPublishedEpisodes() called with: offset=$offset, limit=$limit") Log.d(TAG, "getEpisodes called with: offset=$offset, limit=$limit")
val adapter = getInstance() val adapter = getInstance()
adapter.open() adapter.open()
try { try {

View File

@ -8,6 +8,8 @@ object FeedItemSortQuery {
fun generateFrom(sortOrder: SortOrder?): String { fun generateFrom(sortOrder: SortOrder?): String {
var sortQuery = "" var sortQuery = ""
sortQuery = when (sortOrder) { sortQuery = when (sortOrder) {
SortOrder.FEED_TITLE_A_Z -> PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_FEED + " " + "ASC"
SortOrder.FEED_TITLE_Z_A -> PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_FEED + " " + "DESC"
SortOrder.EPISODE_TITLE_A_Z -> PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_TITLE + " " + "ASC" SortOrder.EPISODE_TITLE_A_Z -> PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_TITLE + " " + "ASC"
SortOrder.EPISODE_TITLE_Z_A -> PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_TITLE + " " + "DESC" SortOrder.EPISODE_TITLE_Z_A -> PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_TITLE + " " + "DESC"
SortOrder.DATE_OLD_NEW -> PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_PUBDATE + " " + "ASC" SortOrder.DATE_OLD_NEW -> PodDBAdapter.TABLE_NAME_FEED_ITEMS + "." + PodDBAdapter.KEY_PUBDATE + " " + "ASC"

View File

@ -2,6 +2,7 @@ package ac.mdiq.podcini.storage.model.feed
import android.text.TextUtils import android.text.TextUtils
import ac.mdiq.podcini.storage.model.feed.FeedFunding.Companion.extractPaymentLinks import ac.mdiq.podcini.storage.model.feed.FeedFunding.Companion.extractPaymentLinks
import android.util.Log
import java.util.* import java.util.*
import kotlin.collections.ArrayList import kotlin.collections.ArrayList
@ -107,10 +108,15 @@ class Feed : FeedFile {
*/ */
var sortOrder: SortOrder? = null var sortOrder: SortOrder? = null
set(sortOrder) { set(sortOrder) {
require(!(sortOrder != null && sortOrder.scope != SortOrder.Scope.INTRA_FEED)) { if (!(sortOrder != null && sortOrder.scope != SortOrder.Scope.INTRA_FEED)) {
("The specified sortOrder " + sortOrder Log.w("Feed sortOrder", "The specified sortOrder " + sortOrder
+ " is invalid. Only those with INTRA_FEED scope are allowed.") + " is invalid. Only those with INTRA_FEED scope are allowed.")
} }
// This looks suicidal:
// require(!(sortOrder != null && sortOrder.scope != SortOrder.Scope.INTRA_FEED)) {
// ("The specified sortOrder " + sortOrder
// + " is invalid. Only those with INTRA_FEED scope are allowed.")
// }
field = sortOrder field = sortOrder
} }

View File

@ -286,10 +286,6 @@ class MainActivity : CastEnabledActivity() {
} }
override fun onSlide(view: View, slideOffset: Float) { override fun onSlide(view: View, slideOffset: Float) {
val audioPlayer = supportFragmentManager.findFragmentByTag(AudioPlayerFragment.TAG) as? AudioPlayerFragment ?: return val audioPlayer = supportFragmentManager.findFragmentByTag(AudioPlayerFragment.TAG) as? AudioPlayerFragment ?: return
// if (slideOffset == 0.0f) { //STATE_COLLAPSED
// audioPlayer.scrollToPage(AudioPlayerFragment.FIRST_PAGE)
// }
audioPlayer.fadePlayerToToolbar(slideOffset) audioPlayer.fadePlayerToToolbar(slideOffset)
} }
} }

View File

@ -30,6 +30,7 @@ import ac.mdiq.podcini.preferences.UserPreferences.setShowRemainTimeSetting
import ac.mdiq.podcini.preferences.UserPreferences.shouldShowRemainingTime import ac.mdiq.podcini.preferences.UserPreferences.shouldShowRemainingTime
import ac.mdiq.podcini.ui.appstartintent.MainActivityStarter import ac.mdiq.podcini.ui.appstartintent.MainActivityStarter
import ac.mdiq.podcini.util.event.MessageEvent import ac.mdiq.podcini.util.event.MessageEvent
import ac.mdiq.podcini.util.event.PlayerErrorEvent
import android.content.DialogInterface import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.graphics.PixelFormat import android.graphics.PixelFormat
@ -489,7 +490,7 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
} }
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
fun onMediaPlayerError(event: ac.mdiq.podcini.util.event.PlayerErrorEvent) { fun onMediaPlayerError(event: PlayerErrorEvent) {
MediaPlayerErrorDialog.show(this, event) MediaPlayerErrorDialog.show(this, event)
} }

View File

@ -20,6 +20,7 @@ import ac.mdiq.podcini.util.Converter.getDurationStringLocalized
import ac.mdiq.podcini.util.Converter.getDurationStringLong import ac.mdiq.podcini.util.Converter.getDurationStringLong
import ac.mdiq.podcini.util.IntentUtils.openInBrowser import ac.mdiq.podcini.util.IntentUtils.openInBrowser
import ac.mdiq.podcini.storage.model.feed.Chapter import ac.mdiq.podcini.storage.model.feed.Chapter
import ac.mdiq.podcini.storage.model.feed.EmbeddedChapterImage
import ac.mdiq.podcini.storage.model.playback.Playable import ac.mdiq.podcini.storage.model.playback.Playable
import ac.mdiq.podcini.ui.common.CircularProgressBar import ac.mdiq.podcini.ui.common.CircularProgressBar
import kotlin.math.max import kotlin.math.max
@ -89,7 +90,7 @@ class ChaptersListAdapter(private val context: Context, private val callback: Ca
Glide.with(context).clear(holder.image) Glide.with(context).clear(holder.image)
} else { } else {
if (media != null) Glide.with(context) if (media != null) Glide.with(context)
.load(ac.mdiq.podcini.storage.model.feed.EmbeddedChapterImage.getModelFor(media!!, position)) .load(EmbeddedChapterImage.getModelFor(media!!, position))
.apply(RequestOptions() .apply(RequestOptions()
.dontAnimate() .dontAnimate()
.transform(FitCenter(), RoundedCorners((4 * context.resources.displayMetrics.density).toInt()))) .transform(FitCenter(), RoundedCorners((4 * context.resources.displayMetrics.density).toInt())))

View File

@ -15,8 +15,9 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
/** /**
* Displays a dialog with a text box for filtering episodes and two radio buttons for exclusion/inclusion * Displays a dialog with a text box for filtering episodes and two radio buttons for exclusion/inclusion
*/ */
abstract class EpisodeFilterDialog(context: Context, filter: FeedFilter) : MaterialAlertDialogBuilder( abstract class EpisodeFilterDialog(context: Context, filter: FeedFilter) :
context) { MaterialAlertDialogBuilder(context) {
private val viewBinding = EpisodeFilterDialogBinding.inflate(LayoutInflater.from(context)) private val viewBinding = EpisodeFilterDialogBinding.inflate(LayoutInflater.from(context))
private val termList: MutableList<String> private val termList: MutableList<String>

View File

@ -15,7 +15,11 @@ import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.SortDialogBinding import ac.mdiq.podcini.databinding.SortDialogBinding
import ac.mdiq.podcini.databinding.SortDialogItemActiveBinding import ac.mdiq.podcini.databinding.SortDialogItemActiveBinding
import ac.mdiq.podcini.databinding.SortDialogItemBinding import ac.mdiq.podcini.databinding.SortDialogItemBinding
import ac.mdiq.podcini.preferences.UserPreferences
import ac.mdiq.podcini.storage.model.feed.SortOrder import ac.mdiq.podcini.storage.model.feed.SortOrder
import android.graphics.Color
import android.util.Log
import android.view.WindowManager
open class ItemSortDialog : BottomSheetDialogFragment() { open class ItemSortDialog : BottomSheetDialogFragment() {
protected var _binding: SortDialogBinding? = null protected var _binding: SortDialogBinding? = null
@ -30,6 +34,11 @@ open class ItemSortDialog : BottomSheetDialogFragment() {
return binding.root return binding.root
} }
override fun onStart() {
super.onStart()
dialog?.window?.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)
}
private fun populateList() { private fun populateList() {
binding.gridLayout.removeAllViews() binding.gridLayout.removeAllViews()
onAddItem(R.string.episode_title, SortOrder.EPISODE_TITLE_A_Z, SortOrder.EPISODE_TITLE_Z_A, true) onAddItem(R.string.episode_title, SortOrder.EPISODE_TITLE_A_Z, SortOrder.EPISODE_TITLE_Z_A, true)

View File

@ -36,6 +36,9 @@ import org.greenrobot.eventbus.ThreadMode
@UnstableApi @UnstableApi
class ChaptersFragment : AppCompatDialogFragment() { class ChaptersFragment : AppCompatDialogFragment() {
private var _binding: SimpleListFragmentBinding? = null
private val binding get() = _binding!!
private lateinit var layoutManager: LinearLayoutManager private lateinit var layoutManager: LinearLayoutManager
private lateinit var progressBar: ProgressBar private lateinit var progressBar: ProgressBar
private lateinit var adapter: ChaptersListAdapter private lateinit var adapter: ChaptersListAdapter
@ -63,12 +66,12 @@ class ChaptersFragment : AppCompatDialogFragment() {
} }
fun onCreateView(inflater: LayoutInflater): View { fun onCreateView(inflater: LayoutInflater): View {
val viewBinding = SimpleListFragmentBinding.inflate(inflater) _binding = SimpleListFragmentBinding.inflate(inflater)
viewBinding.toolbar.visibility = View.GONE binding.toolbar.visibility = View.GONE
Log.d(TAG, "fragment onCreateView") Log.d(TAG, "fragment onCreateView")
val recyclerView = viewBinding.recyclerView val recyclerView = binding.recyclerView
progressBar = viewBinding.progLoading progressBar = binding.progLoading
layoutManager = LinearLayoutManager(activity) layoutManager = LinearLayoutManager(activity)
recyclerView.layoutManager = layoutManager recyclerView.layoutManager = layoutManager
recyclerView.addItemDecoration(DividerItemDecoration(recyclerView.context, layoutManager.orientation)) recyclerView.addItemDecoration(DividerItemDecoration(recyclerView.context, layoutManager.orientation))
@ -100,11 +103,12 @@ class ChaptersFragment : AppCompatDialogFragment() {
EventBus.getDefault().register(this) EventBus.getDefault().register(this)
loadMediaInfo(false) loadMediaInfo(false)
return viewBinding.root return binding.root
} }
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
_binding = null
controller?.release() controller?.release()
controller = null controller = null
EventBus.getDefault().unregister(this) EventBus.getDefault().unregister(this)

View File

@ -25,9 +25,7 @@ import ac.mdiq.podcini.ui.view.EpisodeItemListRecyclerView
import ac.mdiq.podcini.ui.view.LiftOnScrollListener import ac.mdiq.podcini.ui.view.LiftOnScrollListener
import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder
import ac.mdiq.podcini.util.FeedItemUtil import ac.mdiq.podcini.util.FeedItemUtil
import ac.mdiq.podcini.util.event.EpisodeDownloadEvent import ac.mdiq.podcini.util.event.*
import ac.mdiq.podcini.util.event.FeedItemEvent
import ac.mdiq.podcini.util.event.SwipeActionsChangedEvent
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.* import android.view.*
@ -55,12 +53,12 @@ import java.util.*
* Displays all completed downloads and provides a button to delete them. * Displays all completed downloads and provides a button to delete them.
*/ */
class CompletedDownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeListener, Toolbar.OnMenuItemClickListener { class CompletedDownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeListener, Toolbar.OnMenuItemClickListener {
private var runningDownloads: Set<String>? = HashSet()
private var items: MutableList<FeedItem> = mutableListOf()
private var _binding: SimpleListFragmentBinding? = null private var _binding: SimpleListFragmentBinding? = null
private val binding get() = _binding!! private val binding get() = _binding!!
private var runningDownloads: Set<String> = HashSet()
private var items: MutableList<FeedItem> = mutableListOf()
private lateinit var infoBar: TextView private lateinit var infoBar: TextView
private lateinit var adapter: CompletedDownloadsListAdapter private lateinit var adapter: CompletedDownloadsListAdapter
private lateinit var toolbar: MaterialToolbar private lateinit var toolbar: MaterialToolbar
@ -105,12 +103,12 @@ class CompletedDownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeLis
swipeActions = SwipeActions(this, TAG).attachTo(recyclerView) swipeActions = SwipeActions(this, TAG).attachTo(recyclerView)
swipeActions.setFilter(FeedItemFilter(FeedItemFilter.DOWNLOADED)) swipeActions.setFilter(FeedItemFilter(FeedItemFilter.DOWNLOADED))
refreshSwipeTelltale() refreshSwipeTelltale()
binding.leftActionIcon.setOnClickListener({ binding.leftActionIcon.setOnClickListener {
swipeActions.showDialog() swipeActions.showDialog()
}) }
binding.rightActionIcon.setOnClickListener({ binding.rightActionIcon.setOnClickListener {
swipeActions.showDialog() swipeActions.showDialog()
}) }
val animator: RecyclerView.ItemAnimator? = recyclerView.itemAnimator val animator: RecyclerView.ItemAnimator? = recyclerView.itemAnimator
if (animator is SimpleItemAnimator) { if (animator is SimpleItemAnimator) {
@ -127,8 +125,8 @@ class CompletedDownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeLis
speedDialView.overlayLayout = multiSelectDial.fabSDOverlay speedDialView.overlayLayout = multiSelectDial.fabSDOverlay
speedDialView.inflate(R.menu.episodes_apply_action_speeddial) speedDialView.inflate(R.menu.episodes_apply_action_speeddial)
speedDialView.removeActionItemById(R.id.download_batch) speedDialView.removeActionItemById(R.id.download_batch)
speedDialView.removeActionItemById(R.id.mark_read_batch) // speedDialView.removeActionItemById(R.id.mark_read_batch)
speedDialView.removeActionItemById(R.id.mark_unread_batch) // speedDialView.removeActionItemById(R.id.mark_unread_batch)
speedDialView.removeActionItemById(R.id.remove_from_queue_batch) speedDialView.removeActionItemById(R.id.remove_from_queue_batch)
speedDialView.setOnChangeListener(object : SpeedDialView.OnChangeListener { speedDialView.setOnChangeListener(object : SpeedDialView.OnChangeListener {
override fun onMainActionSelected(): Boolean { override fun onMainActionSelected(): Boolean {
@ -286,17 +284,17 @@ class CompletedDownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeLis
} }
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
fun onPlayerStatusChanged(event: ac.mdiq.podcini.util.event.PlayerStatusEvent?) { fun onPlayerStatusChanged(event: PlayerStatusEvent?) {
loadItems() loadItems()
} }
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
fun onDownloadLogChanged(event: ac.mdiq.podcini.util.event.DownloadLogEvent?) { fun onDownloadLogChanged(event: DownloadLogEvent?) {
loadItems() loadItems()
} }
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
fun onUnreadItemsChanged(event: ac.mdiq.podcini.util.event.UnreadItemsUpdateEvent?) { fun onUnreadItemsChanged(event: UnreadItemsUpdateEvent?) {
loadItems() loadItems()
} }
@ -324,10 +322,10 @@ class CompletedDownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeLis
FeedItemFilter(FeedItemFilter.DOWNLOADED), sortOrder) FeedItemFilter(FeedItemFilter.DOWNLOADED), sortOrder)
val mediaUrls: MutableList<String> = ArrayList() val mediaUrls: MutableList<String> = ArrayList()
if (runningDownloads == null) { if (runningDownloads.isEmpty()) {
return@fromCallable downloadedItems return@fromCallable downloadedItems
} }
for (url in runningDownloads!!) { for (url in runningDownloads) {
if (FeedItemUtil.indexOfItemWithDownloadUrl(downloadedItems, url) != -1) { if (FeedItemUtil.indexOfItemWithDownloadUrl(downloadedItems, url) != -1) {
continue // Already in list continue // Already in list
} }
@ -409,7 +407,11 @@ class CompletedDownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeLis
descending: SortOrder, descending: SortOrder,
ascendingIsDefault: Boolean ascendingIsDefault: Boolean
) { ) {
if (ascending == SortOrder.DATE_OLD_NEW || ascending == SortOrder.DURATION_SHORT_LONG || ascending == SortOrder.EPISODE_TITLE_A_Z || ascending == SortOrder.SIZE_SMALL_LARGE) { if (ascending == SortOrder.DATE_OLD_NEW ||
ascending == SortOrder.DURATION_SHORT_LONG ||
ascending == SortOrder.EPISODE_TITLE_A_Z ||
ascending == SortOrder.SIZE_SMALL_LARGE ||
ascending == SortOrder.FEED_TITLE_A_Z) {
super.onAddItem(title, ascending, descending, ascendingIsDefault) super.onAddItem(title, ascending, descending, ascendingIsDefault)
} }
} }
@ -417,7 +419,7 @@ class CompletedDownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeLis
override fun onSelectionChanged() { override fun onSelectionChanged() {
super.onSelectionChanged() super.onSelectionChanged()
UserPreferences.downloadsSortedOrder = sortOrder UserPreferences.downloadsSortedOrder = sortOrder
EventBus.getDefault().post(ac.mdiq.podcini.util.event.DownloadLogEvent.listUpdated()) EventBus.getDefault().post(DownloadLogEvent.listUpdated())
} }
} }

View File

@ -39,6 +39,9 @@ import java.util.*
* Searches iTunes store for top podcasts and displays results in a list. * Searches iTunes store for top podcasts and displays results in a list.
*/ */
class DiscoveryFragment : Fragment(), Toolbar.OnMenuItemClickListener { class DiscoveryFragment : Fragment(), Toolbar.OnMenuItemClickListener {
private var _binding: FragmentItunesSearchBinding? = null
private val binding get() = _binding!!
private lateinit var prefs: SharedPreferences private lateinit var prefs: SharedPreferences
private lateinit var gridView: GridView private lateinit var gridView: GridView
private lateinit var progressBar: ProgressBar private lateinit var progressBar: ProgressBar
@ -94,15 +97,15 @@ class DiscoveryFragment : Fragment(), Toolbar.OnMenuItemClickListener {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
// Inflate the layout for this fragment // Inflate the layout for this fragment
val viewBinding = FragmentItunesSearchBinding.inflate(inflater) _binding = FragmentItunesSearchBinding.inflate(inflater)
// val root = inflater.inflate(R.layout.fragment_itunes_search, container, false) // val root = inflater.inflate(R.layout.fragment_itunes_search, container, false)
Log.d(TAG, "fragment onCreateView") Log.d(TAG, "fragment onCreateView")
gridView = viewBinding.gridView gridView = binding.gridView
adapter = OnlineFeedsAdapter(requireActivity(), ArrayList()) adapter = OnlineFeedsAdapter(requireActivity(), ArrayList())
gridView.setAdapter(adapter) gridView.setAdapter(adapter)
toolbar = viewBinding.toolbar toolbar = binding.toolbar
toolbar.setNavigationOnClickListener { parentFragmentManager.popBackStack() } toolbar.setNavigationOnClickListener { parentFragmentManager.popBackStack() }
toolbar.inflateMenu(R.menu.countries_menu) toolbar.inflateMenu(R.menu.countries_menu)
val discoverHideItem = toolbar.menu.findItem(R.id.discover_hide_item) val discoverHideItem = toolbar.menu.findItem(R.id.discover_hide_item)
@ -121,17 +124,18 @@ class DiscoveryFragment : Fragment(), Toolbar.OnMenuItemClickListener {
startActivity(intent) startActivity(intent)
} }
progressBar = viewBinding.progressBar progressBar = binding.progressBar
txtvError = viewBinding.txtvError txtvError = binding.txtvError
butRetry = viewBinding.butRetry butRetry = binding.butRetry
txtvEmpty = viewBinding.empty txtvEmpty = binding.empty
loadToplist(countryCode) loadToplist(countryCode)
return viewBinding.root return binding.root
} }
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
_binding = null
disposable?.dispose() disposable?.dispose()
adapter = null adapter = null

View File

@ -23,6 +23,7 @@ import ac.mdiq.podcini.util.DateFormatter
import ac.mdiq.podcini.util.PlaybackStatus import ac.mdiq.podcini.util.PlaybackStatus
import ac.mdiq.podcini.util.event.EpisodeDownloadEvent import ac.mdiq.podcini.util.event.EpisodeDownloadEvent
import ac.mdiq.podcini.util.event.FeedItemEvent import ac.mdiq.podcini.util.event.FeedItemEvent
import ac.mdiq.podcini.util.event.PlayerStatusEvent
import ac.mdiq.podcini.util.event.UnreadItemsUpdateEvent import ac.mdiq.podcini.util.event.UnreadItemsUpdateEvent
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
@ -409,7 +410,7 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
} }
@UnstableApi @Subscribe(threadMode = ThreadMode.MAIN) @UnstableApi @Subscribe(threadMode = ThreadMode.MAIN)
fun onPlayerStatusChanged(event: ac.mdiq.podcini.util.event.PlayerStatusEvent?) { fun onPlayerStatusChanged(event: PlayerStatusEvent?) {
updateButtons() updateButtons()
} }

View File

@ -105,12 +105,12 @@ abstract class EpisodesListFragment : Fragment(), SelectableAdapter.OnSelectMode
swipeActions = SwipeActions(this, getFragmentTag()).attachTo(recyclerView) swipeActions = SwipeActions(this, getFragmentTag()).attachTo(recyclerView)
swipeActions.setFilter(getFilter()) swipeActions.setFilter(getFilter())
refreshSwipeTelltale() refreshSwipeTelltale()
binding.leftActionIcon.setOnClickListener({ binding.leftActionIcon.setOnClickListener {
swipeActions.showDialog() swipeActions.showDialog()
}) }
binding.rightActionIcon.setOnClickListener({ binding.rightActionIcon.setOnClickListener {
swipeActions.showDialog() swipeActions.showDialog()
}) }
val animator: RecyclerView.ItemAnimator? = recyclerView.itemAnimator val animator: RecyclerView.ItemAnimator? = recyclerView.itemAnimator
if (animator is SimpleItemAnimator) { if (animator is SimpleItemAnimator) {

View File

@ -54,6 +54,9 @@ import kotlin.math.max
* Fragment which is supposed to be displayed outside of the MediaplayerActivity. * Fragment which is supposed to be displayed outside of the MediaplayerActivity.
*/ */
class ExternalPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener { class ExternalPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
private var _binding: ExternalPlayerFragmentBinding? = null
private val binding get() = _binding!!
private lateinit var imgvCover: ImageView private lateinit var imgvCover: ImageView
private lateinit var butPlay: PlayButton private lateinit var butPlay: PlayButton
@ -79,22 +82,22 @@ class ExternalPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
@UnstableApi @UnstableApi
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?): View { savedInstanceState: Bundle?): View {
val viewBinding = ExternalPlayerFragmentBinding.inflate(inflater) _binding = ExternalPlayerFragmentBinding.inflate(inflater)
Log.d(TAG, "fragment onCreateView") Log.d(TAG, "fragment onCreateView")
episodeTitle = viewBinding.titleView episodeTitle = binding.titleView
butPlaybackSpeed = viewBinding.butPlaybackSpeed butPlaybackSpeed = binding.butPlaybackSpeed
txtvPlaybackSpeed = viewBinding.txtvPlaybackSpeed txtvPlaybackSpeed = binding.txtvPlaybackSpeed
imgvCover = viewBinding.imgvCover imgvCover = binding.imgvCover
butPlay = viewBinding.butPlay butPlay = binding.butPlay
butRev = viewBinding.butRev butRev = binding.butRev
txtvRev = viewBinding.txtvRev txtvRev = binding.txtvRev
butFF = viewBinding.butFF butFF = binding.butFF
txtvFF = viewBinding.txtvFF txtvFF = binding.txtvFF
butSkip = viewBinding.butSkip butSkip = binding.butSkip
sbPosition = viewBinding.sbPosition sbPosition = binding.sbPosition
txtvPosition = viewBinding.txtvPosition txtvPosition = binding.txtvPosition
txtvLength = viewBinding.txtvLength txtvLength = binding.txtvLength
setupLengthTextView() setupLengthTextView()
setupControlButtons() setupControlButtons()
@ -103,7 +106,7 @@ class ExternalPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
} }
sbPosition.setOnSeekBarChangeListener(this) sbPosition.setOnSeekBarChangeListener(this)
viewBinding.externalPlayerFragment.setOnClickListener { binding.externalPlayerFragment.setOnClickListener {
Log.d(TAG, "externalPlayerFragment was clicked") Log.d(TAG, "externalPlayerFragment was clicked")
val media = controller?.getMedia() val media = controller?.getMedia()
if (media != null) { if (media != null) {
@ -118,13 +121,14 @@ class ExternalPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
controller = setupPlaybackController() controller = setupPlaybackController()
controller!!.init() controller!!.init()
loadMediaInfo() // loadMediaInfo()
EventBus.getDefault().register(this) EventBus.getDefault().register(this)
return viewBinding.root return binding.root
} }
@OptIn(UnstableApi::class) override fun onDestroyView() { @OptIn(UnstableApi::class) override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
_binding = null
controller?.release() controller?.release()
controller = null controller = null
EventBus.getDefault().unregister(this) EventBus.getDefault().unregister(this)
@ -134,11 +138,9 @@ class ExternalPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
butPlay.setOnClickListener { butPlay.setOnClickListener {
if (controller == null) { if (controller == null) return@setOnClickListener
return@setOnClickListener
}
val media = controller!!.getMedia()
val media = controller!!.getMedia()
if (media?.getMediaType() == MediaType.VIDEO && controller!!.status != PlayerStatus.PLAYING) { if (media?.getMediaType() == MediaType.VIDEO && controller!!.status != PlayerStatus.PLAYING) {
controller!!.playPause() controller!!.playPause()
requireContext().startActivity(getPlayerActivityIntent(requireContext(), media)) requireContext().startActivity(getPlayerActivityIntent(requireContext(), media))
@ -161,10 +163,10 @@ class ExternalPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener {
SkipPreferenceDialog.SkipDirection.SKIP_REWIND, txtvRev) SkipPreferenceDialog.SkipDirection.SKIP_REWIND, txtvRev)
true true
} }
butPlay.setOnClickListener { // butPlay.setOnClickListener {
controller?.init() // controller?.init()
controller?.playPause() // controller?.playPause()
} // }
butFF.setOnClickListener { butFF.setOnClickListener {
if (controller != null) { if (controller != null) {
val curr: Int = controller!!.position val curr: Int = controller!!.position

View File

@ -71,10 +71,11 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
private var _binding: FeedItemListFragmentBinding? = null private var _binding: FeedItemListFragmentBinding? = null
private val binding get() = _binding!! private val binding get() = _binding!!
private var _speedDialBinding: MultiSelectSpeedDialBinding? = null
private val speedDialBinding get() = _speedDialBinding!!
private lateinit var adapter: FeedItemListAdapter private lateinit var adapter: FeedItemListAdapter
private lateinit var swipeActions: SwipeActions private lateinit var swipeActions: SwipeActions
private lateinit var speedDialBinding: MultiSelectSpeedDialBinding
private lateinit var nextPageLoader: MoreContentListFooterUtil private lateinit var nextPageLoader: MoreContentListFooterUtil
private var displayUpArrow = false private var displayUpArrow = false
@ -96,7 +97,7 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
Log.d(TAG, "fragment onCreateView") Log.d(TAG, "fragment onCreateView")
_binding = FeedItemListFragmentBinding.inflate(inflater) _binding = FeedItemListFragmentBinding.inflate(inflater)
speedDialBinding = MultiSelectSpeedDialBinding.bind(binding.root) _speedDialBinding = MultiSelectSpeedDialBinding.bind(binding.root)
binding.toolbar.inflateMenu(R.menu.feedlist) binding.toolbar.inflateMenu(R.menu.feedlist)
binding.toolbar.setOnMenuItemClickListener(this) binding.toolbar.setOnMenuItemClickListener(this)
@ -198,6 +199,7 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
_binding = null _binding = null
_speedDialBinding
EventBus.getDefault().unregister(this) EventBus.getDefault().unregister(this)
disposable?.dispose() disposable?.dispose()
adapter.endSelectMode() adapter.endSelectMode()
@ -209,9 +211,8 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
} }
private fun updateToolbar() { private fun updateToolbar() {
if (feed == null) { if (feed == null) return
return
}
binding.toolbar.menu.findItem(R.id.visit_website_item).setVisible(feed!!.link != null) binding.toolbar.menu.findItem(R.id.visit_website_item).setVisible(feed!!.link != null)
binding.toolbar.menu.findItem(R.id.refresh_complete_item).setVisible(feed!!.isPaged) binding.toolbar.menu.findItem(R.id.refresh_complete_item).setVisible(feed!!.isPaged)
if (StringUtils.isBlank(feed!!.link)) { if (StringUtils.isBlank(feed!!.link)) {
@ -620,8 +621,11 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
descending: SortOrder, descending: SortOrder,
ascendingIsDefault: Boolean ascendingIsDefault: Boolean
) { ) {
if (ascending == SortOrder.DATE_OLD_NEW || ascending == SortOrder.DURATION_SHORT_LONG || ascending == SortOrder.EPISODE_TITLE_A_Z || (requireArguments().getBoolean( if (ascending == SortOrder.DATE_OLD_NEW ||
ARG_FEED_IS_LOCAL) && ascending == SortOrder.EPISODE_FILENAME_A_Z)) { ascending == SortOrder.DURATION_SHORT_LONG ||
ascending == SortOrder.RANDOM ||
ascending == SortOrder.EPISODE_TITLE_A_Z ||
(requireArguments().getBoolean(ARG_FEED_IS_LOCAL) && ascending == SortOrder.EPISODE_FILENAME_A_Z)) {
super.onAddItem(title, ascending, descending, ascendingIsDefault) super.onAddItem(title, ascending, descending, ascendingIsDefault)
} }
} }

View File

@ -16,6 +16,8 @@ import ac.mdiq.podcini.ui.dialog.*
import ac.mdiq.podcini.ui.menuhandler.MenuItemUtils import ac.mdiq.podcini.ui.menuhandler.MenuItemUtils
import ac.mdiq.podcini.ui.statistics.StatisticsFragment import ac.mdiq.podcini.ui.statistics.StatisticsFragment
import ac.mdiq.podcini.util.event.FeedListUpdateEvent import ac.mdiq.podcini.util.event.FeedListUpdateEvent
import ac.mdiq.podcini.util.event.QueueEvent
import ac.mdiq.podcini.util.event.UnreadItemsUpdateEvent
import android.R.attr import android.R.attr
import android.app.Activity import android.app.Activity
import android.content.Context import android.content.Context
@ -195,7 +197,7 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange
} }
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
fun onUnreadItemsChanged(event: ac.mdiq.podcini.util.event.UnreadItemsUpdateEvent?) { fun onUnreadItemsChanged(event: UnreadItemsUpdateEvent?) {
loadData() loadData()
} }
@ -206,7 +208,7 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange
} }
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
fun onQueueChanged(event: ac.mdiq.podcini.util.event.QueueEvent) { fun onQueueChanged(event: QueueEvent) {
Log.d(TAG, "onQueueChanged($event)") Log.d(TAG, "onQueueChanged($event)")
// we are only interested in the number of queue items, not download status or position // we are only interested in the number of queue items, not download status or position
if (event.action == ac.mdiq.podcini.util.event.QueueEvent.Action.DELETED_MEDIA || event.action == ac.mdiq.podcini.util.event.QueueEvent.Action.SORTED || event.action == ac.mdiq.podcini.util.event.QueueEvent.Action.MOVED) { if (event.action == ac.mdiq.podcini.util.event.QueueEvent.Action.DELETED_MEDIA || event.action == ac.mdiq.podcini.util.event.QueueEvent.Action.SORTED || event.action == ac.mdiq.podcini.util.event.QueueEvent.Action.MOVED) {

View File

@ -7,6 +7,7 @@ import ac.mdiq.podcini.playback.PlaybackController
import ac.mdiq.podcini.playback.event.PlaybackPositionEvent import ac.mdiq.podcini.playback.event.PlaybackPositionEvent
import ac.mdiq.podcini.storage.DBReader import ac.mdiq.podcini.storage.DBReader
import ac.mdiq.podcini.storage.model.feed.Chapter import ac.mdiq.podcini.storage.model.feed.Chapter
import ac.mdiq.podcini.storage.model.feed.EmbeddedChapterImage
import ac.mdiq.podcini.storage.model.feed.FeedMedia import ac.mdiq.podcini.storage.model.feed.FeedMedia
import ac.mdiq.podcini.storage.model.playback.Playable import ac.mdiq.podcini.storage.model.playback.Playable
import ac.mdiq.podcini.ui.activity.MainActivity import ac.mdiq.podcini.ui.activity.MainActivity
@ -279,7 +280,7 @@ class PlayerDetailsFragment : Fragment() {
cover.into(binding.imgvCover) cover.into(binding.imgvCover)
} else { } else {
Glide.with(this) Glide.with(this)
.load(ac.mdiq.podcini.storage.model.feed.EmbeddedChapterImage.getModelFor(media!!, displayedChapterIndex)) .load(EmbeddedChapterImage.getModelFor(media!!, displayedChapterIndex))
.apply(options) .apply(options)
.thumbnail(cover) .thumbnail(cover)
.error(cover) .error(cover)

View File

@ -1,6 +1,34 @@
package ac.mdiq.podcini.ui.fragment package ac.mdiq.podcini.ui.fragment
import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.CheckboxDoNotShowAgainBinding
import ac.mdiq.podcini.databinding.MultiSelectSpeedDialBinding
import ac.mdiq.podcini.databinding.QueueFragmentBinding
import ac.mdiq.podcini.feed.util.PlaybackSpeedUtils
import ac.mdiq.podcini.net.download.FeedUpdateManager
import ac.mdiq.podcini.playback.event.PlaybackPositionEvent
import ac.mdiq.podcini.preferences.UserPreferences
import ac.mdiq.podcini.storage.DBReader
import ac.mdiq.podcini.storage.DBWriter
import ac.mdiq.podcini.storage.model.feed.FeedItem
import ac.mdiq.podcini.storage.model.feed.FeedItemFilter
import ac.mdiq.podcini.storage.model.feed.SortOrder
import ac.mdiq.podcini.ui.activity.MainActivity import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.ui.adapter.QueueRecyclerAdapter
import ac.mdiq.podcini.ui.adapter.SelectableAdapter
import ac.mdiq.podcini.ui.dialog.ConfirmationDialog
import ac.mdiq.podcini.ui.dialog.ItemSortDialog
import ac.mdiq.podcini.ui.fragment.actions.EpisodeMultiSelectActionHandler
import ac.mdiq.podcini.ui.fragment.swipeactions.SwipeActions
import ac.mdiq.podcini.ui.menuhandler.FeedItemMenuHandler
import ac.mdiq.podcini.ui.menuhandler.MenuItemUtils
import ac.mdiq.podcini.ui.view.EmptyViewHandler
import ac.mdiq.podcini.ui.view.EpisodeItemListRecyclerView
import ac.mdiq.podcini.ui.view.LiftOnScrollListener
import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder
import ac.mdiq.podcini.util.Converter
import ac.mdiq.podcini.util.FeedItemUtil
import ac.mdiq.podcini.util.event.*
import android.content.Context import android.content.Context
import android.content.DialogInterface import android.content.DialogInterface
import android.content.SharedPreferences import android.content.SharedPreferences
@ -22,35 +50,6 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import com.google.android.material.snackbar.Snackbar import com.google.android.material.snackbar.Snackbar
import com.leinardi.android.speeddial.SpeedDialActionItem import com.leinardi.android.speeddial.SpeedDialActionItem
import com.leinardi.android.speeddial.SpeedDialView import com.leinardi.android.speeddial.SpeedDialView
import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.CheckboxDoNotShowAgainBinding
import ac.mdiq.podcini.databinding.MultiSelectSpeedDialBinding
import ac.mdiq.podcini.databinding.QueueFragmentBinding
import ac.mdiq.podcini.ui.adapter.QueueRecyclerAdapter
import ac.mdiq.podcini.ui.adapter.SelectableAdapter
import ac.mdiq.podcini.ui.dialog.ConfirmationDialog
import ac.mdiq.podcini.feed.util.PlaybackSpeedUtils
import ac.mdiq.podcini.ui.menuhandler.MenuItemUtils
import ac.mdiq.podcini.storage.DBReader
import ac.mdiq.podcini.storage.DBWriter
import ac.mdiq.podcini.util.FeedItemUtil
import ac.mdiq.podcini.net.download.FeedUpdateManager
import ac.mdiq.podcini.ui.dialog.ItemSortDialog
import ac.mdiq.podcini.util.event.*
import ac.mdiq.podcini.playback.event.PlaybackPositionEvent
import ac.mdiq.podcini.ui.fragment.actions.EpisodeMultiSelectActionHandler
import ac.mdiq.podcini.ui.fragment.swipeactions.SwipeActions
import ac.mdiq.podcini.ui.menuhandler.FeedItemMenuHandler
import ac.mdiq.podcini.storage.model.feed.FeedItem
import ac.mdiq.podcini.storage.model.feed.FeedItemFilter
import ac.mdiq.podcini.storage.model.feed.SortOrder
import ac.mdiq.podcini.preferences.UserPreferences
import ac.mdiq.podcini.ui.dialog.SwipeActionsDialog
import ac.mdiq.podcini.ui.view.EmptyViewHandler
import ac.mdiq.podcini.ui.view.EpisodeItemListRecyclerView
import ac.mdiq.podcini.ui.view.LiftOnScrollListener
import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder
import ac.mdiq.podcini.util.Converter
import io.reactivex.Observable import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable import io.reactivex.disposables.Disposable
@ -126,12 +125,12 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda
swipeActions.setFilter(FeedItemFilter(FeedItemFilter.QUEUED)) swipeActions.setFilter(FeedItemFilter(FeedItemFilter.QUEUED))
swipeActions.attachTo(recyclerView) swipeActions.attachTo(recyclerView)
refreshSwipeTelltale() refreshSwipeTelltale()
binding.leftActionIcon.setOnClickListener({ binding.leftActionIcon.setOnClickListener {
swipeActions.showDialog() swipeActions.showDialog()
}) }
binding.rightActionIcon.setOnClickListener({ binding.rightActionIcon.setOnClickListener {
swipeActions.showDialog() swipeActions.showDialog()
}) }
recyclerAdapter = object : QueueRecyclerAdapter(activity as MainActivity, swipeActions) { recyclerAdapter = object : QueueRecyclerAdapter(activity as MainActivity, swipeActions) {
override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) { override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) {

View File

@ -33,6 +33,9 @@ import org.greenrobot.eventbus.ThreadMode
import java.util.* import java.util.*
class QuickFeedDiscoveryFragment : Fragment(), AdapterView.OnItemClickListener { class QuickFeedDiscoveryFragment : Fragment(), AdapterView.OnItemClickListener {
private var _binding: QuickFeedDiscoveryBinding? = null
private val binding get() = _binding!!
private var disposable: Disposable? = null private var disposable: Disposable? = null
private lateinit var adapter: FeedDiscoverAdapter private lateinit var adapter: FeedDiscoverAdapter
@ -44,18 +47,17 @@ class QuickFeedDiscoveryFragment : Fragment(), AdapterView.OnItemClickListener {
@OptIn(UnstableApi::class) override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { @OptIn(UnstableApi::class) override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
super.onCreateView(inflater, container, savedInstanceState) super.onCreateView(inflater, container, savedInstanceState)
val viewBinding = QuickFeedDiscoveryBinding.inflate(inflater) _binding = QuickFeedDiscoveryBinding.inflate(inflater)
// val root: View = inflater.inflate(R.layout.quick_feed_discovery, container, false)
Log.d(TAG, "fragment onCreateView") Log.d(TAG, "fragment onCreateView")
val discoverMore = viewBinding.discoverMore val discoverMore = binding.discoverMore
discoverMore.setOnClickListener { (activity as MainActivity).loadChildFragment(DiscoveryFragment()) } discoverMore.setOnClickListener { (activity as MainActivity).loadChildFragment(DiscoveryFragment()) }
discoverGridLayout = viewBinding.discoverGrid discoverGridLayout = binding.discoverGrid
errorView = viewBinding.discoverError errorView = binding.discoverError
errorTextView = viewBinding.discoverErrorTxtV errorTextView = binding.discoverErrorTxtV
errorRetry = viewBinding.discoverErrorRetryBtn errorRetry = binding.discoverErrorRetryBtn
poweredByTextView = viewBinding.discoverPoweredByItunes poweredByTextView = binding.discoverPoweredByItunes
adapter = FeedDiscoverAdapter(activity as MainActivity) adapter = FeedDiscoverAdapter(activity as MainActivity)
discoverGridLayout.setAdapter(adapter) discoverGridLayout.setAdapter(adapter)
@ -80,11 +82,12 @@ class QuickFeedDiscoveryFragment : Fragment(), AdapterView.OnItemClickListener {
loadToplist() loadToplist()
EventBus.getDefault().register(this) EventBus.getDefault().register(this)
return viewBinding.root return binding.root
} }
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
_binding = null
EventBus.getDefault().unregister(this) EventBus.getDefault().unregister(this)
disposable?.dispose() disposable?.dispose()
} }

View File

@ -56,6 +56,9 @@ import org.greenrobot.eventbus.ThreadMode
* Performs a search operation on all feeds or one specific feed and displays the search result. * Performs a search operation on all feeds or one specific feed and displays the search result.
*/ */
class SearchFragment : Fragment(), SelectableAdapter.OnSelectModeListener { class SearchFragment : Fragment(), SelectableAdapter.OnSelectModeListener {
private var _binding: SearchFragmentBinding? = null
private val binding get() = _binding!!
private lateinit var adapter: EpisodeItemListAdapter private lateinit var adapter: EpisodeItemListAdapter
private lateinit var adapterFeeds: HorizontalFeedListAdapter private lateinit var adapterFeeds: HorizontalFeedListAdapter
private lateinit var progressBar: ProgressBar private lateinit var progressBar: ProgressBar
@ -87,14 +90,13 @@ class SearchFragment : Fragment(), SelectableAdapter.OnSelectModeListener {
@UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, @UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
val viewBinding = SearchFragmentBinding.inflate(inflater) _binding = SearchFragmentBinding.inflate(inflater)
// val layout: View = inflater.inflate(R.layout.search_fragment, container, false)
Log.d(TAG, "fragment onCreateView") Log.d(TAG, "fragment onCreateView")
setupToolbar(viewBinding.toolbar) setupToolbar(binding.toolbar)
speedDialBinding = MultiSelectSpeedDialBinding.bind(viewBinding.root) speedDialBinding = MultiSelectSpeedDialBinding.bind(binding.root)
progressBar = viewBinding.progressBar progressBar = binding.progressBar
recyclerView = viewBinding.recyclerView recyclerView = binding.recyclerView
recyclerView.setRecycledViewPool((activity as MainActivity).recycledViewPool) recyclerView.setRecycledViewPool((activity as MainActivity).recycledViewPool)
registerForContextMenu(recyclerView) registerForContextMenu(recyclerView)
adapter = object : EpisodeItemListAdapter(activity as MainActivity) { adapter = object : EpisodeItemListAdapter(activity as MainActivity) {
@ -109,9 +111,9 @@ class SearchFragment : Fragment(), SelectableAdapter.OnSelectModeListener {
} }
adapter.setOnSelectModeListener(this) adapter.setOnSelectModeListener(this)
recyclerView.adapter = adapter recyclerView.adapter = adapter
recyclerView.addOnScrollListener(LiftOnScrollListener(viewBinding.appbar)) recyclerView.addOnScrollListener(LiftOnScrollListener(binding.appbar))
val recyclerViewFeeds = viewBinding.recyclerViewFeeds val recyclerViewFeeds = binding.recyclerViewFeeds
val layoutManagerFeeds = LinearLayoutManager(activity) val layoutManagerFeeds = LinearLayoutManager(activity)
layoutManagerFeeds.orientation = RecyclerView.HORIZONTAL layoutManagerFeeds.orientation = RecyclerView.HORIZONTAL
recyclerViewFeeds.layoutManager = layoutManagerFeeds recyclerViewFeeds.layoutManager = layoutManagerFeeds
@ -133,7 +135,7 @@ class SearchFragment : Fragment(), SelectableAdapter.OnSelectModeListener {
emptyViewHandler.setMessage(R.string.type_to_search) emptyViewHandler.setMessage(R.string.type_to_search)
EventBus.getDefault().register(this) EventBus.getDefault().register(this)
chip = viewBinding.feedTitleChip chip = binding.feedTitleChip
chip.setOnCloseIconClickListener { chip.setOnCloseIconClickListener {
requireArguments().putLong(ARG_FEED, 0) requireArguments().putLong(ARG_FEED, 0)
searchWithProgressBar() searchWithProgressBar()
@ -178,11 +180,12 @@ class SearchFragment : Fragment(), SelectableAdapter.OnSelectModeListener {
true true
} }
return viewBinding.root return binding.root
} }
override fun onDestroyView() { override fun onDestroyView() {
super.onDestroyView() super.onDestroyView()
_binding = null
EventBus.getDefault().unregister(this) EventBus.getDefault().unregister(this)
} }

View File

@ -38,7 +38,7 @@ class ApGlideModule : AppGlideModule() {
registry.append(String::class.java, InputStream::class.java, ApOkHttpUrlLoader.Factory()) registry.append(String::class.java, InputStream::class.java, ApOkHttpUrlLoader.Factory())
registry.append(String::class.java, InputStream::class.java, NoHttpStringLoader.StreamFactory()) registry.append(String::class.java, InputStream::class.java, NoHttpStringLoader.StreamFactory())
registry.append(ac.mdiq.podcini.storage.model.feed.EmbeddedChapterImage::class.java, ByteBuffer::class.java, ChapterImageModelLoader.Factory()) registry.append(EmbeddedChapterImage::class.java, ByteBuffer::class.java, ChapterImageModelLoader.Factory())
} }
companion object { companion object {

View File

@ -1,6 +1,7 @@
package ac.mdiq.podcini.ui.glide package ac.mdiq.podcini.ui.glide
import ac.mdiq.podcini.service.download.PodciniHttpClient.getHttpClient import ac.mdiq.podcini.service.download.PodciniHttpClient.getHttpClient
import ac.mdiq.podcini.storage.model.feed.EmbeddedChapterImage
import com.bumptech.glide.Priority import com.bumptech.glide.Priority
import com.bumptech.glide.load.DataSource import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.Options import com.bumptech.glide.load.Options
@ -17,9 +18,9 @@ import java.io.FileInputStream
import java.io.IOException import java.io.IOException
import java.nio.ByteBuffer import java.nio.ByteBuffer
class ChapterImageModelLoader : ModelLoader<ac.mdiq.podcini.storage.model.feed.EmbeddedChapterImage?, ByteBuffer?> { class ChapterImageModelLoader : ModelLoader<EmbeddedChapterImage?, ByteBuffer?> {
class Factory : ModelLoaderFactory<ac.mdiq.podcini.storage.model.feed.EmbeddedChapterImage?, ByteBuffer?> { class Factory : ModelLoaderFactory<EmbeddedChapterImage?, ByteBuffer?> {
override fun build(unused: MultiModelLoaderFactory): ModelLoader<ac.mdiq.podcini.storage.model.feed.EmbeddedChapterImage?, ByteBuffer?> { override fun build(unused: MultiModelLoaderFactory): ModelLoader<EmbeddedChapterImage?, ByteBuffer?> {
return ChapterImageModelLoader() return ChapterImageModelLoader()
} }
@ -28,7 +29,7 @@ class ChapterImageModelLoader : ModelLoader<ac.mdiq.podcini.storage.model.feed.E
} }
} }
override fun buildLoadData(model: ac.mdiq.podcini.storage.model.feed.EmbeddedChapterImage, override fun buildLoadData(model: EmbeddedChapterImage,
width: Int, width: Int,
height: Int, height: Int,
options: Options options: Options
@ -36,11 +37,11 @@ class ChapterImageModelLoader : ModelLoader<ac.mdiq.podcini.storage.model.feed.E
return ModelLoader.LoadData(ObjectKey(model), EmbeddedImageFetcher(model)) return ModelLoader.LoadData(ObjectKey(model), EmbeddedImageFetcher(model))
} }
override fun handles(model: ac.mdiq.podcini.storage.model.feed.EmbeddedChapterImage): Boolean { override fun handles(model: EmbeddedChapterImage): Boolean {
return true return true
} }
internal class EmbeddedImageFetcher(private val image: ac.mdiq.podcini.storage.model.feed.EmbeddedChapterImage) : DataFetcher<ByteBuffer?> { internal class EmbeddedImageFetcher(private val image: EmbeddedChapterImage) : DataFetcher<ByteBuffer?> {
override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in ByteBuffer?>) { override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in ByteBuffer?>) {
var stream: BufferedInputStream? = null var stream: BufferedInputStream? = null
try { try {

View File

@ -10,6 +10,7 @@ import ac.mdiq.podcini.ui.dialog.ConfirmationDialog
import ac.mdiq.podcini.ui.statistics.downloads.DownloadStatisticsFragment import ac.mdiq.podcini.ui.statistics.downloads.DownloadStatisticsFragment
import ac.mdiq.podcini.ui.statistics.subscriptions.SubscriptionStatisticsFragment import ac.mdiq.podcini.ui.statistics.subscriptions.SubscriptionStatisticsFragment
import ac.mdiq.podcini.ui.statistics.years.YearsStatisticsFragment import ac.mdiq.podcini.ui.statistics.years.YearsStatisticsFragment
import ac.mdiq.podcini.util.event.StatisticsEvent
import android.content.Context import android.content.Context
import android.content.DialogInterface import android.content.DialogInterface
import android.os.Bundle import android.os.Bundle
@ -106,7 +107,7 @@ class StatisticsFragment : PagedToolbarFragment() {
val disposable = Completable.fromFuture(DBWriter.resetStatistics()) val disposable = Completable.fromFuture(DBWriter.resetStatistics())
.subscribeOn(Schedulers.io()) .subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()) .observeOn(AndroidSchedulers.mainThread())
.subscribe({ EventBus.getDefault().post(ac.mdiq.podcini.util.event.StatisticsEvent()) }, .subscribe({ EventBus.getDefault().post(StatisticsEvent()) },
{ error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) }) { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
} }

View File

@ -17,19 +17,21 @@ import io.reactivex.schedulers.Schedulers
import java.util.* import java.util.*
class FeedStatisticsFragment : Fragment() { class FeedStatisticsFragment : Fragment() {
private var _binding: FeedStatisticsBinding? = null
private val binding get() = _binding!!
private var feedId: Long = 0 private var feedId: Long = 0
private var disposable: Disposable? = null private var disposable: Disposable? = null
private var viewBinding: FeedStatisticsBinding? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
feedId = requireArguments().getLong(EXTRA_FEED_ID) feedId = requireArguments().getLong(EXTRA_FEED_ID)
viewBinding = FeedStatisticsBinding.inflate(inflater) _binding = FeedStatisticsBinding.inflate(inflater)
if (!requireArguments().getBoolean(EXTRA_DETAILED)) { if (!requireArguments().getBoolean(EXTRA_DETAILED)) {
for (i in 0 until viewBinding!!.root.childCount) { for (i in 0 until binding.root.childCount) {
val child = viewBinding!!.root.getChildAt(i) val child = binding.root.getChildAt(i)
if ("detailed" == child.tag) { if ("detailed" == child.tag) {
child.visibility = View.GONE child.visibility = View.GONE
} }
@ -37,7 +39,7 @@ class FeedStatisticsFragment : Fragment() {
} }
loadStatistics() loadStatistics()
return viewBinding!!.root return binding.root
} }
private fun loadStatistics() { private fun loadStatistics() {
@ -62,21 +64,20 @@ class FeedStatisticsFragment : Fragment() {
} }
private fun showStats(s: StatisticsItem?) { private fun showStats(s: StatisticsItem?) {
viewBinding!!.startedTotalLabel.text = String.format(Locale.getDefault(), "%d / %d", binding.startedTotalLabel.text = String.format(Locale.getDefault(), "%d / %d",
s!!.episodesStarted, s.episodes) s!!.episodesStarted, s.episodes)
viewBinding!!.timePlayedLabel.text = binding.timePlayedLabel.text =
shortLocalizedDuration(requireContext(), s.timePlayed) shortLocalizedDuration(requireContext(), s.timePlayed)
viewBinding!!.totalDurationLabel.text = binding.totalDurationLabel.text =
shortLocalizedDuration(requireContext(), s.time) shortLocalizedDuration(requireContext(), s.time)
viewBinding!!.onDeviceLabel.text = String.format(Locale.getDefault(), "%d", s.episodesDownloadCount) binding.onDeviceLabel.text = String.format(Locale.getDefault(), "%d", s.episodesDownloadCount)
viewBinding!!.spaceUsedLabel.text = Formatter.formatShortFileSize(context, s.totalDownloadSize) binding.spaceUsedLabel.text = Formatter.formatShortFileSize(context, s.totalDownloadSize)
} }
override fun onDestroy() { override fun onDestroy() {
super.onDestroy() super.onDestroy()
if (disposable != null) { _binding = null
disposable!!.dispose() disposable?.dispose()
}
} }
companion object { companion object {

View File

@ -7,6 +7,7 @@ import ac.mdiq.podcini.storage.DBReader
import ac.mdiq.podcini.storage.DBReader.StatisticsResult import ac.mdiq.podcini.storage.DBReader.StatisticsResult
import ac.mdiq.podcini.storage.StatisticsItem import ac.mdiq.podcini.storage.StatisticsItem
import ac.mdiq.podcini.ui.statistics.StatisticsFragment import ac.mdiq.podcini.ui.statistics.StatisticsFragment
import ac.mdiq.podcini.util.event.StatisticsEvent
import android.content.Context import android.content.Context
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
@ -67,7 +68,7 @@ class SubscriptionStatisticsFragment : Fragment() {
} }
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
fun statisticsEvent(event: ac.mdiq.podcini.util.event.StatisticsEvent?) { fun statisticsEvent(event: StatisticsEvent?) {
refreshStatistics() refreshStatistics()
} }

View File

@ -5,6 +5,7 @@ import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.StatisticsFragmentBinding import ac.mdiq.podcini.databinding.StatisticsFragmentBinding
import ac.mdiq.podcini.storage.DBReader import ac.mdiq.podcini.storage.DBReader
import ac.mdiq.podcini.storage.DBReader.MonthlyStatisticsItem import ac.mdiq.podcini.storage.DBReader.MonthlyStatisticsItem
import ac.mdiq.podcini.util.event.StatisticsEvent
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import android.view.LayoutInflater import android.view.LayoutInflater
@ -59,7 +60,7 @@ class YearsStatisticsFragment : Fragment() {
} }
@Subscribe(threadMode = ThreadMode.MAIN) @Subscribe(threadMode = ThreadMode.MAIN)
fun statisticsEvent(event: ac.mdiq.podcini.util.event.StatisticsEvent?) { fun statisticsEvent(event: StatisticsEvent?) {
refreshStatistics() refreshStatistics()
} }

View File

@ -2,6 +2,7 @@ package ac.mdiq.podcini.util
import ac.mdiq.podcini.storage.model.feed.FeedItem import ac.mdiq.podcini.storage.model.feed.FeedItem
import ac.mdiq.podcini.storage.model.feed.SortOrder import ac.mdiq.podcini.storage.model.feed.SortOrder
import android.util.Log
import java.util.* import java.util.*
/** /**
@ -96,7 +97,8 @@ object FeedItemPermutors {
} }
private fun feedTitle(item: FeedItem?): String { private fun feedTitle(item: FeedItem?): String {
return if (item?.feed != null && item.feed!!.title != null) item.feed!!.title!!.lowercase(Locale.getDefault()) else "" Log.d("permutors", "feedTitle ${item?.feed?.title}")
return if (item?.feed?.title != null) item.feed!!.title!!.lowercase(Locale.getDefault()) else ""
} }
/** /**

View File

@ -17,6 +17,7 @@ import ac.mdiq.podcini.net.ssl.SslProviderInstaller
import ac.mdiq.podcini.storage.database.PodDBAdapter import ac.mdiq.podcini.storage.database.PodDBAdapter
import ac.mdiq.podcini.preferences.UserPreferences import ac.mdiq.podcini.preferences.UserPreferences
import ac.mdiq.podcini.preferences.UserPreferences.proxyConfig import ac.mdiq.podcini.preferences.UserPreferences.proxyConfig
import ac.mdiq.podcini.service.download.DownloadServiceInterfaceImpl
import java.io.File import java.io.File
@UnstableApi @UnstableApi
@ -35,7 +36,7 @@ object ClientConfigurator {
SslProviderInstaller.install(context) SslProviderInstaller.install(context)
NetworkUtils.init(context) NetworkUtils.init(context)
NetworkConnectionChangeHandler.init(context) NetworkConnectionChangeHandler.init(context)
DownloadServiceInterface.setImpl(ac.mdiq.podcini.service.download.DownloadServiceInterfaceImpl()) DownloadServiceInterface.setImpl(DownloadServiceInterfaceImpl())
SynchronizationQueueSink.setServiceStarterImpl { SyncService.sync(context) } SynchronizationQueueSink.setServiceStarterImpl { SyncService.sync(context) }
setCacheDirectory(File(context.cacheDir, "okhttp")) setCacheDirectory(File(context.cacheDir, "okhttp"))
setProxyConfig(proxyConfig) setProxyConfig(proxyConfig)

View File

@ -4,9 +4,10 @@
<item <item
android:id="@+id/sort_items" android:id="@+id/sort_items"
android:icon="@drawable/arrows_sort"
android:menuCategory="container" android:menuCategory="container"
android:title="@string/sort" android:title="@string/sort"
custom:showAsAction="never"> custom:showAsAction="always">
</item> </item>
<item <item
android:id="@+id/refresh_item" android:id="@+id/refresh_item"
@ -31,7 +32,7 @@
android:id="@+id/visit_website_item" android:id="@+id/visit_website_item"
android:icon="@drawable/ic_web" android:icon="@drawable/ic_web"
android:menuCategory="container" android:menuCategory="container"
custom:showAsAction="collapseActionView" custom:showAsAction="ifRoom|collapseActionView"
android:title="@string/visit_website_label" android:title="@string/visit_website_label"
android:visible="true"> android:visible="true">
</item> </item>

View File

@ -58,8 +58,8 @@ class LocalFeedUpdaterTest {
init(context) init(context)
val app = context as Application? val app = context as Application?
ac.mdiq.podcini.util.config.ClientConfig.applicationCallbacks = Mockito.mock(ac.mdiq.podcini.util.config.ApplicationCallbacks::class.java) ClientConfig.applicationCallbacks = Mockito.mock(ApplicationCallbacks::class.java)
Mockito.`when`(ac.mdiq.podcini.util.config.ClientConfig.applicationCallbacks?.getApplicationInstance()).thenReturn(app) Mockito.`when`(ClientConfig.applicationCallbacks?.getApplicationInstance()).thenReturn(app)
DownloadServiceInterface.setImpl(DownloadServiceInterfaceStub()) DownloadServiceInterface.setImpl(DownloadServiceInterfaceStub())
// Initialize database // Initialize database

View File

@ -72,8 +72,8 @@ open class DbCleanupTests {
init(context) init(context)
val app = context as Application? val app = context as Application?
ac.mdiq.podcini.util.config.ClientConfig.applicationCallbacks = Mockito.mock(ac.mdiq.podcini.util.config.ApplicationCallbacks::class.java) ClientConfig.applicationCallbacks = Mockito.mock(ApplicationCallbacks::class.java)
Mockito.`when`(ac.mdiq.podcini.util.config.ClientConfig.applicationCallbacks?.getApplicationInstance()).thenReturn(app) Mockito.`when`(ClientConfig.applicationCallbacks?.getApplicationInstance()).thenReturn(app)
} }
@After @After

View File

@ -40,8 +40,8 @@ class DbTasksTest {
init(context) init(context)
val app = context as Application? val app = context as Application?
ac.mdiq.podcini.util.config.ClientConfig.applicationCallbacks = Mockito.mock(ac.mdiq.podcini.util.config.ApplicationCallbacks::class.java) ClientConfig.applicationCallbacks = Mockito.mock(ApplicationCallbacks::class.java)
Mockito.`when`(ac.mdiq.podcini.util.config.ClientConfig.applicationCallbacks?.getApplicationInstance()).thenReturn(app) Mockito.`when`(ClientConfig.applicationCallbacks?.getApplicationInstance()).thenReturn(app)
// create new database // create new database
PodDBAdapter.init(context!!) PodDBAdapter.init(context!!)

View File

@ -64,8 +64,8 @@ class DbWriterTest {
init(context) init(context)
val app = context as Application? val app = context as Application?
ac.mdiq.podcini.util.config.ClientConfig.applicationCallbacks = Mockito.mock(ac.mdiq.podcini.util.config.ApplicationCallbacks::class.java) ClientConfig.applicationCallbacks = Mockito.mock(ApplicationCallbacks::class.java)
Mockito.`when`(ac.mdiq.podcini.util.config.ClientConfig.applicationCallbacks?.getApplicationInstance()).thenReturn(app) Mockito.`when`(ClientConfig.applicationCallbacks?.getApplicationInstance()).thenReturn(app)
DownloadServiceInterface.setImpl(DownloadServiceInterfaceStub()) DownloadServiceInterface.setImpl(DownloadServiceInterfaceStub())
// create new database // create new database

View File

@ -137,3 +137,13 @@
* only the down arrow on top left page collapses the expanded view * only the down arrow on top left page collapses the expanded view
* share notes directly from expanded view of the player * share notes directly from expanded view of the player
* in episode info, changed rendering of description, removed nested scroll * in episode info, changed rendering of description, removed nested scroll
# 4.3.4
* fixed bug player disappear on first play
* more viewbinding GC enhancements
* added sort by feed title in downloads view
* more items on action bar in feed item list view
* some cleaning of redundant qualifiers
* sort dialog no longer dims the main view
* added random sort to feed items view

View File

@ -0,0 +1,10 @@
Version 4.3.4 brings several changes:
* fixed bug player disappear on first play
* more viewbinding GC enhancements
* added sort by feed title in downloads view
* more items on action bar in feed item list view
* some cleaning of redundant qualifiers
* sort dialog no longer dims the main view
* added random sort to feed items view