notes share and faster episode open

This commit is contained in:
Xilin Jia 2024-03-20 13:59:05 +00:00
parent b4badaf059
commit d5b5734712
19 changed files with 530 additions and 286 deletions

View File

@ -149,8 +149,8 @@ android {
// Version code schema (not used):
// "1.2.3-beta4" -> 1020304
// "1.2.3" -> 1020395
versionCode 3020109
versionName "4.2.6"
versionCode 3020110
versionName "4.2.7"
def commit = ""
try {
@ -168,10 +168,9 @@ android {
buildConfigField "String", "PODCASTINDEX_API_KEY", '"' + podcastindexApiKey + '"'
buildConfigField "String", "PODCASTINDEX_API_SECRET", '"' + podcastindexApiSecret + '"'
} else {
buildConfigField "String", "PODCASTINDEX_API_KEY", '"XTMMQGA2YZ4WJUBYY4HK"'
buildConfigField "String", "PODCASTINDEX_API_SECRET", '"XAaAhk4^2YBsTE33vdbwbZNj82ZRLABDDqFdKe7x"'
buildConfigField "String", "PODCASTINDEX_API_KEY", '"QT2RYHSUZ3UC9GDJ5MFY"'
buildConfigField "String", "PODCASTINDEX_API_SECRET", '"Zw2NL74ht5aCtx5zFL$#MY$##qdVCX7x37jq95Sz"'
}
}
signingConfigs {
releaseConfig {
@ -295,7 +294,6 @@ dependencies {
testImplementation "org.robolectric:robolectric:4.11.1"
testImplementation 'javax.inject:javax.inject:1'
playImplementation 'com.google.android.gms:play-services-base:18.3.0'
freeImplementation 'org.conscrypt:conscrypt-android:2.5.2'

View File

@ -64,7 +64,7 @@ import org.greenrobot.eventbus.ThreadMode
@UnstableApi
class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
private lateinit var viewBinding: VideoplayerActivityBinding
private lateinit var binding: VideoplayerActivityBinding
/**
* True if video controls are currently visible.
@ -89,14 +89,17 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
setTheme(R.style.Theme_Podcini_VideoPlayer)
super.onCreate(savedInstanceState)
Log.d(TAG, "onCreate()")
window.setFormat(PixelFormat.TRANSPARENT)
viewBinding = VideoplayerActivityBinding.inflate(LayoutInflater.from(this))
setContentView(viewBinding.root)
binding = VideoplayerActivityBinding.inflate(LayoutInflater.from(this))
setContentView(binding.root)
setupView()
supportActionBar?.setBackgroundDrawable(ColorDrawable(-0x80000000))
supportActionBar?.setDisplayHomeAsUpEnabled(true)
controller = newPlaybackController()
controller!!.init()
loadMediaInfo()
// EventBus.getDefault().register(this)
}
@UnstableApi
@ -105,7 +108,7 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
switchToAudioOnly = false
if (isCasting) {
val intent = getPlayerActivityIntent(this)
if (intent.component!!.className != VideoplayerActivity::class.java.name) {
if (intent.component?.className != VideoplayerActivity::class.java.name) {
destroyingDueToReload = true
finish()
startActivity(intent)
@ -113,19 +116,28 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
}
}
@UnstableApi
override fun onStop() {
override fun onDestroy() {
super.onDestroy()
controller?.release()
controller = null // prevent leak
disposable?.dispose()
// EventBus.getDefault().unregister(this)
}
@UnstableApi
override fun onStop() {
// controller?.release()
// controller = null // prevent leak
// disposable?.dispose()
EventBus.getDefault().unregister(this)
super.onStop()
if (!PictureInPictureUtil.isInPictureInPictureMode(this)) {
videoControlsHider.removeCallbacks(hideVideoControls)
}
// Controller released; we will not receive buffering updates
viewBinding.progressBar.visibility = View.GONE
binding.progressBar.visibility = View.GONE
}
public override fun onUserLeaveHint() {
@ -137,9 +149,6 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
@UnstableApi
override fun onStart() {
super.onStart()
controller = newPlaybackController()
controller!!.init()
loadMediaInfo()
onPositionObserverUpdate()
EventBus.getDefault().register(this)
}
@ -147,7 +156,7 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
@UnstableApi
override fun onPause() {
if (!PictureInPictureUtil.isInPictureInPictureMode(this)) {
if (controller != null && controller!!.status == PlayerStatus.PLAYING) {
if (controller?.status == PlayerStatus.PLAYING) {
controller!!.pause()
}
}
@ -168,7 +177,7 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
private fun newPlaybackController(): PlaybackController {
return object : PlaybackController(this@VideoplayerActivity) {
override fun updatePlayButtonShowsPlay(showPlay: Boolean) {
viewBinding.playButton.setIsShowPlay(showPlay)
binding.playButton.setIsShowPlay(showPlay)
if (showPlay) {
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
} else {
@ -176,7 +185,7 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
setupVideoAspectRatio()
if (videoSurfaceCreated && controller != null) {
Log.d(TAG, "Videosurface already created, setting videosurface now")
controller!!.setVideoSurface(viewBinding.videoView.holder)
controller!!.setVideoSurface(binding.videoView.holder)
}
}
}
@ -195,11 +204,11 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
@Suppress("unused")
fun bufferUpdate(event: BufferUpdateEvent) {
if (event.hasStarted()) {
viewBinding.progressBar.visibility = View.VISIBLE
binding.progressBar.visibility = View.VISIBLE
} else if (event.hasEnded()) {
viewBinding.progressBar.visibility = View.INVISIBLE
binding.progressBar.visibility = View.INVISIBLE
} else {
viewBinding.sbPosition.secondaryProgress = (event.progress * viewBinding.sbPosition.max).toInt()
binding.sbPosition.secondaryProgress = (event.progress * binding.sbPosition.max).toInt()
}
}
@ -214,9 +223,8 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
@UnstableApi
private fun loadMediaInfo() {
Log.d(TAG, "loadMediaInfo()")
if (controller?.getMedia() == null) {
return
}
if (controller?.getMedia() == null) return
if (controller!!.status == PlayerStatus.PLAYING && !controller!!.isPlayingVideoLocally) {
Log.d(TAG, "Closing, no longer video")
destroyingDueToReload = true
@ -238,7 +246,7 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
private fun setupView() {
showTimeLeft = shouldShowRemainingTime()
Log.d("timeleft", if (showTimeLeft) "true" else "false")
viewBinding.durationLabel.setOnClickListener {
binding.durationLabel.setOnClickListener {
showTimeLeft = !showTimeLeft
val media = controller?.getMedia() ?: return@setOnClickListener
@ -251,41 +259,41 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
val duration = converter.convert(media.getDuration())
length = getDurationStringLong(duration)
}
viewBinding.durationLabel.text = length
binding.durationLabel.text = length
setShowRemainTimeSetting(showTimeLeft)
Log.d("timeleft on click", if (showTimeLeft) "true" else "false")
}
viewBinding.sbPosition.setOnSeekBarChangeListener(this)
viewBinding.rewindButton.setOnClickListener { onRewind() }
viewBinding.rewindButton.setOnLongClickListener {
binding.sbPosition.setOnSeekBarChangeListener(this)
binding.rewindButton.setOnClickListener { onRewind() }
binding.rewindButton.setOnLongClickListener {
SkipPreferenceDialog.showSkipPreference(this@VideoplayerActivity,
SkipPreferenceDialog.SkipDirection.SKIP_REWIND, null)
true
}
viewBinding.playButton.setIsVideoScreen(true)
viewBinding.playButton.setOnClickListener { onPlayPause() }
viewBinding.fastForwardButton.setOnClickListener { onFastForward() }
viewBinding.fastForwardButton.setOnLongClickListener {
binding.playButton.setIsVideoScreen(true)
binding.playButton.setOnClickListener { onPlayPause() }
binding.fastForwardButton.setOnClickListener { onFastForward() }
binding.fastForwardButton.setOnLongClickListener {
SkipPreferenceDialog.showSkipPreference(this@VideoplayerActivity,
SkipPreferenceDialog.SkipDirection.SKIP_FORWARD, null)
false
}
// To suppress touches directly below the slider
viewBinding.bottomControlsContainer.setOnTouchListener { _: View?, _: MotionEvent? -> true }
viewBinding.bottomControlsContainer.fitsSystemWindows = true
viewBinding.videoView.holder.addCallback(surfaceHolderCallback)
viewBinding.videoView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
binding.bottomControlsContainer.setOnTouchListener { _: View?, _: MotionEvent? -> true }
binding.bottomControlsContainer.fitsSystemWindows = true
binding.videoView.holder.addCallback(surfaceHolderCallback)
binding.videoView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
setupVideoControlsToggler()
window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN)
viewBinding.videoPlayerContainer.setOnTouchListener(onVideoviewTouched)
viewBinding.videoPlayerContainer.viewTreeObserver.addOnGlobalLayoutListener {
viewBinding.videoView.setAvailableSize(
viewBinding.videoPlayerContainer.width.toFloat(), viewBinding.videoPlayerContainer.height.toFloat())
binding.videoPlayerContainer.setOnTouchListener(onVideoviewTouched)
binding.videoPlayerContainer.viewTreeObserver.addOnGlobalLayoutListener {
binding.videoView.setAvailableSize(
binding.videoPlayerContainer.width.toFloat(), binding.videoPlayerContainer.height.toFloat())
}
}
@ -340,24 +348,24 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
skipAnimation.fillAfter = false
skipAnimation.duration = 800
val params = viewBinding.skipAnimationImage.layoutParams as FrameLayout.LayoutParams
val params = binding.skipAnimationImage.layoutParams as FrameLayout.LayoutParams
if (isForward) {
viewBinding.skipAnimationImage.setImageResource(R.drawable.ic_fast_forward_video_white)
binding.skipAnimationImage.setImageResource(R.drawable.ic_fast_forward_video_white)
params.gravity = Gravity.RIGHT or Gravity.CENTER_VERTICAL
} else {
viewBinding.skipAnimationImage.setImageResource(R.drawable.ic_fast_rewind_video_white)
binding.skipAnimationImage.setImageResource(R.drawable.ic_fast_rewind_video_white)
params.gravity = Gravity.LEFT or Gravity.CENTER_VERTICAL
}
viewBinding.skipAnimationImage.visibility = View.VISIBLE
viewBinding.skipAnimationImage.layoutParams = params
viewBinding.skipAnimationImage.startAnimation(skipAnimation)
binding.skipAnimationImage.visibility = View.VISIBLE
binding.skipAnimationImage.layoutParams = params
binding.skipAnimationImage.startAnimation(skipAnimation)
skipAnimation.setAnimationListener(object : Animation.AnimationListener {
override fun onAnimationStart(animation: Animation) {
}
override fun onAnimationEnd(animation: Animation) {
viewBinding.skipAnimationImage.visibility = View.GONE
binding.skipAnimationImage.visibility = View.GONE
}
override fun onAnimationRepeat(animation: Animation) {
@ -376,7 +384,7 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
val videoSize = controller!!.videoSize
if (videoSize != null && videoSize.first > 0 && videoSize.second > 0) {
Log.d(TAG, "Width,height of video: " + videoSize.first + ", " + videoSize.second)
viewBinding.videoView.setVideoSize(videoSize.first, videoSize.second)
binding.videoView.setVideoSize(videoSize.first, videoSize.second)
} else {
Log.e(TAG, "Could not determine video size")
}
@ -396,9 +404,8 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
@UnstableApi
fun onRewind() {
if (controller == null) {
return
}
if (controller == null) return
val curr = controller!!.position
controller!!.seekTo(curr - rewindSecs * 1000)
setupVideoControlsToggler()
@ -406,18 +413,16 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
@UnstableApi
fun onPlayPause() {
if (controller == null) {
return
}
if (controller == null) return
controller!!.playPause()
setupVideoControlsToggler()
}
@UnstableApi
fun onFastForward() {
if (controller == null) {
return
}
if (controller == null) return
val curr = controller!!.position
controller!!.seekTo(curr + fastForwardSecs * 1000)
setupVideoControlsToggler()
@ -448,31 +453,31 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
}
private fun showVideoControls() {
viewBinding.bottomControlsContainer.visibility = View.VISIBLE
viewBinding.controlsContainer.visibility = View.VISIBLE
binding.bottomControlsContainer.visibility = View.VISIBLE
binding.controlsContainer.visibility = View.VISIBLE
val animation = AnimationUtils.loadAnimation(this, R.anim.fade_in)
if (animation != null) {
viewBinding.bottomControlsContainer.startAnimation(animation)
viewBinding.controlsContainer.startAnimation(animation)
binding.bottomControlsContainer.startAnimation(animation)
binding.controlsContainer.startAnimation(animation)
}
viewBinding.videoView.systemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE
binding.videoView.systemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE
}
private fun hideVideoControls(showAnimation: Boolean) {
if (showAnimation) {
val animation = AnimationUtils.loadAnimation(this, R.anim.fade_out)
if (animation != null) {
viewBinding.bottomControlsContainer.startAnimation(animation)
viewBinding.controlsContainer.startAnimation(animation)
binding.bottomControlsContainer.startAnimation(animation)
binding.controlsContainer.startAnimation(animation)
}
}
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LOW_PROFILE
or View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)
viewBinding.bottomControlsContainer.fitsSystemWindows = true
binding.bottomControlsContainer.fitsSystemWindows = true
viewBinding.bottomControlsContainer.visibility = View.GONE
viewBinding.controlsContainer.visibility = View.GONE
binding.bottomControlsContainer.visibility = View.GONE
binding.controlsContainer.visibility = View.GONE
}
@Subscribe(threadMode = ThreadMode.MAIN)
@ -515,9 +520,8 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
@UnstableApi
override fun onPrepareOptionsMenu(menu: Menu): Boolean {
super.onPrepareOptionsMenu(menu)
if (controller == null) {
return false
}
if (controller == null) return false
val media = controller!!.getMedia()
val isFeedMedia = (media is FeedMedia)
@ -573,33 +577,42 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
else -> {
val media = controller?.getMedia() ?: return false
val feedItem = getFeedItem(media) // some options option requires FeedItem
if (item.itemId == R.id.add_to_favorites_item && feedItem != null) {
DBWriter.addFavoriteItem(feedItem)
isFavorite = true
invalidateOptionsMenu()
} else if (item.itemId == R.id.remove_from_favorites_item && feedItem != null) {
DBWriter.removeFavoriteItem(feedItem)
isFavorite = false
invalidateOptionsMenu()
} else if (item.itemId == R.id.disable_sleeptimer_item
|| item.itemId == R.id.set_sleeptimer_item) {
SleepTimerDialog().show(supportFragmentManager, "SleepTimerDialog")
} else if (item.itemId == R.id.audio_controls) {
val dialog = PlaybackControlsDialog.newInstance()
dialog.show(supportFragmentManager, "playback_controls")
} else if (item.itemId == R.id.open_feed_item && feedItem != null) {
val intent = MainActivity.getIntentToOpenFeed(this, feedItem.feedId)
startActivity(intent)
} else if (item.itemId == R.id.visit_website_item) {
val url = getWebsiteLinkWithFallback(media)
if (url != null) openInBrowser(this@VideoplayerActivity, url)
} else if (item.itemId == R.id.share_item && feedItem != null) {
val shareDialog = ShareDialog.newInstance(feedItem)
shareDialog.show(supportFragmentManager, "ShareEpisodeDialog")
} else if (item.itemId == R.id.playback_speed) {
VariableSpeedDialog().show(supportFragmentManager, null)
} else {
return false
when {
item.itemId == R.id.add_to_favorites_item && feedItem != null -> {
DBWriter.addFavoriteItem(feedItem)
isFavorite = true
invalidateOptionsMenu()
}
item.itemId == R.id.remove_from_favorites_item && feedItem != null -> {
DBWriter.removeFavoriteItem(feedItem)
isFavorite = false
invalidateOptionsMenu()
}
item.itemId == R.id.disable_sleeptimer_item || item.itemId == R.id.set_sleeptimer_item -> {
SleepTimerDialog().show(supportFragmentManager, "SleepTimerDialog")
}
item.itemId == R.id.audio_controls -> {
val dialog = PlaybackControlsDialog.newInstance()
dialog.show(supportFragmentManager, "playback_controls")
}
item.itemId == R.id.open_feed_item && feedItem != null -> {
val intent = MainActivity.getIntentToOpenFeed(this, feedItem.feedId)
startActivity(intent)
}
item.itemId == R.id.visit_website_item -> {
val url = getWebsiteLinkWithFallback(media)
if (url != null) openInBrowser(this@VideoplayerActivity, url)
}
item.itemId == R.id.share_item && feedItem != null -> {
val shareDialog = ShareDialog.newInstance(feedItem)
shareDialog.show(supportFragmentManager, "ShareEpisodeDialog")
}
item.itemId == R.id.playback_speed -> {
VariableSpeedDialog().show(supportFragmentManager, null)
}
else -> {
return false
}
}
return true
}
@ -607,9 +620,7 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
}
fun onPositionObserverUpdate() {
if (controller == null) {
return
}
if (controller == null) return
val converter = TimeSpeedConverter(controller!!.currentPlaybackSpeedMultiplier)
val currentPosition = converter.convert(controller!!.position)
@ -617,16 +628,15 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
val remainingTime = converter.convert(
controller!!.duration - controller!!.position)
// Log.d(TAG, "currentPosition " + Converter.getDurationStringLong(currentPosition));
if (currentPosition == Playable.INVALID_TIME
|| duration == Playable.INVALID_TIME) {
if (currentPosition == Playable.INVALID_TIME || duration == Playable.INVALID_TIME) {
Log.w(TAG, "Could not react to position observer update because of invalid time")
return
}
viewBinding.positionLabel.text = getDurationStringLong(currentPosition)
binding.positionLabel.text = getDurationStringLong(currentPosition)
if (showTimeLeft) {
viewBinding.durationLabel.text = "-" + getDurationStringLong(remainingTime)
binding.durationLabel.text = "-" + getDurationStringLong(remainingTime)
} else {
viewBinding.durationLabel.text = getDurationStringLong(duration)
binding.durationLabel.text = getDurationStringLong(duration)
}
updateProgressbarPosition(currentPosition, duration)
}
@ -634,25 +644,24 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
private fun updateProgressbarPosition(position: Int, duration: Int) {
Log.d(TAG, "updateProgressbarPosition($position, $duration)")
val progress = (position.toFloat()) / duration
viewBinding.sbPosition.progress = (progress * viewBinding.sbPosition.max).toInt()
binding.sbPosition.progress = (progress * binding.sbPosition.max).toInt()
}
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
if (controller == null) {
return
}
if (controller == null) return
if (fromUser) {
prog = progress / (seekBar.max.toFloat())
val converter = TimeSpeedConverter(controller!!.currentPlaybackSpeedMultiplier)
val position = converter.convert((prog * controller!!.duration).toInt())
viewBinding.seekPositionLabel.text = getDurationStringLong(position)
binding.seekPositionLabel.text = getDurationStringLong(position)
}
}
override fun onStartTrackingTouch(seekBar: SeekBar) {
viewBinding.seekCardView.scaleX = .8f
viewBinding.seekCardView.scaleY = .8f
viewBinding.seekCardView.animate()
binding.seekCardView.scaleX = .8f
binding.seekCardView.scaleY = .8f
binding.seekCardView.animate()
.setInterpolator(FastOutSlowInInterpolator())
.alpha(1f).scaleX(1f).scaleY(1f)
.setDuration(200)
@ -664,9 +673,9 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
if (controller != null) {
controller!!.seekTo((prog * controller!!.duration).toInt())
}
viewBinding.seekCardView.scaleX = 1f
viewBinding.seekCardView.scaleY = 1f
viewBinding.seekCardView.animate()
binding.seekCardView.scaleX = 1f
binding.seekCardView.scaleY = 1f
binding.seekCardView.animate()
.setInterpolator(FastOutSlowInInterpolator())
.alpha(0f).scaleX(.8f).scaleY(.8f)
.setDuration(200)
@ -763,14 +772,18 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
private const val TAG = "VideoplayerActivity"
private fun getWebsiteLinkWithFallback(media: Playable?): String? {
if (media == null) {
return null
} else if (!media.getWebsiteLink().isNullOrBlank()) {
return media.getWebsiteLink()
} else if (media is FeedMedia) {
return getLinkWithFallback(media.getItem())
when {
media == null -> {
return null
}
!media.getWebsiteLink().isNullOrBlank() -> {
return media.getWebsiteLink()
}
media is FeedMedia -> {
return getLinkWithFallback(media.getItem())
}
else -> return null
}
return null
}
private fun getFeedItem(playable: Playable?): FeedItem? {

View File

@ -7,6 +7,7 @@ import ac.mdiq.podcini.ui.fragment.ItemPagerFragment
import ac.mdiq.podcini.ui.menuhandler.FeedItemMenuHandler
import ac.mdiq.podcini.storage.model.feed.FeedItem
import ac.mdiq.podcini.ui.common.ThemeUtils
import ac.mdiq.podcini.ui.fragment.ItemPageFragment
import ac.mdiq.podcini.ui.view.viewholder.EpisodeItemViewHolder
import android.R.color
import android.app.Activity
@ -83,7 +84,7 @@ open class EpisodeItemListAdapter(mainActivity: MainActivity) :
if (!inActionMode()) {
val ids: LongArray = FeedItemUtil.getIds(episodes)
val position = ArrayUtils.indexOf(ids, item.id)
activity?.loadChildFragment(ItemPagerFragment.newInstance(ids, position))
activity?.loadChildFragment(ItemPageFragment.newInstance(ids, position))
} else {
toggleSelection(holder.bindingAdapterPosition)
}

View File

@ -12,6 +12,7 @@ import ac.mdiq.podcini.util.FeedItemUtil
import ac.mdiq.podcini.ui.fragment.ItemPagerFragment
import ac.mdiq.podcini.ui.menuhandler.FeedItemMenuHandler
import ac.mdiq.podcini.storage.model.feed.FeedItem
import ac.mdiq.podcini.ui.fragment.ItemPageFragment
import ac.mdiq.podcini.ui.view.viewholder.HorizontalItemViewHolder
import org.apache.commons.lang3.ArrayUtils
import java.lang.ref.WeakReference
@ -65,7 +66,7 @@ open class HorizontalItemListAdapter(mainActivity: MainActivity) : RecyclerView.
if (activity != null) {
val ids: LongArray = FeedItemUtil.getIds(data)
val clickPosition = ArrayUtils.indexOf(ids, item.id)
activity.loadChildFragment(ItemPagerFragment.newInstance(ids, clickPosition))
activity.loadChildFragment(ItemPageFragment.newInstance(ids, clickPosition))
}
}
}

View File

@ -30,7 +30,7 @@ open class QueueRecyclerAdapter(mainActivity: MainActivity, private val swipeAct
@UnstableApi @SuppressLint("ClickableViewAccessibility")
override fun afterBindViewHolder(holder: EpisodeItemViewHolder, pos: Int) {
if (!dragDropEnabled) {
if (inActionMode() || !dragDropEnabled) {
holder.dragHandle.setVisibility(View.GONE)
holder.dragHandle.setOnTouchListener(null)
// holder.coverHolder.setOnTouchListener(null)
@ -38,17 +38,15 @@ open class QueueRecyclerAdapter(mainActivity: MainActivity, private val swipeAct
holder.dragHandle.setVisibility(View.VISIBLE)
holder.dragHandle.setOnTouchListener { _: View?, event: MotionEvent ->
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
Log.d(TAG, "startDrag()")
swipeActions.startDrag(holder)
}
false
}
holder.coverHolder.setOnTouchListener { v1, event ->
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
if (!inActionMode() && event.actionMasked == MotionEvent.ACTION_DOWN) {
val isLtr = holder.itemView.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR
val factor = (if (isLtr) 1 else -1).toFloat()
if (factor * event.x < factor * 0.5 * v1.width) {
Log.d(TAG, "startDrag()")
swipeActions.startDrag(holder)
} else {
Log.d(TAG, "Ignoring drag in right half of the image")

View File

@ -90,19 +90,18 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
val viewBinding = FeedinfoBinding.inflate(inflater)
// val root: View = inflater.inflate(R.layout.feedinfo, null)
val binding = FeedinfoBinding.inflate(inflater)
Log.d(TAG, "fragment onCreateView")
toolbar = viewBinding.toolbar
toolbar = binding.toolbar
toolbar.title = ""
toolbar.inflateMenu(R.menu.feedinfo)
toolbar.setNavigationOnClickListener { parentFragmentManager.popBackStack() }
toolbar.setOnMenuItemClickListener(this)
refreshToolbarState()
val appBar: AppBarLayout = viewBinding.appBar
val collapsingToolbar: CollapsingToolbarLayout = viewBinding.collapsingToolbar
val appBar: AppBarLayout = binding.appBar
val collapsingToolbar: CollapsingToolbarLayout = binding.collapsingToolbar
val iconTintManager: ToolbarIconTintManager =
object : ToolbarIconTintManager(requireContext(), toolbar, collapsingToolbar) {
override fun doTint(themedContext: Context) {
@ -115,22 +114,22 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
iconTintManager.updateTint()
appBar.addOnOffsetChangedListener(iconTintManager)
imgvCover = viewBinding.header.imgvCover
txtvTitle = viewBinding.header.txtvTitle
txtvAuthorHeader = viewBinding.header.txtvAuthor
imgvBackground = viewBinding.imgvBackground
header = viewBinding.header.root
infoContainer = viewBinding.infoContainer
// viewBinding.header.butShowInfo.visibility = View.INVISIBLE
viewBinding.header.butShowSettings.visibility = View.INVISIBLE
viewBinding.header.butFilter.visibility = View.INVISIBLE
imgvCover = binding.header.imgvCover
txtvTitle = binding.header.txtvTitle
txtvAuthorHeader = binding.header.txtvAuthor
imgvBackground = binding.imgvBackground
header = binding.header.root
infoContainer = binding.infoContainer
// binding.header.butShowInfo.visibility = View.INVISIBLE
binding.header.butShowSettings.visibility = View.INVISIBLE
binding.header.butFilter.visibility = View.INVISIBLE
// https://github.com/bumptech/glide/issues/529
imgvBackground.colorFilter = LightingColorFilter(-0x7d7d7e, 0x000000)
txtvDescription = viewBinding.txtvDescription
txtvUrl = viewBinding.txtvUrl
lblSupport = viewBinding.lblSupport
txtvFundingUrl = viewBinding.txtvFundingUrl
txtvDescription = binding.txtvDescription
txtvUrl = binding.txtvUrl
lblSupport = binding.lblSupport
txtvFundingUrl = binding.txtvFundingUrl
txtvUrl.setOnClickListener(copyUrlToClipboard)
@ -139,12 +138,12 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
FeedStatisticsFragment.newInstance(feedId, false), "feed_statistics_fragment")
.commitAllowingStateLoss()
viewBinding.btnvOpenStatistics.setOnClickListener {
binding.btnvOpenStatistics.setOnClickListener {
val fragment = StatisticsFragment()
(activity as MainActivity).loadChildFragment(fragment, TransitionEffect.SLIDE)
}
return viewBinding.root
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {

View File

@ -63,7 +63,6 @@ import org.greenrobot.eventbus.ThreadMode
import java.util.concurrent.Callable
import java.util.concurrent.ExecutionException
/**
* Displays a list of FeedItems.
*/
@ -72,7 +71,7 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
private lateinit var adapter: FeedItemListAdapter
private lateinit var swipeActions: SwipeActions
private lateinit var viewBinding: FeedItemListFragmentBinding
private lateinit var binding: FeedItemListFragmentBinding
private lateinit var speedDialBinding: MultiSelectSpeedDialBinding
private lateinit var nextPageLoader: MoreContentListFooterUtil
@ -94,43 +93,44 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
): View {
Log.d(TAG, "fragment onCreateView")
viewBinding = FeedItemListFragmentBinding.inflate(inflater)
speedDialBinding = MultiSelectSpeedDialBinding.bind(viewBinding.root)
viewBinding.toolbar.inflateMenu(R.menu.feedlist)
viewBinding.toolbar.setOnMenuItemClickListener(this)
viewBinding.toolbar.setOnLongClickListener {
viewBinding.recyclerView.scrollToPosition(5)
viewBinding.recyclerView.post { viewBinding.recyclerView.smoothScrollToPosition(0) }
viewBinding.appBar.setExpanded(true)
binding = FeedItemListFragmentBinding.inflate(inflater)
speedDialBinding = MultiSelectSpeedDialBinding.bind(binding.root)
binding.toolbar.inflateMenu(R.menu.feedlist)
binding.toolbar.setOnMenuItemClickListener(this)
binding.toolbar.setOnLongClickListener {
binding.recyclerView.scrollToPosition(5)
binding.recyclerView.post { binding.recyclerView.smoothScrollToPosition(0) }
binding.appBar.setExpanded(true)
false
}
displayUpArrow = parentFragmentManager.backStackEntryCount != 0
if (savedInstanceState != null) {
displayUpArrow = savedInstanceState.getBoolean(KEY_UP_ARROW)
}
(activity as MainActivity).setupToolbarToggle(viewBinding.toolbar, displayUpArrow)
(activity as MainActivity).setupToolbarToggle(binding.toolbar, displayUpArrow)
updateToolbar()
viewBinding.recyclerView.setRecycledViewPool((activity as MainActivity).recycledViewPool)
binding.recyclerView.setRecycledViewPool((activity as MainActivity).recycledViewPool)
adapter = FeedItemListAdapter(activity as MainActivity)
adapter.setOnSelectModeListener(this)
viewBinding.recyclerView.adapter = adapter
swipeActions = SwipeActions(this, TAG).attachTo(viewBinding.recyclerView)
viewBinding.progressBar.visibility = View.VISIBLE
binding.recyclerView.adapter = adapter
swipeActions = SwipeActions(this, TAG).attachTo(binding.recyclerView)
binding.progressBar.visibility = View.VISIBLE
val iconTintManager: ToolbarIconTintManager = object : ToolbarIconTintManager(
requireContext(), viewBinding.toolbar, viewBinding.collapsingToolbar) {
requireContext(), binding.toolbar, binding.collapsingToolbar) {
override fun doTint(themedContext: Context) {
viewBinding.toolbar.menu.findItem(R.id.refresh_item)
binding.toolbar.menu.findItem(R.id.refresh_item)
.setIcon(AppCompatResources.getDrawable(themedContext, R.drawable.ic_refresh))
viewBinding.toolbar.menu.findItem(R.id.action_search)
binding.toolbar.menu.findItem(R.id.action_search)
.setIcon(AppCompatResources.getDrawable(themedContext, R.drawable.ic_search))
}
}
iconTintManager.updateTint()
viewBinding.appBar.addOnOffsetChangedListener(iconTintManager)
binding.appBar.addOnOffsetChangedListener(iconTintManager)
nextPageLoader = MoreContentListFooterUtil(viewBinding.moreContent.moreContentListFooter)
nextPageLoader = MoreContentListFooterUtil(binding.moreContent.moreContentListFooter)
nextPageLoader.setClickListener(object : MoreContentListFooterUtil.Listener {
override fun onClick() {
if (feed != null) {
@ -138,22 +138,22 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
}
}
})
viewBinding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(view: RecyclerView, deltaX: Int, deltaY: Int) {
super.onScrolled(view, deltaX, deltaY)
val hasMorePages = feed != null && feed!!.isPaged && feed!!.nextPageLink != null
val pageLoaderVisible = viewBinding.recyclerView.isScrolledToBottom && hasMorePages
val pageLoaderVisible = binding.recyclerView.isScrolledToBottom && hasMorePages
nextPageLoader.root.visibility = if (pageLoaderVisible) View.VISIBLE else View.GONE
viewBinding.recyclerView.setPadding(
viewBinding.recyclerView.paddingLeft, 0, viewBinding.recyclerView.paddingRight,
binding.recyclerView.setPadding(
binding.recyclerView.paddingLeft, 0, binding.recyclerView.paddingRight,
if (pageLoaderVisible) nextPageLoader.root.measuredHeight else 0)
}
})
EventBus.getDefault().register(this)
viewBinding.swipeRefresh.setDistanceToTriggerSync(resources.getInteger(R.integer.swipe_refresh_distance))
viewBinding.swipeRefresh.setOnRefreshListener {
binding.swipeRefresh.setDistanceToTriggerSync(resources.getInteger(R.integer.swipe_refresh_distance))
binding.swipeRefresh.setOnRefreshListener {
FeedUpdateManager.runOnceOrAsk(requireContext(), feed)
}
@ -181,7 +181,7 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
adapter.endSelectMode()
true
}
return viewBinding.root
return binding.root
}
override fun onDestroyView() {
@ -201,22 +201,22 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
if (feed == null) {
return
}
viewBinding.toolbar.menu.findItem(R.id.visit_website_item).setVisible(feed!!.link != null)
viewBinding.toolbar.menu.findItem(R.id.refresh_complete_item).setVisible(feed!!.isPaged)
binding.toolbar.menu.findItem(R.id.visit_website_item).setVisible(feed!!.link != null)
binding.toolbar.menu.findItem(R.id.refresh_complete_item).setVisible(feed!!.isPaged)
if (StringUtils.isBlank(feed!!.link)) {
viewBinding.toolbar.menu.findItem(R.id.visit_website_item).setVisible(false)
binding.toolbar.menu.findItem(R.id.visit_website_item).setVisible(false)
}
if (feed!!.isLocalFeed) {
viewBinding.toolbar.menu.findItem(R.id.share_item).setVisible(false)
binding.toolbar.menu.findItem(R.id.share_item).setVisible(false)
}
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
val horizontalSpacing = resources.getDimension(R.dimen.additional_horizontal_spacing).toInt()
viewBinding.header.headerContainer.setPadding(
horizontalSpacing, viewBinding.header.headerContainer.paddingTop,
horizontalSpacing, viewBinding.header.headerContainer.paddingBottom)
binding.header.headerContainer.setPadding(
horizontalSpacing, binding.header.headerContainer.paddingTop,
horizontalSpacing, binding.header.headerContainer.paddingBottom)
}
@UnstableApi override fun onMenuItemClick(item: MenuItem): Boolean {
@ -288,7 +288,7 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
val activity: MainActivity = activity as MainActivity
if (feed != null) {
val ids: LongArray = FeedItemUtil.getIds(feed!!.items)
activity.loadChildFragment(ItemPagerFragment.newInstance(ids, position))
activity.loadChildFragment(ItemPageFragment.newInstance(ids, position))
}
}
@ -339,7 +339,7 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
Log.d(TAG, "onEventMainThread() called with: event = [$event]")
for (i in 0 until adapter.itemCount) {
val holder: EpisodeItemViewHolder? =
viewBinding.recyclerView.findViewHolderForAdapterPosition(i) as? EpisodeItemViewHolder
binding.recyclerView.findViewHolderForAdapterPosition(i) as? EpisodeItemViewHolder
if (holder != null && holder.isCurrentlyPlayingItem) {
holder.notifyPlaybackPositionUpdated(event)
break
@ -372,7 +372,7 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
override fun onEndSelectMode() {
speedDialBinding.fabSD.close()
speedDialBinding.fabSD.visibility = View.GONE
swipeActions.attachTo(viewBinding.recyclerView)
swipeActions.attachTo(binding.recyclerView)
}
@UnstableApi private fun updateUi() {
@ -405,7 +405,7 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
if (!event.isFeedUpdateRunning) {
nextPageLoader.root.visibility = View.GONE
}
viewBinding.swipeRefresh.isRefreshing = event.isFeedUpdateRunning
binding.swipeRefresh.isRefreshing = event.isFeedUpdateRunning
}
@UnstableApi private fun refreshHeaderView() {
@ -416,34 +416,34 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
}
loadFeedImage()
if (feed!!.hasLastUpdateFailed()) {
viewBinding.header.txtvFailure.visibility = View.VISIBLE
binding.header.txtvFailure.visibility = View.VISIBLE
} else {
viewBinding.header.txtvFailure.visibility = View.GONE
binding.header.txtvFailure.visibility = View.GONE
}
if (feed!!.preferences != null && !feed!!.preferences!!.keepUpdated) {
viewBinding.header.txtvUpdatesDisabled.text = ("{md-pause-circle-outline} "
binding.header.txtvUpdatesDisabled.text = ("{md-pause-circle-outline} "
+ this.getString(R.string.updates_disabled_label))
Iconify.addIcons(viewBinding.header.txtvUpdatesDisabled)
viewBinding.header.txtvUpdatesDisabled.visibility = View.VISIBLE
Iconify.addIcons(binding.header.txtvUpdatesDisabled)
binding.header.txtvUpdatesDisabled.visibility = View.VISIBLE
} else {
viewBinding.header.txtvUpdatesDisabled.visibility = View.GONE
binding.header.txtvUpdatesDisabled.visibility = View.GONE
}
viewBinding.header.txtvTitle.text = feed!!.title
viewBinding.header.txtvAuthor.text = feed!!.author
binding.header.txtvTitle.text = feed!!.title
binding.header.txtvAuthor.text = feed!!.author
if (feed != null && feed!!.itemFilter != null) {
val filter: FeedItemFilter? = feed!!.itemFilter
if (filter != null && filter.values.isNotEmpty()) {
viewBinding.header.txtvInformation.text = ("{md-info-outline} " + this.getString(R.string.filtered_label))
Iconify.addIcons(viewBinding.header.txtvInformation)
viewBinding.header.txtvInformation.setOnClickListener {
binding.header.txtvInformation.text = ("{md-info-outline} " + this.getString(R.string.filtered_label))
Iconify.addIcons(binding.header.txtvInformation)
binding.header.txtvInformation.setOnClickListener {
FeedItemFilterDialog.newInstance(feed!!).show(childFragmentManager, null)
}
viewBinding.header.txtvInformation.visibility = View.VISIBLE
binding.header.txtvInformation.visibility = View.VISIBLE
} else {
viewBinding.header.txtvInformation.visibility = View.GONE
binding.header.txtvInformation.visibility = View.GONE
}
} else {
viewBinding.header.txtvInformation.visibility = View.GONE
binding.header.txtvInformation.visibility = View.GONE
}
}
@ -451,20 +451,19 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
if (feed == null || headerCreated) return
// https://github.com/bumptech/glide/issues/529
viewBinding.imgvBackground.colorFilter = LightingColorFilter(-0x99999a, 0x000000)
// viewBinding.header.butShowInfo.setOnClickListener { showFeedInfo() }
viewBinding.header.imgvCover.setOnClickListener { showFeedInfo() }
viewBinding.header.butShowSettings.setOnClickListener {
binding.imgvBackground.colorFilter = LightingColorFilter(-0x99999a, 0x000000)
binding.header.imgvCover.setOnClickListener { showFeedInfo() }
binding.header.butShowSettings.setOnClickListener {
if (feed != null) {
val fragment = FeedSettingsFragment.newInstance(feed!!)
(activity as MainActivity).loadChildFragment(fragment, TransitionEffect.SLIDE)
}
}
viewBinding.header.butFilter.setOnClickListener {
binding.header.butFilter.setOnClickListener {
if (feed != null) FeedItemFilterDialog.newInstance(feed!!).show(childFragmentManager, null)
}
viewBinding.header.txtvFailure.setOnClickListener { showErrorDetails() }
viewBinding.header.counts.text = adapter.itemCount.toString()
binding.header.txtvFailure.setOnClickListener { showErrorDetails() }
binding.header.counts.text = adapter.itemCount.toString()
headerCreated = true
}
@ -504,7 +503,7 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
.error(R.color.image_readability_tint)
.transform(FastBlurTransformation())
.dontAnimate())
.into(viewBinding.imgvBackground)
.into(binding.imgvBackground)
Glide.with(this)
.load(feed!!.imageUrl)
@ -513,7 +512,7 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
.error(R.color.light_gray)
.fitCenter()
.dontAnimate())
.into(viewBinding.header.imgvCover)
.into(binding.header.imgvCover)
}
@UnstableApi private fun loadItems() {
@ -528,10 +527,10 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
Log.d(TAG, "loadItems subscribe called ${feed?.title}")
swipeActions.setFilter(feed?.itemFilter)
refreshHeaderView()
viewBinding.progressBar.visibility = View.GONE
binding.progressBar.visibility = View.GONE
adapter.setDummyViews(0)
if (feed != null) adapter.updateItems(feed!!.items)
viewBinding.header.counts.text = (feed?.items?.size?:0).toString()
binding.header.counts.text = (feed?.items?.size?:0).toString()
updateToolbar()
}, { error: Throwable? ->
feed = null
@ -563,8 +562,8 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
return
}
when (event.keyCode) {
KeyEvent.KEYCODE_T -> viewBinding.recyclerView.smoothScrollToPosition(0)
KeyEvent.KEYCODE_B -> viewBinding.recyclerView.smoothScrollToPosition(adapter.itemCount - 1)
KeyEvent.KEYCODE_T -> binding.recyclerView.smoothScrollToPosition(0)
KeyEvent.KEYCODE_B -> binding.recyclerView.smoothScrollToPosition(adapter.itemCount - 1)
else -> {}
}
}

View File

@ -35,7 +35,6 @@ class ItemDescriptionFragment : Fragment() {
@UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
Log.d(TAG, "Creating view")
val binding = ItemDescriptionFragmentBinding.inflate(inflater)
// val root = inflater.inflate(R.layout.item_description_fragment, container, false)
Log.d(TAG, "fragment onCreateView")
webvDescription = binding.webview

View File

@ -16,6 +16,7 @@ import ac.mdiq.podcini.ui.adapter.actionbutton.*
import ac.mdiq.podcini.ui.common.CircularProgressBar
import ac.mdiq.podcini.ui.common.ThemeUtils
import ac.mdiq.podcini.ui.gui.ShownotesCleaner
import ac.mdiq.podcini.ui.menuhandler.FeedItemMenuHandler
import ac.mdiq.podcini.ui.view.ShownotesWebView
import ac.mdiq.podcini.util.Converter
import ac.mdiq.podcini.util.DateFormatter
@ -25,6 +26,7 @@ import ac.mdiq.podcini.util.event.FeedItemEvent
import ac.mdiq.podcini.util.event.UnreadItemsUpdateEvent
import android.os.Build
import android.os.Bundle
import android.text.Html
import android.text.Layout
import android.text.TextUtils
import android.util.Log
@ -32,11 +34,12 @@ import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.ImageView
import android.widget.ProgressBar
import android.widget.TextView
import androidx.annotation.OptIn
import androidx.appcompat.widget.Toolbar
import androidx.core.app.ShareCompat
import androidx.fragment.app.Fragment
import androidx.media3.common.util.UnstableApi
import com.bumptech.glide.Glide
@ -98,20 +101,20 @@ class ItemFragment : Fragment() {
@UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
super.onCreateView(inflater, container, savedInstanceState)
val viewBinding = FeeditemFragmentBinding.inflate(inflater)
root = viewBinding.root
val binding = FeeditemFragmentBinding.inflate(inflater)
root = binding.root
Log.d(TAG, "fragment onCreateView")
txtvPodcast = viewBinding.txtvPodcast
txtvPodcast = binding.txtvPodcast
txtvPodcast.setOnClickListener { openPodcast() }
txtvTitle = viewBinding.txtvTitle
txtvTitle = binding.txtvTitle
if (Build.VERSION.SDK_INT >= 23) {
txtvTitle.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL)
}
txtvDuration = viewBinding.txtvDuration
txtvPublished = viewBinding.txtvPublished
txtvDuration = binding.txtvDuration
txtvPublished = binding.txtvPublished
txtvTitle.ellipsize = TextUtils.TruncateAt.END
webvDescription = viewBinding.webvDescription
webvDescription = binding.webvDescription
webvDescription.setTimecodeSelectedListener { time: Int? ->
val cMedia = controller?.getMedia()
if (item?.media?.getIdentifier() == cMedia?.getIdentifier()) {
@ -123,17 +126,17 @@ class ItemFragment : Fragment() {
}
registerForContextMenu(webvDescription)
imgvCover = viewBinding.imgvCover
imgvCover = binding.imgvCover
imgvCover.setOnClickListener { openPodcast() }
progbarDownload = viewBinding.circularProgressBar
progbarLoading = viewBinding.progbarLoading
butAction1 = viewBinding.butAction1
butAction2 = viewBinding.butAction2
butAction1Icon = viewBinding.butAction1Icon
butAction2Icon = viewBinding.butAction2Icon
butAction1Text = viewBinding.butAction1Text
butAction2Text = viewBinding.butAction2Text
noMediaLabel = viewBinding.noMediaLabel
progbarDownload = binding.circularProgressBar
progbarLoading = binding.progbarLoading
butAction1 = binding.butAction1
butAction2 = binding.butAction2
butAction1Icon = binding.butAction1Icon
butAction2Icon = binding.butAction2Icon
butAction1Text = binding.butAction1Text
butAction2Text = binding.butAction2Text
noMediaLabel = binding.noMediaLabel
butAction1.setOnClickListener(View.OnClickListener {
if (actionButton1 is StreamActionButton && !UserPreferences.isStreamOverDownload
@ -165,7 +168,7 @@ class ItemFragment : Fragment() {
controller?.init()
load()
return viewBinding.root
return binding.root
}
@OptIn(UnstableApi::class) private fun showOnDemandConfigBalloon(offerStreaming: Boolean) {
@ -393,6 +396,9 @@ class ItemFragment : Fragment() {
DBReader.loadDescriptionOfFeedItem(feedItem)
val t = ShownotesCleaner(context, feedItem.description?:"", duration)
webviewData = t.processShownotes()
val bundle = Bundle()
bundle.putString("description", feedItem.description?:"")
this.arguments = bundle
}
return feedItem
}

View File

@ -0,0 +1,177 @@
package ac.mdiq.podcini.ui.fragment
import ac.mdiq.podcini.R
import ac.mdiq.podcini.databinding.FeeditemPageFragmentBinding
import ac.mdiq.podcini.storage.DBReader
import ac.mdiq.podcini.storage.model.feed.FeedItem
import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.ui.menuhandler.FeedItemMenuHandler
import ac.mdiq.podcini.util.event.FeedItemEvent
import android.os.Build
import android.os.Bundle
import android.text.Html
import android.util.Log
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.Toolbar
import androidx.core.app.ShareCompat
import androidx.fragment.app.Fragment
import androidx.media3.common.util.UnstableApi
import com.google.android.material.appbar.MaterialToolbar
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import kotlin.math.max
/**
* Displays information about a list of FeedItems.
*/
class ItemPageFragment : Fragment(), Toolbar.OnMenuItemClickListener {
private lateinit var page: View
private lateinit var toolbar: MaterialToolbar
private lateinit var itemFragment: ItemFragment
private var feedItems: LongArray? = null
private var item: FeedItem? = null
private var disposable: Disposable? = null
@UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
super.onCreateView(inflater, container, savedInstanceState)
val binding = FeeditemPageFragmentBinding.inflate(inflater)
Log.d(TAG, "fragment onCreateView")
toolbar = binding.toolbar
toolbar.title = ""
toolbar.inflateMenu(R.menu.feeditem_options)
toolbar.setNavigationOnClickListener { parentFragmentManager.popBackStack() }
toolbar.setOnMenuItemClickListener(this)
feedItems = requireArguments().getLongArray(ARG_FEEDITEMS)
val feedItemPos = max(0.0, requireArguments().getInt(ARG_FEEDITEM_POS).toDouble()).toInt()
page = binding.fragmentView
loadItem(feedItems!![feedItemPos])
val transaction = requireActivity().supportFragmentManager.beginTransaction()
itemFragment = ItemFragment.newInstance(if (feedItems!= null) feedItems!![feedItemPos] else 0L)
transaction.replace(R.id.fragment_view, itemFragment)
transaction.commit()
EventBus.getDefault().register(this)
return binding.root
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putInt(KEY_PAGER_ID, page.id)
}
override fun onDestroyView() {
super.onDestroyView()
EventBus.getDefault().unregister(this)
disposable?.dispose()
}
@UnstableApi private fun loadItem(itemId: Long) {
disposable?.dispose()
disposable = Observable.fromCallable { DBReader.getFeedItem(itemId) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ result: FeedItem? ->
item = result
refreshToolbarState()
}, { obj: Throwable -> obj.printStackTrace() })
}
@UnstableApi fun refreshToolbarState() {
if (item == null) return
if (item!!.hasMedia()) {
FeedItemMenuHandler.onPrepareMenu(toolbar.menu, item)
} else {
// these are already available via button1 and button2
FeedItemMenuHandler.onPrepareMenu(toolbar.menu, item,
R.id.mark_read_item, R.id.visit_website_item)
}
}
@UnstableApi override fun onMenuItemClick(menuItem: MenuItem): Boolean {
when (menuItem.itemId) {
R.id.open_podcast -> {
openPodcast()
return true
}
R.id.share_notes -> {
if (item == null) return false
val bundle = itemFragment.arguments
val notes = bundle?.getString("description", "")
if (!notes.isNullOrEmpty()) {
val shareText = if (Build.VERSION.SDK_INT >= 24) Html.fromHtml(notes, Html.FROM_HTML_MODE_LEGACY).toString()
else Html.fromHtml(notes).toString()
val context = requireContext()
val intent = ShareCompat.IntentBuilder(context)
.setType("text/plain")
.setText(shareText)
.setChooserTitle(R.string.share_notes_label)
.createChooserIntent()
context.startActivity(intent)
}
return true
}
else -> {
if (item == null) return false
return FeedItemMenuHandler.onMenuItemClicked(this, menuItem.itemId, item!!)
}
}
}
@UnstableApi @Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(event: FeedItemEvent) {
for (item in event.items) {
if (this.item != null && this.item!!.id == item.id) {
this.item = item
refreshToolbarState()
return
}
}
}
@UnstableApi private fun openPodcast() {
if (item == null) {
return
}
val fragment: Fragment = FeedItemlistFragment.newInstance(item!!.feedId)
(activity as MainActivity).loadChildFragment(fragment)
}
companion object {
const val TAG: String = "ItemPageFragment"
private const val ARG_FEEDITEMS = "feeditems"
private const val ARG_FEEDITEM_POS = "feeditem_pos"
private const val KEY_PAGER_ID = "pager_id"
/**
* Creates a new instance of an ItemPageFragment.
*
* @param feeditems The IDs of the FeedItems that belong to the same list
* @param feedItemPos The position of the FeedItem that is currently shown
* @return The ItemFragment instance
*/
fun newInstance(feeditems: LongArray?, feedItemPos: Int): ItemPageFragment {
val fragment = ItemPageFragment()
val args = Bundle()
if (feeditems != null) args.putLongArray(ARG_FEEDITEMS, feeditems)
args.putInt(ARG_FEEDITEM_POS, max(0.0, feedItemPos.toDouble()).toInt())
fragment.arguments = args
return fragment
}
}
}

View File

@ -7,13 +7,16 @@ import ac.mdiq.podcini.storage.model.feed.FeedItem
import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.ui.menuhandler.FeedItemMenuHandler
import ac.mdiq.podcini.util.event.FeedItemEvent
import android.os.Build
import android.os.Bundle
import android.text.Html
import android.util.Log
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import androidx.appcompat.widget.Toolbar
import androidx.core.app.ShareCompat
import androidx.fragment.app.Fragment
import androidx.media3.common.util.UnstableApi
import androidx.viewpager2.adapter.FragmentStateAdapter
@ -43,7 +46,6 @@ class ItemPagerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
): View {
super.onCreateView(inflater, container, savedInstanceState)
val binding = FeeditemPagerFragmentBinding.inflate(inflater)
// val layout: View = inflater.inflate(R.layout.feeditem_pager_fragment, container, false)
Log.d(TAG, "fragment onCreateView")
toolbar = binding.toolbar
@ -107,9 +109,8 @@ class ItemPagerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
}
@UnstableApi fun refreshToolbarState() {
if (item == null) {
return
}
if (item == null) return
if (item!!.hasMedia()) {
FeedItemMenuHandler.onPrepareMenu(toolbar.menu, item)
} else {
@ -120,12 +121,33 @@ class ItemPagerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
}
@UnstableApi override fun onMenuItemClick(menuItem: MenuItem): Boolean {
if (menuItem.itemId == R.id.open_podcast) {
openPodcast()
return true
when (menuItem.itemId) {
R.id.open_podcast -> {
openPodcast()
return true
}
R.id.share_notes -> {
if (item == null) return false
DBReader.loadDescriptionOfFeedItem(item!!)
if (!item!!.description.isNullOrEmpty()) {
val shareText = if (Build.VERSION.SDK_INT >= 24) Html.fromHtml(item!!.description,
Html.FROM_HTML_MODE_LEGACY).toString()
else Html.fromHtml(item!!.description).toString()
val context = requireContext()
val intent = ShareCompat.IntentBuilder(context)
.setType("text/plain")
.setText(shareText)
.setChooserTitle(R.string.share_notes_label)
.createChooserIntent()
context.startActivity(intent)
}
return true
}
else -> {
if (item == null) return false
return FeedItemMenuHandler.onMenuItemClicked(this, menuItem.itemId, item!!)
}
}
if (item == null) return false
return FeedItemMenuHandler.onMenuItemClicked(this, menuItem.itemId, item!!)
}
@UnstableApi @Subscribe(threadMode = ThreadMode.MAIN)
@ -149,6 +171,7 @@ class ItemPagerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
private inner class ItemPagerAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {
override fun createFragment(position: Int): Fragment {
Log.d(TAG, "createFragment $position")
return ItemFragment.newInstance(if (feedItems!= null) feedItems!![position] else 0L)
}

View File

@ -68,7 +68,7 @@ open class SwipeActions(dragDirs: Int, private val fragment: Fragment, private v
}
@UnstableApi override fun onSwiped(viewHolder: RecyclerView.ViewHolder, swipeDir: Int) {
if (!actions!!.hasActions()) {
if (actions != null && !actions!!.hasActions()) {
//open settings dialog if no prefs are set
SwipeActionsDialog(fragment.requireContext(), tag).show(object : SwipeActionsDialog.Callback {
override fun onCall() {
@ -80,7 +80,7 @@ open class SwipeActions(dragDirs: Int, private val fragment: Fragment, private v
val item = (viewHolder as EpisodeItemViewHolder).feedItem
if (item != null && filter != null)
if (actions != null && item != null && filter != null)
(if (swipeDir == ItemTouchHelper.RIGHT) actions!!.right else actions!!.left)?.performAction(item, fragment, filter!!)
}
@ -91,7 +91,7 @@ open class SwipeActions(dragDirs: Int, private val fragment: Fragment, private v
var dx = dx
val right: SwipeAction
val left: SwipeAction
if (actions!!.hasActions()) {
if (actions != null && actions!!.hasActions()) {
right = actions!!.right!!
left = actions!!.left!!
} else {
@ -182,7 +182,7 @@ open class SwipeActions(dragDirs: Int, private val fragment: Fragment, private v
}
fun startDrag(holder: EpisodeItemViewHolder?) {
itemTouchHelper.startDrag(holder!!)
if (holder != null) itemTouchHelper.startDrag(holder)
}
class Actions(prefs: String?) {

View File

@ -1,29 +1,30 @@
package ac.mdiq.podcini.ui.menuhandler
import ac.mdiq.podcini.ui.activity.MainActivity
import android.os.Handler
import android.util.Log
import android.view.KeyEvent
import android.view.Menu
import androidx.fragment.app.Fragment
import androidx.media3.common.util.UnstableApi
import com.google.android.material.snackbar.Snackbar
import ac.mdiq.podcini.R
import ac.mdiq.podcini.net.sync.SynchronizationSettings
import ac.mdiq.podcini.net.sync.model.EpisodeAction
import ac.mdiq.podcini.net.sync.queue.SynchronizationQueueSink
import ac.mdiq.podcini.preferences.PlaybackPreferences
import ac.mdiq.podcini.receiver.MediaButtonReceiver
import ac.mdiq.podcini.service.playback.PlaybackServiceInterface
import ac.mdiq.podcini.storage.DBWriter
import ac.mdiq.podcini.net.sync.SynchronizationSettings
import ac.mdiq.podcini.net.sync.queue.SynchronizationQueueSink
import ac.mdiq.podcini.util.*
import ac.mdiq.podcini.ui.dialog.ShareDialog
import ac.mdiq.podcini.storage.model.feed.FeedItem
import ac.mdiq.podcini.storage.model.feed.FeedMedia
import ac.mdiq.podcini.net.sync.model.EpisodeAction
import ac.mdiq.podcini.ui.activity.MainActivity
import ac.mdiq.podcini.ui.dialog.ShareDialog
import ac.mdiq.podcini.ui.view.LocalDeleteModal
import ac.mdiq.podcini.util.*
import android.os.Handler
import android.util.Log
import android.view.KeyEvent
import android.view.Menu
import androidx.annotation.OptIn
import androidx.fragment.app.Fragment
import androidx.media3.common.util.UnstableApi
import com.google.android.material.snackbar.Snackbar
import kotlin.math.ceil
/**
* Handles interactions with the FeedItemMenu.
*/
@ -57,7 +58,6 @@ object FeedItemMenuHandler {
setItemVisibility(menu, R.id.visit_website_item, !(selectedItem.feed?.isLocalFeed?:false)
&& ShareUtils.hasLinkToShare(selectedItem))
setItemVisibility(menu, R.id.share_item, !(selectedItem.feed?.isLocalFeed?:false))
// setItemVisibility(menu, R.id.remove_inbox_item, selectedItem.isNew)
setItemVisibility(menu, R.id.mark_read_item, !selectedItem.isPlayed())
setItemVisibility(menu, R.id.mark_unread_item, selectedItem.isPlayed())
setItemVisibility(menu, R.id.reset_position, hasMedia && selectedItem.media?.getPosition() != 0)
@ -144,9 +144,6 @@ object FeedItemMenuHandler {
selectedItem.media!!.id)
}
}
// R.id.remove_inbox_item -> {
// removeNewFlagWithUndo(fragment, selectedItem)
// }
R.id.mark_read_item -> {
selectedItem.setPlayed(true)
DBWriter.markItemPlayed(selectedItem, FeedItem.PLAYED, true)
@ -258,7 +255,7 @@ object FeedItemMenuHandler {
if (showSnackbar) {
(fragment.activity as MainActivity).showSnackbarAbovePlayer(
playStateStringRes, duration)
.setAction(fragment.getString(R.string.undo)) { v ->
.setAction(fragment.getString(R.string.undo)) {
DBWriter.markItemPlayed(item.playState, item.id)
// don't forget to cancel the thing that's going to remove the media
h.removeCallbacks(r)

View File

@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/feeditem_page_fragment"
android:fitsSystemWindows="true"
android:orientation="vertical">
<com.google.android.material.appbar.MaterialToolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="?attr/actionBarSize"
android:theme="?attr/actionBarTheme"
app:navigationContentDescription="@string/toolbar_back_button_content_description"
app:navigationIcon="?homeAsUpIndicator" />
<androidx.fragment.app.FragmentContainerView
android:id="@+id/fragment_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foreground="?android:windowContentOverlay"
/>
</LinearLayout>

View File

@ -28,19 +28,6 @@
android:layout_width="148dp"
android:layout_height="match_parent" />
<!-- <ImageButton-->
<!-- android:id="@+id/butShowInfo"-->
<!-- android:layout_width="40dp"-->
<!-- android:layout_height="40dp"-->
<!-- android:background="?attr/selectableItemBackground"-->
<!-- android:contentDescription="@string/show_info_label"-->
<!-- android:layout_marginLeft="-8dp"-->
<!-- android:layout_marginStart="-8dp"-->
<!-- android:scaleType="fitXY"-->
<!-- android:padding="8dp"-->
<!-- app:srcCompat="@drawable/ic_info_white"-->
<!-- tools:visibility="visible" />-->
<ImageButton
android:id="@+id/butFilter"
android:layout_width="40dp"

View File

@ -56,7 +56,11 @@
android:menuCategory="container"
android:title="@string/share_label">
</item>
<item
android:id="@+id/share_notes"
android:menuCategory="container"
android:title="@string/share_notes_label">
</item>
<item
android:id="@+id/open_podcast"
custom:showAsAction="collapseActionView"

View File

@ -166,8 +166,6 @@
<!-- Actions on feeds -->
<string name="multi_select_mark_played_confirmation">Please confirm that you want to mark all selected items as played.</string>
<string name="multi_select_mark_unplayed_confirmation">Please confirm that you want to mark all selected items as unplayed.</string>
<string name="show_info_label">Show information</string>
@ -177,6 +175,7 @@
<string name="remove_feed_label">Remove podcast</string>
<string name="share_label">Share</string>
<string name="share_file_label">Share file</string>
<string name="share_notes_label">Share notes</string>
<string name="feed_delete_confirmation_msg">Please confirm that you want to delete the podcast \"%1$s\", ALL its episodes (including downloaded episodes), and its statistics.</string>
<string name="feed_delete_confirmation_msg_batch">Please confirm that you want to remove the selected podcasts, ALL their episodes (including downloaded episodes), and its statistics.</string>
<string name="feed_delete_confirmation_local_msg">Please confirm that you want to remove the podcast \"%1$s\" and its statistics. The files in the local source folder will not be deleted.</string>

View File

@ -85,4 +85,12 @@
* corrected action icons for themes
* revealed info bar in Downloads view
* revealed info bar in Subscriptions view
* reset tags list in Subscriptions when new tag is added
* reset tags list in Subscriptions when new tag is added
## 4.2.7
* disabled drag actions when in multi-select mode (fixed crash bug)
* renewed PodcastIndex API keys
* added share notes menu option in episode view
* press on title area of an episode now opens the episode info faster and more economically - without horizontal swipe
* press on the icon of an episode opens the episode info the original way - with horizontal swipe

View File

@ -0,0 +1,8 @@
Version 4.2.7 brings several changes:
* disabled drag actions when in multi-select mode (fixed crash bug)
* renewed PodcastIndex API keys
* added share notes menu option in episode view
* press on title area of an episode now opens the episode info faster and more economically - without horizontal swipe
* press on the icon of an episode opens the episode info the original way - with horizontal swipe