From 5cb3b20b00c81a272f91297fefee4e67d547bb90 Mon Sep 17 00:00:00 2001
From: Xilin Jia <6257601+XilinJia@users.noreply.github.com>
Date: Sun, 24 Mar 2024 20:36:12 +0000
Subject: [PATCH] 4.3.3 release
---
README.md | 4 +-
app/build.gradle | 4 +-
.../podcini/ui/activity/BugReportActivity.kt | 2 +-
.../mdiq/podcini/ui/activity/MainActivity.kt | 22 +-
.../ui/activity/VideoplayerActivity.kt | 7 -
.../ui/activity/WidgetConfigActivity.kt | 14 +-
.../podcini/ui/dialog/ItemFilterDialog.kt | 13 +-
.../ui/fragment/AudioPlayerFragment.kt | 160 +++++-----
.../ui/fragment/EpisodeInfoFragment.kt | 29 +-
.../ui/fragment/ItemDescriptionFragment.kt | 173 ----------
...erFragment.kt => PlayerDetailsFragment.kt} | 295 ++++++++++++------
.../ui/menuhandler/FeedItemMenuHandler.kt | 5 +-
.../podcini/ui/view/NestedScrollableHost.kt | 175 -----------
.../mdiq/podcini/ui/view/ShownotesWebView.kt | 2 +
.../podcini/ui/widget/WidgetUpdaterWorker.kt | 2 +-
.../res/drawable/baseline_audiotrack_24.xml | 8 +
.../main/res/layout/audioplayer_fragment.xml | 12 +-
app/src/main/res/layout/cover_fragment.xml | 172 ----------
app/src/main/res/layout/echo_activity.xml | 135 --------
.../main/res/layout/episode_info_fragment.xml | 13 +-
.../res/layout/item_description_fragment.xml | 17 -
.../res/layout/player_details_fragment.xml | 135 ++++++++
app/src/main/res/layout/player_widget.xml | 32 +-
app/src/main/res/menu/feeditem_options.xml | 9 +-
.../main/res/menu/feeditemlist_context.xml | 1 +
app/src/main/res/menu/mediaplayer.xml | 22 +-
app/src/main/res/values/strings.xml | 2 +-
app/src/main/res/xml/player_widget_info.xml | 21 +-
changelog.md | 14 +-
.../android/en-US/changelogs/3020114.txt | 12 +
30 files changed, 543 insertions(+), 969 deletions(-)
delete mode 100644 app/src/main/java/ac/mdiq/podcini/ui/fragment/ItemDescriptionFragment.kt
rename app/src/main/java/ac/mdiq/podcini/ui/fragment/{CoverFragment.kt => PlayerDetailsFragment.kt} (62%)
delete mode 100644 app/src/main/java/ac/mdiq/podcini/ui/view/NestedScrollableHost.kt
create mode 100644 app/src/main/res/drawable/baseline_audiotrack_24.xml
delete mode 100644 app/src/main/res/layout/cover_fragment.xml
delete mode 100644 app/src/main/res/layout/echo_activity.xml
delete mode 100644 app/src/main/res/layout/item_description_fragment.xml
create mode 100644 app/src/main/res/layout/player_details_fragment.xml
create mode 100644 fastlane/metadata/android/en-US/changelogs/3020114.txt
diff --git a/README.md b/README.md
index a595ba3d..fe0279f2 100644
--- a/README.md
+++ b/README.md
@@ -3,7 +3,7 @@
Podcini is an open source podcast manager/player project.
-This app is a fork of [AntennaPod]() as of Feb 5 2024.
+This project is a fork of [AntennaPod]() as of Feb 5 2024.
Compared to AntennaPod this project:
1. Migrated the media player to `androidx.media3`,
@@ -11,7 +11,7 @@ Compared to AntennaPod this project:
3. Relies on the most recent dependencies,
4. Is __purely__ Kotlin based,
4. Targets Android 14,
-5. Aims to be as effective as possible.
+5. Aims to improve efficiency and provide more user-friendly features
## Version 4
diff --git a/app/build.gradle b/app/build.gradle
index e2e0133d..2b1c21a2 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 3020113
- versionName "4.3.2"
+ versionCode 3020114
+ versionName "4.3.3"
def commit = ""
try {
diff --git a/app/src/main/java/ac/mdiq/podcini/ui/activity/BugReportActivity.kt b/app/src/main/java/ac/mdiq/podcini/ui/activity/BugReportActivity.kt
index 6f618acc..1405411b 100644
--- a/app/src/main/java/ac/mdiq/podcini/ui/activity/BugReportActivity.kt
+++ b/app/src/main/java/ac/mdiq/podcini/ui/activity/BugReportActivity.kt
@@ -37,7 +37,7 @@ class BugReportActivity : AppCompatActivity() {
super.onCreate(savedInstanceState)
supportActionBar!!.setDisplayShowHomeEnabled(true)
_binding = BugReportBinding.inflate(layoutInflater)
- setContentView(R.layout.bug_report)
+ setContentView(binding.root)
var stacktrace = "No crash report recorded"
try {
diff --git a/app/src/main/java/ac/mdiq/podcini/ui/activity/MainActivity.kt b/app/src/main/java/ac/mdiq/podcini/ui/activity/MainActivity.kt
index de2ea2a1..6e9ca9fa 100644
--- a/app/src/main/java/ac/mdiq/podcini/ui/activity/MainActivity.kt
+++ b/app/src/main/java/ac/mdiq/podcini/ui/activity/MainActivity.kt
@@ -104,7 +104,8 @@ class MainActivity : CastEnabledActivity() {
WindowCompat.setDecorFitsSystemWindows(window, false)
super.onCreate(savedInstanceState)
_binding = MainActivityBinding.inflate(layoutInflater)
- setContentView(R.layout.main_activity)
+// setContentView(R.layout.main_activity)
+ setContentView(binding.root)
recycledViewPool.setMaxRecycledViews(R.id.view_type_episode_item, 25)
dummyView = object : View(this) {}
@@ -162,6 +163,7 @@ class MainActivity : CastEnabledActivity() {
checkFirstLaunch()
this.bottomSheet = BottomSheetBehavior.from(audioPlayerFragmentView) as LockableBottomSheetBehavior<*>
this.bottomSheet.isHideable = false
+ this.bottomSheet.isDraggable = false
this.bottomSheet.setBottomSheetCallback(bottomSheetCallback)
restartUpdateAlarm(this, false)
@@ -272,18 +274,22 @@ class MainActivity : CastEnabledActivity() {
private val bottomSheetCallback: BottomSheetCallback = @UnstableApi object : BottomSheetCallback() {
override fun onStateChanged(view: View, state: Int) {
- if (state == BottomSheetBehavior.STATE_COLLAPSED) {
- onSlide(view,0.0f)
- } else if (state == BottomSheetBehavior.STATE_EXPANDED) {
- onSlide(view, 1.0f)
+ when (state) {
+ BottomSheetBehavior.STATE_COLLAPSED -> {
+ onSlide(view,0.0f)
+ }
+ BottomSheetBehavior.STATE_EXPANDED -> {
+ onSlide(view, 1.0f)
+ }
+ else -> {}
}
}
override fun onSlide(view: View, slideOffset: Float) {
val audioPlayer = supportFragmentManager.findFragmentByTag(AudioPlayerFragment.TAG) as? AudioPlayerFragment ?: return
- if (slideOffset == 0.0f) { //STATE_COLLAPSED
- audioPlayer.scrollToPage(AudioPlayerFragment.FIRST_PAGE)
- }
+// if (slideOffset == 0.0f) { //STATE_COLLAPSED
+// audioPlayer.scrollToPage(AudioPlayerFragment.FIRST_PAGE)
+// }
audioPlayer.fadePlayerToToolbar(slideOffset)
}
}
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 3f5c5c26..5e4d1c1a 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
@@ -101,7 +101,6 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
controller = newPlaybackController()
controller!!.init()
loadMediaInfo()
-// EventBus.getDefault().register(this)
}
@UnstableApi
@@ -124,16 +123,10 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
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)) {
diff --git a/app/src/main/java/ac/mdiq/podcini/ui/activity/WidgetConfigActivity.kt b/app/src/main/java/ac/mdiq/podcini/ui/activity/WidgetConfigActivity.kt
index 84bb92f6..8824eda7 100644
--- a/app/src/main/java/ac/mdiq/podcini/ui/activity/WidgetConfigActivity.kt
+++ b/app/src/main/java/ac/mdiq/podcini/ui/activity/WidgetConfigActivity.kt
@@ -19,13 +19,15 @@ import ac.mdiq.podcini.receiver.PlayerWidget
import ac.mdiq.podcini.ui.widget.WidgetUpdaterWorker
class WidgetConfigActivity : AppCompatActivity() {
+
private var _binding: ActivityWidgetConfigBinding? = null
private val binding get() = _binding!!
+ private var _wpBinding: PlayerWidgetBinding? = null
+ private val wpBinding get() = _wpBinding!!
private var appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID
private lateinit var widgetPreview: View
- private lateinit var wpBinding: PlayerWidgetBinding
private lateinit var opacitySeekBar: SeekBar
private lateinit var opacityTextView: TextView
private lateinit var ckPlaybackSpeed: CheckBox
@@ -37,7 +39,7 @@ class WidgetConfigActivity : AppCompatActivity() {
setTheme(getTheme(this))
super.onCreate(savedInstanceState)
_binding = ActivityWidgetConfigBinding.inflate(layoutInflater)
- setContentView(R.layout.activity_widget_config)
+ setContentView(binding.root)
val configIntent = intent
val extras = configIntent.extras
@@ -55,10 +57,10 @@ class WidgetConfigActivity : AppCompatActivity() {
opacityTextView = binding.widgetOpacityTextView
opacitySeekBar = binding.widgetOpacitySeekBar
- widgetPreview = binding.widgetConfigPreview.widgetLayout
- wpBinding = PlayerWidgetBinding.bind(widgetPreview)
+ widgetPreview = binding.widgetConfigPreview.playerWidget
+ _wpBinding = PlayerWidgetBinding.bind(widgetPreview)
- binding.butConfirm.setOnClickListener { confirmCreateWidget() }
+ binding.butConfirm.setOnClickListener{ confirmCreateWidget() }
opacitySeekBar.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar, i: Int, b: Boolean) {
opacityTextView.text = seekBar.progress.toString() + "%"
@@ -96,7 +98,9 @@ class WidgetConfigActivity : AppCompatActivity() {
override fun onDestroy() {
super.onDestroy()
_binding = null
+ _wpBinding = null
}
+
private fun setInitialState() {
val prefs = getSharedPreferences(PlayerWidget.PREFS_NAME, MODE_PRIVATE)
ckPlaybackSpeed.isChecked = prefs.getBoolean(PlayerWidget.KEY_WIDGET_PLAYBACK_SPEED + appWidgetId, false)
diff --git a/app/src/main/java/ac/mdiq/podcini/ui/dialog/ItemFilterDialog.kt b/app/src/main/java/ac/mdiq/podcini/ui/dialog/ItemFilterDialog.kt
index 1cca0ab1..2ebd2243 100644
--- a/app/src/main/java/ac/mdiq/podcini/ui/dialog/ItemFilterDialog.kt
+++ b/app/src/main/java/ac/mdiq/podcini/ui/dialog/ItemFilterDialog.kt
@@ -1,8 +1,14 @@
package ac.mdiq.podcini.ui.dialog
+import ac.mdiq.podcini.R
+import ac.mdiq.podcini.databinding.FilterDialogBinding
+import ac.mdiq.podcini.databinding.FilterDialogRowBinding
+import ac.mdiq.podcini.feed.FeedItemFilterGroup
+import ac.mdiq.podcini.storage.model.feed.FeedItemFilter
import android.app.Dialog
import android.content.DialogInterface
import android.os.Bundle
+import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
@@ -13,13 +19,6 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.bottomsheet.BottomSheetDialog
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
import com.google.android.material.button.MaterialButtonToggleGroup
-import ac.mdiq.podcini.R
-import ac.mdiq.podcini.databinding.FilterDialogBinding
-import ac.mdiq.podcini.feed.FeedItemFilterGroup
-import ac.mdiq.podcini.databinding.FilterDialogRowBinding
-import ac.mdiq.podcini.storage.model.feed.FeedItemFilter
-import ac.mdiq.podcini.ui.fragment.ItemDescriptionFragment
-import android.util.Log
abstract class ItemFilterDialog : BottomSheetDialogFragment() {
private lateinit var rows: LinearLayout
diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt
index 9dfe5f2f..5d965994 100644
--- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt
+++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/AudioPlayerFragment.kt
@@ -1,10 +1,38 @@
package ac.mdiq.podcini.ui.fragment
+import ac.mdiq.podcini.R
+import ac.mdiq.podcini.databinding.AudioplayerFragmentBinding
+import ac.mdiq.podcini.feed.util.PlaybackSpeedUtils
+import ac.mdiq.podcini.playback.PlaybackController
+import ac.mdiq.podcini.playback.cast.CastEnabledActivity
+import ac.mdiq.podcini.playback.event.*
+import ac.mdiq.podcini.preferences.UserPreferences
+import ac.mdiq.podcini.receiver.MediaButtonReceiver
+import ac.mdiq.podcini.storage.model.feed.Chapter
+import ac.mdiq.podcini.storage.model.feed.FeedItem
+import ac.mdiq.podcini.storage.model.feed.FeedMedia
+import ac.mdiq.podcini.storage.model.playback.Playable
import ac.mdiq.podcini.ui.activity.MainActivity
+import ac.mdiq.podcini.ui.common.PlaybackSpeedIndicatorView
+import ac.mdiq.podcini.ui.dialog.MediaPlayerErrorDialog
+import ac.mdiq.podcini.ui.dialog.SkipPreferenceDialog
+import ac.mdiq.podcini.ui.dialog.SleepTimerDialog
+import ac.mdiq.podcini.ui.dialog.VariableSpeedDialog
+import ac.mdiq.podcini.ui.menuhandler.FeedItemMenuHandler
+import ac.mdiq.podcini.ui.view.ChapterSeekBar
+import ac.mdiq.podcini.ui.view.PlayButton
+import ac.mdiq.podcini.util.ChapterUtils
+import ac.mdiq.podcini.util.Converter
+import ac.mdiq.podcini.util.TimeSpeedConverter
+import ac.mdiq.podcini.util.event.FavoritesEvent
+import ac.mdiq.podcini.util.event.PlayerErrorEvent
+import ac.mdiq.podcini.util.event.UnreadItemsUpdateEvent
import android.annotation.SuppressLint
import android.app.Activity
import android.content.Intent
+import android.os.Build
import android.os.Bundle
+import android.text.Html
import android.util.Log
import android.view.*
import android.widget.ImageButton
@@ -13,40 +41,13 @@ import android.widget.SeekBar
import android.widget.TextView
import androidx.appcompat.widget.Toolbar
import androidx.cardview.widget.CardView
+import androidx.core.app.ShareCompat
import androidx.fragment.app.Fragment
import androidx.interpolator.view.animation.FastOutSlowInInterpolator
import androidx.media3.common.util.UnstableApi
-import androidx.viewpager2.adapter.FragmentStateAdapter
-import androidx.viewpager2.widget.ViewPager2
import com.google.android.material.appbar.MaterialToolbar
import com.google.android.material.bottomsheet.BottomSheetBehavior
import com.google.android.material.elevation.SurfaceColors
-import ac.mdiq.podcini.R
-import ac.mdiq.podcini.databinding.AudioplayerFragmentBinding
-import ac.mdiq.podcini.feed.util.PlaybackSpeedUtils
-import ac.mdiq.podcini.receiver.MediaButtonReceiver
-import ac.mdiq.podcini.util.ChapterUtils
-import ac.mdiq.podcini.util.TimeSpeedConverter
-import ac.mdiq.podcini.playback.PlaybackController
-import ac.mdiq.podcini.ui.dialog.MediaPlayerErrorDialog
-import ac.mdiq.podcini.ui.dialog.SkipPreferenceDialog
-import ac.mdiq.podcini.ui.dialog.SleepTimerDialog
-import ac.mdiq.podcini.ui.dialog.VariableSpeedDialog
-import ac.mdiq.podcini.util.event.FavoritesEvent
-import ac.mdiq.podcini.ui.menuhandler.FeedItemMenuHandler
-import ac.mdiq.podcini.storage.model.feed.Chapter
-import ac.mdiq.podcini.storage.model.feed.FeedItem
-import ac.mdiq.podcini.storage.model.feed.FeedMedia
-import ac.mdiq.podcini.storage.model.playback.Playable
-import ac.mdiq.podcini.playback.cast.CastEnabledActivity
-import ac.mdiq.podcini.playback.event.*
-import ac.mdiq.podcini.preferences.UserPreferences
-import ac.mdiq.podcini.ui.common.PlaybackSpeedIndicatorView
-import ac.mdiq.podcini.ui.view.ChapterSeekBar
-import ac.mdiq.podcini.ui.view.PlayButton
-import ac.mdiq.podcini.util.Converter
-import ac.mdiq.podcini.util.event.PlayerErrorEvent
-import ac.mdiq.podcini.util.event.UnreadItemsUpdateEvent
import io.reactivex.Maybe
import io.reactivex.MaybeEmitter
import io.reactivex.android.schedulers.AndroidSchedulers
@@ -72,7 +73,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
lateinit var txtvPlaybackSpeed: TextView
private lateinit var episodeTitle: TextView
- private lateinit var pager: ViewPager2
+ private lateinit var itemDesrView: View
private lateinit var txtvPosition: TextView
private lateinit var txtvLength: TextView
private lateinit var sbPosition: ChapterSeekBar
@@ -110,7 +111,10 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
toolbar = binding.toolbar
toolbar.title = ""
toolbar.setNavigationOnClickListener {
- (activity as MainActivity).bottomSheet.setState(BottomSheetBehavior.STATE_EXPANDED)
+ val bottomSheet = (activity as MainActivity).bottomSheet
+ if (bottomSheet.state == BottomSheetBehavior.STATE_EXPANDED)
+ bottomSheet.state = BottomSheetBehavior.STATE_COLLAPSED
+ else bottomSheet.state = BottomSheetBehavior.STATE_EXPANDED
}
toolbar.setOnMenuItemClickListener(this)
@@ -123,6 +127,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
playerFragment.setBackgroundColor(
SurfaceColors.getColorForElevation(requireContext(), 8 * resources.displayMetrics.density))
+ itemDesrView = binding.itemDescription
episodeTitle = binding.titleView
butPlaybackSpeed = binding.butPlaybackSpeed
txtvPlaybackSpeed = binding.txtvPlaybackSpeed
@@ -146,20 +151,10 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
}
sbPosition.setOnSeekBarChangeListener(this)
- pager = binding.pager
- pager.adapter = AudioPlayerPagerAdapter(this@AudioPlayerFragment)
- // Required for getChildAt(int) in ViewPagerBottomSheetBehavior to return the correct page
- pager.offscreenPageLimit = NUM_CONTENT_FRAGMENTS
- pager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
- override fun onPageSelected(position: Int) {
- pager.post {
- if (activity != null) {
- // By the time this is posted, the activity might be closed again.
- (activity as MainActivity).bottomSheet.updateScrollingChild()
- }
- }
- }
- })
+ val fm = requireActivity().supportFragmentManager
+ val transaction = fm.beginTransaction()
+ val itemDescFrag = PlayerDetailsFragment()
+ transaction.replace(R.id.itemDescription, itemDescFrag).commit()
controller = newPlaybackController()
controller?.init()
@@ -474,26 +469,45 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
(activity as? CastEnabledActivity)?.requestCastButton(toolbar.menu)
}
- override fun onMenuItemClick(item: MenuItem): Boolean {
+ override fun onMenuItemClick(menuItem: MenuItem): Boolean {
val media: Playable = controller?.getMedia() ?: return false
- val feedItem: FeedItem? = if ((media is FeedMedia)) media.item else null
- if (feedItem != null && FeedItemMenuHandler.onMenuItemClicked(this, item.itemId, feedItem)) {
+ val feedItem: FeedItem? = if (media is FeedMedia) media.item else null
+ if (feedItem != null && FeedItemMenuHandler.onMenuItemClicked(this, menuItem.itemId, feedItem)) {
return true
}
- val itemId = item.itemId
- if (itemId == R.id.disable_sleeptimer_item || itemId == R.id.set_sleeptimer_item) {
- SleepTimerDialog().show(childFragmentManager, "SleepTimerDialog")
- return true
- } else if (itemId == R.id.open_feed_item) {
- if (feedItem != null) {
- val intent: Intent = MainActivity.getIntentToOpenFeed(requireContext(), feedItem.feedId)
- startActivity(intent)
+ val itemId = menuItem.itemId
+ when (itemId) {
+ R.id.disable_sleeptimer_item, R.id.set_sleeptimer_item -> {
+ SleepTimerDialog().show(childFragmentManager, "SleepTimerDialog")
+ return true
}
- return true
+ R.id.open_feed_item -> {
+ if (feedItem != null) {
+ val intent: Intent = MainActivity.getIntentToOpenFeed(requireContext(), feedItem.feedId)
+ startActivity(intent)
+ }
+ return true
+ }
+ R.id.share_notes -> {
+ if (feedItem == null) return false
+ val notes = feedItem.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 -> return false
}
- return false
}
fun fadePlayerToToolbar(slideOffset: Float) {
@@ -506,39 +520,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
toolbar.visibility = if (toolbarFadeProgress < 0.01f) View.INVISIBLE else View.VISIBLE
}
- private class AudioPlayerPagerAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {
- override fun createFragment(position: Int): Fragment {
- Log.d(TAG, "getItem($position)")
-
- return when (position) {
- FIRST_PAGE -> ItemDescriptionFragment()
- SECOND_PAGE -> CoverFragment()
- else -> ItemDescriptionFragment()
- }
- }
-
- override fun getItemCount(): Int {
- return NUM_CONTENT_FRAGMENTS
- }
- companion object {
- private const val TAG = "AudioPlayerPagerAdapter"
- }
- }
-
- @JvmOverloads
- fun scrollToPage(page: Int, smoothScroll: Boolean = false) {
- pager.setCurrentItem(page, smoothScroll)
-
- val visibleChild = childFragmentManager.findFragmentByTag("f$FIRST_PAGE")
- if (visibleChild is ItemDescriptionFragment) {
- visibleChild.scrollToTop()
- }
- }
-
companion object {
const val TAG: String = "AudioPlayerFragment"
- const val FIRST_PAGE: Int = 0
- const val SECOND_PAGE: Int = 1
- private const val NUM_CONTENT_FRAGMENTS = 2
}
}
diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/EpisodeInfoFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/EpisodeInfoFragment.kt
index 9421e5d2..34929168 100644
--- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/EpisodeInfoFragment.kt
+++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/EpisodeInfoFragment.kt
@@ -220,10 +220,6 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
@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 notes = item!!.description
@@ -279,11 +275,11 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
return
}
if (item!!.hasMedia()) {
- FeedItemMenuHandler.onPrepareMenu(toolbar.menu, item)
+ FeedItemMenuHandler.onPrepareMenu(toolbar.menu, item, R.id.open_podcast)
} else {
// these are already available via button1 and button2
FeedItemMenuHandler.onPrepareMenu(toolbar.menu, item,
- R.id.mark_read_item, R.id.visit_website_item)
+ R.id.open_podcast, R.id.mark_read_item, R.id.visit_website_item)
}
if (item!!.feed != null) txtvPodcast.text = item!!.feed!!.title
txtvTitle.text = item!!.title
@@ -337,14 +333,19 @@ class EpisodeInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
Converter.getDurationStringLocalized(requireContext(), media.getDuration().toLong()))
}
if (item != null) {
- actionButton1 = if (PlaybackStatus.isCurrentlyPlaying(media)) {
- PauseActionButton(item!!)
- } else if (item!!.feed != null && item!!.feed!!.isLocalFeed) {
- PlayLocalActionButton(item)
- } else if (media.isDownloaded()) {
- PlayActionButton(item!!)
- } else {
- StreamActionButton(item!!)
+ actionButton1 = when {
+ PlaybackStatus.isCurrentlyPlaying(media) -> {
+ PauseActionButton(item!!)
+ }
+ item!!.feed != null && item!!.feed!!.isLocalFeed -> {
+ PlayLocalActionButton(item)
+ }
+ media.isDownloaded() -> {
+ PlayActionButton(item!!)
+ }
+ else -> {
+ StreamActionButton(item!!)
+ }
}
actionButton2 = if (dls != null && media.download_url != null && dls.isDownloadingEpisode(media.download_url!!)) {
CancelDownloadActionButton(item!!)
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
deleted file mode 100644
index 7bb978ad..00000000
--- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/ItemDescriptionFragment.kt
+++ /dev/null
@@ -1,173 +0,0 @@
-package ac.mdiq.podcini.ui.fragment
-
-import ac.mdiq.podcini.databinding.ItemDescriptionFragmentBinding
-import ac.mdiq.podcini.playback.PlaybackController
-import ac.mdiq.podcini.storage.DBReader
-import ac.mdiq.podcini.storage.model.feed.FeedMedia
-import ac.mdiq.podcini.ui.gui.ShownotesCleaner
-import ac.mdiq.podcini.ui.view.ShownotesWebView
-import android.app.Activity
-import android.os.Bundle
-import android.util.Log
-import android.view.LayoutInflater
-import android.view.MenuItem
-import android.view.View
-import android.view.View.OnLayoutChangeListener
-import android.view.ViewGroup
-import androidx.fragment.app.Fragment
-import androidx.media3.common.util.UnstableApi
-import io.reactivex.Maybe
-import io.reactivex.MaybeEmitter
-import io.reactivex.android.schedulers.AndroidSchedulers
-import io.reactivex.disposables.Disposable
-import io.reactivex.schedulers.Schedulers
-
-/**
- * Displays the description of a Playable object in a Webview.
- */
-@UnstableApi
-class ItemDescriptionFragment : Fragment() {
- private lateinit var webvDescription: ShownotesWebView
-
- private var _binding: ItemDescriptionFragmentBinding? = null
- private val binding get() = _binding!!
-
- private var webViewLoader: Disposable? = null
- private var controller: PlaybackController? = null
-
- @UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
- Log.d(TAG, "fragment onCreateView")
- _binding = ItemDescriptionFragmentBinding.inflate(inflater)
-
- Log.d(TAG, "fragment onCreateView")
- webvDescription = binding.webview
- webvDescription.setTimecodeSelectedListener { time: Int? ->
- controller?.seekTo(time!!)
- }
- webvDescription.setPageFinishedListener {
- // Restoring the scroll position might not always work
- webvDescription.postDelayed({ this@ItemDescriptionFragment.restoreFromPreference() }, 50)
- }
-
- binding.root.addOnLayoutChangeListener(object : OnLayoutChangeListener {
- override fun onLayoutChange(v: View, left: Int, top: Int, right: Int,
- bottom: Int, oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int
- ) {
- if (binding.root.measuredHeight != webvDescription.minimumHeight) {
- webvDescription.setMinimumHeight(binding.root.measuredHeight)
- }
- binding.root.removeOnLayoutChangeListener(this)
- }
- })
- registerForContextMenu(webvDescription)
- controller = object : PlaybackController(requireActivity()) {
- override fun loadMediaInfo() {
- load()
- }
- }
- controller?.init()
-
- load()
- return binding.root
- }
-
- override fun onDestroyView() {
- super.onDestroyView()
- _binding = null
- controller?.release()
- controller = null
- Log.d(TAG, "Fragment destroyed")
- webvDescription.removeAllViews()
- webvDescription.destroy()
- }
-
- override fun onContextItemSelected(item: MenuItem): Boolean {
- return webvDescription.onContextItemSelected(item)
- }
-
- @UnstableApi private fun load() {
- Log.d(TAG, "load() called")
- webViewLoader?.dispose()
-
- val context = context ?: return
- webViewLoader = Maybe.create { emitter: MaybeEmitter ->
- val media = controller?.getMedia()
- if (media == null) {
- emitter.onComplete()
- return@create
- }
- if (media is FeedMedia) {
- var item = media.item
- if (item == null) {
- item = DBReader.getFeedItem(media.itemId)
- media.setItem(item)
- }
- if (item != null && item.description == null) DBReader.loadDescriptionOfFeedItem(item)
- }
- val shownotesCleaner = ShownotesCleaner(context, media.getDescription()?:"", media.getDuration())
- emitter.onSuccess(shownotesCleaner.processShownotes())
- }
- .subscribeOn(Schedulers.io())
- .observeOn(AndroidSchedulers.mainThread())
- .subscribe({ data: String? ->
- webvDescription.loadDataWithBaseURL("https://127.0.0.1", data!!, "text/html",
- "utf-8", "about:blank")
- Log.d(TAG, "Webview loaded")
- }, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
- }
-
- @UnstableApi override fun onPause() {
- super.onPause()
- savePreference()
- }
-
- @UnstableApi private fun savePreference() {
- Log.d(TAG, "Saving preferences")
- val prefs = requireActivity().getSharedPreferences(PREF, Activity.MODE_PRIVATE)
- val editor = prefs.edit()
- if (controller?.getMedia() != null) {
- Log.d(TAG, "Saving scroll position: " + webvDescription.scrollY)
- editor.putInt(PREF_SCROLL_Y, webvDescription.scrollY)
- editor.putString(PREF_PLAYABLE_ID, controller!!.getMedia()!!.getIdentifier().toString())
- } else {
- Log.d(TAG, "savePreferences was called while media or webview was null")
- editor.putInt(PREF_SCROLL_Y, -1)
- editor.putString(PREF_PLAYABLE_ID, "")
- }
- editor.apply()
- }
-
- @UnstableApi private fun restoreFromPreference(): Boolean {
- Log.d(TAG, "Restoring from preferences")
- val activity: Activity? = activity
- if (activity != null) {
- val prefs = activity.getSharedPreferences(PREF, Activity.MODE_PRIVATE)
- val id = prefs.getString(PREF_PLAYABLE_ID, "")
- val scrollY = prefs.getInt(PREF_SCROLL_Y, -1)
- if (controller != null && scrollY != -1 && controller!!.getMedia() != null && id == controller!!.getMedia()!!.getIdentifier().toString()) {
- Log.d(TAG, "Restored scroll Position: $scrollY")
- webvDescription.scrollTo(webvDescription.scrollX, scrollY)
- return true
- }
- }
- return false
- }
-
- fun scrollToTop() {
- webvDescription.scrollTo(0, 0)
- savePreference()
- }
-
- override fun onStop() {
- super.onStop()
- webViewLoader?.dispose()
- }
-
- companion object {
- private const val TAG = "ItemDescriptionFragment"
-
- private const val PREF = "ItemDescriptionFragmentPrefs"
- private const val PREF_SCROLL_Y = "prefScrollY"
- private const val PREF_PLAYABLE_ID = "prefPlayableId"
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/ui/fragment/CoverFragment.kt b/app/src/main/java/ac/mdiq/podcini/ui/fragment/PlayerDetailsFragment.kt
similarity index 62%
rename from app/src/main/java/ac/mdiq/podcini/ui/fragment/CoverFragment.kt
rename to app/src/main/java/ac/mdiq/podcini/ui/fragment/PlayerDetailsFragment.kt
index 745ff39f..109ecfb0 100644
--- a/app/src/main/java/ac/mdiq/podcini/ui/fragment/CoverFragment.kt
+++ b/app/src/main/java/ac/mdiq/podcini/ui/fragment/PlayerDetailsFragment.kt
@@ -1,34 +1,37 @@
package ac.mdiq.podcini.ui.fragment
import ac.mdiq.podcini.R
-import ac.mdiq.podcini.ui.activity.MainActivity
+import ac.mdiq.podcini.databinding.PlayerDetailsFragmentBinding
import ac.mdiq.podcini.feed.util.ImageResourceUtils
-import ac.mdiq.podcini.util.ChapterUtils
-import ac.mdiq.podcini.util.DateFormatter
import ac.mdiq.podcini.playback.PlaybackController
-import ac.mdiq.podcini.databinding.CoverFragmentBinding
import ac.mdiq.podcini.playback.event.PlaybackPositionEvent
+import ac.mdiq.podcini.storage.DBReader
import ac.mdiq.podcini.storage.model.feed.Chapter
import ac.mdiq.podcini.storage.model.feed.FeedMedia
import ac.mdiq.podcini.storage.model.playback.Playable
+import ac.mdiq.podcini.ui.activity.MainActivity
+import ac.mdiq.podcini.ui.gui.ShownotesCleaner
+import ac.mdiq.podcini.ui.view.ShownotesWebView
+import ac.mdiq.podcini.util.ChapterUtils
+import ac.mdiq.podcini.util.DateFormatter
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
import android.animation.ObjectAnimator
+import android.app.Activity
import android.content.ClipData
import android.content.ClipboardManager
import android.content.Intent
-import android.content.res.Configuration
import android.graphics.ColorFilter
import android.graphics.drawable.Drawable
import android.os.Build
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
+import android.view.MenuItem
import android.view.View
+import android.view.View.OnLayoutChangeListener
import android.view.ViewGroup
-import android.widget.LinearLayout
-import androidx.annotation.OptIn
import androidx.core.content.ContextCompat
import androidx.core.graphics.BlendModeColorFilterCompat
import androidx.core.graphics.BlendModeCompat
@@ -46,36 +49,36 @@ import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import org.apache.commons.lang3.StringUtils
-import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
/**
- * Displays the cover and the title of a FeedItem.
+ * Displays the description of a Playable object in a Webview.
*/
-class CoverFragment : Fragment() {
- private var _binding: CoverFragmentBinding? = null
+@UnstableApi
+class PlayerDetailsFragment : Fragment() {
+ private lateinit var webvDescription: ShownotesWebView
+
+ private var _binding: PlayerDetailsFragmentBinding? = null
private val binding get() = _binding!!
- private var controller: PlaybackController? = null
- private var disposable: Disposable? = null
- private var displayedChapterIndex = -1
private var media: Playable? = null
+ private var displayedChapterIndex = -1
+
+ private var disposable: Disposable? = null
+ private var webViewLoader: Disposable? = null
+ private var controller: PlaybackController? = null
@UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
-
Log.d(TAG, "fragment onCreateView")
- _binding = CoverFragmentBinding.inflate(inflater)
+ _binding = PlayerDetailsFragmentBinding.inflate(inflater)
+
binding.imgvCover.setOnClickListener { onPlayPause() }
- binding.openDescription.setOnClickListener {
- (requireParentFragment() as AudioPlayerFragment)
- .scrollToPage(AudioPlayerFragment.FIRST_PAGE, true)
- }
+
val colorFilter: ColorFilter? = BlendModeColorFilterCompat.createBlendModeColorFilterCompat(
binding.txtvPodcastTitle.currentTextColor, BlendModeCompat.SRC_IN)
binding.butNextChapter.colorFilter = colorFilter
binding.butPrevChapter.colorFilter = colorFilter
- binding.descriptionIcon.colorFilter = colorFilter
binding.chapterButton.setOnClickListener {
ChaptersFragment().show(
childFragmentManager, ChaptersFragment.TAG)
@@ -83,41 +86,88 @@ class CoverFragment : Fragment() {
binding.butPrevChapter.setOnClickListener { seekToPrevChapter() }
binding.butNextChapter.setOnClickListener { seekToNextChapter() }
+ Log.d(TAG, "fragment onCreateView")
+ webvDescription = binding.webview
+ webvDescription.setTimecodeSelectedListener { time: Int? ->
+ controller?.seekTo(time!!)
+ }
+ webvDescription.setPageFinishedListener {
+ // Restoring the scroll position might not always work
+ webvDescription.postDelayed({ this@PlayerDetailsFragment.restoreFromPreference() }, 50)
+ }
+
+ binding.root.addOnLayoutChangeListener(object : OnLayoutChangeListener {
+ override fun onLayoutChange(v: View, left: Int, top: Int, right: Int,
+ bottom: Int, oldLeft: Int, oldTop: Int, oldRight: Int, oldBottom: Int
+ ) {
+ if (binding.root.measuredHeight != webvDescription.minimumHeight) {
+ webvDescription.setMinimumHeight(binding.root.measuredHeight)
+ }
+ binding.root.removeOnLayoutChangeListener(this)
+ }
+ })
+ registerForContextMenu(webvDescription)
controller = object : PlaybackController(requireActivity()) {
override fun loadMediaInfo() {
- this@CoverFragment.loadMediaInfo(false)
+ load()
+ loadMediaInfo(false)
}
}
controller?.init()
+ load()
loadMediaInfo(false)
- EventBus.getDefault().register(this)
-
return binding.root
}
- @OptIn(UnstableApi::class) override fun onDestroyView() {
+ override fun onDestroyView() {
super.onDestroyView()
_binding = null
controller?.release()
controller = null
- EventBus.getDefault().unregister(this)
Log.d(TAG, "Fragment destroyed")
+ webvDescription.removeAllViews()
+ webvDescription.destroy()
}
- override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
- configureForOrientation(resources.configuration)
+ override fun onContextItemSelected(item: MenuItem): Boolean {
+ return webvDescription.onContextItemSelected(item)
+ }
+
+ @UnstableApi private fun load() {
+ Log.d(TAG, "load() called")
+ webViewLoader?.dispose()
+
+ val context = context ?: return
+ webViewLoader = Maybe.create { emitter: MaybeEmitter ->
+ media = controller?.getMedia()
+ if (media == null) {
+ emitter.onComplete()
+ return@create
+ }
+ if (media is FeedMedia) {
+ val feedMedia = media as FeedMedia
+ val item = feedMedia.item
+ if (item != null && item.description == null) DBReader.loadDescriptionOfFeedItem(item)
+ }
+ val shownotesCleaner = ShownotesCleaner(context, media!!.getDescription()?:"", media!!.getDuration())
+ emitter.onSuccess(shownotesCleaner.processShownotes())
+ }
+ .subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe({ data: String? ->
+ webvDescription.loadDataWithBaseURL("https://127.0.0.1", data!!, "text/html",
+ "utf-8", "about:blank")
+ Log.d(TAG, "Webview loaded")
+ }, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
}
@UnstableApi private fun loadMediaInfo(includingChapters: Boolean) {
disposable?.dispose()
disposable = Maybe.create { emitter: MaybeEmitter ->
- val media: Playable? = controller?.getMedia()
+ media = controller?.getMedia()
if (media != null) {
- if (includingChapters) {
- ChapterUtils.loadChapters(media, requireContext(), false)
- }
- emitter.onSuccess(media)
+ emitter.onSuccess(media!!)
} else {
emitter.onComplete()
}
@@ -126,19 +176,12 @@ class CoverFragment : Fragment() {
.subscribe({ media: Playable ->
this.media = media
displayMediaInfo(media)
- if (!includingChapters) {
- loadMediaInfo(true)
- }
}, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
}
@UnstableApi private fun displayMediaInfo(media: Playable) {
val pubDateStr = DateFormatter.formatAbbrev(context, media.getPubDate())
- binding.txtvPodcastTitle.text = (StringUtils.stripToEmpty(media.getFeedTitle())
- + "\u00A0"
- + "・"
- + "\u00A0"
- + StringUtils.replace(StringUtils.stripToEmpty(pubDateStr), " ", "\u00A0"))
+ binding.txtvPodcastTitle.text = StringUtils.stripToEmpty(media.getFeedTitle())
if (media is FeedMedia) {
val items = media.item
if (items != null) {
@@ -149,6 +192,7 @@ class CoverFragment : Fragment() {
binding.txtvPodcastTitle.setOnClickListener(null)
}
binding.txtvPodcastTitle.setOnLongClickListener { copyText(media.getFeedTitle()) }
+ binding.episodeDate.text = StringUtils.stripToEmpty(pubDateStr)
binding.txtvEpisodeTitle.text = media.getEpisodeTitle()
binding.txtvEpisodeTitle.setOnLongClickListener { copyText(media.getEpisodeTitle()) }
binding.txtvEpisodeTitle.setOnClickListener {
@@ -217,6 +261,36 @@ class CoverFragment : Fragment() {
displayCoverImage()
}
+ private fun displayCoverImage() {
+ if (media == null) return
+ val options: RequestOptions = RequestOptions()
+ .dontAnimate()
+ .transform(FitCenter(),
+ RoundedCorners((16 * resources.displayMetrics.density).toInt()))
+
+ val cover: RequestBuilder = Glide.with(this)
+ .load(media!!.getImageLocation())
+ .error(Glide.with(this)
+ .load(ImageResourceUtils.getFallbackImageLocation(media!!))
+ .apply(options))
+ .apply(options)
+
+ if (displayedChapterIndex == -1 || media!!.getChapters().isEmpty() || media!!.getChapters()[displayedChapterIndex].imageUrl.isNullOrEmpty()) {
+ cover.into(binding.imgvCover)
+ } else {
+ Glide.with(this)
+ .load(ac.mdiq.podcini.storage.model.feed.EmbeddedChapterImage.getModelFor(media!!, displayedChapterIndex))
+ .apply(options)
+ .thumbnail(cover)
+ .error(cover)
+ .into(binding.imgvCover)
+ }
+ }
+
+ @UnstableApi fun onPlayPause() {
+ controller?.playPause()
+ }
+
private val currentChapter: Chapter?
get() {
if (media == null || media!!.getChapters().isEmpty() || displayedChapterIndex == -1) {
@@ -251,13 +325,47 @@ class CoverFragment : Fragment() {
controller!!.seekTo(media!!.getChapters()[displayedChapterIndex].start.toInt())
}
- @UnstableApi override fun onStart() {
- super.onStart()
+
+ @UnstableApi override fun onPause() {
+ super.onPause()
+ savePreference()
}
- @UnstableApi override fun onStop() {
- super.onStop()
- disposable?.dispose()
+ @UnstableApi private fun savePreference() {
+ Log.d(TAG, "Saving preferences")
+ val prefs = requireActivity().getSharedPreferences(PREF, Activity.MODE_PRIVATE)
+ val editor = prefs.edit()
+ if (controller?.getMedia() != null) {
+ Log.d(TAG, "Saving scroll position: " + webvDescription.scrollY)
+ editor.putInt(PREF_SCROLL_Y, webvDescription.scrollY)
+ editor.putString(PREF_PLAYABLE_ID, controller!!.getMedia()!!.getIdentifier().toString())
+ } else {
+ Log.d(TAG, "savePreferences was called while media or webview was null")
+ editor.putInt(PREF_SCROLL_Y, -1)
+ editor.putString(PREF_PLAYABLE_ID, "")
+ }
+ editor.apply()
+ }
+
+ @UnstableApi private fun restoreFromPreference(): Boolean {
+ Log.d(TAG, "Restoring from preferences")
+ val activity: Activity? = activity
+ if (activity != null) {
+ val prefs = activity.getSharedPreferences(PREF, Activity.MODE_PRIVATE)
+ val id = prefs.getString(PREF_PLAYABLE_ID, "")
+ val scrollY = prefs.getInt(PREF_SCROLL_Y, -1)
+ if (controller != null && scrollY != -1 && controller!!.getMedia() != null && id == controller!!.getMedia()!!.getIdentifier().toString()) {
+ Log.d(TAG, "Restored scroll Position: $scrollY")
+ webvDescription.scrollTo(webvDescription.scrollX, scrollY)
+ return true
+ }
+ }
+ return false
+ }
+
+ fun scrollToTop() {
+ webvDescription.scrollTo(0, 0)
+ savePreference()
}
@Subscribe(threadMode = ThreadMode.MAIN)
@@ -268,64 +376,39 @@ class CoverFragment : Fragment() {
}
}
- private fun displayCoverImage() {
- if (media == null) return
- val options: RequestOptions = RequestOptions()
- .dontAnimate()
- .transform(FitCenter(),
- RoundedCorners((16 * resources.displayMetrics.density).toInt()))
+// override fun onConfigurationChanged(newConfig: Configuration) {
+// super.onConfigurationChanged(newConfig)
+// configureForOrientation(newConfig)
+// }
+//
+// private fun configureForOrientation(newConfig: Configuration) {
+// val isPortrait = newConfig.orientation == Configuration.ORIENTATION_PORTRAIT
+//
+// binding.coverFragment.orientation = if (isPortrait) LinearLayout.VERTICAL else LinearLayout.HORIZONTAL
+//
+// if (isPortrait) {
+// binding.coverHolder.layoutParams =
+// LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f)
+// binding.coverFragmentTextContainer.layoutParams =
+// LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
+// } else {
+// binding.coverHolder.layoutParams =
+// LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1f)
+// binding.coverFragmentTextContainer.layoutParams =
+// LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1f)
+// }
+//
+// (binding.episodeDetails.parent as ViewGroup).removeView(binding.episodeDetails)
+// if (isPortrait) {
+// binding.coverFragment.addView(binding.episodeDetails)
+// } else {
+// binding.coverFragmentTextContainer.addView(binding.episodeDetails)
+// }
+// }
- val cover: RequestBuilder = Glide.with(this)
- .load(media!!.getImageLocation())
- .error(Glide.with(this)
- .load(ImageResourceUtils.getFallbackImageLocation(media!!))
- .apply(options))
- .apply(options)
-
- if (displayedChapterIndex == -1 || media!!.getChapters().isEmpty() || media!!.getChapters()[displayedChapterIndex].imageUrl.isNullOrEmpty()) {
- cover.into(binding.imgvCover)
- } else {
- Glide.with(this)
- .load(ac.mdiq.podcini.storage.model.feed.EmbeddedChapterImage.getModelFor(media!!, displayedChapterIndex))
- .apply(options)
- .thumbnail(cover)
- .error(cover)
- .into(binding.imgvCover)
- }
- }
-
- override fun onConfigurationChanged(newConfig: Configuration) {
- super.onConfigurationChanged(newConfig)
- configureForOrientation(newConfig)
- }
-
- private fun configureForOrientation(newConfig: Configuration) {
- val isPortrait = newConfig.orientation == Configuration.ORIENTATION_PORTRAIT
-
- binding.coverFragment.orientation = if (isPortrait) LinearLayout.VERTICAL else LinearLayout.HORIZONTAL
-
- if (isPortrait) {
- binding.coverHolder.layoutParams =
- LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f)
- binding.coverFragmentTextContainer.layoutParams =
- LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
- } else {
- binding.coverHolder.layoutParams =
- LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1f)
- binding.coverFragmentTextContainer.layoutParams =
- LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1f)
- }
-
- (binding.episodeDetails.parent as ViewGroup).removeView(binding.episodeDetails)
- if (isPortrait) {
- binding.coverFragment.addView(binding.episodeDetails)
- } else {
- binding.coverFragmentTextContainer.addView(binding.episodeDetails)
- }
- }
-
- @UnstableApi fun onPlayPause() {
- controller?.playPause()
+ override fun onStop() {
+ super.onStop()
+ webViewLoader?.dispose()
}
@UnstableApi private fun copyText(text: String): Boolean {
@@ -339,6 +422,10 @@ class CoverFragment : Fragment() {
}
companion object {
- private const val TAG = "CoverFragment"
+ private const val TAG = "ItemDescriptionFragment"
+
+ private const val PREF = "ItemDescriptionFragmentPrefs"
+ private const val PREF_SCROLL_Y = "prefScrollY"
+ private const val PREF_PLAYABLE_ID = "prefPlayableId"
}
}
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 2ba47bf3..10322903 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
@@ -42,9 +42,8 @@ object FeedItemMenuHandler {
*/
@UnstableApi
fun onPrepareMenu(menu: Menu?, selectedItem: FeedItem?): Boolean {
- if (menu == null || selectedItem == null) {
- return false
- }
+ if (menu == null || selectedItem == null) return false
+
val hasMedia = selectedItem.media != null
val isPlaying = hasMedia && PlaybackStatus.isPlaying(selectedItem.media)
val isInQueue: Boolean = selectedItem.isTagged(FeedItem.TAG_QUEUE)
diff --git a/app/src/main/java/ac/mdiq/podcini/ui/view/NestedScrollableHost.kt b/app/src/main/java/ac/mdiq/podcini/ui/view/NestedScrollableHost.kt
deleted file mode 100644
index 682b2fd9..00000000
--- a/app/src/main/java/ac/mdiq/podcini/ui/view/NestedScrollableHost.kt
+++ /dev/null
@@ -1,175 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- *
- * Source: https://github.com/android/views-widgets-samples/blob/87e58d1/ViewPager2/app/src/main/java/androidx/viewpager2/integration/testapp/NestedScrollableHost.kt
- * And modified for our need
- */
-package ac.mdiq.podcini.ui.view
-
-import android.content.Context
-import android.util.AttributeSet
-import android.view.MotionEvent
-import android.view.View
-import android.view.ViewConfiguration
-import android.view.ViewTreeObserver
-import android.widget.FrameLayout
-import androidx.viewpager2.widget.ViewPager2
-import ac.mdiq.podcini.R
-import kotlin.math.abs
-
-/**
- * Layout to wrap a scrollable component inside a ViewPager2. Provided as a solution to the problem
- * where pages of ViewPager2 have nested scrollable elements that scroll in the same direction as
- * ViewPager2. The scrollable element needs to be the immediate and only child of this host layout.
- *
- * This solution has limitations when using multiple levels of nested scrollable elements
- * (e.g. a horizontal RecyclerView in a vertical RecyclerView in a horizontal ViewPager2).
- */
-// KhaledAlharthi/NestedScrollableHost.java
-class NestedScrollableHost : FrameLayout {
- private var parentViewPager: ViewPager2? = null
- private var touchSlop = 0
- private var initialX = 0f
- private var initialY = 0f
- private var preferVertical = 1
- private var preferHorizontal = 1
- private var scrollDirection = 0
-
- constructor(context: Context) : super(context) {
- init(context)
- }
-
- constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) {
- init(context)
- setAttributes(context, attrs)
- }
-
- constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(context, attrs, defStyleAttr) {
- init(context)
- setAttributes(context, attrs)
- }
-
- constructor(context: Context, attrs: AttributeSet?,
- defStyleAttr: Int, defStyleRes: Int
- ) : super(context, attrs, defStyleAttr, defStyleRes) {
- init(context)
- setAttributes(context, attrs)
- }
-
- private fun setAttributes(context: Context, attrs: AttributeSet?) {
- val a = context.theme.obtainStyledAttributes(attrs, R.styleable.NestedScrollableHost, 0, 0)
-
- try {
- preferHorizontal = a.getInteger(R.styleable.NestedScrollableHost_preferHorizontal, 1)
- preferVertical = a.getInteger(R.styleable.NestedScrollableHost_preferVertical, 1)
- scrollDirection = a.getInteger(R.styleable.NestedScrollableHost_scrollDirection, 0)
- } finally {
- a.recycle()
- }
- }
-
- private fun init(context: Context) {
- touchSlop = ViewConfiguration.get(context).scaledTouchSlop
-
-
- viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
- override fun onPreDraw(): Boolean {
- var v = parent as? View
- while (v != null && v !is ViewPager2 || isntSameDirection(v)) {
- v = v!!.parent as? View
- }
- parentViewPager = v as? ViewPager2
-
- viewTreeObserver.removeOnPreDrawListener(this)
- return false
- }
- })
- }
-
- private fun isntSameDirection(v: View?): Boolean {
- val orientation: Int = when (scrollDirection) {
- 0 -> return false
- 1 -> ViewPager2.ORIENTATION_VERTICAL
- 2 -> ViewPager2.ORIENTATION_HORIZONTAL
- else -> return false
- }
- return ((v is ViewPager2) && v.orientation != orientation)
- }
-
-
- override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {
- handleInterceptTouchEvent(ev)
- return super.onInterceptTouchEvent(ev)
- }
-
-
- private fun canChildScroll(orientation: Int, delta: Float): Boolean {
- val direction = -delta.toInt()
- val child = getChildAt(0)
- return when (orientation) {
- 0 -> {
- child.canScrollHorizontally(direction)
- }
- 1 -> {
- child.canScrollVertically(direction)
- }
- else -> {
- throw IllegalArgumentException()
- }
- }
- }
-
- private fun handleInterceptTouchEvent(e: MotionEvent) {
- if (parentViewPager == null) {
- return
- }
- val orientation = parentViewPager!!.orientation
- val preferedDirection = preferHorizontal + preferVertical > 2
-
- // Early return if child can't scroll in same direction as parent and theres no prefered scroll direction
- if (!canChildScroll(orientation, -1f) && !canChildScroll(orientation, 1f) && !preferedDirection) {
- return
- }
-
- if (e.action == MotionEvent.ACTION_DOWN) {
- initialX = e.x
- initialY = e.y
- parent.requestDisallowInterceptTouchEvent(true)
- } else if (e.action == MotionEvent.ACTION_MOVE) {
- val dx = e.x - initialX
- val dy = e.y - initialY
- val isVpHorizontal = orientation == ViewPager2.ORIENTATION_HORIZONTAL
-
- // assuming ViewPager2 touch-slop is 2x touch-slop of child
- val scaledDx = (abs(dx.toDouble()) * (if (isVpHorizontal) 1f else 0.5f) * preferHorizontal).toFloat()
- val scaledDy = (abs(dy.toDouble()) * (if (isVpHorizontal) 0.5f else 1f) * preferVertical).toFloat()
- if (scaledDx > touchSlop || scaledDy > touchSlop) {
- if (isVpHorizontal == (scaledDy > scaledDx)) {
- // Gesture is perpendicular, allow all parents to intercept
- parent.requestDisallowInterceptTouchEvent(preferedDirection)
- } else {
- // Gesture is parallel, query child if movement in that direction is possible
- if (canChildScroll(orientation, if (isVpHorizontal) dx else dy)) {
- // Child can scroll, disallow all parents to intercept
- parent.requestDisallowInterceptTouchEvent(true)
- } else {
- // Child cannot scroll, allow all parents to intercept
- parent.requestDisallowInterceptTouchEvent(false)
- }
- }
- }
- }
- }
-}
diff --git a/app/src/main/java/ac/mdiq/podcini/ui/view/ShownotesWebView.kt b/app/src/main/java/ac/mdiq/podcini/ui/view/ShownotesWebView.kt
index ed1ba853..21af30b3 100644
--- a/app/src/main/java/ac/mdiq/podcini/ui/view/ShownotesWebView.kt
+++ b/app/src/main/java/ac/mdiq/podcini/ui/view/ShownotesWebView.kt
@@ -62,6 +62,7 @@ class ShownotesWebView : WebView, View.OnLongClickListener {
setOnLongClickListener(this)
setWebViewClient(object : WebViewClient() {
+ @Deprecated("Deprecated in Java")
override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {
if (ShownotesCleaner.isTimecodeLink(url) && timecodeSelectedListener != null) {
timecodeSelectedListener!!.accept(ShownotesCleaner.getTimecodeLinkTime(url))
@@ -173,6 +174,7 @@ class ShownotesWebView : WebView, View.OnLongClickListener {
this.pageFinishedListener = pageFinishedListener
}
+ @Deprecated("Deprecated in Java")
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
setMeasuredDimension(max(measuredWidth, minimumWidth), max(measuredHeight, minimumHeight))
diff --git a/app/src/main/java/ac/mdiq/podcini/ui/widget/WidgetUpdaterWorker.kt b/app/src/main/java/ac/mdiq/podcini/ui/widget/WidgetUpdaterWorker.kt
index 68466eec..1212d007 100644
--- a/app/src/main/java/ac/mdiq/podcini/ui/widget/WidgetUpdaterWorker.kt
+++ b/app/src/main/java/ac/mdiq/podcini/ui/widget/WidgetUpdaterWorker.kt
@@ -42,7 +42,7 @@ class WidgetUpdaterWorker(context: Context,
fun enqueueWork(context: Context) {
val workRequest: OneTimeWorkRequest = OneTimeWorkRequest.Builder(WidgetUpdaterWorker::class.java).build()
- WorkManager.getInstance(context!!).enqueueUniqueWork(TAG, ExistingWorkPolicy.REPLACE, workRequest)
+ WorkManager.getInstance(context).enqueueUniqueWork(TAG, ExistingWorkPolicy.REPLACE, workRequest)
}
}
}
diff --git a/app/src/main/res/drawable/baseline_audiotrack_24.xml b/app/src/main/res/drawable/baseline_audiotrack_24.xml
new file mode 100644
index 00000000..2c248ea7
--- /dev/null
+++ b/app/src/main/res/drawable/baseline_audiotrack_24.xml
@@ -0,0 +1,8 @@
+
+
+
diff --git a/app/src/main/res/layout/audioplayer_fragment.xml b/app/src/main/res/layout/audioplayer_fragment.xml
index dd5c1cd0..e5113d5a 100644
--- a/app/src/main/res/layout/audioplayer_fragment.xml
+++ b/app/src/main/res/layout/audioplayer_fragment.xml
@@ -31,15 +31,13 @@
app:navigationContentDescription="@string/toolbar_back_button_content_description"
app:navigationIcon="@drawable/ic_arrow_down" />
-
+ android:layout_marginBottom="12dp" />
+ tools:progress="100"/>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/echo_activity.xml b/app/src/main/res/layout/echo_activity.xml
deleted file mode 100644
index 3c5590d3..00000000
--- a/app/src/main/res/layout/echo_activity.xml
+++ /dev/null
@@ -1,135 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/episode_info_fragment.xml b/app/src/main/res/layout/episode_info_fragment.xml
index 6c765060..96b73097 100644
--- a/app/src/main/res/layout/episode_info_fragment.xml
+++ b/app/src/main/res/layout/episode_info_fragment.xml
@@ -207,19 +207,12 @@
-
-
-
-
-
+ android:foreground="?android:windowContentOverlay" />
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/player_details_fragment.xml b/app/src/main/res/layout/player_details_fragment.xml
new file mode 100644
index 00000000..ed95b116
--- /dev/null
+++ b/app/src/main/res/layout/player_details_fragment.xml
@@ -0,0 +1,135 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/player_widget.xml b/app/src/main/res/layout/player_widget.xml
index fde313b5..30adbc0c 100644
--- a/app/src/main/res/layout/player_widget.xml
+++ b/app/src/main/res/layout/player_widget.xml
@@ -14,13 +14,28 @@
android:background="#262C31"
tools:ignore="UselessParent">
+
+
+ android:layout_marginStart="12dp"
+ android:layout_marginEnd="12dp"/>
-
-
-
-
-
diff --git a/app/src/main/res/menu/mediaplayer.xml b/app/src/main/res/menu/mediaplayer.xml
index c7cae491..4534dd98 100644
--- a/app/src/main/res/menu/mediaplayer.xml
+++ b/app/src/main/res/menu/mediaplayer.xml
@@ -28,6 +28,14 @@
android:title="@string/set_sleeptimer_label">
+ -
+
+
-
- -
-
-
-
+
+ -
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index fda2d144..2e661e25 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -777,7 +777,7 @@
Audio controls
Playback speed
- Switch to audio only
+ Audio only
Type
diff --git a/app/src/main/res/xml/player_widget_info.xml b/app/src/main/res/xml/player_widget_info.xml
index 81279397..106e9c8b 100644
--- a/app/src/main/res/xml/player_widget_info.xml
+++ b/app/src/main/res/xml/player_widget_info.xml
@@ -1,12 +1,13 @@
-
+
diff --git a/changelog.md b/changelog.md
index 0911adf6..09a7fccb 100644
--- a/changelog.md
+++ b/changelog.md
@@ -124,4 +124,16 @@
* further optimized efficiencies of episode info view
* episode info view opened from icon is now the same as that opened from title area, no long supports horizontal swipes (change from 4.2.7)
* enhanced viewbingding GC
-* some code cleaning
\ No newline at end of file
+* some code cleaning
+
+## 4.3.3
+
+* fixed bug in adding widget to home screen
+* minor adjustment of widget layout
+* added "audio only" to action bar in video player
+* added "mark favorite" to action bar in episode view
+* revamped and enhanced expanded view of the player
+* vertical swipe no longer collapses the expanded view
+* only the down arrow on top left page collapses the expanded view
+* share notes directly from expanded view of the player
+* in episode info, changed rendering of description, removed nested scroll
\ No newline at end of file
diff --git a/fastlane/metadata/android/en-US/changelogs/3020114.txt b/fastlane/metadata/android/en-US/changelogs/3020114.txt
new file mode 100644
index 00000000..8c566149
--- /dev/null
+++ b/fastlane/metadata/android/en-US/changelogs/3020114.txt
@@ -0,0 +1,12 @@
+
+Version 4.3.3 brings several changes:
+
+* fixed bug in adding widget to home screen
+* minor adjustment of widget layout
+* added "audio only" to action bar in video player
+* added "mark favorite" to action bar in episode view
+* revamped and enhanced expanded view of the player
+* vertical swipe no longer collapses the expanded view
+* only the down arrow on top left page collapses the expanded view
+* share notes directly from expanded view of the player
+* in episode info, changed rendering of description, removed nested scroll