From d5b5734712d962dc9f881875deeda9e2e42fe43b Mon Sep 17 00:00:00 2001 From: Xilin Jia <6257601+XilinJia@users.noreply.github.com> Date: Wed, 20 Mar 2024 13:59:05 +0000 Subject: [PATCH] notes share and faster episode open --- app/build.gradle | 10 +- .../ui/activity/VideoplayerActivity.kt | 255 +++++++++--------- .../ui/adapter/EpisodeItemListAdapter.kt | 3 +- .../ui/adapter/HorizontalItemListAdapter.kt | 3 +- .../ui/adapter/QueueRecyclerAdapter.kt | 6 +- .../podcini/ui/fragment/FeedInfoFragment.kt | 39 ++- .../ui/fragment/FeedItemlistFragment.kt | 129 +++++---- .../ui/fragment/ItemDescriptionFragment.kt | 1 - .../mdiq/podcini/ui/fragment/ItemFragment.kt | 44 +-- .../podcini/ui/fragment/ItemPageFragment.kt | 177 ++++++++++++ .../podcini/ui/fragment/ItemPagerFragment.kt | 41 ++- .../ui/fragment/swipeactions/SwipeActions.kt | 8 +- .../ui/menuhandler/FeedItemMenuHandler.kt | 33 ++- .../res/layout/feeditem_page_fragment.xml | 27 ++ .../main/res/layout/feeditemlist_header.xml | 13 - app/src/main/res/menu/feeditem_options.xml | 6 +- app/src/main/res/values/strings.xml | 3 +- changelog.md | 10 +- .../android/en-US/changelogs/3020110.txt | 8 + 19 files changed, 530 insertions(+), 286 deletions(-) create mode 100644 app/src/main/java/ac/mdiq/podcini/ui/fragment/ItemPageFragment.kt create mode 100644 app/src/main/res/layout/feeditem_page_fragment.xml create mode 100644 fastlane/metadata/android/en-US/changelogs/3020110.txt diff --git a/app/build.gradle b/app/build.gradle index 4d55a4a7..e833e22e 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -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' diff --git a/app/src/main/java/ac/mdiq/podcini/ui/activity/VideoplayerActivity.kt b/app/src/main/java/ac/mdiq/podcini/ui/activity/VideoplayerActivity.kt index b6526351..ea9b382a 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/activity/VideoplayerActivity.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/activity/VideoplayerActivity.kt @@ -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? { diff --git a/app/src/main/java/ac/mdiq/podcini/ui/adapter/EpisodeItemListAdapter.kt b/app/src/main/java/ac/mdiq/podcini/ui/adapter/EpisodeItemListAdapter.kt index 6136d816..0849996e 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/adapter/EpisodeItemListAdapter.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/adapter/EpisodeItemListAdapter.kt @@ -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) } diff --git a/app/src/main/java/ac/mdiq/podcini/ui/adapter/HorizontalItemListAdapter.kt b/app/src/main/java/ac/mdiq/podcini/ui/adapter/HorizontalItemListAdapter.kt index 183c80d8..d534aacb 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/adapter/HorizontalItemListAdapter.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/adapter/HorizontalItemListAdapter.kt @@ -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)) } } } diff --git a/app/src/main/java/ac/mdiq/podcini/ui/adapter/QueueRecyclerAdapter.kt b/app/src/main/java/ac/mdiq/podcini/ui/adapter/QueueRecyclerAdapter.kt index c99c5852..eb473a7d 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/adapter/QueueRecyclerAdapter.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/adapter/QueueRecyclerAdapter.kt @@ -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") diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/FeedInfoFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/FeedInfoFragment.kt index 5871a084..5541ce6d 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/FeedInfoFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/FeedInfoFragment.kt @@ -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?) { diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/FeedItemlistFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/FeedItemlistFragment.kt index 6a5ab024..430cf898 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/FeedItemlistFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/FeedItemlistFragment.kt @@ -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 -> {} } } diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/ItemDescriptionFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/ItemDescriptionFragment.kt index 514a1f16..a3362364 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/ItemDescriptionFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/ItemDescriptionFragment.kt @@ -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 diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/ItemFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/ItemFragment.kt index ead2cdd4..e5bfd60f 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/ItemFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/ItemFragment.kt @@ -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 } diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/ItemPageFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/ItemPageFragment.kt new file mode 100644 index 00000000..e2732c24 --- /dev/null +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/ItemPageFragment.kt @@ -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 + } + } +} diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/ItemPagerFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/ItemPagerFragment.kt index f6cd8f99..a8c1104c 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/ItemPagerFragment.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/ItemPagerFragment.kt @@ -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) } diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/swipeactions/SwipeActions.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/swipeactions/SwipeActions.kt index 8294c45e..1c6af85c 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/swipeactions/SwipeActions.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/swipeactions/SwipeActions.kt @@ -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?) { diff --git a/app/src/main/java/ac/mdiq/podcini/ui/menuhandler/FeedItemMenuHandler.kt b/app/src/main/java/ac/mdiq/podcini/ui/menuhandler/FeedItemMenuHandler.kt index 68184f6c..2ba47bf3 100644 --- a/app/src/main/java/ac/mdiq/podcini/ui/menuhandler/FeedItemMenuHandler.kt +++ b/app/src/main/java/ac/mdiq/podcini/ui/menuhandler/FeedItemMenuHandler.kt @@ -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) diff --git a/app/src/main/res/layout/feeditem_page_fragment.xml b/app/src/main/res/layout/feeditem_page_fragment.xml new file mode 100644 index 00000000..1a0c6c89 --- /dev/null +++ b/app/src/main/res/layout/feeditem_page_fragment.xml @@ -0,0 +1,27 @@ + + + + + + + + diff --git a/app/src/main/res/layout/feeditemlist_header.xml b/app/src/main/res/layout/feeditemlist_header.xml index 0ea8f388..01717b8e 100644 --- a/app/src/main/res/layout/feeditemlist_header.xml +++ b/app/src/main/res/layout/feeditemlist_header.xml @@ -28,19 +28,6 @@ android:layout_width="148dp" android:layout_height="match_parent" /> - - - - - - - - - - - - - - + + - - Please confirm that you want to mark all selected items as played. Please confirm that you want to mark all selected items as unplayed. Show information @@ -177,6 +175,7 @@ Remove podcast Share Share file + Share notes Please confirm that you want to delete the podcast \"%1$s\", ALL its episodes (including downloaded episodes), and its statistics. Please confirm that you want to remove the selected podcasts, ALL their episodes (including downloaded episodes), and its statistics. 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. diff --git a/changelog.md b/changelog.md index 2b5f55b9..7fddde42 100644 --- a/changelog.md +++ b/changelog.md @@ -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 \ No newline at end of file +* 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 \ No newline at end of file diff --git a/fastlane/metadata/android/en-US/changelogs/3020110.txt b/fastlane/metadata/android/en-US/changelogs/3020110.txt new file mode 100644 index 00000000..90007eda --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/3020110.txt @@ -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