4.3.3 release
This commit is contained in:
parent
64e3e64d21
commit
5cb3b20b00
|
@ -3,7 +3,7 @@
|
|||
<img width="100" src="https://raw.githubusercontent.com/xilinjia/podcini/main/images/icon 256x256.png" align="left" style="margin-right:15px"/>
|
||||
Podcini is an open source podcast manager/player project.
|
||||
|
||||
This app is a fork of [AntennaPod](<https://github.com/AntennaPod/AntennaPod>) as of Feb 5 2024.
|
||||
This project is a fork of [AntennaPod](<https://github.com/AntennaPod/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. <!-- NOTE: wording of this can be improved -->
|
||||
5. Aims to improve efficiency and provide more user-friendly features
|
||||
|
||||
## Version 4
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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)) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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!!)
|
||||
|
|
|
@ -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<String?> ->
|
||||
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"
|
||||
}
|
||||
}
|
|
@ -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<String?> ->
|
||||
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<Playable> { emitter: MaybeEmitter<Playable?> ->
|
||||
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<Drawable> = 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<Drawable> = 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"
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
<vector android:height="24dp"
|
||||
android:viewportHeight="24"
|
||||
android:viewportWidth="24"
|
||||
android:width="24dp"
|
||||
xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="?attr/action_icon_color"
|
||||
android:pathData="M12,3v9.28c-0.47,-0.17 -0.97,-0.28 -1.5,-0.28C8.01,12 6,14.01 6,16.5S8.01,21 10.5,21c2.31,0 4.2,-1.75 4.45,-4H15V6h4V3h-7z"/>
|
||||
</vector>
|
|
@ -31,15 +31,13 @@
|
|||
app:navigationContentDescription="@string/toolbar_back_button_content_description"
|
||||
app:navigationIcon="@drawable/ic_arrow_down" />
|
||||
|
||||
<androidx.viewpager2.widget.ViewPager2
|
||||
android:id="@+id/pager"
|
||||
<androidx.fragment.app.FragmentContainerView
|
||||
android:id="@+id/itemDescription"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_above="@id/playtime_layout"
|
||||
android:layout_below="@id/toolbar"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:foreground="?android:windowContentOverlay"
|
||||
android:orientation="vertical" />
|
||||
android:layout_marginBottom="12dp" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="match_parent"
|
||||
|
@ -52,7 +50,7 @@
|
|||
android:id="@+id/cardViewSeek"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignBottom="@+id/pager"
|
||||
android:layout_alignBottom="@+id/itemDescription"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_marginLeft="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
|
@ -104,7 +102,7 @@
|
|||
android:layout_marginRight="8dp"
|
||||
android:clickable="true"
|
||||
android:max="500"
|
||||
tools:progress="100" />
|
||||
tools:progress="100"/>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
@ -1,172 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:squareImageView="http://schemas.android.com/apk/ac.mdiq.podcini"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/cover_fragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:orientation="vertical"
|
||||
android:padding="8dp">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/coverHolder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="0dp"
|
||||
android:layout_weight="1">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imgvCover"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginHorizontal="32dp"
|
||||
android:foreground="?attr/selectableItemBackgroundBorderless"
|
||||
android:importantForAccessibility="no"
|
||||
android:scaleType="fitCenter"
|
||||
squareImageView:direction="minimum"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
tools:src="@android:drawable/sym_def_app_icon" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/cover_fragment_text_container"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginVertical="8dp"
|
||||
android:gravity="center"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvPodcastTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:selectableItemBackground"
|
||||
android:ellipsize="none"
|
||||
android:gravity="center_horizontal"
|
||||
android:maxLines="2"
|
||||
android:paddingTop="2dp"
|
||||
android:paddingBottom="2dp"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textIsSelectable="false"
|
||||
android:textSize="@dimen/text_size_small"
|
||||
tools:text="Podcast" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvEpisodeTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="none"
|
||||
android:gravity="center_horizontal"
|
||||
android:maxLines="2"
|
||||
android:paddingTop="2dp"
|
||||
android:paddingBottom="2dp"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textIsSelectable="false"
|
||||
android:textSize="@dimen/text_size_small"
|
||||
android:layout_marginBottom="32dp"
|
||||
tools:text="Episode" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/episode_details"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
android:baselineAligned="false"
|
||||
android:orientation="horizontal"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/openDescription"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="8dp"
|
||||
android:paddingHorizontal="8dp"
|
||||
android:background="@drawable/grey_border"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:gravity="center"
|
||||
android:minWidth="150dp"
|
||||
android:layout_weight="1"
|
||||
android:orientation="horizontal">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/description_icon"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="36dp"
|
||||
android:contentDescription="@string/shownotes_contentdescription"
|
||||
android:padding="2dp"
|
||||
app:srcCompat="@drawable/ic_info" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/shownotes_label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="none"
|
||||
android:layout_marginLeft="2dp"
|
||||
android:layout_marginStart="2dp"
|
||||
android:gravity="center_horizontal"
|
||||
android:maxLines="2"
|
||||
android:text="@string/shownotes_label"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="16sp" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/chapterButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="8dp"
|
||||
android:layout_weight="1"
|
||||
android:background="@drawable/grey_border"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:gravity="center"
|
||||
android:minWidth="150dp"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/butPrevChapter"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/prev_chapter"
|
||||
android:scaleType="fitCenter"
|
||||
app:srcCompat="@drawable/ic_chapter_prev" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/chapters_label"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:text="@string/chapters_label"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="@dimen/text_size_navdrawer" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/butNextChapter"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/next_chapter"
|
||||
android:scaleType="fitCenter"
|
||||
app:srcCompat="@drawable/ic_chapter_next" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</LinearLayout>
|
|
@ -1,135 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/echoImage"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:importantForAccessibility="no" />
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:fitsSystemWindows="true">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/echoProgressImage"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="4dp"
|
||||
android:layout_marginHorizontal="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:importantForAccessibility="no"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/closeButton"
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_margin="16dp"
|
||||
android:src="@drawable/ic_close_white"
|
||||
android:contentDescription="@string/close_label"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_below="@id/echoProgressImage" />
|
||||
|
||||
<ImageView
|
||||
android:layout_width="32dp"
|
||||
android:layout_height="32dp"
|
||||
android:layout_margin="16dp"
|
||||
android:src="@drawable/logo_monochrome"
|
||||
android:importantForAccessibility="no"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_below="@id/echoProgressImage" />
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:padding="32dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/aboveLabel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAlignment="center"
|
||||
android:textColor="#ffffff"
|
||||
android:fontFamily="@font/sarabun_regular"
|
||||
app:fontFamily="@font/sarabun_regular"
|
||||
tools:text="text above"
|
||||
style="@style/TextAppearance.Material3.TitleLarge" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/largeLabel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAlignment="center"
|
||||
android:textColor="#ffffff"
|
||||
android:layout_marginVertical="8dp"
|
||||
android:fontFamily="@font/sarabun_semi_bold"
|
||||
app:fontFamily="@font/sarabun_semi_bold"
|
||||
tools:text="large"
|
||||
style="@style/TextAppearance.Material3.DisplayLarge"
|
||||
tools:targetApi="p" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/belowLabel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAlignment="center"
|
||||
android:textColor="#ffffff"
|
||||
android:fontFamily="@font/sarabun_regular"
|
||||
app:fontFamily="@font/sarabun_regular"
|
||||
tools:text="text below"
|
||||
style="@style/TextAppearance.Material3.TitleLarge" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/smallLabel"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:textAlignment="center"
|
||||
android:textColor="#ffffff"
|
||||
android:textSize="16sp"
|
||||
android:layout_marginTop="32dp"
|
||||
android:fontFamily="@font/sarabun_regular"
|
||||
app:fontFamily="@font/sarabun_regular"
|
||||
tools:text="small" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/echoLogo"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:visibility="gone"
|
||||
android:layout_margin="32dp"
|
||||
android:src="@drawable/echo"
|
||||
android:importantForAccessibility="no"
|
||||
android:layout_alignParentBottom="true" />
|
||||
|
||||
<com.google.android.material.button.MaterialButton
|
||||
android:id="@+id/shareButton"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="56dp"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:layout_margin="32dp"
|
||||
android:text="@string/share_label"
|
||||
android:drawableLeft="@drawable/ic_share"
|
||||
android:textColor="#fff"
|
||||
android:contentDescription="@string/share_label"
|
||||
style="@style/Widget.Material3.Button.OutlinedButton"
|
||||
app:strokeColor="#fff"
|
||||
tools:ignore="RtlHardcoded" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</RelativeLayout>
|
|
@ -207,19 +207,12 @@
|
|||
|
||||
</LinearLayout>
|
||||
|
||||
<ac.mdiq.podcini.ui.view.NestedScrollableHost
|
||||
<ac.mdiq.podcini.ui.view.ShownotesWebView
|
||||
android:id="@+id/webvDescription"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_below="@id/header"
|
||||
app:preferVertical="3">
|
||||
|
||||
<ac.mdiq.podcini.ui.view.ShownotesWebView
|
||||
android:id="@+id/webvDescription"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:foreground="?android:windowContentOverlay" />
|
||||
|
||||
</ac.mdiq.podcini.ui.view.NestedScrollableHost>
|
||||
android:foreground="?android:windowContentOverlay" />
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="match_parent"
|
||||
|
|
|
@ -1,17 +0,0 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ac.mdiq.podcini.ui.view.NestedScrollableHost
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/item_description_fragment"
|
||||
android:fillViewport="false"
|
||||
app:preferVertical="10"
|
||||
android:nestedScrollingEnabled="true">
|
||||
|
||||
<ac.mdiq.podcini.ui.view.ShownotesWebView
|
||||
android:id="@+id/webview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
|
||||
</ac.mdiq.podcini.ui.view.NestedScrollableHost>
|
|
@ -0,0 +1,135 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<ScrollView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:id="@+id/item_description_fragment"
|
||||
android:fillViewport="false">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/playtime_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvPodcastTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?android:selectableItemBackground"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_horizontal"
|
||||
android:maxLines="2"
|
||||
android:paddingTop="2dp"
|
||||
android:paddingBottom="2dp"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textStyle="bold"
|
||||
android:textSize="@dimen/text_size_large"
|
||||
android:layout_marginBottom="5dp"
|
||||
tools:text="Podcast" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/episodeDate"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_horizontal"
|
||||
android:maxLines="1"
|
||||
android:paddingTop="2dp"
|
||||
android:paddingBottom="2dp"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textIsSelectable="false"
|
||||
android:textSize="14sp"
|
||||
tools:text="Episode" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvEpisodeTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center_horizontal"
|
||||
android:maxLines="2"
|
||||
android:paddingTop="2dp"
|
||||
android:paddingBottom="2dp"
|
||||
android:textColor="?android:attr/textColorPrimary"
|
||||
android:textStyle="bold"
|
||||
android:textSize="18sp"
|
||||
tools:text="Episode" />
|
||||
|
||||
<ac.mdiq.podcini.ui.view.ShownotesWebView
|
||||
android:id="@+id/webview"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/chapterButton"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginHorizontal="8dp"
|
||||
android:layout_margin="20dp"
|
||||
android:background="@drawable/grey_border"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:gravity="center"
|
||||
android:minWidth="150dp"
|
||||
android:orientation="horizontal"
|
||||
android:visibility="gone"
|
||||
tools:visibility="visible">
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/butPrevChapter"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/prev_chapter"
|
||||
android:scaleType="fitCenter"
|
||||
app:srcCompat="@drawable/ic_chapter_prev" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/chapters_label"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_weight="1"
|
||||
android:gravity="center"
|
||||
android:text="@string/chapters_label"
|
||||
android:textColor="?android:attr/textColorSecondary"
|
||||
android:textSize="@dimen/text_size_navdrawer" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/butNextChapter"
|
||||
android:layout_width="36dp"
|
||||
android:layout_height="36dp"
|
||||
android:background="?attr/selectableItemBackgroundBorderless"
|
||||
android:contentDescription="@string/next_chapter"
|
||||
android:scaleType="fitCenter"
|
||||
app:srcCompat="@drawable/ic_chapter_next" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/coverHolder"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="10dp">
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/imgvCover"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:layout_gravity="center"
|
||||
android:layout_marginHorizontal="32dp"
|
||||
android:foreground="?attr/selectableItemBackgroundBorderless"
|
||||
android:importantForAccessibility="no"
|
||||
android:scaleType="fitCenter"
|
||||
app:layout_constraintDimensionRatio="1:1"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
tools:src="@android:drawable/sym_def_app_icon" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</ScrollView>
|
|
@ -14,13 +14,28 @@
|
|||
android:background="#262C31"
|
||||
tools:ignore="UselessParent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_marginStart="12dp"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="15sp"
|
||||
android:textStyle="bold"
|
||||
android:visibility="gone" />
|
||||
|
||||
<ImageButton
|
||||
android:id="@+id/butPlay"
|
||||
android:layout_width="@android:dimen/app_icon_size"
|
||||
android:layout_height="match_parent"
|
||||
android:contentDescription="@string/play_label"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_margin="12dp"
|
||||
android:layout_below="@id/txtvTitle"
|
||||
android:layout_marginHorizontal="12dp"
|
||||
android:background="?android:attr/selectableItemBackground"
|
||||
android:scaleType="fitCenter"
|
||||
android:padding="8dp"
|
||||
|
@ -31,6 +46,7 @@
|
|||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_below="@id/txtvTitle"
|
||||
android:layout_toStartOf="@id/butPlay"
|
||||
android:background="@android:color/transparent"
|
||||
android:gravity="fill_horizontal"
|
||||
|
@ -42,7 +58,8 @@
|
|||
android:layout_height="match_parent"
|
||||
android:src="@mipmap/ic_launcher"
|
||||
android:importantForAccessibility="no"
|
||||
android:layout_margin="12dp" />
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginEnd="12dp"/>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/layout_center"
|
||||
|
@ -61,17 +78,6 @@
|
|||
android:textSize="16sp"
|
||||
android:textStyle="bold" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvTitle"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:maxLines="1"
|
||||
android:ellipsize="end"
|
||||
android:textColor="@color/white"
|
||||
android:textSize="16sp"
|
||||
android:textStyle="bold"
|
||||
android:visibility="gone" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/txtvProgress"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
|
@ -32,12 +32,14 @@
|
|||
|
||||
<item
|
||||
android:id="@+id/add_to_favorites_item"
|
||||
android:menuCategory="container"
|
||||
android:icon="@drawable/ic_star_border"
|
||||
custom:showAsAction="ifRoom|collapseActionView"
|
||||
android:title="@string/add_to_favorite_label" />
|
||||
|
||||
<item
|
||||
android:id="@+id/remove_from_favorites_item"
|
||||
android:menuCategory="container"
|
||||
android:icon="@drawable/ic_star"
|
||||
custom:showAsAction="ifRoom|collapseActionView"
|
||||
android:title="@string/remove_from_favorite_label" />
|
||||
|
||||
<item
|
||||
|
@ -53,12 +55,11 @@
|
|||
</item>
|
||||
<item
|
||||
android:id="@+id/share_item"
|
||||
android:menuCategory="container"
|
||||
android:icon="@drawable/ic_share"
|
||||
android:title="@string/share_label">
|
||||
</item>
|
||||
<item
|
||||
android:id="@+id/share_notes"
|
||||
android:menuCategory="container"
|
||||
android:title="@string/share_notes_label">
|
||||
</item>
|
||||
<item
|
||||
|
|
|
@ -47,6 +47,7 @@
|
|||
|
||||
<item
|
||||
android:id="@+id/share_item"
|
||||
android:icon="@drawable/ic_share"
|
||||
android:menuCategory="container"
|
||||
android:title="@string/share_label" />
|
||||
|
||||
|
|
|
@ -28,6 +28,14 @@
|
|||
android:title="@string/set_sleeptimer_label">
|
||||
</item>
|
||||
|
||||
<item
|
||||
android:id="@+id/player_switch_to_audio_only"
|
||||
custom:showAsAction="always"
|
||||
android:icon="@drawable/baseline_audiotrack_24"
|
||||
android:title="@string/player_switch_to_audio_only"
|
||||
android:visible="false">
|
||||
</item>
|
||||
|
||||
<item
|
||||
android:id="@+id/audio_controls"
|
||||
android:title="@string/audio_controls"
|
||||
|
@ -58,13 +66,6 @@
|
|||
android:visible="false">
|
||||
</item>
|
||||
|
||||
<item
|
||||
android:id="@+id/player_switch_to_audio_only"
|
||||
custom:showAsAction="collapseActionView"
|
||||
android:title="@string/player_switch_to_audio_only"
|
||||
android:visible="false">
|
||||
</item>
|
||||
|
||||
<item
|
||||
android:id="@+id/player_show_chapters"
|
||||
custom:showAsAction="never"
|
||||
|
@ -76,7 +77,12 @@
|
|||
android:id="@+id/share_item"
|
||||
android:icon="@drawable/ic_share"
|
||||
android:menuCategory="container"
|
||||
custom:showAsAction="always"
|
||||
custom:showAsAction="ifRoom"
|
||||
android:title="@string/share_label">
|
||||
</item>
|
||||
|
||||
<item
|
||||
android:id="@+id/share_notes"
|
||||
android:title="@string/share_notes_label">
|
||||
</item>
|
||||
</menu>
|
|
@ -777,7 +777,7 @@
|
|||
<!-- Audio controls -->
|
||||
<string name="audio_controls">Audio controls</string>
|
||||
<string name="playback_speed">Playback speed</string>
|
||||
<string name="player_switch_to_audio_only">Switch to audio only</string>
|
||||
<string name="player_switch_to_audio_only">Audio only</string>
|
||||
|
||||
<!-- proxy settings -->
|
||||
<string name="proxy_type_label">Type</string>
|
||||
|
|
|
@ -1,12 +1,13 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<appwidget-provider xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:resizeMode="horizontal"
|
||||
android:initialLayout="@layout/player_widget"
|
||||
android:updatePeriodMillis="86400000"
|
||||
android:previewImage="@drawable/ic_widget_preview"
|
||||
android:minHeight="40dp"
|
||||
android:minWidth="250dp"
|
||||
android:minResizeWidth="40dp"
|
||||
android:widgetFeatures="reconfigurable"
|
||||
android:configure="ac.mdiq.podcini.ui.activity.WidgetConfigActivity">
|
||||
<appwidget-provider
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:resizeMode="horizontal|vertical"
|
||||
android:initialLayout="@layout/player_widget"
|
||||
android:updatePeriodMillis="86400000"
|
||||
android:previewImage="@drawable/ic_widget_preview"
|
||||
android:minHeight="40dp"
|
||||
android:minWidth="100dp"
|
||||
android:minResizeWidth="70dp"
|
||||
android:configure="ac.mdiq.podcini.ui.activity.WidgetConfigActivity"
|
||||
android:widgetFeatures="reconfigurable">
|
||||
</appwidget-provider>
|
||||
|
|
14
changelog.md
14
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
|
||||
* 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
|
|
@ -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
|
Loading…
Reference in New Issue