null safety tuning and bug fixes

This commit is contained in:
Xilin Jia 2024-02-17 23:46:14 +01:00
parent 0e36e6f39e
commit 45abbd34b9
92 changed files with 2063 additions and 2102 deletions

View File

@ -4,7 +4,7 @@ PodVinci is an open source podcast manager/player project.
This is based on a fork from the popular project AntennaPod (https://github.com/AntennaPod/AntennaPod) as of Feb 5 2024.
This project is purely Kotlin based, relies on the most recent dependencies, and most importantly has migrated the media player to androidx.media3, and added mechanism of AudioOffloadMode which is supposed to be kind to device battery. App build is also upgraded to target Android 14.
Differing from the forked project, this project is purely Kotlin based, relies on the most recent dependencies, and most importantly has migrated the media player to androidx.media3, and added mechanism of AudioOffloadMode which is supposed to be kind to device battery. Efficiencies are also sought on running the app. App build is also upgraded to target Android 14.
## Privacy Policy

View File

@ -8,7 +8,6 @@
tools:ignore="ScopedStorage" />
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<supports-screens
@ -43,6 +42,13 @@
android:allowAudioPlaybackCapture="true"
android:networkSecurityConfig="@xml/network_security_config">
<!-- <service-->
<!-- android:name=".core.service.playback.PlaybackService"-->
<!-- android:foregroundServiceType="mediaPlayback"-->
<!-- android:exported="false"-->
<!-- tools:replace="android:exported">-->
<!-- </service>-->
<activity
android:name=".activity.PlaybackSpeedDialogActivity"
android:noHistory="true"

View File

@ -104,20 +104,20 @@ class OnlineFeedViewActivity : AppCompatActivity() {
private var parser: Disposable? = null
private var updater: Disposable? = null
private var headerBinding: OnlinefeedviewHeaderBinding? = null
private var viewBinding: OnlinefeedviewActivityBinding? = null
private lateinit var headerBinding: OnlinefeedviewHeaderBinding
private lateinit var viewBinding: OnlinefeedviewActivityBinding
override fun onCreate(savedInstanceState: Bundle?) {
setTheme(getTranslucentTheme(this))
super.onCreate(savedInstanceState)
viewBinding = OnlinefeedviewActivityBinding.inflate(layoutInflater)
setContentView(viewBinding!!.root)
setContentView(viewBinding.root)
viewBinding!!.transparentBackground.setOnClickListener { v: View? -> finish() }
viewBinding!!.closeButton.setOnClickListener { view: View? -> finish() }
viewBinding!!.card.setOnClickListener(null)
viewBinding!!.card.setCardBackgroundColor(getColorFromAttr(this, R.attr.colorSurface))
viewBinding.transparentBackground.setOnClickListener { v: View? -> finish() }
viewBinding.closeButton.setOnClickListener { view: View? -> finish() }
viewBinding.card.setOnClickListener(null)
viewBinding.card.setCardBackgroundColor(getColorFromAttr(this, R.attr.colorSurface))
headerBinding = OnlinefeedviewHeaderBinding.inflate(layoutInflater)
var feedUrl: String? = null
@ -165,8 +165,8 @@ class OnlineFeedViewActivity : AppCompatActivity() {
* Displays a progress indicator.
*/
private fun setLoadingLayout() {
viewBinding!!.progressBar.visibility = View.VISIBLE
viewBinding!!.feedDisplayContainer.visibility = View.GONE
viewBinding.progressBar.visibility = View.VISIBLE
viewBinding.feedDisplayContainer.visibility = View.GONE
}
override fun onStart() {
@ -189,15 +189,9 @@ class OnlineFeedViewActivity : AppCompatActivity() {
public override fun onDestroy() {
super.onDestroy()
if (updater != null) {
updater!!.dispose()
}
if (download != null) {
download!!.dispose()
}
if (parser != null) {
parser!!.dispose()
}
updater?.dispose()
download?.dispose()
parser?.dispose()
}
override fun onSaveInstanceState(outState: Bundle) {
@ -264,8 +258,9 @@ class OnlineFeedViewActivity : AppCompatActivity() {
val results = searcher.search(query)?.blockingGet()
if (results.isNullOrEmpty()) return null
for (result in results) {
if (result?.feedUrl != null && result.author != null && result.author.equals(artistName,
ignoreCase = true) && result.title.equals(trackName, ignoreCase = true)) {
if (result?.feedUrl != null && result.author != null &&
result.author.equals(artistName, ignoreCase = true) &&
result.title.equals(trackName, ignoreCase = true)) {
return result.feedUrl
}
}
@ -301,7 +296,7 @@ class OnlineFeedViewActivity : AppCompatActivity() {
if (username != null && password != null) {
Toast.makeText(this, R.string.download_error_unauthorized, Toast.LENGTH_LONG).show()
}
if (downloader!!.downloadRequest.source != null) {
if (downloader?.downloadRequest?.source != null) {
dialog = FeedViewAuthenticationDialog(this@OnlineFeedViewActivity,
R.string.authentication_notification_title, downloader!!.downloadRequest.source!!).create()
dialog?.show()
@ -376,8 +371,7 @@ class OnlineFeedViewActivity : AppCompatActivity() {
} else {
throw UnsupportedFeedtypeException(getString(R.string.download_error_unsupported_type_html))
}
}
else null
} else null
} else {
throw e
}
@ -395,18 +389,18 @@ class OnlineFeedViewActivity : AppCompatActivity() {
* This method is executed on the GUI thread.
*/
@UnstableApi private fun showFeedInformation(feed: Feed, alternateFeedUrls: Map<String, String>) {
viewBinding!!.progressBar.visibility = View.GONE
viewBinding!!.feedDisplayContainer.visibility = View.VISIBLE
viewBinding.progressBar.visibility = View.GONE
viewBinding.feedDisplayContainer.visibility = View.VISIBLE
if (isFeedFoundBySearch) {
val resId = R.string.no_feed_url_podcast_found_by_search
Snackbar.make(findViewById(android.R.id.content), resId, Snackbar.LENGTH_LONG).show()
}
viewBinding!!.backgroundImage.colorFilter = LightingColorFilter(-0x7d7d7e, 0x000000)
viewBinding.backgroundImage.colorFilter = LightingColorFilter(-0x7d7d7e, 0x000000)
viewBinding!!.listView.addHeaderView(headerBinding!!.root)
viewBinding!!.listView.setSelector(android.R.color.transparent)
viewBinding!!.listView.adapter = FeedItemlistDescriptionAdapter(this, 0, feed.items)
viewBinding.listView.addHeaderView(headerBinding.root)
viewBinding.listView.setSelector(android.R.color.transparent)
viewBinding.listView.adapter = FeedItemlistDescriptionAdapter(this, 0, feed.items)
if (StringUtils.isNotBlank(feed.imageUrl)) {
Glide.with(this)
@ -416,7 +410,7 @@ class OnlineFeedViewActivity : AppCompatActivity() {
.error(R.color.light_gray)
.fitCenter()
.dontAnimate())
.into(viewBinding!!.coverImage)
.into(viewBinding.coverImage)
Glide.with(this)
.load(feed.imageUrl)
.apply(RequestOptions()
@ -424,14 +418,14 @@ class OnlineFeedViewActivity : AppCompatActivity() {
.error(R.color.image_readability_tint)
.transform(FastBlurTransformation())
.dontAnimate())
.into(viewBinding!!.backgroundImage)
.into(viewBinding.backgroundImage)
}
viewBinding!!.titleLabel.text = feed.title
viewBinding!!.authorLabel.text = feed.author
headerBinding!!.txtvDescription.text = HtmlToPlainText.getPlainText(feed.description)
viewBinding.titleLabel.text = feed.title
viewBinding.authorLabel.text = feed.author
headerBinding.txtvDescription.text = HtmlToPlainText.getPlainText(feed.description)
viewBinding!!.subscribeButton.setOnClickListener { v: View? ->
viewBinding.subscribeButton.setOnClickListener { v: View? ->
if (feedInFeedlist()) {
openFeed()
} else {
@ -441,29 +435,29 @@ class OnlineFeedViewActivity : AppCompatActivity() {
}
}
viewBinding!!.stopPreviewButton.setOnClickListener { v: View? ->
viewBinding.stopPreviewButton.setOnClickListener { v: View? ->
writeNoMediaPlaying()
sendLocalBroadcast(this, PlaybackServiceInterface.ACTION_SHUTDOWN_PLAYBACK_SERVICE)
}
if (isEnableAutodownload) {
val preferences = getSharedPreferences(PREFS, MODE_PRIVATE)
viewBinding!!.autoDownloadCheckBox.isChecked = preferences.getBoolean(PREF_LAST_AUTO_DOWNLOAD, true)
viewBinding.autoDownloadCheckBox.isChecked = preferences.getBoolean(PREF_LAST_AUTO_DOWNLOAD, true)
}
headerBinding!!.txtvDescription.maxLines = DESCRIPTION_MAX_LINES_COLLAPSED
headerBinding!!.txtvDescription.setOnClickListener { v: View? ->
if (headerBinding!!.txtvDescription.maxLines > DESCRIPTION_MAX_LINES_COLLAPSED) {
headerBinding!!.txtvDescription.maxLines = DESCRIPTION_MAX_LINES_COLLAPSED
headerBinding.txtvDescription.maxLines = DESCRIPTION_MAX_LINES_COLLAPSED
headerBinding.txtvDescription.setOnClickListener { v: View? ->
if (headerBinding.txtvDescription.maxLines > DESCRIPTION_MAX_LINES_COLLAPSED) {
headerBinding.txtvDescription.maxLines = DESCRIPTION_MAX_LINES_COLLAPSED
} else {
headerBinding!!.txtvDescription.maxLines = 2000
headerBinding.txtvDescription.maxLines = 2000
}
}
if (alternateFeedUrls.isEmpty()) {
viewBinding!!.alternateUrlsSpinner.visibility = View.GONE
viewBinding.alternateUrlsSpinner.visibility = View.GONE
} else {
viewBinding!!.alternateUrlsSpinner.visibility = View.VISIBLE
viewBinding.alternateUrlsSpinner.visibility = View.VISIBLE
val alternateUrlsList: MutableList<String> = ArrayList()
val alternateUrlsTitleList: MutableList<String?> = ArrayList()
@ -486,8 +480,8 @@ class OnlineFeedViewActivity : AppCompatActivity() {
}
adapter.setDropDownViewResource(R.layout.alternate_urls_dropdown_item)
viewBinding!!.alternateUrlsSpinner.adapter = adapter
viewBinding!!.alternateUrlsSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
viewBinding.alternateUrlsSpinner.adapter = adapter
viewBinding.alternateUrlsSpinner.onItemSelectedListener = object : AdapterView.OnItemSelectedListener {
override fun onItemSelected(parent: AdapterView<*>?, view: View, position: Int, id: Long) {
selectedDownloadUrl = alternateUrlsList[position]
}
@ -510,13 +504,15 @@ class OnlineFeedViewActivity : AppCompatActivity() {
}
@UnstableApi private fun handleUpdatedFeedStatus() {
if (DownloadServiceInterface.get() == null || selectedDownloadUrl == null) return
if (DownloadServiceInterface.get()!!.isDownloadingEpisode(selectedDownloadUrl!!)) {
viewBinding!!.subscribeButton.isEnabled = false
viewBinding!!.subscribeButton.setText(R.string.subscribing_label)
val dli = DownloadServiceInterface.get()
if (dli == null || selectedDownloadUrl == null) return
if (dli.isDownloadingEpisode(selectedDownloadUrl!!)) {
viewBinding.subscribeButton.isEnabled = false
viewBinding.subscribeButton.setText(R.string.subscribing_label)
} else if (feedInFeedlist()) {
viewBinding!!.subscribeButton.isEnabled = true
viewBinding!!.subscribeButton.setText(R.string.open_podcast)
viewBinding.subscribeButton.isEnabled = true
viewBinding.subscribeButton.setText(R.string.open_podcast)
if (didPressSubscribe) {
didPressSubscribe = false
@ -524,7 +520,7 @@ class OnlineFeedViewActivity : AppCompatActivity() {
val feedPreferences = feed1.preferences
if (feedPreferences != null) {
if (isEnableAutodownload) {
val autoDownload = viewBinding!!.autoDownloadCheckBox.isChecked
val autoDownload = viewBinding.autoDownloadCheckBox.isChecked
feedPreferences.autoDownload = autoDownload
val preferences = getSharedPreferences(PREFS, MODE_PRIVATE)
@ -541,10 +537,10 @@ class OnlineFeedViewActivity : AppCompatActivity() {
openFeed()
}
} else {
viewBinding!!.subscribeButton.isEnabled = true
viewBinding!!.subscribeButton.setText(R.string.subscribe_label)
viewBinding.subscribeButton.isEnabled = true
viewBinding.subscribeButton.setText(R.string.subscribe_label)
if (isEnableAutodownload) {
viewBinding!!.autoDownloadCheckBox.visibility = View.VISIBLE
viewBinding.autoDownloadCheckBox.visibility = View.VISIBLE
}
}
}
@ -622,7 +618,7 @@ class OnlineFeedViewActivity : AppCompatActivity() {
@Subscribe(threadMode = ThreadMode.MAIN)
fun playbackStateChanged(event: PlayerStatusEvent?) {
val isPlayingPreview = currentlyPlayingMediaType == RemoteMedia.PLAYABLE_TYPE_REMOTE_MEDIA.toLong()
viewBinding!!.stopPreviewButton.visibility = if (isPlayingPreview) View.VISIBLE else View.GONE
viewBinding.stopPreviewButton.visibility = if (isPlayingPreview) View.VISIBLE else View.GONE
}
/**
@ -631,7 +627,7 @@ class OnlineFeedViewActivity : AppCompatActivity() {
*/
private fun showFeedDiscoveryDialog(feedFile: File, baseUrl: String): Boolean {
val fd = FeedDiscoverer()
val urlsMap: Map<String, String>?
val urlsMap: Map<String, String>
try {
urlsMap = fd.findLinks(feedFile, baseUrl)
if (urlsMap.isEmpty()) {

View File

@ -45,54 +45,59 @@ import java.io.Reader
*/
class OpmlImportActivity : AppCompatActivity() {
private var uri: Uri? = null
private var viewBinding: OpmlSelectionBinding? = null
private lateinit var viewBinding: OpmlSelectionBinding
private lateinit var selectAll: MenuItem
private lateinit var deselectAll: MenuItem
private var listAdapter: ArrayAdapter<String>? = null
private var selectAll: MenuItem? = null
private var deselectAll: MenuItem? = null
private var readElements: ArrayList<OpmlElement>? = null
@UnstableApi override fun onCreate(savedInstanceState: Bundle?) {
setTheme(getTheme(this))
super.onCreate(savedInstanceState)
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setDisplayHomeAsUpEnabled(true)
viewBinding = OpmlSelectionBinding.inflate(layoutInflater)
setContentView(viewBinding!!.root)
setContentView(viewBinding.root)
viewBinding!!.feedlist.choiceMode = ListView.CHOICE_MODE_MULTIPLE
viewBinding!!.feedlist.onItemClickListener =
viewBinding.feedlist.choiceMode = ListView.CHOICE_MODE_MULTIPLE
viewBinding.feedlist.onItemClickListener =
OnItemClickListener { parent: AdapterView<*>?, view: View?, position: Int, id: Long ->
val checked = viewBinding!!.feedlist.checkedItemPositions
val checked = viewBinding.feedlist.checkedItemPositions
var checkedCount = 0
for (i in 0 until checked.size()) {
if (checked.valueAt(i)) {
checkedCount++
}
}
if (checkedCount == listAdapter!!.count) {
selectAll!!.setVisible(false)
deselectAll!!.setVisible(true)
} else {
deselectAll!!.setVisible(false)
selectAll!!.setVisible(true)
if (listAdapter != null) {
if (checkedCount == listAdapter!!.count) {
selectAll.setVisible(false)
deselectAll.setVisible(true)
} else {
deselectAll.setVisible(false)
selectAll.setVisible(true)
}
}
}
viewBinding!!.butCancel.setOnClickListener { v: View? ->
viewBinding.butCancel.setOnClickListener { v: View? ->
setResult(RESULT_CANCELED)
finish()
}
viewBinding!!.butConfirm.setOnClickListener { v: View? ->
viewBinding!!.progressBar.visibility = View.VISIBLE
viewBinding.butConfirm.setOnClickListener { v: View? ->
viewBinding.progressBar.visibility = View.VISIBLE
Completable.fromAction {
val checked = viewBinding!!.feedlist.checkedItemPositions
val checked = viewBinding.feedlist.checkedItemPositions
for (i in 0 until checked.size()) {
if (!checked.valueAt(i)) {
continue
}
val element = readElements!![checked.keyAt(i)]
val feed = Feed(element.xmlUrl, null,
if (element.text != null) element.text else "Unknown podcast")
feed.items = mutableListOf()
DBTasks.updateFeed(this, feed, false)
if (!readElements.isNullOrEmpty()) {
val element = readElements!![checked.keyAt(i)]
val feed = Feed(element.xmlUrl, null,
if (element.text != null) element.text else "Unknown podcast")
feed.items = mutableListOf()
DBTasks.updateFeed(this, feed, false)
}
}
runOnce(this)
}
@ -100,14 +105,14 @@ class OpmlImportActivity : AppCompatActivity() {
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{
viewBinding!!.progressBar.visibility = View.GONE
viewBinding.progressBar.visibility = View.GONE
val intent = Intent(this@OpmlImportActivity, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
finish()
}, { e: Throwable ->
e.printStackTrace()
viewBinding!!.progressBar.visibility = View.GONE
viewBinding.progressBar.visibility = View.GONE
Toast.makeText(this, e.message, Toast.LENGTH_LONG).show()
})
}
@ -139,7 +144,7 @@ class OpmlImportActivity : AppCompatActivity() {
private val titleList: List<String>
get() {
val result: MutableList<String> = ArrayList()
if (readElements != null) {
if (!readElements.isNullOrEmpty()) {
for (element in readElements!!) {
if (element.text != null) result.add(element.text!!)
}
@ -153,7 +158,7 @@ class OpmlImportActivity : AppCompatActivity() {
inflater.inflate(R.menu.opml_selection_options, menu)
selectAll = menu.findItem(R.id.select_all_item)
deselectAll = menu.findItem(R.id.deselect_all_item)
deselectAll?.setVisible(false)
deselectAll.setVisible(false)
return true
}
@ -161,15 +166,15 @@ class OpmlImportActivity : AppCompatActivity() {
val itemId = item.itemId
when (itemId) {
R.id.select_all_item -> {
selectAll!!.setVisible(false)
selectAll.setVisible(false)
selectAllItems(true)
deselectAll!!.setVisible(true)
deselectAll.setVisible(true)
return true
}
R.id.deselect_all_item -> {
deselectAll!!.setVisible(false)
deselectAll.setVisible(false)
selectAllItems(false)
selectAll!!.setVisible(true)
selectAll.setVisible(true)
return true
}
android.R.id.home -> {
@ -180,8 +185,8 @@ class OpmlImportActivity : AppCompatActivity() {
}
private fun selectAllItems(b: Boolean) {
for (i in 0 until viewBinding!!.feedlist.count) {
viewBinding!!.feedlist.setItemChecked(i, b)
for (i in 0 until viewBinding.feedlist.count) {
viewBinding.feedlist.setItemChecked(i, b)
}
}
@ -204,13 +209,13 @@ class OpmlImportActivity : AppCompatActivity() {
/** Starts the import process. */
private fun startImport() {
viewBinding!!.progressBar.visibility = View.VISIBLE
viewBinding.progressBar.visibility = View.VISIBLE
Observable.fromCallable {
val opmlFileStream = contentResolver.openInputStream(uri!!)
val bomInputStream = BOMInputStream(opmlFileStream)
val bom = bomInputStream.bom
val charsetName = if ((bom == null)) "UTF-8" else bom.charsetName
val charsetName = if (bom == null) "UTF-8" else bom.charsetName
val reader: Reader = InputStreamReader(bomInputStream, charsetName)
val opmlReader = OpmlReader()
val result = opmlReader.readDocument(reader)
@ -221,13 +226,13 @@ class OpmlImportActivity : AppCompatActivity() {
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ result: ArrayList<OpmlElement>? ->
viewBinding!!.progressBar.visibility = View.GONE
viewBinding.progressBar.visibility = View.GONE
Log.d(TAG, "Parsing was successful")
readElements = result
listAdapter = ArrayAdapter(this@OpmlImportActivity,
android.R.layout.simple_list_item_multiple_choice,
titleList)
viewBinding!!.feedlist.adapter = listAdapter
viewBinding.feedlist.adapter = listAdapter
}, { e: Throwable ->
Log.d(TAG, Log.getStackTraceString(e))
val message = if (e.message == null) "" else e.message!!
@ -240,7 +245,7 @@ class OpmlImportActivity : AppCompatActivity() {
return@subscribe
}
}
viewBinding!!.progressBar.visibility = View.GONE
viewBinding.progressBar.visibility = View.GONE
val alert = MaterialAlertDialogBuilder(this)
alert.setTitle(R.string.error_label)
val userReadable = getString(R.string.opml_reader_error)

View File

@ -30,7 +30,7 @@ import org.greenrobot.eventbus.ThreadMode
* PreferenceController.
*/
class PreferenceActivity : AppCompatActivity(), SearchPreferenceResultListener {
private var binding: SettingsActivityBinding? = null
private lateinit var binding: SettingsActivityBinding
@SuppressLint("CommitTransaction")
override fun onCreate(savedInstanceState: Bundle?) {
@ -41,11 +41,11 @@ class PreferenceActivity : AppCompatActivity(), SearchPreferenceResultListener {
ab?.setDisplayHomeAsUpEnabled(true)
binding = SettingsActivityBinding.inflate(layoutInflater)
setContentView(binding!!.root)
setContentView(binding.root)
if (supportFragmentManager.findFragmentByTag(FRAGMENT_TAG) == null) {
supportFragmentManager.beginTransaction()
.replace(binding!!.settingsContainer.id, MainPreferencesFragment(), FRAGMENT_TAG)
.replace(binding.settingsContainer.id, MainPreferencesFragment(), FRAGMENT_TAG)
.commit()
}
val intent = intent
@ -96,7 +96,7 @@ class PreferenceActivity : AppCompatActivity(), SearchPreferenceResultListener {
startActivity(intent)
} else {
supportFragmentManager.beginTransaction()
.replace(binding!!.settingsContainer.id, fragment!!)
.replace(binding.settingsContainer.id, fragment!!)
.addToBackStack(getString(getTitleOfPage(screen)))
.commit()
}
@ -156,7 +156,7 @@ class PreferenceActivity : AppCompatActivity(), SearchPreferenceResultListener {
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(event: MessageEvent) {
Log.d(FRAGMENT_TAG, "onEvent($event)")
val s = Snackbar.make(binding!!.root, event.message, Snackbar.LENGTH_LONG)
val s = Snackbar.make(binding.root, event.message, Snackbar.LENGTH_LONG)
if (event.action != null) {
s.setAction(event.actionText) { v: View? -> event.action!!.accept(this) }
}

View File

@ -1,7 +1,13 @@
package ac.mdiq.podvinci.activity
import ac.mdiq.podvinci.activity.MainActivity
import ac.mdiq.podvinci.R
import ac.mdiq.podvinci.activity.MainActivity.Companion.EXTRA_FEED_ID
import ac.mdiq.podvinci.core.preferences.ThemeSwitcher
import ac.mdiq.podvinci.core.storage.DBReader
import ac.mdiq.podvinci.core.storage.NavDrawerData
import ac.mdiq.podvinci.databinding.SubscriptionSelectionActivityBinding
import ac.mdiq.podvinci.model.feed.Feed
import ac.mdiq.podvinci.storage.preferences.UserPreferences
import android.app.Activity
import android.content.Intent
import android.graphics.Bitmap
@ -15,19 +21,13 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.pm.ShortcutInfoCompat
import androidx.core.content.pm.ShortcutManagerCompat
import androidx.core.graphics.drawable.IconCompat
import androidx.media3.common.util.UnstableApi
import com.bumptech.glide.Glide
import com.bumptech.glide.load.DataSource
import com.bumptech.glide.load.engine.GlideException
import com.bumptech.glide.request.RequestListener
import com.bumptech.glide.request.RequestOptions
import com.bumptech.glide.request.target.Target
import ac.mdiq.podvinci.R
import ac.mdiq.podvinci.core.preferences.ThemeSwitcher
import ac.mdiq.podvinci.core.storage.DBReader
import ac.mdiq.podvinci.core.storage.NavDrawerData
import ac.mdiq.podvinci.databinding.SubscriptionSelectionActivityBinding
import ac.mdiq.podvinci.model.feed.Feed
import ac.mdiq.podvinci.storage.preferences.UserPreferences
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
@ -39,29 +39,29 @@ class SelectSubscriptionActivity : AppCompatActivity() {
@Volatile
private var listItems: List<Feed>? = null
private var viewBinding: SubscriptionSelectionActivityBinding? = null
private lateinit var viewBinding: SubscriptionSelectionActivityBinding
override fun onCreate(savedInstanceState: Bundle?) {
setTheme(ThemeSwitcher.getTranslucentTheme(this))
super.onCreate(savedInstanceState)
viewBinding = SubscriptionSelectionActivityBinding.inflate(layoutInflater)
setContentView(viewBinding!!.root)
setSupportActionBar(viewBinding!!.toolbar)
setContentView(viewBinding.root)
setSupportActionBar(viewBinding.toolbar)
setTitle(R.string.shortcut_select_subscription)
viewBinding!!.transparentBackground.setOnClickListener { v: View? -> finish() }
viewBinding!!.card.setOnClickListener(null)
viewBinding.transparentBackground.setOnClickListener { v: View? -> finish() }
viewBinding.card.setOnClickListener(null)
loadSubscriptions()
val checkedPosition = arrayOfNulls<Int>(1)
viewBinding!!.list.choiceMode = ListView.CHOICE_MODE_SINGLE
viewBinding!!.list.onItemClickListener =
viewBinding.list.choiceMode = ListView.CHOICE_MODE_SINGLE
viewBinding.list.onItemClickListener =
AdapterView.OnItemClickListener { listView: AdapterView<*>?, view1: View?, position: Int, rowId: Long ->
checkedPosition[0] = position
}
viewBinding!!.shortcutBtn.setOnClickListener { view: View? ->
viewBinding.shortcutBtn.setOnClickListener { view: View? ->
if (checkedPosition[0] != null && Intent.ACTION_CREATE_SHORTCUT == intent.action) {
getBitmapFromUrl(listItems!![checkedPosition[0]!!])
}
@ -83,7 +83,7 @@ class SelectSubscriptionActivity : AppCompatActivity() {
return result
}
private fun addShortcut(feed: Feed, bitmap: Bitmap?) {
@UnstableApi private fun addShortcut(feed: Feed, bitmap: Bitmap?) {
val intent = Intent(this, MainActivity::class.java)
intent.setAction(Intent.ACTION_MAIN)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
@ -114,15 +114,15 @@ class SelectSubscriptionActivity : AppCompatActivity() {
.load(feed.imageUrl)
.apply(RequestOptions.overrideOf(iconSize, iconSize))
.listener(object : RequestListener<Bitmap?> {
override fun onLoadFailed(e: GlideException?, model: Any?,
target: Target<Bitmap?>, isFirstResource: Boolean
@UnstableApi override fun onLoadFailed(e: GlideException?, model: Any?,
target: Target<Bitmap?>, isFirstResource: Boolean
): Boolean {
addShortcut(feed, null)
return true
}
override fun onResourceReady(resource: Bitmap, model: Any,
target: Target<Bitmap?>, dataSource: DataSource, isFirstResource: Boolean
@UnstableApi override fun onResourceReady(resource: Bitmap, model: Any,
target: Target<Bitmap?>, dataSource: DataSource, isFirstResource: Boolean
): Boolean {
addShortcut(feed, resource)
return true
@ -131,9 +131,8 @@ class SelectSubscriptionActivity : AppCompatActivity() {
}
private fun loadSubscriptions() {
if (disposable != null) {
disposable?.dispose()
}
disposable?.dispose()
disposable = Observable.fromCallable {
val data: NavDrawerData = DBReader.getNavDrawerData(UserPreferences.subscriptionsFilter)
getFeedItems(data.items, ArrayList())
@ -149,7 +148,7 @@ class SelectSubscriptionActivity : AppCompatActivity() {
}
val adapter: ArrayAdapter<String> = ArrayAdapter<String>(this,
R.layout.simple_list_item_multiple_choice_on_start, titles)
viewBinding!!.list.adapter = adapter
viewBinding.list.adapter = adapter
}, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
}

View File

@ -66,6 +66,9 @@ import org.greenrobot.eventbus.ThreadMode
*/
@UnstableApi
class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
private lateinit var viewBinding: VideoplayerActivityBinding
/**
* True if video controls are currently visible.
*/
@ -74,7 +77,6 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
private var destroyingDueToReload = false
private var lastScreenTap: Long = 0
private val videoControlsHider = Handler(Looper.getMainLooper())
private var viewBinding: VideoplayerActivityBinding? = null
private var controller: PlaybackController? = null
private var showTimeLeft = false
private var isFavorite = false
@ -94,10 +96,10 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
window.setFormat(PixelFormat.TRANSPARENT)
viewBinding = VideoplayerActivityBinding.inflate(LayoutInflater.from(this))
setContentView(viewBinding!!.root)
setContentView(viewBinding.root)
setupView()
supportActionBar!!.setBackgroundDrawable(ColorDrawable(-0x80000000))
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
supportActionBar?.setBackgroundDrawable(ColorDrawable(-0x80000000))
supportActionBar?.setDisplayHomeAsUpEnabled(true)
}
@UnstableApi
@ -116,20 +118,17 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
@UnstableApi
override fun onStop() {
if (controller != null) {
controller!!.release()
controller = null // prevent leak
}
if (disposable != null) {
disposable!!.dispose()
}
controller?.release()
controller = null // prevent leak
disposable?.dispose()
EventBus.getDefault().unregister(this)
super.onStop()
if (!PictureInPictureUtil.isInPictureInPictureMode(this)) {
videoControlsHider.removeCallbacks(hideVideoControls)
}
// Controller released; we will not receive buffering updates
viewBinding!!.progressBar.visibility = View.GONE
viewBinding.progressBar.visibility = View.GONE
}
public override fun onUserLeaveHint() {
@ -172,7 +171,7 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
private fun newPlaybackController(): PlaybackController {
return object : PlaybackController(this@VideoplayerActivity) {
override fun updatePlayButtonShowsPlay(showPlay: Boolean) {
viewBinding!!.playButton.setIsShowPlay(showPlay)
viewBinding.playButton.setIsShowPlay(showPlay)
if (showPlay) {
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
} else {
@ -180,7 +179,7 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
setupVideoAspectRatio()
if (videoSurfaceCreated && controller != null) {
Log.d(TAG, "Videosurface already created, setting videosurface now")
controller!!.setVideoSurface(viewBinding!!.videoView.holder)
controller!!.setVideoSurface(viewBinding.videoView.holder)
}
}
}
@ -199,11 +198,11 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
@Suppress("unused")
fun bufferUpdate(event: BufferUpdateEvent) {
if (event.hasStarted()) {
viewBinding!!.progressBar.visibility = View.VISIBLE
viewBinding.progressBar.visibility = View.VISIBLE
} else if (event.hasEnded()) {
viewBinding!!.progressBar.visibility = View.INVISIBLE
viewBinding.progressBar.visibility = View.INVISIBLE
} else {
viewBinding!!.sbPosition.secondaryProgress = (event.progress * viewBinding!!.sbPosition.max).toInt()
viewBinding.sbPosition.secondaryProgress = (event.progress * viewBinding.sbPosition.max).toInt()
}
}
@ -216,9 +215,9 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
}
@UnstableApi
protected fun loadMediaInfo() {
private fun loadMediaInfo() {
Log.d(TAG, "loadMediaInfo()")
if (controller == null || controller!!.getMedia() == null) {
if (controller?.getMedia() == null) {
return
}
if (controller!!.status == PlayerStatus.PLAYING && !controller!!.isPlayingVideoLocally) {
@ -239,12 +238,12 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
}
@UnstableApi
protected fun setupView() {
private fun setupView() {
showTimeLeft = shouldShowRemainingTime()
Log.d("timeleft", if (showTimeLeft) "true" else "false")
viewBinding!!.durationLabel.setOnClickListener { v: View? ->
viewBinding.durationLabel.setOnClickListener { v: View? ->
showTimeLeft = !showTimeLeft
val media = controller!!.getMedia() ?: return@setOnClickListener
val media = controller?.getMedia() ?: return@setOnClickListener
val converter = TimeSpeedConverter(controller!!.currentPlaybackSpeedMultiplier)
val length: String
@ -255,48 +254,48 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
val duration = converter.convert(media.getDuration())
length = getDurationStringLong(duration)
}
viewBinding!!.durationLabel.text = length
viewBinding.durationLabel.text = length
setShowRemainTimeSetting(showTimeLeft)
Log.d("timeleft on click", if (showTimeLeft) "true" else "false")
}
viewBinding!!.sbPosition.setOnSeekBarChangeListener(this)
viewBinding!!.rewindButton.setOnClickListener { v: View? -> onRewind() }
viewBinding!!.rewindButton.setOnLongClickListener { v: View? ->
viewBinding.sbPosition.setOnSeekBarChangeListener(this)
viewBinding.rewindButton.setOnClickListener { v: View? -> onRewind() }
viewBinding.rewindButton.setOnLongClickListener { v: View? ->
SkipPreferenceDialog.showSkipPreference(this@VideoplayerActivity,
SkipPreferenceDialog.SkipDirection.SKIP_REWIND, null)
true
}
viewBinding!!.playButton.setIsVideoScreen(true)
viewBinding!!.playButton.setOnClickListener { v: View? -> onPlayPause() }
viewBinding!!.fastForwardButton.setOnClickListener { v: View? -> onFastForward() }
viewBinding!!.fastForwardButton.setOnLongClickListener { v: View? ->
viewBinding.playButton.setIsVideoScreen(true)
viewBinding.playButton.setOnClickListener { v: View? -> onPlayPause() }
viewBinding.fastForwardButton.setOnClickListener { v: View? -> onFastForward() }
viewBinding.fastForwardButton.setOnLongClickListener { v: View? ->
SkipPreferenceDialog.showSkipPreference(this@VideoplayerActivity,
SkipPreferenceDialog.SkipDirection.SKIP_FORWARD, null)
false
}
// To suppress touches directly below the slider
viewBinding!!.bottomControlsContainer.setOnTouchListener { view: View?, motionEvent: MotionEvent? -> true }
viewBinding!!.bottomControlsContainer.fitsSystemWindows = true
viewBinding!!.videoView.holder.addCallback(surfaceHolderCallback)
viewBinding!!.videoView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
viewBinding.bottomControlsContainer.setOnTouchListener { view: View?, motionEvent: MotionEvent? -> true }
viewBinding.bottomControlsContainer.fitsSystemWindows = true
viewBinding.videoView.holder.addCallback(surfaceHolderCallback)
viewBinding.videoView.systemUiVisibility = View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
setupVideoControlsToggler()
window.setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
WindowManager.LayoutParams.FLAG_FULLSCREEN)
viewBinding!!.videoPlayerContainer.setOnTouchListener(onVideoviewTouched)
viewBinding!!.videoPlayerContainer.viewTreeObserver.addOnGlobalLayoutListener {
viewBinding!!.videoView.setAvailableSize(
viewBinding!!.videoPlayerContainer.width.toFloat(), viewBinding!!.videoPlayerContainer.height.toFloat())
viewBinding.videoPlayerContainer.setOnTouchListener(onVideoviewTouched)
viewBinding.videoPlayerContainer.viewTreeObserver.addOnGlobalLayoutListener {
viewBinding.videoView.setAvailableSize(
viewBinding.videoPlayerContainer.width.toFloat(), viewBinding.videoPlayerContainer.height.toFloat())
}
}
private val hideVideoControls = Runnable {
if (videoControlsShowing) {
Log.d(TAG, "Hiding video controls")
supportActionBar!!.hide()
supportActionBar?.hide()
hideVideoControls(true)
videoControlsShowing = false
}
@ -320,7 +319,7 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
showSkipAnimation(false)
}
if (videoControlsShowing) {
supportActionBar!!.hide()
supportActionBar?.hide()
hideVideoControls(false)
videoControlsShowing = false
}
@ -344,24 +343,24 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
skipAnimation.fillAfter = false
skipAnimation.duration = 800
val params = viewBinding!!.skipAnimationImage.layoutParams as FrameLayout.LayoutParams
val params = viewBinding.skipAnimationImage.layoutParams as FrameLayout.LayoutParams
if (isForward) {
viewBinding!!.skipAnimationImage.setImageResource(R.drawable.ic_fast_forward_video_white)
viewBinding.skipAnimationImage.setImageResource(R.drawable.ic_fast_forward_video_white)
params.gravity = Gravity.RIGHT or Gravity.CENTER_VERTICAL
} else {
viewBinding!!.skipAnimationImage.setImageResource(R.drawable.ic_fast_rewind_video_white)
viewBinding.skipAnimationImage.setImageResource(R.drawable.ic_fast_rewind_video_white)
params.gravity = Gravity.LEFT or Gravity.CENTER_VERTICAL
}
viewBinding!!.skipAnimationImage.visibility = View.VISIBLE
viewBinding!!.skipAnimationImage.layoutParams = params
viewBinding!!.skipAnimationImage.startAnimation(skipAnimation)
viewBinding.skipAnimationImage.visibility = View.VISIBLE
viewBinding.skipAnimationImage.layoutParams = params
viewBinding.skipAnimationImage.startAnimation(skipAnimation)
skipAnimation.setAnimationListener(object : Animation.AnimationListener {
override fun onAnimationStart(animation: Animation) {
}
override fun onAnimationEnd(animation: Animation) {
viewBinding!!.skipAnimationImage.visibility = View.GONE
viewBinding.skipAnimationImage.visibility = View.GONE
}
override fun onAnimationRepeat(animation: Animation) {
@ -380,7 +379,7 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
val videoSize = controller!!.videoSize
if (videoSize != null && videoSize.first > 0 && videoSize.second > 0) {
Log.d(TAG, "Width,height of video: " + videoSize.first + ", " + videoSize.second)
viewBinding!!.videoView.setVideoSize(videoSize.first, videoSize.second)
viewBinding.videoView.setVideoSize(videoSize.first, videoSize.second)
} else {
Log.e(TAG, "Could not determine video size")
}
@ -389,10 +388,10 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
private fun toggleVideoControlsVisibility() {
if (videoControlsShowing) {
supportActionBar!!.hide()
supportActionBar?.hide()
hideVideoControls(true)
} else {
supportActionBar!!.show()
supportActionBar?.show()
showVideoControls()
}
videoControlsShowing = !videoControlsShowing
@ -436,7 +435,7 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
override fun surfaceCreated(holder: SurfaceHolder) {
Log.d(TAG, "Videoview holder created")
videoSurfaceCreated = true
if (controller != null && controller!!.status == PlayerStatus.PLAYING) {
if (controller?.status == PlayerStatus.PLAYING) {
controller!!.setVideoSurface(holder)
}
setupVideoAspectRatio()
@ -452,31 +451,31 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
}
private fun showVideoControls() {
viewBinding!!.bottomControlsContainer.visibility = View.VISIBLE
viewBinding!!.controlsContainer.visibility = View.VISIBLE
viewBinding.bottomControlsContainer.visibility = View.VISIBLE
viewBinding.controlsContainer.visibility = View.VISIBLE
val animation = AnimationUtils.loadAnimation(this, R.anim.fade_in)
if (animation != null) {
viewBinding!!.bottomControlsContainer.startAnimation(animation)
viewBinding!!.controlsContainer.startAnimation(animation)
viewBinding.bottomControlsContainer.startAnimation(animation)
viewBinding.controlsContainer.startAnimation(animation)
}
viewBinding!!.videoView.systemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE
viewBinding.videoView.systemUiVisibility = View.SYSTEM_UI_FLAG_VISIBLE
}
private fun hideVideoControls(showAnimation: Boolean) {
if (showAnimation) {
val animation = AnimationUtils.loadAnimation(this, R.anim.fade_out)
if (animation != null) {
viewBinding!!.bottomControlsContainer.startAnimation(animation)
viewBinding!!.controlsContainer.startAnimation(animation)
viewBinding.bottomControlsContainer.startAnimation(animation)
viewBinding.controlsContainer.startAnimation(animation)
}
}
window.decorView.systemUiVisibility = (View.SYSTEM_UI_FLAG_LOW_PROFILE
or View.SYSTEM_UI_FLAG_FULLSCREEN or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)
viewBinding!!.bottomControlsContainer.fitsSystemWindows = true
viewBinding.bottomControlsContainer.fitsSystemWindows = true
viewBinding!!.bottomControlsContainer.visibility = View.GONE
viewBinding!!.controlsContainer.visibility = View.GONE
viewBinding.bottomControlsContainer.visibility = View.GONE
viewBinding.controlsContainer.visibility = View.GONE
}
@Subscribe(threadMode = ThreadMode.MAIN)
@ -501,12 +500,10 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
Log.d(TAG, "onEvent($event)")
val errorDialog = MaterialAlertDialogBuilder(this)
errorDialog.setMessage(event.message)
if (event.action != null) {
errorDialog.setPositiveButton(event.actionText) { dialog: DialogInterface?, which: Int ->
event.action!!.accept(
this)
}
errorDialog.setPositiveButton(event.actionText) { dialog: DialogInterface?, which: Int ->
event.action?.accept(this)
}
errorDialog.show()
}
@ -533,7 +530,7 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
menu.findItem(R.id.visit_website_item).setVisible(hasWebsiteLink)
val isItemAndHasLink = isFeedMedia && hasLinkToShare((media as FeedMedia).getItem())
val isItemHasDownloadLink = isFeedMedia && (media as FeedMedia?)!!.download_url != null
val isItemHasDownloadLink = isFeedMedia && (media as FeedMedia?)?.download_url != null
menu.findItem(R.id.share_item).setVisible(hasWebsiteLink || isItemAndHasLink || isItemHasDownloadLink)
menu.findItem(R.id.add_to_favorites_item).setVisible(false)
@ -555,56 +552,61 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == R.id.player_switch_to_audio_only) {
switchToAudioOnly = true
finish()
return true
} else if (item.itemId == android.R.id.home) {
val intent = Intent(this@VideoplayerActivity, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
finish()
return true
} else if (item.itemId == R.id.player_show_chapters) {
ChaptersFragment().show(supportFragmentManager, ChaptersFragment.TAG)
return true
// some options option requires FeedItem
when {
item.itemId == R.id.player_switch_to_audio_only -> {
switchToAudioOnly = true
finish()
return true
}
item.itemId == android.R.id.home -> {
val intent = Intent(this@VideoplayerActivity, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
finish()
return true
}
item.itemId == R.id.player_show_chapters -> {
ChaptersFragment().show(supportFragmentManager, ChaptersFragment.TAG)
return true
}
controller == null -> {
return false
}
else -> {
val media = controller?.getMedia() ?: return false
val feedItem = getFeedItem(media) // some options option requires FeedItem
if (item.itemId == R.id.add_to_favorites_item && feedItem != null) {
DBWriter.addFavoriteItem(feedItem)
isFavorite = true
invalidateOptionsMenu()
} else if (item.itemId == R.id.remove_from_favorites_item && feedItem != null) {
DBWriter.removeFavoriteItem(feedItem)
isFavorite = false
invalidateOptionsMenu()
} else if (item.itemId == R.id.disable_sleeptimer_item
|| item.itemId == R.id.set_sleeptimer_item) {
SleepTimerDialog().show(supportFragmentManager, "SleepTimerDialog")
} else if (item.itemId == R.id.audio_controls) {
val dialog = PlaybackControlsDialog.newInstance()
dialog.show(supportFragmentManager, "playback_controls")
} else if (item.itemId == R.id.open_feed_item && feedItem != null) {
val intent = MainActivity.getIntentToOpenFeed(this, feedItem.feedId)
startActivity(intent)
} else if (item.itemId == R.id.visit_website_item) {
val url = getWebsiteLinkWithFallback(media)
if (url != null) openInBrowser(this@VideoplayerActivity, url)
} else if (item.itemId == R.id.share_item && feedItem != null) {
val shareDialog = ShareDialog.newInstance(feedItem)
shareDialog.show(supportFragmentManager, "ShareEpisodeDialog")
} else if (item.itemId == R.id.playback_speed) {
VariableSpeedDialog().show(supportFragmentManager, null)
} else {
return false
}
return true
}
}
if (controller == null) {
return false
}
val media = controller!!.getMedia() ?: return false
val feedItem = getFeedItem(media) // some options option requires FeedItem
if (item.itemId == R.id.add_to_favorites_item && feedItem != null) {
DBWriter.addFavoriteItem(feedItem)
isFavorite = true
invalidateOptionsMenu()
} else if (item.itemId == R.id.remove_from_favorites_item && feedItem != null) {
DBWriter.removeFavoriteItem(feedItem)
isFavorite = false
invalidateOptionsMenu()
} else if (item.itemId == R.id.disable_sleeptimer_item
|| item.itemId == R.id.set_sleeptimer_item) {
SleepTimerDialog().show(supportFragmentManager, "SleepTimerDialog")
} else if (item.itemId == R.id.audio_controls) {
val dialog = PlaybackControlsDialog.newInstance()
dialog.show(supportFragmentManager, "playback_controls")
} else if (item.itemId == R.id.open_feed_item && feedItem != null) {
val intent = MainActivity.getIntentToOpenFeed(this, feedItem.feedId)
startActivity(intent)
} else if (item.itemId == R.id.visit_website_item) {
val url = getWebsiteLinkWithFallback(media)
if (url != null) openInBrowser(this@VideoplayerActivity, url)
} else if (item.itemId == R.id.share_item && feedItem != null) {
val shareDialog = ShareDialog.newInstance(feedItem)
shareDialog.show(supportFragmentManager, "ShareEpisodeDialog")
} else if (item.itemId == R.id.playback_speed) {
VariableSpeedDialog().show(supportFragmentManager, null)
} else {
return false
}
return true
}
fun onPositionObserverUpdate() {
@ -623,11 +625,11 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
Log.w(TAG, "Could not react to position observer update because of invalid time")
return
}
viewBinding!!.positionLabel.text = getDurationStringLong(currentPosition)
viewBinding.positionLabel.text = getDurationStringLong(currentPosition)
if (showTimeLeft) {
viewBinding!!.durationLabel.text = "-" + getDurationStringLong(remainingTime)
viewBinding.durationLabel.text = "-" + getDurationStringLong(remainingTime)
} else {
viewBinding!!.durationLabel.text = getDurationStringLong(duration)
viewBinding.durationLabel.text = getDurationStringLong(duration)
}
updateProgressbarPosition(currentPosition, duration)
}
@ -635,7 +637,7 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
private fun updateProgressbarPosition(position: Int, duration: Int) {
Log.d(TAG, "updateProgressbarPosition($position, $duration)")
val progress = (position.toFloat()) / duration
viewBinding!!.sbPosition.progress = (progress * viewBinding!!.sbPosition.max).toInt()
viewBinding.sbPosition.progress = (progress * viewBinding.sbPosition.max).toInt()
}
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
@ -646,14 +648,14 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
prog = progress / (seekBar.max.toFloat())
val converter = TimeSpeedConverter(controller!!.currentPlaybackSpeedMultiplier)
val position = converter.convert((prog * controller!!.duration).toInt())
viewBinding!!.seekPositionLabel.text = getDurationStringLong(position)
viewBinding.seekPositionLabel.text = getDurationStringLong(position)
}
}
override fun onStartTrackingTouch(seekBar: SeekBar) {
viewBinding!!.seekCardView.scaleX = .8f
viewBinding!!.seekCardView.scaleY = .8f
viewBinding!!.seekCardView.animate()
viewBinding.seekCardView.scaleX = .8f
viewBinding.seekCardView.scaleY = .8f
viewBinding.seekCardView.animate()
.setInterpolator(FastOutSlowInInterpolator())
.alpha(1f).scaleX(1f).scaleY(1f)
.setDuration(200)
@ -665,9 +667,9 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
if (controller != null) {
controller!!.seekTo((prog * controller!!.duration).toInt())
}
viewBinding!!.seekCardView.scaleX = 1f
viewBinding!!.seekCardView.scaleY = 1f
viewBinding!!.seekCardView.animate()
viewBinding.seekCardView.scaleX = 1f
viewBinding.seekCardView.scaleY = 1f
viewBinding.seekCardView.animate()
.setInterpolator(FastOutSlowInInterpolator())
.alpha(0f).scaleX(.8f).scaleY(.8f)
.setDuration(200)
@ -676,26 +678,27 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
}
private fun checkFavorite() {
val feedItem = getFeedItem(controller!!.getMedia()) ?: return
if (disposable != null) {
disposable!!.dispose()
}
val feedItem = getFeedItem(controller?.getMedia()) ?: return
disposable?.dispose()
disposable = Observable.fromCallable { DBReader.getFeedItem(feedItem.id) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ item: FeedItem? ->
val isFav = item!!.isTagged(FeedItem.TAG_FAVORITE)
if (isFavorite != isFav) {
isFavorite = isFav
invalidateOptionsMenu()
if (item != null) {
val isFav = item.isTagged(FeedItem.TAG_FAVORITE)
if (isFavorite != isFav) {
isFavorite = isFav
invalidateOptionsMenu()
}
}
}, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
}
private fun compatEnterPictureInPicture() {
if (PictureInPictureUtil.supportsPictureInPicture(this) && Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
supportActionBar!!.hide()
supportActionBar?.hide()
hideVideoControls(false)
enterPictureInPictureMode()
}
@ -753,7 +756,7 @@ class VideoplayerActivity : CastEnabledActivity(), OnSeekBarChangeListener {
}
//Go to x% of video:
if (keyCode >= KeyEvent.KEYCODE_0 && keyCode <= KeyEvent.KEYCODE_9) {
controller!!.seekTo((0.1f * (keyCode - KeyEvent.KEYCODE_0) * controller!!.duration).toInt())
controller?.seekTo((0.1f * (keyCode - KeyEvent.KEYCODE_0) * controller!!.duration).toInt())
return true
}
return super.onKeyUp(keyCode, event)

View File

@ -19,13 +19,13 @@ import ac.mdiq.podvinci.core.widget.WidgetUpdaterWorker
class WidgetConfigActivity : AppCompatActivity() {
private var appWidgetId = AppWidgetManager.INVALID_APPWIDGET_ID
private var opacitySeekBar: SeekBar? = null
private var opacityTextView: TextView? = null
private var widgetPreview: View? = null
private var ckPlaybackSpeed: CheckBox? = null
private var ckRewind: CheckBox? = null
private var ckFastForward: CheckBox? = null
private var ckSkip: CheckBox? = null
private lateinit var widgetPreview: View
private lateinit var opacitySeekBar: SeekBar
private lateinit var opacityTextView: TextView
private lateinit var ckPlaybackSpeed: CheckBox
private lateinit var ckRewind: CheckBox
private lateinit var ckFastForward: CheckBox
private lateinit var ckSkip: CheckBox
override fun onCreate(savedInstanceState: Bundle?) {
setTheme(getTheme(this))
@ -50,84 +50,82 @@ class WidgetConfigActivity : AppCompatActivity() {
opacitySeekBar = findViewById(R.id.widget_opacity_seekBar)
widgetPreview = findViewById(R.id.widgetLayout)
findViewById<View>(R.id.butConfirm).setOnClickListener { v: View? -> confirmCreateWidget() }
if (opacitySeekBar != null) {
opacitySeekBar!!.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar, i: Int, b: Boolean) {
opacityTextView?.text = seekBar.progress.toString() + "%"
val color = getColorWithAlpha(PlayerWidget.DEFAULT_COLOR, opacitySeekBar!!.progress)
widgetPreview?.setBackgroundColor(color)
}
opacitySeekBar.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar, i: Int, b: Boolean) {
opacityTextView.text = seekBar.progress.toString() + "%"
val color = getColorWithAlpha(PlayerWidget.DEFAULT_COLOR, opacitySeekBar.progress)
widgetPreview.setBackgroundColor(color)
}
override fun onStartTrackingTouch(seekBar: SeekBar) {
}
override fun onStartTrackingTouch(seekBar: SeekBar) {
}
override fun onStopTrackingTouch(seekBar: SeekBar) {
}
})
widgetPreview.findViewById<View>(R.id.txtNoPlaying).visibility = View.GONE
val title = widgetPreview.findViewById<TextView>(R.id.txtvTitle)
title.visibility = View.VISIBLE
title.setText(R.string.app_name)
val progress = widgetPreview.findViewById<TextView>(R.id.txtvProgress)
progress.visibility = View.VISIBLE
progress.setText(R.string.position_default_label)
override fun onStopTrackingTouch(seekBar: SeekBar) {
}
})
}
if (widgetPreview != null) {
widgetPreview!!.findViewById<View>(R.id.txtNoPlaying).visibility = View.GONE
val title = widgetPreview!!.findViewById<TextView>(R.id.txtvTitle)
title.visibility = View.VISIBLE
title.setText(R.string.app_name)
val progress = widgetPreview!!.findViewById<TextView>(R.id.txtvProgress)
progress.visibility = View.VISIBLE
progress.setText(R.string.position_default_label)
}
ckPlaybackSpeed = findViewById(R.id.ckPlaybackSpeed)
ckPlaybackSpeed?.setOnClickListener { v: View? -> displayPreviewPanel() }
ckPlaybackSpeed.setOnClickListener { v: View? -> displayPreviewPanel() }
ckRewind = findViewById(R.id.ckRewind)
ckRewind?.setOnClickListener { v: View? -> displayPreviewPanel() }
ckRewind.setOnClickListener { v: View? -> displayPreviewPanel() }
ckFastForward = findViewById(R.id.ckFastForward)
ckFastForward?.setOnClickListener { v: View? -> displayPreviewPanel() }
ckFastForward.setOnClickListener { v: View? -> displayPreviewPanel() }
ckSkip = findViewById(R.id.ckSkip)
ckSkip?.setOnClickListener { v: View? -> displayPreviewPanel() }
ckSkip.setOnClickListener { v: View? -> displayPreviewPanel() }
setInitialState()
}
private fun setInitialState() {
val prefs = getSharedPreferences(PlayerWidget.PREFS_NAME, MODE_PRIVATE)
ckPlaybackSpeed!!.isChecked = prefs.getBoolean(PlayerWidget.KEY_WIDGET_PLAYBACK_SPEED + appWidgetId, false)
ckRewind!!.isChecked = prefs.getBoolean(PlayerWidget.KEY_WIDGET_REWIND + appWidgetId, false)
ckFastForward!!.isChecked = prefs.getBoolean(PlayerWidget.KEY_WIDGET_FAST_FORWARD + appWidgetId, false)
ckSkip!!.isChecked = prefs.getBoolean(PlayerWidget.KEY_WIDGET_SKIP + appWidgetId, false)
ckPlaybackSpeed.isChecked = prefs.getBoolean(PlayerWidget.KEY_WIDGET_PLAYBACK_SPEED + appWidgetId, false)
ckRewind.isChecked = prefs.getBoolean(PlayerWidget.KEY_WIDGET_REWIND + appWidgetId, false)
ckFastForward.isChecked = prefs.getBoolean(PlayerWidget.KEY_WIDGET_FAST_FORWARD + appWidgetId, false)
ckSkip.isChecked = prefs.getBoolean(PlayerWidget.KEY_WIDGET_SKIP + appWidgetId, false)
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
val color = prefs.getInt(PlayerWidget.KEY_WIDGET_COLOR + appWidgetId, PlayerWidget.DEFAULT_COLOR)
val opacity = Color.alpha(color) * 100 / 0xFF
opacitySeekBar!!.setProgress(opacity, false)
opacitySeekBar.setProgress(opacity, false)
}
displayPreviewPanel()
}
private fun displayPreviewPanel() {
val showExtendedPreview =
ckPlaybackSpeed!!.isChecked || ckRewind!!.isChecked || ckFastForward!!.isChecked || ckSkip!!.isChecked
widgetPreview!!.findViewById<View>(R.id.extendedButtonsContainer).visibility =
ckPlaybackSpeed.isChecked || ckRewind.isChecked || ckFastForward.isChecked || ckSkip.isChecked
widgetPreview.findViewById<View>(R.id.extendedButtonsContainer).visibility =
if (showExtendedPreview) View.VISIBLE else View.GONE
widgetPreview!!.findViewById<View>(R.id.butPlay).visibility =
widgetPreview.findViewById<View>(R.id.butPlay).visibility =
if (showExtendedPreview) View.GONE else View.VISIBLE
widgetPreview!!.findViewById<View>(R.id.butPlaybackSpeed).visibility =
if (ckPlaybackSpeed!!.isChecked) View.VISIBLE else View.GONE
widgetPreview!!.findViewById<View>(R.id.butFastForward).visibility =
if (ckFastForward!!.isChecked) View.VISIBLE else View.GONE
widgetPreview!!.findViewById<View>(R.id.butSkip).visibility =
if (ckSkip!!.isChecked) View.VISIBLE else View.GONE
widgetPreview!!.findViewById<View>(R.id.butRew).visibility =
if (ckRewind!!.isChecked) View.VISIBLE else View.GONE
widgetPreview.findViewById<View>(R.id.butPlaybackSpeed).visibility =
if (ckPlaybackSpeed.isChecked) View.VISIBLE else View.GONE
widgetPreview.findViewById<View>(R.id.butFastForward).visibility =
if (ckFastForward.isChecked) View.VISIBLE else View.GONE
widgetPreview.findViewById<View>(R.id.butSkip).visibility =
if (ckSkip.isChecked) View.VISIBLE else View.GONE
widgetPreview.findViewById<View>(R.id.butRew).visibility =
if (ckRewind.isChecked) View.VISIBLE else View.GONE
}
private fun confirmCreateWidget() {
val backgroundColor = getColorWithAlpha(PlayerWidget.DEFAULT_COLOR, opacitySeekBar!!.progress)
val backgroundColor = getColorWithAlpha(PlayerWidget.DEFAULT_COLOR, opacitySeekBar.progress)
val prefs = getSharedPreferences(PlayerWidget.PREFS_NAME, MODE_PRIVATE)
val editor = prefs.edit()
editor.putInt(PlayerWidget.KEY_WIDGET_COLOR + appWidgetId, backgroundColor)
editor.putBoolean(PlayerWidget.KEY_WIDGET_PLAYBACK_SPEED + appWidgetId, ckPlaybackSpeed!!.isChecked)
editor.putBoolean(PlayerWidget.KEY_WIDGET_SKIP + appWidgetId, ckSkip!!.isChecked)
editor.putBoolean(PlayerWidget.KEY_WIDGET_REWIND + appWidgetId, ckRewind!!.isChecked)
editor.putBoolean(PlayerWidget.KEY_WIDGET_FAST_FORWARD + appWidgetId, ckFastForward!!.isChecked)
editor.putBoolean(PlayerWidget.KEY_WIDGET_PLAYBACK_SPEED + appWidgetId, ckPlaybackSpeed.isChecked)
editor.putBoolean(PlayerWidget.KEY_WIDGET_SKIP + appWidgetId, ckSkip.isChecked)
editor.putBoolean(PlayerWidget.KEY_WIDGET_REWIND + appWidgetId, ckRewind.isChecked)
editor.putBoolean(PlayerWidget.KEY_WIDGET_FAST_FORWARD + appWidgetId, ckFastForward.isChecked)
editor.apply()
val resultValue = Intent()

View File

@ -26,8 +26,8 @@ import ac.mdiq.podvinci.ui.common.CircularProgressBar
import kotlin.math.max
import kotlin.math.min
class ChaptersListAdapter(private val context: Context, private val callback: Callback?) :
RecyclerView.Adapter<ChapterHolder?>() {
class ChaptersListAdapter(private val context: Context, private val callback: Callback?) : RecyclerView.Adapter<ChapterHolder>() {
private var media: Playable? = null
private var currentChapterIndex = -1
private var currentChapterPosition: Long = -1
@ -45,18 +45,13 @@ class ChaptersListAdapter(private val context: Context, private val callback: Ca
}
override fun onBindViewHolder(holder: ChapterHolder, position: Int) {
val sc = getItem(position)
if (sc == null) {
holder.title.text = "Error"
return
}
val sc = getItem(position)?: return
holder.title.text = sc.title
holder.start.text = getDurationStringLong(sc
.start.toInt())
val duration = if (position + 1 < media!!.getChapters().size) {
holder.start.text = getDurationStringLong(sc.start.toInt())
val duration = if (position + 1 < itemCount) {
media!!.getChapters()[position + 1].start - sc.start
} else {
media!!.getDuration() - sc.start
(media?.getDuration()?:0) - sc.start
}
holder.duration.text = context.getString(R.string.chapter_duration,
getDurationStringLocalized(context, duration.toInt().toLong()))
@ -112,10 +107,7 @@ class ChaptersListAdapter(private val context: Context, private val callback: Ca
}
override fun getItemCount(): Int {
if (media == null) {
return 0
}
return media!!.getChapters().size
return media?.getChapters()?.size?:0
}
class ChapterHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
@ -131,7 +123,7 @@ class ChaptersListAdapter(private val context: Context, private val callback: Ca
fun notifyChapterChanged(newChapterIndex: Int) {
currentChapterIndex = newChapterIndex
currentChapterPosition = getItem(newChapterIndex).start
currentChapterPosition = getItem(newChapterIndex)?.start?:0
notifyDataSetChanged()
}
@ -142,8 +134,10 @@ class ChaptersListAdapter(private val context: Context, private val callback: Ca
notifyItemChanged(currentChapterIndex, "foo")
}
fun getItem(position: Int): Chapter {
return media!!.getChapters()[position]
fun getItem(position: Int): Chapter? {
val chapters = media?.getChapters()?: return null
if (position < 0 || position >= chapters.size) return null
return chapters[position]
}
interface Callback {

View File

@ -57,9 +57,9 @@ class CoverLoader(activity: MainActivity) {
}
fun load() {
if (fallbackTitle == null || imgvCover == null) return
if (imgvCover == null) return
val coverTarget = CoverTarget(fallbackTitle!!, imgvCover!!, textAndImageCombined)
val coverTarget = CoverTarget(fallbackTitle, imgvCover!!, textAndImageCombined)
if (resource != 0) {
Glide.with(imgvCover!!).clear(coverTarget)
@ -87,11 +87,12 @@ class CoverLoader(activity: MainActivity) {
builder.into<CoverTarget>(coverTarget)
}
internal class CoverTarget(fallbackTitle: TextView,
internal class CoverTarget(fallbackTitle: TextView?,
coverImage: ImageView,
private val textAndImageCombined: Boolean
) : CustomViewTarget<ImageView, Drawable>(coverImage) {
private val fallbackTitle: WeakReference<TextView> = WeakReference<TextView>(fallbackTitle)
private val fallbackTitle: WeakReference<TextView?> = WeakReference<TextView?>(fallbackTitle)
private val cover: WeakReference<ImageView> = WeakReference(coverImage)
override fun onLoadFailed(errorDrawable: Drawable?) {

View File

@ -16,8 +16,8 @@ import ac.mdiq.podvinci.core.util.StorageUtils.getTotalSpaceAvailable
import ac.mdiq.podvinci.storage.preferences.UserPreferences.getDataFolder
import java.io.File
class DataFolderAdapter(context: Context, selectionHandler: Consumer<String>) :
RecyclerView.Adapter<DataFolderAdapter.ViewHolder?>() {
class DataFolderAdapter(context: Context, selectionHandler: Consumer<String>) : RecyclerView.Adapter<DataFolderAdapter.ViewHolder?>() {
private val selectionHandler: Consumer<String>
private val currentPath: String?
private val entries: List<StoragePath>
@ -63,10 +63,7 @@ class DataFolderAdapter(context: Context, selectionHandler: Consumer<String>) :
private fun getCurrentPath(): String? {
val dataFolder = getDataFolder(null)
if (dataFolder != null) {
return dataFolder.absolutePath
}
return null
return dataFolder?.absolutePath
}
private fun getStorageEntries(context: Context): List<StoragePath> {

View File

@ -19,6 +19,7 @@ import ac.mdiq.podvinci.model.feed.Feed
import ac.mdiq.podvinci.model.feed.FeedMedia
import ac.mdiq.podvinci.ui.common.ThemeUtils
import ac.mdiq.podvinci.view.viewholder.DownloadLogItemViewHolder
import androidx.media3.common.util.UnstableApi
/**
* Displays a list of DownloadStatus entries.
@ -31,7 +32,7 @@ class DownloadLogAdapter(private val context: Activity) : BaseAdapter() {
notifyDataSetChanged()
}
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
@UnstableApi override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val holder: DownloadLogItemViewHolder
if (convertView == null) {
holder = DownloadLogItemViewHolder(context, parent)
@ -44,7 +45,7 @@ class DownloadLogAdapter(private val context: Activity) : BaseAdapter() {
return holder.itemView
}
private fun bind(holder: DownloadLogItemViewHolder, status: DownloadResult, position: Int) {
@UnstableApi private fun bind(holder: DownloadLogItemViewHolder, status: DownloadResult, position: Int) {
var statusText: String? = ""
if (status.feedfileType == Feed.FEEDFILETYPE_FEED) {
statusText += context.getString(R.string.download_type_feed)
@ -56,7 +57,7 @@ class DownloadLogAdapter(private val context: Activity) : BaseAdapter() {
System.currentTimeMillis(), DateUtils.MINUTE_IN_MILLIS, 0)
holder.status.text = statusText
if (status.title != null) {
if (status.title.isNotEmpty()) {
holder.title.text = status.title
} else {
holder.title.setText(R.string.download_log_title_unknown)
@ -132,7 +133,7 @@ class DownloadLogAdapter(private val context: Activity) : BaseAdapter() {
}
override fun getItem(position: Int): DownloadResult? {
if (position < downloadLog.size) {
if (position in downloadLog.indices) {
return downloadLog[position]
}
return null

View File

@ -1,22 +1,23 @@
package ac.mdiq.podvinci.adapter
import ac.mdiq.podvinci.activity.MainActivity
import android.R.color
import android.app.Activity
import android.os.Build
import android.view.*
import androidx.media3.common.util.UnstableApi
import androidx.recyclerview.widget.RecyclerView
import ac.mdiq.podvinci.R
import ac.mdiq.podvinci.activity.MainActivity
import ac.mdiq.podvinci.core.util.FeedItemUtil
import ac.mdiq.podvinci.fragment.ItemPagerFragment
import ac.mdiq.podvinci.menuhandler.FeedItemMenuHandler
import ac.mdiq.podvinci.model.feed.FeedItem
import ac.mdiq.podvinci.ui.common.ThemeUtils
import ac.mdiq.podvinci.view.viewholder.EpisodeItemViewHolder
import android.R.color
import android.app.Activity
import android.os.Build
import android.view.*
import androidx.media3.common.util.UnstableApi
import androidx.recyclerview.widget.RecyclerView
import org.apache.commons.lang3.ArrayUtils
import java.lang.ref.WeakReference
/**
* List adapter for the list of new episodes.
*/
@ -53,7 +54,7 @@ open class EpisodeItemListAdapter(mainActivity: MainActivity) : SelectableAdapte
}
@UnstableApi override fun onBindViewHolder(holder: EpisodeItemViewHolder, pos: Int) {
if (pos >= episodes.size) {
if (pos >= episodes.size || pos < 0) {
beforeBindViewHolder(holder, pos)
holder.bindDummy()
afterBindViewHolder(holder, pos)
@ -72,10 +73,10 @@ open class EpisodeItemListAdapter(mainActivity: MainActivity) : SelectableAdapte
holder.itemView.setOnClickListener { v: View? ->
val activity: MainActivity? = mainActivityRef.get()
if (activity != null && !inActionMode()) {
if (!inActionMode()) {
val ids: LongArray = FeedItemUtil.getIds(episodes)
val position = ArrayUtils.indexOf(ids, item.id)
activity.loadChildFragment(ItemPagerFragment.newInstance(ids, position))
activity?.loadChildFragment(ItemPagerFragment.newInstance(ids, position))
} else {
toggleSelection(holder.bindingAdapterPosition)
}
@ -88,8 +89,7 @@ open class EpisodeItemListAdapter(mainActivity: MainActivity) : SelectableAdapte
}
holder.itemView.setOnTouchListener(View.OnTouchListener { v: View?, e: MotionEvent ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
if (e.isFromSource(InputDevice.SOURCE_MOUSE)
&& e.buttonState == MotionEvent.BUTTON_SECONDARY) {
if (e.isFromSource(InputDevice.SOURCE_MOUSE) && e.buttonState == MotionEvent.BUTTON_SECONDARY) {
longPressedItem = item
longPressedPosition = holder.bindingAdapterPosition
return@OnTouchListener false
@ -146,26 +146,28 @@ open class EpisodeItemListAdapter(mainActivity: MainActivity) : SelectableAdapte
}
override fun getItemId(position: Int): Long {
if (position >= episodes.size) {
return RecyclerView.NO_ID // Dummy views
}
val item: FeedItem = episodes[position]
return item.id
// if (position >= episodes.size) {
// return RecyclerView.NO_ID // Dummy views
// }
// val item = episodes[position]
// return item.id ?: RecyclerView.NO_POSITION.toLong()
return getItem(position)?.id ?: RecyclerView.NO_ID
}
override fun getItemCount(): Int {
return dummyViews + episodes.size
}
protected fun getItem(index: Int): FeedItem {
return episodes[index]
protected fun getItem(index: Int): FeedItem? {
// return episodes[index]
return if (index in episodes.indices) episodes[index] else null
}
protected val activity: Activity?
get() = mainActivityRef.get()
@UnstableApi override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) {
val inflater: MenuInflater = mainActivityRef.get()!!.menuInflater
val inflater: MenuInflater = activity!!.menuInflater
if (inActionMode()) {
inflater.inflate(R.menu.multi_select_context_popup, menu)
} else {
@ -201,8 +203,9 @@ open class EpisodeItemListAdapter(mainActivity: MainActivity) : SelectableAdapte
get() {
val items: MutableList<FeedItem> = ArrayList()
for (i in 0 until itemCount) {
if (isSelected(i)) {
items.add(getItem(i))
if (i < episodes.size && isSelected(i)) {
val item = getItem(i)
if (item != null) items.add(item)
}
}
return items

View File

@ -17,9 +17,9 @@ class FeedDiscoverAdapter(mainActivity: MainActivity) : BaseAdapter() {
private val mainActivityRef: WeakReference<MainActivity> = WeakReference<MainActivity>(mainActivity)
private val data: MutableList<PodcastSearchResult> = ArrayList()
fun updateData(newData: List<PodcastSearchResult>?) {
fun updateData(newData: List<PodcastSearchResult>) {
data.clear()
data.addAll(newData!!)
data.addAll(newData)
notifyDataSetChanged()
}
@ -27,8 +27,8 @@ class FeedDiscoverAdapter(mainActivity: MainActivity) : BaseAdapter() {
return data.size
}
override fun getItem(position: Int): PodcastSearchResult {
return data[position]
override fun getItem(position: Int): PodcastSearchResult? {
return if (position in data.indices) data[position] else null
}
override fun getItemId(position: Int): Long {
@ -48,12 +48,11 @@ class FeedDiscoverAdapter(mainActivity: MainActivity) : BaseAdapter() {
holder = convertView.tag as Holder
}
val podcast: PodcastSearchResult = getItem(position)
holder.imageView!!.contentDescription = podcast.title
val podcast: PodcastSearchResult? = getItem(position)
holder.imageView!!.contentDescription = podcast?.title
Glide.with(mainActivityRef.get()!!)
.load(podcast.imageUrl)
.load(podcast?.imageUrl)
.apply(RequestOptions()
.placeholder(R.color.light_gray)
.transform(FitCenter(), RoundedCorners((8 * mainActivityRef.get()!!.resources.displayMetrics.density).toInt()))

View File

@ -1,13 +1,5 @@
package ac.mdiq.podvinci.adapter
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.TextView
import androidx.media3.common.util.UnstableApi
import ac.mdiq.podvinci.R
import ac.mdiq.podvinci.core.service.playback.PlaybackService.Companion.getPlayerActivityIntent
import ac.mdiq.podvinci.core.util.DateFormatter.formatAbbrev
@ -19,15 +11,20 @@ import ac.mdiq.podvinci.model.feed.FeedItem
import ac.mdiq.podvinci.model.playback.MediaType
import ac.mdiq.podvinci.model.playback.Playable
import ac.mdiq.podvinci.model.playback.RemoteMedia
import java.lang.Boolean
import kotlin.Int
import android.content.Context
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.TextView
import androidx.media3.common.util.UnstableApi
/**
* List adapter for showing a list of FeedItems with their title and description.
*/
class FeedItemlistDescriptionAdapter(context: Context?, resource: Int, objects: List<FeedItem?>?) :
ArrayAdapter<FeedItem?>(
context!!, resource, objects!!) {
class FeedItemlistDescriptionAdapter(context: Context, resource: Int, objects: List<FeedItem?>?) :
ArrayAdapter<FeedItem?>(context, resource, objects!!) {
@UnstableApi override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
var convertView = convertView
val holder: Holder
@ -59,7 +56,7 @@ class FeedItemlistDescriptionAdapter(context: Context?, resource: Int, objects:
holder.description!!.text = description
holder.description!!.maxLines = MAX_LINES_COLLAPSED
}
holder.description!!.tag = Boolean.FALSE // not expanded
holder.description!!.tag = false
holder.preview!!.visibility = View.GONE
holder.preview!!.setOnClickListener { v: View? ->
if (item.media == null) {
@ -79,13 +76,13 @@ class FeedItemlistDescriptionAdapter(context: Context?, resource: Int, objects:
}
}
convertView!!.setOnClickListener { v: View? ->
if (holder.description!!.tag === Boolean.TRUE) {
if (holder.description!!.tag == true) {
holder.description!!.maxLines = MAX_LINES_COLLAPSED
holder.preview!!.visibility = View.GONE
holder.description!!.tag = Boolean.FALSE
holder.description!!.tag = false
} else {
holder.description!!.maxLines = 30
holder.description!!.tag = Boolean.TRUE
holder.description!!.tag = true
holder.preview!!.visibility = if (item.media != null) View.VISIBLE else View.GONE
holder.preview!!.setText(R.string.preview_episode)

View File

@ -21,7 +21,7 @@ import java.lang.ref.WeakReference
open class HorizontalFeedListAdapter(mainActivity: MainActivity) :
RecyclerView.Adapter<HorizontalFeedListAdapter.Holder>(), View.OnCreateContextMenuListener {
private val mainActivityRef: WeakReference<MainActivity> = WeakReference<MainActivity>(mainActivity)
private val mainActivityRef: WeakReference<MainActivity> = WeakReference<MainActivity>(mainActivity)
private val data: MutableList<Feed> = ArrayList()
private var dummyViews = 0
var longPressedItem: Feed? = null

View File

@ -18,6 +18,7 @@ import java.lang.ref.WeakReference
open class HorizontalItemListAdapter(mainActivity: MainActivity) : RecyclerView.Adapter<HorizontalItemViewHolder?>(),
View.OnCreateContextMenuListener {
private val mainActivityRef: WeakReference<MainActivity> = WeakReference<MainActivity>(mainActivity)
private var data: List<FeedItem> = ArrayList()
var longPressedItem: FeedItem? = null
@ -70,10 +71,11 @@ open class HorizontalItemListAdapter(mainActivity: MainActivity) : RecyclerView.
}
override fun getItemId(position: Int): Long {
if (position >= data.size) {
return RecyclerView.NO_ID // Dummy views
if (position in data.indices) {
val item: FeedItem = data[position]
return item.id
}
return data[position].id
return RecyclerView.NO_ID // Dummy views
}
override fun getItemCount(): Int {
@ -108,9 +110,8 @@ open class HorizontalItemListAdapter(mainActivity: MainActivity) : RecyclerView.
@UnstableApi override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) {
val inflater: MenuInflater = mainActivityRef.get()!!.menuInflater
if (longPressedItem == null) {
return
}
if (longPressedItem == null) return
menu.clear()
inflater.inflate(R.menu.feeditemlist_context, menu)
menu.setHeaderTitle(longPressedItem!!.title)

View File

@ -31,6 +31,7 @@ import ac.mdiq.podvinci.storage.preferences.UserPreferences.hiddenDrawerItems
import ac.mdiq.podvinci.storage.preferences.UserPreferences.isEnableAutodownload
import ac.mdiq.podvinci.storage.preferences.UserPreferences.subscriptionsFilter
import ac.mdiq.podvinci.ui.home.HomeFragment
import androidx.media3.common.util.UnstableApi
import org.apache.commons.lang3.ArrayUtils
import java.lang.ref.WeakReference
import java.text.NumberFormat
@ -42,6 +43,7 @@ import kotlin.math.abs
*/
class NavListAdapter(private val itemAccess: ItemAccess, context: Activity) :
RecyclerView.Adapter<NavListAdapter.Holder>(), OnSharedPreferenceChangeListener {
private val fragmentTags: MutableList<String?> = ArrayList()
private val titles: Array<String> = context.resources.getStringArray(R.array.nav_drawer_titles)
private val activity = WeakReference(context)
@ -87,7 +89,7 @@ class NavListAdapter(private val itemAccess: ItemAccess, context: Activity) :
return titles[index]
}
@DrawableRes
@UnstableApi @DrawableRes
private fun getDrawable(tag: String?): Int {
return when (tag) {
HomeFragment.TAG -> R.drawable.ic_home
@ -157,7 +159,7 @@ class NavListAdapter(private val itemAccess: ItemAccess, context: Activity) :
}
}
override fun onBindViewHolder(holder: Holder, position: Int) {
@UnstableApi override fun onBindViewHolder(holder: Holder, position: Int) {
val viewType = getItemViewType(position)
holder.itemView.setOnCreateContextMenuListener(null)
@ -199,7 +201,7 @@ class NavListAdapter(private val itemAccess: ItemAccess, context: Activity) :
}
}
private fun bindNavView(title: String, position: Int, holder: NavHolder) {
@UnstableApi private fun bindNavView(title: String, position: Int, holder: NavHolder) {
val context = activity.get() ?: return
holder.title.text = title
@ -209,43 +211,48 @@ class NavListAdapter(private val itemAccess: ItemAccess, context: Activity) :
holder.count.isClickable = false
val tag = fragmentTags[position]
if (tag == QueueFragment.TAG) {
val queueSize = itemAccess.queueSize
if (queueSize > 0) {
holder.count.text = NumberFormat.getInstance().format(queueSize.toLong())
holder.count.visibility = View.VISIBLE
when {
tag == QueueFragment.TAG -> {
val queueSize = itemAccess.queueSize
if (queueSize > 0) {
holder.count.text = NumberFormat.getInstance().format(queueSize.toLong())
holder.count.visibility = View.VISIBLE
}
}
} else if (tag == InboxFragment.TAG) {
val unreadItems = itemAccess.numberOfNewItems
if (unreadItems > 0) {
holder.count.text = NumberFormat.getInstance().format(unreadItems.toLong())
holder.count.visibility = View.VISIBLE
tag == InboxFragment.TAG -> {
val unreadItems = itemAccess.numberOfNewItems
if (unreadItems > 0) {
holder.count.text = NumberFormat.getInstance().format(unreadItems.toLong())
holder.count.visibility = View.VISIBLE
}
}
} else if (tag == SubscriptionFragment.TAG) {
val sum = itemAccess.feedCounterSum
if (sum > 0) {
holder.count.text = NumberFormat.getInstance().format(sum.toLong())
holder.count.visibility = View.VISIBLE
tag == SubscriptionFragment.TAG -> {
val sum = itemAccess.feedCounterSum
if (sum > 0) {
holder.count.text = NumberFormat.getInstance().format(sum.toLong())
holder.count.visibility = View.VISIBLE
}
}
} else if (tag == CompletedDownloadsFragment.TAG && isEnableAutodownload) {
val epCacheSize = episodeCacheSize
// don't count episodes that can be reclaimed
val spaceUsed = (itemAccess.numberOfDownloadedItems
- itemAccess.reclaimableItems)
if (epCacheSize in 1..spaceUsed) {
holder.count.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_disc_alert, 0)
holder.count.visibility = View.VISIBLE
holder.count.setOnClickListener { v: View? ->
MaterialAlertDialogBuilder(context)
.setTitle(R.string.episode_cache_full_title)
.setMessage(R.string.episode_cache_full_message)
.setPositiveButton(android.R.string.ok, null)
.setNeutralButton(R.string.open_autodownload_settings) { dialog: DialogInterface?, which: Int ->
val intent = Intent(context, PreferenceActivity::class.java)
intent.putExtra(PreferenceActivity.OPEN_AUTO_DOWNLOAD_SETTINGS, true)
context.startActivity(intent)
}
.show()
tag == CompletedDownloadsFragment.TAG && isEnableAutodownload -> {
val epCacheSize = episodeCacheSize
// don't count episodes that can be reclaimed
val spaceUsed = (itemAccess.numberOfDownloadedItems
- itemAccess.reclaimableItems)
if (epCacheSize in 1..spaceUsed) {
holder.count.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_disc_alert, 0)
holder.count.visibility = View.VISIBLE
holder.count.setOnClickListener { v: View? ->
MaterialAlertDialogBuilder(context)
.setTitle(R.string.episode_cache_full_title)
.setMessage(R.string.episode_cache_full_message)
.setPositiveButton(android.R.string.ok, null)
.setNeutralButton(R.string.open_autodownload_settings) { dialog: DialogInterface?, which: Int ->
val intent = Intent(context, PreferenceActivity::class.java)
intent.putExtra(PreferenceActivity.OPEN_AUTO_DOWNLOAD_SETTINGS, true)
context.startActivity(intent)
}
.show()
}
}
}
}

View File

@ -16,11 +16,9 @@ import ac.mdiq.podvinci.view.viewholder.EpisodeItemViewHolder
/**
* List adapter for the queue.
*/
open class QueueRecyclerAdapter(mainActivity: MainActivity, swipeActions: SwipeActions) : EpisodeItemListAdapter(mainActivity) {
private val swipeActions: SwipeActions = swipeActions
open class QueueRecyclerAdapter(mainActivity: MainActivity, private val swipeActions: SwipeActions) : EpisodeItemListAdapter(mainActivity) {
private var dragDropEnabled: Boolean
init {
dragDropEnabled = !(UserPreferences.isQueueKeepSorted || UserPreferences.isQueueLocked)
}
@ -46,7 +44,7 @@ open class QueueRecyclerAdapter(mainActivity: MainActivity, swipeActions: SwipeA
false
}
holder.coverHolder.setOnTouchListener { v1, event ->
if (event.actionMasked === MotionEvent.ACTION_DOWN) {
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
val isLtr = holder.itemView.getLayoutDirection() == View.LAYOUT_DIRECTION_LTR
val factor = (if (isLtr) 1 else -1).toFloat()
if (factor * event.x < factor * 0.5 * v1.width) {
@ -75,10 +73,10 @@ open class QueueRecyclerAdapter(mainActivity: MainActivity, swipeActions: SwipeA
if (!inActionMode()) {
menu.findItem(R.id.multi_select).setVisible(true)
val keepSorted: Boolean = UserPreferences.isQueueKeepSorted
if (getItem(0).id === longPressedItem?.id || keepSorted) {
if (getItem(0)?.id === longPressedItem?.id || keepSorted) {
menu.findItem(R.id.move_to_top_item).setVisible(false)
}
if (getItem(itemCount - 1).id === longPressedItem?.id || keepSorted) {
if (getItem(itemCount - 1)?.id === longPressedItem?.id || keepSorted) {
menu.findItem(R.id.move_to_bottom_item).setVisible(false)
}
} else {

View File

@ -23,10 +23,7 @@ abstract class SelectableAdapter<T : RecyclerView.ViewHolder?>(private val activ
if (inActionMode()) {
endSelectMode()
}
if (onSelectModeListener != null) {
onSelectModeListener!!.onStartSelectMode()
}
onSelectModeListener?.onStartSelectMode()
shouldSelectLazyLoadedItems = false
selectedIds.clear()
@ -75,7 +72,7 @@ abstract class SelectableAdapter<T : RecyclerView.ViewHolder?>(private val activ
fun endSelectMode() {
if (inActionMode()) {
callOnEndSelectMode()
actionMode!!.finish()
actionMode?.finish()
}
}
@ -164,9 +161,7 @@ abstract class SelectableAdapter<T : RecyclerView.ViewHolder?>(private val activ
}
private fun callOnEndSelectMode() {
if (onSelectModeListener != null) {
onSelectModeListener!!.onEndSelectMode()
}
onSelectModeListener?.onEndSelectMode()
}
fun shouldSelectLazyLoadedItems(): Boolean {

View File

@ -12,7 +12,7 @@ abstract class SimpleChipAdapter(private val context: Context) : RecyclerView.Ad
setHasStableIds(true)
}
protected abstract fun getChips(): List<String?>
protected abstract fun getChips(): List<String>
protected abstract fun onRemoveClicked(position: Int)

View File

@ -16,8 +16,8 @@ import ac.mdiq.podvinci.R
*/
class SimpleIconListAdapter<T : SimpleIconListAdapter.ListItem>(private val context: Context,
private val listItems: List<T>
) : ArrayAdapter<T>(
context, R.layout.simple_icon_list_item, listItems) {
) : ArrayAdapter<T>(context, R.layout.simple_icon_list_item, listItems) {
override fun getView(position: Int, view: View?, parent: ViewGroup): View {
var view = view
if (view == null) {

View File

@ -28,6 +28,7 @@ import java.text.NumberFormat
open class SubscriptionsRecyclerAdapter(mainActivity: MainActivity) :
SelectableAdapter<SubscriptionsRecyclerAdapter.SubscriptionViewHolder?>(mainActivity),
View.OnCreateContextMenuListener {
private val mainActivityRef: WeakReference<MainActivity> = WeakReference<MainActivity>(mainActivity)
private var listItems: List<NavDrawerData.DrawerItem>
private var selectedItem: NavDrawerData.DrawerItem? = null
@ -54,8 +55,7 @@ open class SubscriptionsRecyclerAdapter(mainActivity: MainActivity) :
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): SubscriptionViewHolder {
val itemView: View =
LayoutInflater.from(mainActivityRef.get()).inflate(R.layout.subscription_item, parent, false)
itemView.findViewById<View>(R.id.titleLabel).visibility =
if (viewType == COVER_WITH_TITLE) View.VISIBLE else View.GONE
itemView.findViewById<View>(R.id.titleLabel).visibility = if (viewType == COVER_WITH_TITLE) View.VISIBLE else View.GONE
return SubscriptionViewHolder(itemView)
}

View File

@ -6,6 +6,7 @@ import ac.mdiq.podvinci.R
import ac.mdiq.podvinci.core.storage.DBWriter
import ac.mdiq.podvinci.model.feed.FeedItem
import ac.mdiq.podvinci.view.LocalDeleteModal.showLocalFeedDeleteWarningIfNecessary
import androidx.media3.common.util.UnstableApi
class DeleteActionButton(item: FeedItem) : ItemActionButton(item) {
override fun getLabel(): Int {
@ -14,11 +15,10 @@ class DeleteActionButton(item: FeedItem) : ItemActionButton(item) {
override fun getDrawable(): Int {
return R.drawable.ic_delete
}
override fun onClick(context: Context) {
@UnstableApi override fun onClick(context: Context) {
val media = item.media ?: return
showLocalFeedDeleteWarningIfNecessary(context, listOf(item)
) { DBWriter.deleteFeedMediaOfItem(context, media.id) }
showLocalFeedDeleteWarningIfNecessary(context, listOf(item)) { DBWriter.deleteFeedMediaOfItem(context, media.id) }
}
override val visibility: Int

View File

@ -30,19 +30,29 @@ abstract class ItemActionButton internal constructor(@JvmField var item: FeedIte
fun forItem(item: FeedItem): ItemActionButton {
val media = item.media ?: return MarkAsPlayedActionButton(item)
val isDownloadingMedia = if (media.download_url==null) false else DownloadServiceInterface.get()?.isDownloadingEpisode(media.download_url!!)?:false
return if (isCurrentlyPlaying(media)) {
PauseActionButton(item)
} else if (item.feed != null && item.feed!!.isLocalFeed) {
PlayLocalActionButton(item)
} else if (media.isDownloaded()) {
PlayActionButton(item)
} else if (isDownloadingMedia) {
CancelDownloadActionButton(item)
} else if (isStreamOverDownload) {
StreamActionButton(item)
} else {
DownloadActionButton(item)
val isDownloadingMedia = when (media.download_url) {
null -> false
else -> DownloadServiceInterface.get()?.isDownloadingEpisode(media.download_url!!)?:false
}
return when {
isCurrentlyPlaying(media) -> {
PauseActionButton(item)
}
item.feed != null && item.feed!!.isLocalFeed -> {
PlayLocalActionButton(item)
}
media.isDownloaded() -> {
PlayActionButton(item)
}
isDownloadingMedia -> {
CancelDownloadActionButton(item)
}
isStreamOverDownload -> {
StreamActionButton(item)
}
else -> {
DownloadActionButton(item)
}
}
}
}

View File

@ -5,6 +5,7 @@ import android.view.View
import ac.mdiq.podvinci.R
import ac.mdiq.podvinci.core.storage.DBWriter
import ac.mdiq.podvinci.model.feed.FeedItem
import androidx.media3.common.util.UnstableApi
class MarkAsPlayedActionButton(item: FeedItem) : ItemActionButton(item) {
override fun getLabel(): Int {
@ -13,12 +14,12 @@ class MarkAsPlayedActionButton(item: FeedItem) : ItemActionButton(item) {
override fun getDrawable(): Int {
return R.drawable.ic_check
}
override fun onClick(context: Context) {
@UnstableApi override fun onClick(context: Context) {
if (!item.isPlayed()) {
DBWriter.markItemPlayed(item, FeedItem.PLAYED, true)
}
}
override val visibility: Int
get() = if ((item.isPlayed())) View.INVISIBLE else View.VISIBLE
get() = if (item.isPlayed()) View.INVISIBLE else View.VISIBLE
}

View File

@ -18,5 +18,5 @@ class VisitWebsiteActionButton(item: FeedItem) : ItemActionButton(item) {
}
override val visibility: Int
get() = if ((item.link == null)) View.INVISIBLE else View.VISIBLE
get() = if (item.link == null) View.INVISIBLE else View.VISIBLE
}

View File

@ -14,6 +14,7 @@ import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.request.RequestOptions
import ac.mdiq.podvinci.R
import ac.mdiq.podvinci.net.discovery.PodcastSearchResult
import androidx.media3.common.util.UnstableApi
class ItunesAdapter(
/**
@ -26,7 +27,7 @@ class ItunesAdapter(
*/
private val data: List<PodcastSearchResult> = objects
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
@UnstableApi override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
//Current podcast
val podcast: PodcastSearchResult = data[position]
@ -38,8 +39,7 @@ class ItunesAdapter(
//Handle view holder stuff
if (convertView == null) {
view = (context as MainActivity).layoutInflater
.inflate(R.layout.itunes_podcast_listitem, parent, false)
view = (context as MainActivity).layoutInflater.inflate(R.layout.itunes_podcast_listitem, parent, false)
viewHolder = PodcastViewHolder(view)
view.tag = viewHolder
} else {
@ -86,7 +86,7 @@ class ItunesAdapter(
/**
* TextView holding the Podcast title
*/
val titleView = view.findViewById<TextView>(R.id.txtvTitle)
val titleView: TextView = view.findViewById(R.id.txtvTitle)
val authorView: TextView = view.findViewById(R.id.txtvAuthor)
}

View File

@ -20,17 +20,15 @@ class DocumentFileExportWorker(private val exportWriter: ExportWriter,
private val outputFileUri: Uri
) {
fun exportObservable(): Observable<DocumentFile?> {
val output = DocumentFile.fromSingleUri(
context, outputFileUri)
val output = DocumentFile.fromSingleUri(context, outputFileUri)
return Observable.create { subscriber: ObservableEmitter<DocumentFile?> ->
var outputStream: OutputStream? = null
var writer: OutputStreamWriter? = null
try {
val uri = output!!.uri
if (output == null) throw IOException()
val uri = output.uri
outputStream = context.contentResolver.openOutputStream(uri, "wt")
if (outputStream == null) {
throw IOException()
}
if (outputStream == null) throw IOException()
writer = OutputStreamWriter(outputStream, Charset.forName("UTF-8"))
exportWriter.writeDocument(DBReader.getFeedList(), writer, context)
subscriber.onNext(output)

View File

@ -8,6 +8,8 @@ import ac.mdiq.podvinci.R
import ac.mdiq.podvinci.core.dialog.ConfirmationDialog
import ac.mdiq.podvinci.core.storage.DBWriter
import ac.mdiq.podvinci.model.feed.Feed
import androidx.annotation.OptIn
import androidx.media3.common.util.UnstableApi
import io.reactivex.Completable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.schedulers.Schedulers
@ -28,7 +30,7 @@ object RemoveFeedDialog {
private fun showDialog(context: Context, feeds: List<Feed>, message: String, callback: Runnable?) {
val dialog: ConfirmationDialog = object : ConfirmationDialog(context, R.string.remove_feed_label, message) {
override fun onConfirmButtonPressed(clickedDialog: DialogInterface) {
@OptIn(UnstableApi::class) override fun onConfirmButtonPressed(clickedDialog: DialogInterface) {
callback?.run()
clickedDialog.dismiss()

View File

@ -43,7 +43,7 @@ class TagSettingsDialog : DialogFragment() {
viewBinding!!.tagsRecycler.layoutManager = GridLayoutManager(context, 2)
viewBinding!!.tagsRecycler.addItemDecoration(ItemOffsetDecoration(requireContext(), 4))
adapter = object : SimpleChipAdapter(requireContext()) {
override fun getChips(): List<String?> {
override fun getChips(): List<String> {
return displayedTags?: listOf()
}

View File

@ -36,7 +36,7 @@ import io.reactivex.schedulers.Schedulers
*/
@UnstableApi
class AddFeedFragment : Fragment() {
private var viewBinding: AddfeedBinding? = null
private lateinit var viewBinding: AddfeedBinding
private var activity: MainActivity? = null
private var displayUpArrow = false
@ -51,60 +51,58 @@ class AddFeedFragment : Fragment() {
): View {
super.onCreateView(inflater, container, savedInstanceState)
viewBinding = AddfeedBinding.inflate(inflater)
activity = getActivity() as MainActivity?
activity = getActivity() as? MainActivity
displayUpArrow = parentFragmentManager.backStackEntryCount != 0
if (savedInstanceState != null) {
displayUpArrow = savedInstanceState.getBoolean(KEY_UP_ARROW)
}
(getActivity() as MainActivity).setupToolbarToggle(viewBinding!!.toolbar, displayUpArrow)
(getActivity() as MainActivity).setupToolbarToggle(viewBinding.toolbar, displayUpArrow)
viewBinding!!.searchItunesButton.setOnClickListener { v: View? ->
viewBinding.searchItunesButton.setOnClickListener { v: View? ->
activity?.loadChildFragment(OnlineSearchFragment.newInstance(
ItunesPodcastSearcher::class.java))
}
viewBinding!!.searchFyydButton.setOnClickListener { v: View? ->
viewBinding.searchFyydButton.setOnClickListener { v: View? ->
activity?.loadChildFragment(OnlineSearchFragment.newInstance(
FyydPodcastSearcher::class.java))
}
viewBinding!!.searchGPodderButton.setOnClickListener { v: View? ->
viewBinding.searchGPodderButton.setOnClickListener { v: View? ->
activity?.loadChildFragment(OnlineSearchFragment.newInstance(
GpodnetPodcastSearcher::class.java))
}
viewBinding!!.searchPodcastIndexButton.setOnClickListener { v: View? ->
viewBinding.searchPodcastIndexButton.setOnClickListener { v: View? ->
activity?.loadChildFragment(OnlineSearchFragment.newInstance(
PodcastIndexPodcastSearcher::class.java))
}
viewBinding!!.combinedFeedSearchEditText.setOnEditorActionListener { v: TextView?, actionId: Int, event: KeyEvent? ->
viewBinding.combinedFeedSearchEditText.setOnEditorActionListener { v: TextView?, actionId: Int, event: KeyEvent? ->
performSearch()
true
}
viewBinding!!.addViaUrlButton.setOnClickListener { v: View? -> showAddViaUrlDialog() }
viewBinding.addViaUrlButton.setOnClickListener { v: View? -> showAddViaUrlDialog() }
viewBinding!!.opmlImportButton.setOnClickListener { v: View? ->
viewBinding.opmlImportButton.setOnClickListener { v: View? ->
try {
chooseOpmlImportPathLauncher.launch("*/*")
} catch (e: ActivityNotFoundException) {
e.printStackTrace()
(getActivity() as MainActivity)
.showSnackbarAbovePlayer(R.string.unable_to_start_system_file_manager, Snackbar.LENGTH_LONG)
activity?.showSnackbarAbovePlayer(R.string.unable_to_start_system_file_manager, Snackbar.LENGTH_LONG)
}
}
viewBinding!!.addLocalFolderButton.setOnClickListener { v: View? ->
viewBinding.addLocalFolderButton.setOnClickListener { v: View? ->
try {
addLocalFolderLauncher.launch(null)
} catch (e: ActivityNotFoundException) {
e.printStackTrace()
(getActivity() as MainActivity)
.showSnackbarAbovePlayer(R.string.unable_to_start_system_file_manager, Snackbar.LENGTH_LONG)
activity?.showSnackbarAbovePlayer(R.string.unable_to_start_system_file_manager, Snackbar.LENGTH_LONG)
}
}
viewBinding!!.searchButton.setOnClickListener { view: View? -> performSearch() }
viewBinding.searchButton.setOnClickListener { view: View? -> performSearch() }
return viewBinding!!.root
return viewBinding.root
}
override fun onSaveInstanceState(outState: Bundle) {
@ -127,8 +125,7 @@ class AddFeedFragment : Fragment() {
}
}
builder.setView(dialogBinding.root)
builder.setPositiveButton(R.string.confirm_label
) { dialog: DialogInterface?, which: Int -> addUrl(dialogBinding.urlEditText.text.toString()) }
builder.setPositiveButton(R.string.confirm_label) { dialog: DialogInterface?, which: Int -> addUrl(dialogBinding.urlEditText.text.toString()) }
builder.setNegativeButton(R.string.cancel_label, null)
builder.show()
}
@ -141,16 +138,16 @@ class AddFeedFragment : Fragment() {
}
private fun performSearch() {
viewBinding!!.combinedFeedSearchEditText.clearFocus()
viewBinding.combinedFeedSearchEditText.clearFocus()
val inVal = requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inVal.hideSoftInputFromWindow(viewBinding!!.combinedFeedSearchEditText.windowToken, 0)
val query = viewBinding!!.combinedFeedSearchEditText.text.toString()
inVal.hideSoftInputFromWindow(viewBinding.combinedFeedSearchEditText.windowToken, 0)
val query = viewBinding.combinedFeedSearchEditText.text.toString()
if (query.matches("http[s]?://.*".toRegex())) {
addUrl(query)
return
}
activity?.loadChildFragment(OnlineSearchFragment.newInstance(CombinedSearcher::class.java, query))
viewBinding!!.combinedFeedSearchEditText.post { viewBinding!!.combinedFeedSearchEditText.setText("") }
viewBinding.combinedFeedSearchEditText.post { viewBinding.combinedFeedSearchEditText.setText("") }
}
override fun onCreate(savedInstanceState: Bundle?) {
@ -205,8 +202,7 @@ class AddFeedFragment : Fragment() {
private class AddLocalFolder : ActivityResultContracts.OpenDocumentTree() {
override fun createIntent(context: Context, input: Uri?): Intent {
return super.createIntent(context, input)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
return super.createIntent(context, input).addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
}

View File

@ -64,22 +64,23 @@ import kotlin.math.min
*/
@UnstableApi
class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar.OnMenuItemClickListener {
var butPlaybackSpeed: PlaybackSpeedIndicatorView? = null
var txtvPlaybackSpeed: TextView? = null
private var pager: ViewPager2? = null
private var txtvPosition: TextView? = null
private var txtvLength: TextView? = null
private var sbPosition: ChapterSeekBar? = null
private var butRev: ImageButton? = null
private var txtvRev: TextView? = null
private var butPlay: PlayButton? = null
private var butFF: ImageButton? = null
private var txtvFF: TextView? = null
private var butSkip: ImageButton? = null
private var toolbar: MaterialToolbar? = null
private var progressIndicator: ProgressBar? = null
private var cardViewSeek: CardView? = null
private var txtvSeek: TextView? = null
lateinit var butPlaybackSpeed: PlaybackSpeedIndicatorView
lateinit var txtvPlaybackSpeed: TextView
private lateinit var pager: ViewPager2
private lateinit var txtvPosition: TextView
private lateinit var txtvLength: TextView
private lateinit var sbPosition: ChapterSeekBar
private lateinit var butRev: ImageButton
private lateinit var txtvRev: TextView
private lateinit var butPlay: PlayButton
private lateinit var butFF: ImageButton
private lateinit var txtvFF: TextView
private lateinit var butSkip: ImageButton
private lateinit var toolbar: MaterialToolbar
private lateinit var progressIndicator: ProgressBar
private lateinit var cardViewSeek: CardView
private lateinit var txtvSeek: TextView
private var controller: PlaybackController? = null
private var disposable: Disposable? = null
@ -97,11 +98,11 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
val root: View = inflater.inflate(R.layout.audioplayer_fragment, container, false)
root.setOnTouchListener { v: View?, event: MotionEvent? -> true } // Avoid clicks going through player to fragments below
toolbar = root.findViewById(R.id.toolbar)
toolbar?.title = ""
toolbar?.setNavigationOnClickListener { v: View? ->
toolbar.title = ""
toolbar.setNavigationOnClickListener { v: View? ->
(activity as MainActivity).bottomSheet?.setState(BottomSheetBehavior.STATE_COLLAPSED)
}
toolbar?.setOnMenuItemClickListener(this)
toolbar.setOnMenuItemClickListener(this)
val externalPlayerFragment = ExternalPlayerFragment()
childFragmentManager.beginTransaction()
@ -127,19 +128,19 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
setupLengthTextView()
setupControlButtons()
butPlaybackSpeed?.setOnClickListener { v: View? ->
butPlaybackSpeed.setOnClickListener { v: View? ->
VariableSpeedDialog().show(
childFragmentManager, null)
}
sbPosition?.setOnSeekBarChangeListener(this)
sbPosition.setOnSeekBarChangeListener(this)
pager = root.findViewById(R.id.pager)
pager?.adapter = AudioPlayerPagerAdapter(this@AudioPlayerFragment)
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() {
pager.offscreenPageLimit = NUM_CONTENT_FRAGMENTS
pager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
pager?.post {
pager.post {
if (activity != null) {
// By the time this is posted, the activity might be closed again.
(activity as MainActivity).bottomSheet?.updateScrollingChild()
@ -167,39 +168,39 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
}
}
sbPosition?.setDividerPos(dividerPos)
sbPosition.setDividerPos(dividerPos)
}
private fun setupControlButtons() {
butRev?.setOnClickListener { v: View? ->
butRev.setOnClickListener { v: View? ->
if (controller != null) {
val curr: Int = controller!!.position
controller!!.seekTo(curr - UserPreferences.rewindSecs * 1000)
}
}
butRev?.setOnLongClickListener { v: View? ->
butRev.setOnLongClickListener { v: View? ->
SkipPreferenceDialog.showSkipPreference(requireContext(),
SkipPreferenceDialog.SkipDirection.SKIP_REWIND, txtvRev)
true
}
butPlay?.setOnClickListener { v: View? ->
butPlay.setOnClickListener { v: View? ->
if (controller != null) {
controller!!.init()
controller!!.playPause()
}
}
butFF?.setOnClickListener { v: View? ->
butFF.setOnClickListener { v: View? ->
if (controller != null) {
val curr: Int = controller!!.position
controller!!.seekTo(curr + UserPreferences.fastForwardSecs * 1000)
}
}
butFF?.setOnLongClickListener { v: View? ->
butFF.setOnLongClickListener { v: View? ->
SkipPreferenceDialog.showSkipPreference(requireContext(),
SkipPreferenceDialog.SkipDirection.SKIP_FORWARD, txtvFF)
false
}
butSkip?.setOnClickListener { v: View? ->
butSkip.setOnClickListener { v: View? ->
activity?.sendBroadcast(
MediaButtonReceiver.createIntent(requireContext(), KeyEvent.KEYCODE_MEDIA_NEXT))
}
@ -222,7 +223,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
private fun setupLengthTextView() {
showTimeLeft = UserPreferences.shouldShowRemainingTime()
txtvLength?.setOnClickListener(View.OnClickListener { v: View? ->
txtvLength.setOnClickListener(View.OnClickListener { v: View? ->
if (controller == null) {
return@OnClickListener
}
@ -235,8 +236,8 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
@Subscribe(threadMode = ThreadMode.MAIN)
fun updatePlaybackSpeedButton(event: SpeedChangedEvent) {
val speedStr: String = DecimalFormat("0.00").format(event.newSpeed.toDouble())
txtvPlaybackSpeed?.text = speedStr
butPlaybackSpeed?.setSpeed(event.newSpeed)
txtvPlaybackSpeed.text = speedStr
butPlaybackSpeed.setSpeed(event.newSpeed)
}
private fun loadMediaInfo(includingChapters: Boolean) {
@ -268,7 +269,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
private fun newPlaybackController(): PlaybackController {
return object : PlaybackController(activity) {
override fun updatePlayButtonShowsPlay(showPlay: Boolean) {
butPlay?.setIsShowPlay(showPlay)
butPlay.setIsShowPlay(showPlay)
}
override fun loadMediaInfo() {
@ -311,41 +312,37 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
controller?.init()
loadMediaInfo(false)
EventBus.getDefault().register(this)
txtvRev?.text = NumberFormat.getInstance().format(UserPreferences.rewindSecs.toLong())
txtvFF?.text = NumberFormat.getInstance().format(UserPreferences.fastForwardSecs.toLong())
txtvRev.text = NumberFormat.getInstance().format(UserPreferences.rewindSecs.toLong())
txtvFF.text = NumberFormat.getInstance().format(UserPreferences.fastForwardSecs.toLong())
}
override fun onStop() {
super.onStop()
controller?.release()
controller = null
progressIndicator?.visibility = View.GONE // Controller released; we will not receive buffering updates
progressIndicator.visibility = View.GONE // Controller released; we will not receive buffering updates
EventBus.getDefault().unregister(this)
if (disposable != null) {
disposable!!.dispose()
}
disposable?.dispose()
}
@Subscribe(threadMode = ThreadMode.MAIN)
@Suppress("unused")
fun bufferUpdate(event: BufferUpdateEvent) {
if (event.hasStarted()) {
progressIndicator?.visibility = View.VISIBLE
progressIndicator.visibility = View.VISIBLE
} else if (event.hasEnded()) {
progressIndicator?.visibility = View.GONE
progressIndicator.visibility = View.GONE
} else if (controller != null && controller!!.isStreaming) {
sbPosition?.setSecondaryProgress((event.progress * sbPosition!!.max).toInt())
sbPosition.setSecondaryProgress((event.progress * sbPosition.max).toInt())
} else {
sbPosition?.setSecondaryProgress(0)
sbPosition.setSecondaryProgress(0)
}
}
@UnstableApi
@Subscribe(threadMode = ThreadMode.MAIN)
fun updatePosition(event: PlaybackPositionEvent) {
if (controller == null || txtvPosition == null || txtvLength == null || sbPosition == null) {
return
}
if (controller == null) return
val converter = TimeSpeedConverter(controller!!.currentPlaybackSpeedMultiplier)
val currentPosition: Int = converter.convert(event.position)
@ -357,23 +354,23 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
Log.w(TAG, "Could not react to position observer update because of invalid time")
return
}
txtvPosition?.text = Converter.getDurationStringLong(currentPosition)
txtvPosition?.setContentDescription(getString(R.string.position,
txtvPosition.text = Converter.getDurationStringLong(currentPosition)
txtvPosition.setContentDescription(getString(R.string.position,
Converter.getDurationStringLocalized(requireContext(), currentPosition.toLong())))
showTimeLeft = UserPreferences.shouldShowRemainingTime()
if (showTimeLeft) {
txtvLength?.setContentDescription(getString(R.string.remaining_time,
txtvLength.setContentDescription(getString(R.string.remaining_time,
Converter.getDurationStringLocalized(requireContext(), remainingTime.toLong())))
txtvLength?.text = (if ((remainingTime > 0)) "-" else "") + Converter.getDurationStringLong(remainingTime)
txtvLength.text = (if ((remainingTime > 0)) "-" else "") + Converter.getDurationStringLong(remainingTime)
} else {
txtvLength?.setContentDescription(getString(R.string.chapter_duration,
txtvLength.setContentDescription(getString(R.string.chapter_duration,
Converter.getDurationStringLocalized(requireContext(), duration.toLong())))
txtvLength?.text = Converter.getDurationStringLong(duration)
txtvLength.text = Converter.getDurationStringLong(duration)
}
if (sbPosition == null || !sbPosition!!.isPressed) {
if (!sbPosition.isPressed) {
val progress: Float = (event.position.toFloat()) / event.duration
sbPosition?.progress = (progress * sbPosition!!.max).toInt()
sbPosition.progress = (progress * sbPosition.max).toInt()
}
}
@ -388,9 +385,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
}
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
if (controller == null || txtvLength == null) {
return
}
if (controller == null) return
if (fromUser) {
val prog: Float = progress / (seekBar.max.toFloat())
@ -398,19 +393,19 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
var position: Int = converter.convert((prog * controller!!.duration).toInt())
val newChapterIndex: Int = ChapterUtils.getCurrentChapterIndex(controller!!.getMedia(), position)
if (newChapterIndex > -1) {
if ((sbPosition == null || !sbPosition!!.isPressed) && currentChapterIndex != newChapterIndex) {
if (!sbPosition.isPressed && currentChapterIndex != newChapterIndex) {
currentChapterIndex = newChapterIndex
val media = controller!!.getMedia()
position = media?.getChapters()?.get(currentChapterIndex)?.start?.toInt() ?: 0
seekedToChapterStart = true
controller!!.seekTo(position)
updateUi(controller!!.getMedia())
sbPosition?.highlightCurrentChapter()
sbPosition.highlightCurrentChapter()
}
txtvSeek?.text = controller!!.getMedia()?.getChapters()?.get(newChapterIndex)?.title ?: (""
txtvSeek.text = controller!!.getMedia()?.getChapters()?.get(newChapterIndex)?.title ?: (""
+ "\n" + Converter.getDurationStringLong(position))
} else {
txtvSeek?.text = Converter.getDurationStringLong(position)
txtvSeek.text = Converter.getDurationStringLong(position)
}
} else if (duration != controller!!.duration) {
updateUi(controller!!.getMedia())
@ -419,9 +414,9 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
override fun onStartTrackingTouch(seekBar: SeekBar) {
// interrupt position Observer, restart later
cardViewSeek?.scaleX = .8f
cardViewSeek?.scaleY = .8f
cardViewSeek?.animate()
cardViewSeek.scaleX = .8f
cardViewSeek.scaleY = .8f
cardViewSeek.animate()
?.setInterpolator(FastOutSlowInInterpolator())
?.alpha(1f)?.scaleX(1f)?.scaleY(1f)
?.setDuration(200)
@ -437,9 +432,9 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
controller!!.seekTo((prog * controller!!.duration).toInt())
}
}
cardViewSeek?.scaleX = 1f
cardViewSeek?.scaleY = 1f
cardViewSeek?.animate()
cardViewSeek.scaleX = 1f
cardViewSeek.scaleY = 1f
cardViewSeek.animate()
?.setInterpolator(FastOutSlowInInterpolator())
?.alpha(0f)?.scaleX(.8f)?.scaleY(.8f)
?.setDuration(200)
@ -447,29 +442,26 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
}
fun setupOptionsMenu(media: Playable?) {
if (toolbar == null) return
if (toolbar!!.menu.size() == 0) {
toolbar!!.inflateMenu(R.menu.mediaplayer)
}
if (controller == null) {
return
if (toolbar.menu.size() == 0) {
toolbar.inflateMenu(R.menu.mediaplayer)
}
val isFeedMedia = media is FeedMedia
toolbar?.menu?.findItem(R.id.open_feed_item)?.setVisible(isFeedMedia)
toolbar.menu?.findItem(R.id.open_feed_item)?.setVisible(isFeedMedia)
if (isFeedMedia) {
FeedItemMenuHandler.onPrepareMenu(toolbar!!.menu, (media as FeedMedia).getItem())
FeedItemMenuHandler.onPrepareMenu(toolbar.menu, (media as FeedMedia).getItem())
}
toolbar!!.menu.findItem(R.id.set_sleeptimer_item).setVisible(!controller!!.sleepTimerActive())
toolbar!!.menu.findItem(R.id.disable_sleeptimer_item).setVisible(controller!!.sleepTimerActive())
(activity as CastEnabledActivity).requestCastButton(toolbar!!.menu)
if (controller != null) {
toolbar.menu.findItem(R.id.set_sleeptimer_item).setVisible(!controller!!.sleepTimerActive())
toolbar.menu.findItem(R.id.disable_sleeptimer_item).setVisible(controller!!.sleepTimerActive())
}
(activity as CastEnabledActivity).requestCastButton(toolbar.menu)
}
override fun onMenuItemClick(item: MenuItem): Boolean {
if (controller == null) {
return false
}
if (controller == null) return false
val media: Playable = controller!!.getMedia() ?: return false
val feedItem: FeedItem? = if ((media is FeedMedia)) media.getItem() else null
@ -497,8 +489,8 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
player.alpha = 1 - playerFadeProgress
player.visibility = if (playerFadeProgress > 0.99f) View.INVISIBLE else View.VISIBLE
val toolbarFadeProgress = (max(0.0, min(0.2, (slideOffset - 0.6f).toDouble())) / 0.2f).toFloat()
toolbar?.setAlpha(toolbarFadeProgress)
toolbar?.visibility = if (toolbarFadeProgress < 0.01f) View.INVISIBLE else View.VISIBLE
toolbar.setAlpha(toolbarFadeProgress)
toolbar.visibility = if (toolbarFadeProgress < 0.01f) View.INVISIBLE else View.VISIBLE
}
private class AudioPlayerPagerAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {
@ -522,11 +514,7 @@ class AudioPlayerFragment : Fragment(), SeekBar.OnSeekBarChangeListener, Toolbar
@JvmOverloads
fun scrollToPage(page: Int, smoothScroll: Boolean = false) {
if (pager == null) {
return
}
pager?.setCurrentItem(page, smoothScroll)
pager.setCurrentItem(page, smoothScroll)
val visibleChild = childFragmentManager.findFragmentByTag("f$POS_DESCRIPTION")
if (visibleChild is ItemDescriptionFragment) {

View File

@ -37,14 +37,15 @@ import org.greenrobot.eventbus.ThreadMode
@UnstableApi
class ChaptersFragment : AppCompatDialogFragment() {
private var adapter: ChaptersListAdapter? = null
private lateinit var layoutManager: LinearLayoutManager
private lateinit var progressBar: ProgressBar
private lateinit var adapter: ChaptersListAdapter
private var controller: PlaybackController? = null
private var disposable: Disposable? = null
private var focusedChapter = -1
private var media: Playable? = null
private var layoutManager: LinearLayoutManager? = null
private var progressBar: ProgressBar? = null
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
val dialog = MaterialAlertDialogBuilder(requireContext())
.setTitle(getString(R.string.chapters_label))
@ -55,7 +56,7 @@ class ChaptersFragment : AppCompatDialogFragment() {
dialog.show()
dialog.getButton(DialogInterface.BUTTON_NEUTRAL).visibility = View.INVISIBLE
dialog.getButton(DialogInterface.BUTTON_NEUTRAL).setOnClickListener { v: View? ->
progressBar!!.visibility = View.VISIBLE
progressBar.visibility = View.VISIBLE
loadMediaInfo(true)
}
@ -70,22 +71,21 @@ class ChaptersFragment : AppCompatDialogFragment() {
progressBar = root.findViewById(R.id.progLoading)
layoutManager = LinearLayoutManager(activity)
recyclerView.layoutManager = layoutManager
recyclerView.addItemDecoration(DividerItemDecoration(recyclerView.context,
layoutManager!!.orientation))
recyclerView.addItemDecoration(DividerItemDecoration(recyclerView.context, layoutManager.orientation))
adapter = ChaptersListAdapter(requireContext(), object : ChaptersListAdapter.Callback {
override fun onPlayChapterButtonClicked(pos: Int) {
if (controller!!.status != PlayerStatus.PLAYING) {
if (controller?.status != PlayerStatus.PLAYING) {
controller!!.playPause()
}
val chapter = adapter!!.getItem(pos)
controller!!.seekTo(chapter.start.toInt())
val chapter = adapter.getItem(pos)
if (chapter != null) controller!!.seekTo(chapter.start.toInt())
updateChapterSelection(pos, true)
}
})
recyclerView.adapter = adapter
progressBar?.visibility = View.VISIBLE
progressBar.visibility = View.VISIBLE
val wrapHeight = CoordinatorLayout.LayoutParams(
CoordinatorLayout.LayoutParams.MATCH_PARENT, CoordinatorLayout.LayoutParams.WRAP_CONTENT)
@ -108,11 +108,8 @@ class ChaptersFragment : AppCompatDialogFragment() {
override fun onStop() {
super.onStop()
if (disposable != null) {
disposable!!.dispose()
}
controller!!.release()
disposable?.dispose()
controller?.release()
controller = null
EventBus.getDefault().unregister(this)
}
@ -120,20 +117,18 @@ class ChaptersFragment : AppCompatDialogFragment() {
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(event: PlaybackPositionEvent) {
updateChapterSelection(getCurrentChapter(media), false)
adapter!!.notifyTimeChanged(event.position.toLong())
adapter.notifyTimeChanged(event.position.toLong())
}
private fun getCurrentChapter(media: Playable?): Int {
if (controller == null) {
return -1
}
if (controller == null) return -1
return getCurrentChapterIndex(media, controller!!.position)
}
private fun loadMediaInfo(forceRefresh: Boolean) {
if (disposable != null) {
disposable!!.dispose()
}
disposable?.dispose()
disposable = Maybe.create { emitter: MaybeEmitter<Any> ->
val media = controller!!.getMedia()
if (media != null) {
@ -152,36 +147,29 @@ class ChaptersFragment : AppCompatDialogFragment() {
private fun onMediaChanged(media: Playable) {
this.media = media
focusedChapter = -1
if (adapter == null) {
return
}
if (media.getChapters().isEmpty()) {
dismiss()
Toast.makeText(context, R.string.no_chapters_label, Toast.LENGTH_LONG).show()
} else {
progressBar!!.visibility = View.GONE
progressBar.visibility = View.GONE
}
adapter!!.setMedia(media)
(dialog as AlertDialog?)!!.getButton(DialogInterface.BUTTON_NEUTRAL).visibility =
View.INVISIBLE
adapter.setMedia(media)
(dialog as AlertDialog).getButton(DialogInterface.BUTTON_NEUTRAL).visibility = View.INVISIBLE
if ((media is FeedMedia) && media.getItem() != null && !TextUtils.isEmpty(media.getItem()!!.podcastIndexChapterUrl)) {
(dialog as AlertDialog?)!!.getButton(DialogInterface.BUTTON_NEUTRAL).visibility = View.VISIBLE
(dialog as AlertDialog).getButton(DialogInterface.BUTTON_NEUTRAL).visibility = View.VISIBLE
}
val positionOfCurrentChapter = getCurrentChapter(media)
updateChapterSelection(positionOfCurrentChapter, true)
}
private fun updateChapterSelection(position: Int, scrollTo: Boolean) {
if (adapter == null) {
return
}
if (position != -1 && focusedChapter != position) {
focusedChapter = position
adapter!!.notifyChapterChanged(focusedChapter)
if (scrollTo && (layoutManager!!.findFirstCompletelyVisibleItemPosition() >= position
|| layoutManager!!.findLastCompletelyVisibleItemPosition() <= position)) {
layoutManager!!.scrollToPositionWithOffset(position, 100)
adapter.notifyChapterChanged(focusedChapter)
if (scrollTo && (layoutManager.findFirstCompletelyVisibleItemPosition() >= position
|| layoutManager.findLastCompletelyVisibleItemPosition() <= position)) {
layoutManager.scrollToPositionWithOffset(position, 100)
}
}
}

View File

@ -39,6 +39,8 @@ import ac.mdiq.podvinci.view.EmptyViewHandler
import ac.mdiq.podvinci.view.EpisodeItemListRecyclerView
import ac.mdiq.podvinci.view.LiftOnScrollListener
import ac.mdiq.podvinci.view.viewholder.EpisodeItemViewHolder
import androidx.recyclerview.widget.RecyclerView
import androidx.recyclerview.widget.SimpleItemAnimator
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
@ -52,75 +54,83 @@ import org.greenrobot.eventbus.ThreadMode
*/
class CompletedDownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeListener, Toolbar.OnMenuItemClickListener {
private var runningDownloads: Set<String>? = HashSet()
private var items: MutableList<FeedItem>? = ArrayList()
private var adapter: CompletedDownloadsListAdapter? = null
private var recyclerView: EpisodeItemListRecyclerView? = null
private var items: MutableList<FeedItem> = mutableListOf()
private lateinit var adapter: CompletedDownloadsListAdapter
private lateinit var toolbar: MaterialToolbar
private lateinit var recyclerView: EpisodeItemListRecyclerView
private lateinit var swipeActions: SwipeActions
private lateinit var speedDialView: SpeedDialView
private lateinit var progressBar: ProgressBar
private lateinit var emptyView: EmptyViewHandler
private var disposable: Disposable? = null
private var emptyView: EmptyViewHandler? = null
private var displayUpArrow = false
private var speedDialView: SpeedDialView? = null
private var swipeActions: SwipeActions? = null
private var progressBar: ProgressBar? = null
private var toolbar: MaterialToolbar? = null
@UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val root: View = inflater.inflate(R.layout.simple_list_fragment, container, false)
toolbar = root.findViewById(R.id.toolbar)
toolbar?.setTitle(R.string.downloads_label)
toolbar?.inflateMenu(R.menu.downloads_completed)
toolbar?.setOnMenuItemClickListener(this)
toolbar?.setOnLongClickListener { v: View? ->
recyclerView?.scrollToPosition(5)
recyclerView?.post { recyclerView!!.smoothScrollToPosition(0) }
toolbar.setTitle(R.string.downloads_label)
toolbar.inflateMenu(R.menu.downloads_completed)
toolbar.setOnMenuItemClickListener(this)
toolbar.setOnLongClickListener { v: View? ->
recyclerView.scrollToPosition(5)
recyclerView.post { recyclerView.smoothScrollToPosition(0) }
false
}
displayUpArrow = parentFragmentManager.backStackEntryCount != 0
if (savedInstanceState != null) {
displayUpArrow = savedInstanceState.getBoolean(KEY_UP_ARROW)
}
if (toolbar != null) (activity as MainActivity).setupToolbarToggle(toolbar!!, displayUpArrow)
(activity as MainActivity).setupToolbarToggle(toolbar, displayUpArrow)
recyclerView = root.findViewById(R.id.recyclerView)
recyclerView?.setRecycledViewPool((activity as MainActivity).recycledViewPool)
recyclerView.setRecycledViewPool((activity as MainActivity).recycledViewPool)
adapter = CompletedDownloadsListAdapter(activity as MainActivity)
adapter?.setOnSelectModeListener(this)
recyclerView?.adapter = adapter
recyclerView?.addOnScrollListener(LiftOnScrollListener(root.findViewById(R.id.appbar)))
adapter.setOnSelectModeListener(this)
recyclerView.adapter = adapter
recyclerView.addOnScrollListener(LiftOnScrollListener(root.findViewById(R.id.appbar)))
swipeActions = SwipeActions(this, TAG).attachTo(recyclerView)
swipeActions?.setFilter(FeedItemFilter(FeedItemFilter.DOWNLOADED))
swipeActions.setFilter(FeedItemFilter(FeedItemFilter.DOWNLOADED))
val animator: RecyclerView.ItemAnimator? = recyclerView.itemAnimator
if (animator is SimpleItemAnimator) {
animator.supportsChangeAnimations = false
}
progressBar = root.findViewById(R.id.progLoading)
progressBar?.visibility = View.VISIBLE
progressBar.visibility = View.VISIBLE
speedDialView = root.findViewById(R.id.fabSD)
speedDialView?.overlayLayout = root.findViewById(R.id.fabSDOverlay)
speedDialView?.inflate(R.menu.episodes_apply_action_speeddial)
speedDialView?.removeActionItemById(R.id.download_batch)
speedDialView?.removeActionItemById(R.id.mark_read_batch)
speedDialView?.removeActionItemById(R.id.mark_unread_batch)
speedDialView?.removeActionItemById(R.id.remove_from_queue_batch)
speedDialView?.removeActionItemById(R.id.remove_all_inbox_item)
speedDialView?.setOnChangeListener(object : SpeedDialView.OnChangeListener {
speedDialView.overlayLayout = root.findViewById(R.id.fabSDOverlay)
speedDialView.inflate(R.menu.episodes_apply_action_speeddial)
speedDialView.removeActionItemById(R.id.download_batch)
speedDialView.removeActionItemById(R.id.mark_read_batch)
speedDialView.removeActionItemById(R.id.mark_unread_batch)
speedDialView.removeActionItemById(R.id.remove_from_queue_batch)
speedDialView.removeActionItemById(R.id.remove_all_inbox_item)
speedDialView.setOnChangeListener(object : SpeedDialView.OnChangeListener {
override fun onMainActionSelected(): Boolean {
return false
}
override fun onToggleChanged(open: Boolean) {
if (open && adapter?.selectedCount == 0) {
if (open && adapter.selectedCount == 0) {
(activity as MainActivity).showSnackbarAbovePlayer(R.string.no_items_selected,
Snackbar.LENGTH_SHORT)
speedDialView?.close()
speedDialView.close()
}
}
})
speedDialView?.setOnActionSelectedListener { actionItem: SpeedDialActionItem ->
adapter?.selectedItems?.let {
speedDialView.setOnActionSelectedListener { actionItem: SpeedDialActionItem ->
adapter.selectedItems.let {
EpisodeMultiSelectActionHandler((activity as MainActivity), actionItem.id)
.handleAction(it.filterIsInstance<FeedItem>())
}
adapter?.endSelectMode()
adapter.endSelectMode()
true
}
if (arguments != null && requireArguments().getBoolean(ARG_SHOW_LOGS, false)) {
@ -139,11 +149,10 @@ class CompletedDownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeLis
override fun onDestroyView() {
EventBus.getDefault().unregister(this)
adapter?.endSelectMode()
if (toolbar != null) {
toolbar!!.setOnMenuItemClickListener(null)
toolbar!!.setOnLongClickListener(null)
}
adapter.endSelectMode()
toolbar.setOnMenuItemClickListener(null)
toolbar.setOnLongClickListener(null)
super.onDestroyView()
}
@ -157,7 +166,7 @@ class CompletedDownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeLis
disposable?.dispose()
}
override fun onMenuItemClick(item: MenuItem): Boolean {
@UnstableApi override fun onMenuItemClick(item: MenuItem): Boolean {
when (item.itemId) {
R.id.refresh_item -> {
FeedUpdateManager.runOnceOrAsk(requireContext())
@ -193,21 +202,20 @@ class CompletedDownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeLis
return // Refreshed anyway
}
for (downloadUrl in event.urls) {
var pos = -1
items?.let { pos = FeedItemUtil.indexOfItemWithDownloadUrl(it.toList(), downloadUrl)}
val pos = FeedItemUtil.indexOfItemWithDownloadUrl(items.toList(), downloadUrl)
if (pos >= 0) {
adapter?.notifyItemChangedCompat(pos)
adapter.notifyItemChangedCompat(pos)
}
}
}
override fun onContextItemSelected(item: MenuItem): Boolean {
val selectedItem: FeedItem? = adapter?.longPressedItem
val selectedItem: FeedItem? = adapter.longPressedItem
if (selectedItem == null) {
Log.i(TAG, "Selected item at current position was null, ignoring selection")
return super.onContextItemSelected(item)
}
if (adapter != null && adapter!!.onContextItemSelected(item)) {
if (adapter.onContextItemSelected(item)) {
return true
}
@ -216,51 +224,48 @@ class CompletedDownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeLis
private fun addEmptyView() {
emptyView = EmptyViewHandler(activity)
emptyView?.setIcon(R.drawable.ic_download)
emptyView?.setTitle(R.string.no_comp_downloads_head_label)
emptyView?.setMessage(R.string.no_comp_downloads_label)
if (recyclerView != null) emptyView?.attachToRecyclerView(recyclerView!!)
emptyView.setIcon(R.drawable.ic_download)
emptyView.setTitle(R.string.no_comp_downloads_head_label)
emptyView.setMessage(R.string.no_comp_downloads_label)
emptyView.attachToRecyclerView(recyclerView)
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(event: FeedItemEvent) {
Log.d(TAG, "onEventMainThread() called with: event = [$event]")
if (items == null) {
return
} else if (adapter == null) {
loadItems()
return
}
var i = 0
val size: Int = event.items.size
while (i < size) {
val item: FeedItem = event.items[i]
var pos = -1
items?.let { pos = FeedItemUtil.indexOfItemWithId(it.toList(), item.id) }
val pos = FeedItemUtil.indexOfItemWithId(items.toList(), item.id)
if (pos >= 0) {
items?.removeAt(pos)
items.removeAt(pos)
val media = item.media
if (media != null && media.isDownloaded()) {
items?.add(pos, item)
adapter?.notifyItemChangedCompat(pos)
items.add(pos, item)
// adapter.notifyItemChangedCompat(pos)
} else {
adapter?.notifyItemRemoved(pos)
// adapter.notifyItemRemoved(pos)
}
}
i++
}
// have to do this as adapter.notifyItemRemoved(pos) when pos == 0 causes crash
if (size > 0) {
adapter.setDummyViews(0)
adapter.updateItems(items)
}
}
@UnstableApi @Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(event: PlaybackPositionEvent?) {
if (adapter != null) {
for (i in 0 until adapter!!.itemCount) {
val holder: EpisodeItemViewHolder =
recyclerView?.findViewHolderForAdapterPosition(i) as EpisodeItemViewHolder
if (holder.isCurrentlyPlayingItem) {
if (event != null) holder.notifyPlaybackPositionUpdated(event)
break
}
fun onEventMainThread(event: PlaybackPositionEvent) {
// if (event == null) return
for (i in 0 until adapter.itemCount) {
val holder: EpisodeItemViewHolder? = recyclerView.findViewHolderForAdapterPosition(i) as? EpisodeItemViewHolder
if (holder != null && holder.isCurrentlyPlayingItem) {
holder.notifyPlaybackPositionUpdated(event)
break
}
}
}
@ -283,7 +288,7 @@ class CompletedDownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeLis
private fun loadItems() {
disposable?.dispose()
emptyView?.hide()
emptyView.hide()
disposable = Observable.fromCallable {
val sortOrder: SortOrder? = UserPreferences.downloadsSortedOrder
val downloadedItems: List<FeedItem> = DBReader.getEpisodes(0, Int.MAX_VALUE,
@ -306,35 +311,35 @@ class CompletedDownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeLis
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ result: List<FeedItem>? ->
items = result?.toMutableList()
adapter?.setDummyViews(0)
progressBar?.visibility = View.GONE
if (result != null) adapter?.updateItems(result)
{ result: List<FeedItem> ->
items = result.toMutableList()
adapter.setDummyViews(0)
progressBar.visibility = View.GONE
adapter.updateItems(result)
}, { error: Throwable? ->
adapter?.setDummyViews(0)
adapter?.updateItems(emptyList())
adapter.setDummyViews(0)
adapter.updateItems(emptyList())
Log.e(TAG, Log.getStackTraceString(error))
})
}
override fun onStartSelectMode() {
swipeActions?.detach()
speedDialView?.visibility = View.VISIBLE
swipeActions.detach()
speedDialView.visibility = View.VISIBLE
}
override fun onEndSelectMode() {
speedDialView?.close()
speedDialView?.visibility = View.GONE
swipeActions?.attachTo(recyclerView)
speedDialView.close()
speedDialView.visibility = View.GONE
swipeActions.attachTo(recyclerView)
}
@UnstableApi private inner class CompletedDownloadsListAdapter(mainActivity: MainActivity) :
EpisodeItemListAdapter(mainActivity) {
@UnstableApi private inner class CompletedDownloadsListAdapter(mainActivity: MainActivity) : EpisodeItemListAdapter(mainActivity) {
@UnstableApi override fun afterBindViewHolder(holder: EpisodeItemViewHolder, pos: Int) {
if (holder.feedItem != null && !inActionMode()) {
if (holder.feedItem!!.isDownloaded) {
val actionButton = DeleteActionButton(getItem(pos))
val item = getItem(pos) ?: return
val actionButton = DeleteActionButton(item)
actionButton.configure(holder.secondaryActionButton, holder.secondaryActionIcon, requireContext())
}
}
@ -345,8 +350,7 @@ class CompletedDownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeLis
if (!inActionMode()) {
menu.findItem(R.id.multi_select).setVisible(true)
}
MenuItemUtils.setOnClickListeners(menu
) { item: MenuItem ->
MenuItemUtils.setOnClickListeners(menu) { item: MenuItem ->
this@CompletedDownloadsFragment.onContextItemSelected(item)
}
}

View File

@ -1,6 +1,17 @@
package ac.mdiq.podvinci.fragment
import ac.mdiq.podvinci.R
import ac.mdiq.podvinci.activity.MainActivity
import ac.mdiq.podvinci.core.feed.util.ImageResourceUtils
import ac.mdiq.podvinci.core.util.ChapterUtils
import ac.mdiq.podvinci.core.util.DateFormatter
import ac.mdiq.podvinci.core.util.playback.PlaybackController
import ac.mdiq.podvinci.databinding.CoverFragmentBinding
import ac.mdiq.podvinci.event.playback.PlaybackPositionEvent
import ac.mdiq.podvinci.model.feed.Chapter
import ac.mdiq.podvinci.model.feed.EmbeddedChapterImage
import ac.mdiq.podvinci.model.feed.FeedMedia
import ac.mdiq.podvinci.model.playback.Playable
import android.animation.Animator
import android.animation.AnimatorListenerAdapter
import android.animation.AnimatorSet
@ -13,7 +24,6 @@ import android.graphics.ColorFilter
import android.graphics.drawable.Drawable
import android.os.Build
import android.os.Bundle
import android.text.TextUtils
import android.util.Log
import android.view.LayoutInflater
import android.view.View
@ -30,17 +40,6 @@ import com.bumptech.glide.load.resource.bitmap.FitCenter
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.request.RequestOptions
import com.google.android.material.snackbar.Snackbar
import ac.mdiq.podvinci.R
import ac.mdiq.podvinci.core.feed.util.ImageResourceUtils
import ac.mdiq.podvinci.core.util.ChapterUtils
import ac.mdiq.podvinci.core.util.DateFormatter
import ac.mdiq.podvinci.core.util.playback.PlaybackController
import ac.mdiq.podvinci.databinding.CoverFragmentBinding
import ac.mdiq.podvinci.event.playback.PlaybackPositionEvent
import ac.mdiq.podvinci.model.feed.Chapter
import ac.mdiq.podvinci.model.feed.EmbeddedChapterImage
import ac.mdiq.podvinci.model.feed.FeedMedia
import ac.mdiq.podvinci.model.playback.Playable
import io.reactivex.Maybe
import io.reactivex.MaybeEmitter
import io.reactivex.android.schedulers.AndroidSchedulers
@ -55,7 +54,7 @@ import org.greenrobot.eventbus.ThreadMode
* Displays the cover and the title of a FeedItem.
*/
class CoverFragment : Fragment() {
private var viewBinding: CoverFragmentBinding? = null
private lateinit var viewBinding: CoverFragmentBinding
private var controller: PlaybackController? = null
private var disposable: Disposable? = null
private var displayedChapterIndex = -1
@ -63,23 +62,23 @@ class CoverFragment : Fragment() {
@UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
viewBinding = CoverFragmentBinding.inflate(inflater)
viewBinding!!.imgvCover.setOnClickListener { v: View? -> onPlayPause() }
viewBinding!!.openDescription.setOnClickListener { view: View? ->
viewBinding.imgvCover.setOnClickListener { v: View? -> onPlayPause() }
viewBinding.openDescription.setOnClickListener { view: View? ->
(requireParentFragment() as AudioPlayerFragment)
.scrollToPage(AudioPlayerFragment.POS_DESCRIPTION, true)
}
val colorFilter: ColorFilter? = BlendModeColorFilterCompat.createBlendModeColorFilterCompat(
viewBinding!!.txtvPodcastTitle.currentTextColor, BlendModeCompat.SRC_IN)
viewBinding!!.butNextChapter.colorFilter = colorFilter
viewBinding!!.butPrevChapter.colorFilter = colorFilter
viewBinding!!.descriptionIcon.colorFilter = colorFilter
viewBinding!!.chapterButton.setOnClickListener { v: View? ->
viewBinding.txtvPodcastTitle.currentTextColor, BlendModeCompat.SRC_IN)
viewBinding.butNextChapter.colorFilter = colorFilter
viewBinding.butPrevChapter.colorFilter = colorFilter
viewBinding.descriptionIcon.colorFilter = colorFilter
viewBinding.chapterButton.setOnClickListener { v: View? ->
ChaptersFragment().show(
childFragmentManager, ChaptersFragment.TAG)
}
viewBinding!!.butPrevChapter.setOnClickListener { v: View? -> seekToPrevChapter() }
viewBinding!!.butNextChapter.setOnClickListener { v: View? -> seekToNextChapter() }
return viewBinding!!.root
viewBinding.butPrevChapter.setOnClickListener { v: View? -> seekToPrevChapter() }
viewBinding.butNextChapter.setOnClickListener { v: View? -> seekToNextChapter() }
return viewBinding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
@ -87,9 +86,8 @@ class CoverFragment : Fragment() {
}
@UnstableApi private fun loadMediaInfo(includingChapters: Boolean) {
if (disposable != null) {
disposable!!.dispose()
}
disposable?.dispose()
disposable = Maybe.create<Playable> { emitter: MaybeEmitter<Playable?> ->
val media: Playable? = controller?.getMedia()
if (media != null) {
@ -111,9 +109,9 @@ class CoverFragment : Fragment() {
}, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
}
private fun displayMediaInfo(media: Playable) {
@UnstableApi private fun displayMediaInfo(media: Playable) {
val pubDateStr = DateFormatter.formatAbbrev(activity, media.getPubDate())
viewBinding!!.txtvPodcastTitle.text = (StringUtils.stripToEmpty(media.getFeedTitle())
viewBinding.txtvPodcastTitle.text = (StringUtils.stripToEmpty(media.getFeedTitle())
+ "\u00A0"
+ ""
+ "\u00A0"
@ -122,36 +120,36 @@ class CoverFragment : Fragment() {
if (media.getItem() != null) {
val openFeed: Intent =
MainActivity.getIntentToOpenFeed(requireContext(), media.getItem()!!.feedId)
viewBinding!!.txtvPodcastTitle.setOnClickListener { v: View? -> startActivity(openFeed) }
viewBinding.txtvPodcastTitle.setOnClickListener { v: View? -> startActivity(openFeed) }
}
} else {
viewBinding!!.txtvPodcastTitle.setOnClickListener(null)
viewBinding.txtvPodcastTitle.setOnClickListener(null)
}
viewBinding!!.txtvPodcastTitle.setOnLongClickListener { v: View? -> copyText(media.getFeedTitle()) }
viewBinding!!.txtvEpisodeTitle.text = media.getEpisodeTitle()
viewBinding!!.txtvEpisodeTitle.setOnLongClickListener { v: View? -> copyText(media.getEpisodeTitle()) }
viewBinding!!.txtvEpisodeTitle.setOnClickListener { v: View? ->
val lines = viewBinding!!.txtvEpisodeTitle.lineCount
viewBinding.txtvPodcastTitle.setOnLongClickListener { v: View? -> copyText(media.getFeedTitle()) }
viewBinding.txtvEpisodeTitle.text = media.getEpisodeTitle()
viewBinding.txtvEpisodeTitle.setOnLongClickListener { v: View? -> copyText(media.getEpisodeTitle()) }
viewBinding.txtvEpisodeTitle.setOnClickListener { v: View? ->
val lines = viewBinding.txtvEpisodeTitle.lineCount
val animUnit = 1500
if (lines > viewBinding!!.txtvEpisodeTitle.maxLines) {
val titleHeight = (viewBinding!!.txtvEpisodeTitle.height
- viewBinding!!.txtvEpisodeTitle.paddingTop
- viewBinding!!.txtvEpisodeTitle.paddingBottom)
if (lines > viewBinding.txtvEpisodeTitle.maxLines) {
val titleHeight = (viewBinding.txtvEpisodeTitle.height
- viewBinding.txtvEpisodeTitle.paddingTop
- viewBinding.txtvEpisodeTitle.paddingBottom)
val verticalMarquee: ObjectAnimator = ObjectAnimator.ofInt(
viewBinding!!.txtvEpisodeTitle, "scrollY", 0,
(lines - viewBinding!!.txtvEpisodeTitle.maxLines)
* (titleHeight / viewBinding!!.txtvEpisodeTitle.maxLines))
viewBinding.txtvEpisodeTitle, "scrollY", 0,
(lines - viewBinding.txtvEpisodeTitle.maxLines)
* (titleHeight / viewBinding.txtvEpisodeTitle.maxLines))
.setDuration((lines * animUnit).toLong())
val fadeOut: ObjectAnimator = ObjectAnimator.ofFloat(
viewBinding!!.txtvEpisodeTitle, "alpha", 0f)
viewBinding.txtvEpisodeTitle, "alpha", 0f)
fadeOut.setStartDelay(animUnit.toLong())
fadeOut.addListener(object : AnimatorListenerAdapter() {
override fun onAnimationEnd(animation: Animator) {
viewBinding!!.txtvEpisodeTitle.scrollTo(0, 0)
viewBinding.txtvEpisodeTitle.scrollTo(0, 0)
}
})
val fadeBackIn: ObjectAnimator = ObjectAnimator.ofFloat(
viewBinding!!.txtvEpisodeTitle, "alpha", 1f)
viewBinding.txtvEpisodeTitle, "alpha", 1f)
val set = AnimatorSet()
set.playSequentially(verticalMarquee, fadeOut, fadeBackIn)
set.start()
@ -173,9 +171,9 @@ class CoverFragment : Fragment() {
chapterControlVisible = fm?.getItem() != null && fm.getItem()!!.hasChapters()
}
val newVisibility = if (chapterControlVisible) View.VISIBLE else View.GONE
if (viewBinding!!.chapterButton.visibility != newVisibility) {
viewBinding!!.chapterButton.visibility = newVisibility
ObjectAnimator.ofFloat(viewBinding!!.chapterButton,
if (viewBinding.chapterButton.visibility != newVisibility) {
viewBinding.chapterButton.visibility = newVisibility
ObjectAnimator.ofFloat(viewBinding.chapterButton,
"alpha",
(if (chapterControlVisible) 0 else 1).toFloat(),
(if (chapterControlVisible) 1 else 0).toFloat())
@ -187,10 +185,10 @@ class CoverFragment : Fragment() {
if (media != null && chapterIndex > -1) {
if (media!!.getPosition() > media!!.getDuration() || chapterIndex >= media!!.getChapters().size - 1) {
displayedChapterIndex = media!!.getChapters().size - 1
viewBinding!!.butNextChapter.visibility = View.INVISIBLE
viewBinding.butNextChapter.visibility = View.INVISIBLE
} else {
displayedChapterIndex = chapterIndex
viewBinding!!.butNextChapter.visibility = View.VISIBLE
viewBinding.butNextChapter.visibility = View.VISIBLE
}
}
@ -245,10 +243,7 @@ class CoverFragment : Fragment() {
@UnstableApi override fun onStop() {
super.onStop()
if (disposable != null) {
disposable!!.dispose()
}
disposable?.dispose()
controller?.release()
controller = null
EventBus.getDefault().unregister(this)
@ -277,14 +272,14 @@ class CoverFragment : Fragment() {
.apply(options)
if (displayedChapterIndex == -1 || media!!.getChapters().isEmpty() || media!!.getChapters()[displayedChapterIndex].imageUrl.isNullOrEmpty()) {
cover.into(viewBinding!!.imgvCover)
cover.into(viewBinding.imgvCover)
} else {
Glide.with(this)
.load(EmbeddedChapterImage.getModelFor(media!!, displayedChapterIndex))
.apply(options)
.thumbnail(cover)
.error(cover)
.into(viewBinding!!.imgvCover)
.into(viewBinding.imgvCover)
}
}
@ -296,26 +291,26 @@ class CoverFragment : Fragment() {
private fun configureForOrientation(newConfig: Configuration) {
val isPortrait = newConfig.orientation == Configuration.ORIENTATION_PORTRAIT
viewBinding!!.coverFragment.orientation = if (isPortrait) LinearLayout.VERTICAL else LinearLayout.HORIZONTAL
viewBinding.coverFragment.orientation = if (isPortrait) LinearLayout.VERTICAL else LinearLayout.HORIZONTAL
if (isPortrait) {
viewBinding!!.coverHolder.layoutParams =
viewBinding.coverHolder.layoutParams =
LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 0, 1f)
viewBinding!!.coverFragmentTextContainer.layoutParams =
viewBinding.coverFragmentTextContainer.layoutParams =
LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.WRAP_CONTENT)
} else {
viewBinding!!.coverHolder.layoutParams =
viewBinding.coverHolder.layoutParams =
LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1f)
viewBinding!!.coverFragmentTextContainer.layoutParams =
viewBinding.coverFragmentTextContainer.layoutParams =
LinearLayout.LayoutParams(0, ViewGroup.LayoutParams.MATCH_PARENT, 1f)
}
(viewBinding!!.episodeDetails.parent as ViewGroup).removeView(viewBinding!!.episodeDetails)
(viewBinding.episodeDetails.parent as ViewGroup).removeView(viewBinding.episodeDetails)
if (isPortrait) {
viewBinding!!.coverFragment.addView(viewBinding!!.episodeDetails)
viewBinding.coverFragment.addView(viewBinding.episodeDetails)
} else {
viewBinding!!.coverFragmentTextContainer.addView(viewBinding!!.episodeDetails)
viewBinding.coverFragmentTextContainer.addView(viewBinding.episodeDetails)
}
}
@ -326,7 +321,7 @@ class CoverFragment : Fragment() {
controller!!.playPause()
}
private fun copyText(text: String): Boolean {
@UnstableApi private fun copyText(text: String): Boolean {
val clipboardManager: ClipboardManager? = ContextCompat.getSystemService(requireContext(), ClipboardManager::class.java)
clipboardManager?.setPrimaryClip(ClipData.newPlainText("PodVinci", text))
if (Build.VERSION.SDK_INT <= 32) {

View File

@ -38,17 +38,18 @@ import java.util.*
* Searches iTunes store for top podcasts and displays results in a list.
*/
class DiscoveryFragment : Fragment(), Toolbar.OnMenuItemClickListener {
private var prefs: SharedPreferences? = null
private lateinit var prefs: SharedPreferences
private lateinit var gridView: GridView
private lateinit var progressBar: ProgressBar
private lateinit var txtvError: TextView
private lateinit var butRetry: Button
private lateinit var txtvEmpty: TextView
private lateinit var toolbar: MaterialToolbar
/**
* Adapter responsible with the search results.
*/
private var adapter: ItunesAdapter? = null
private var gridView: GridView? = null
private var progressBar: ProgressBar? = null
private var txtvError: TextView? = null
private var butRetry: Button? = null
private var txtvEmpty: TextView? = null
/**
* List of podcasts retreived from the search.
@ -59,7 +60,7 @@ class DiscoveryFragment : Fragment(), Toolbar.OnMenuItemClickListener {
private var countryCode: String? = "US"
private var hidden = false
private var needsConfirm = false
private var toolbar: MaterialToolbar? = null
/**
* Replace adapter data with provided search results from SearchTask.
@ -68,28 +69,26 @@ class DiscoveryFragment : Fragment(), Toolbar.OnMenuItemClickListener {
*/
private fun updateData(result: List<PodcastSearchResult>?) {
this.searchResults = result
adapter!!.clear()
if (result != null && result.size > 0) {
gridView!!.visibility = View.VISIBLE
txtvEmpty!!.visibility = View.GONE
adapter?.clear()
if (!result.isNullOrEmpty()) {
gridView.visibility = View.VISIBLE
txtvEmpty.visibility = View.GONE
for (p in result) {
adapter!!.add(p)
}
adapter!!.notifyDataSetInvalidated()
adapter?.notifyDataSetInvalidated()
} else {
gridView!!.visibility = View.GONE
txtvEmpty!!.visibility = View.VISIBLE
gridView.visibility = View.GONE
txtvEmpty.visibility = View.VISIBLE
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
prefs = requireActivity().getSharedPreferences(ItunesTopListLoader.PREFS, Context.MODE_PRIVATE)
if (prefs != null) {
countryCode = prefs!!.getString(ItunesTopListLoader.PREF_KEY_COUNTRY_CODE, Locale.getDefault().country)
hidden = prefs!!.getBoolean(ItunesTopListLoader.PREF_KEY_HIDDEN_DISCOVERY_COUNTRY, false)
needsConfirm = prefs!!.getBoolean(ItunesTopListLoader.PREF_KEY_NEEDS_CONFIRM, true)
}
countryCode = prefs.getString(ItunesTopListLoader.PREF_KEY_COUNTRY_CODE, Locale.getDefault().country)
hidden = prefs.getBoolean(ItunesTopListLoader.PREF_KEY_HIDDEN_DISCOVERY_COUNTRY, false)
needsConfirm = prefs.getBoolean(ItunesTopListLoader.PREF_KEY_NEEDS_CONFIRM, true)
}
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
@ -97,19 +96,18 @@ class DiscoveryFragment : Fragment(), Toolbar.OnMenuItemClickListener {
val root = inflater.inflate(R.layout.fragment_itunes_search, container, false)
gridView = root.findViewById(R.id.gridView)
adapter = ItunesAdapter(requireActivity(), ArrayList())
gridView?.setAdapter(adapter)
gridView.setAdapter(adapter)
toolbar = root.findViewById(R.id.toolbar)
toolbar?.setNavigationOnClickListener(View.OnClickListener { v: View? -> parentFragmentManager.popBackStack() })
toolbar?.inflateMenu(R.menu.countries_menu)
if (toolbar != null) {
val discoverHideItem = toolbar!!.getMenu().findItem(R.id.discover_hide_item)
discoverHideItem.setChecked(hidden)
}
toolbar?.setOnMenuItemClickListener(this)
toolbar.setNavigationOnClickListener { v: View? -> parentFragmentManager.popBackStack() }
toolbar.inflateMenu(R.menu.countries_menu)
val discoverHideItem = toolbar.menu.findItem(R.id.discover_hide_item)
discoverHideItem.setChecked(hidden)
toolbar.setOnMenuItemClickListener(this)
//Show information about the podcast when the list item is clicked
gridView?.setOnItemClickListener(OnItemClickListener { parent: AdapterView<*>?, view1: View?, position: Int, id: Long ->
gridView.onItemClickListener = OnItemClickListener { parent: AdapterView<*>?, view1: View?, position: Int, id: Long ->
val podcast = searchResults!![position]
if (podcast.feedUrl == null) {
return@OnItemClickListener
@ -117,7 +115,7 @@ class DiscoveryFragment : Fragment(), Toolbar.OnMenuItemClickListener {
val intent = Intent(activity, OnlineFeedViewActivity::class.java)
intent.putExtra(OnlineFeedViewActivity.ARG_FEEDURL, podcast.feedUrl)
startActivity(intent)
})
}
progressBar = root.findViewById(R.id.progressBar)
txtvError = root.findViewById(R.id.txtvError)
@ -130,45 +128,42 @@ class DiscoveryFragment : Fragment(), Toolbar.OnMenuItemClickListener {
override fun onDestroy() {
super.onDestroy()
if (disposable != null) {
disposable!!.dispose()
}
disposable?.dispose()
adapter = null
}
private fun loadToplist(country: String?) {
if (disposable != null) {
disposable!!.dispose()
}
disposable?.dispose()
gridView!!.visibility = View.GONE
txtvError!!.visibility = View.GONE
butRetry!!.visibility = View.GONE
butRetry!!.setText(R.string.retry_label)
txtvEmpty!!.visibility = View.GONE
progressBar!!.visibility = View.VISIBLE
gridView.visibility = View.GONE
txtvError.visibility = View.GONE
butRetry.visibility = View.GONE
butRetry.setText(R.string.retry_label)
txtvEmpty.visibility = View.GONE
progressBar.visibility = View.VISIBLE
if (hidden) {
gridView!!.visibility = View.GONE
txtvError!!.visibility = View.VISIBLE
txtvError!!.text = resources.getString(R.string.discover_is_hidden)
butRetry!!.visibility = View.GONE
txtvEmpty!!.visibility = View.GONE
progressBar!!.visibility = View.GONE
gridView.visibility = View.GONE
txtvError.visibility = View.VISIBLE
txtvError.text = resources.getString(R.string.discover_is_hidden)
butRetry.visibility = View.GONE
txtvEmpty.visibility = View.GONE
progressBar.visibility = View.GONE
return
}
if (BuildConfig.FLAVOR == "free" && needsConfirm) {
txtvError!!.visibility = View.VISIBLE
txtvError!!.text = ""
butRetry!!.visibility = View.VISIBLE
butRetry!!.setText(R.string.discover_confirm)
butRetry!!.setOnClickListener { v: View? ->
prefs!!.edit().putBoolean(ItunesTopListLoader.PREF_KEY_NEEDS_CONFIRM, false).apply()
txtvError.visibility = View.VISIBLE
txtvError.text = ""
butRetry.visibility = View.VISIBLE
butRetry.setText(R.string.discover_confirm)
butRetry.setOnClickListener { v: View? ->
prefs.edit().putBoolean(ItunesTopListLoader.PREF_KEY_NEEDS_CONFIRM, false).apply()
needsConfirm = false
loadToplist(country)
}
txtvEmpty!!.visibility = View.GONE
progressBar!!.visibility = View.GONE
txtvEmpty.visibility = View.GONE
progressBar.visibility = View.GONE
return
}
@ -179,16 +174,16 @@ class DiscoveryFragment : Fragment(), Toolbar.OnMenuItemClickListener {
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ podcasts: List<PodcastSearchResult>? ->
progressBar!!.visibility = View.GONE
progressBar.visibility = View.GONE
topList = podcasts
updateData(topList)
}, { error: Throwable ->
Log.e(TAG, Log.getStackTraceString(error))
progressBar!!.visibility = View.GONE
txtvError!!.text = error.message
txtvError!!.visibility = View.VISIBLE
butRetry!!.setOnClickListener { v: View? -> loadToplist(country) }
butRetry!!.visibility = View.VISIBLE
progressBar.visibility = View.GONE
txtvError.text = error.message
txtvError.visibility = View.VISIBLE
butRetry.setOnClickListener { v: View? -> loadToplist(country) }
butRetry.visibility = View.VISIBLE
})
}
@ -200,7 +195,7 @@ class DiscoveryFragment : Fragment(), Toolbar.OnMenuItemClickListener {
if (itemId == R.id.discover_hide_item) {
item.setChecked(!item.isChecked)
hidden = item.isChecked
prefs!!.edit().putBoolean(ItunesTopListLoader.PREF_KEY_HIDDEN_DISCOVERY_COUNTRY, hidden).apply()
prefs.edit().putBoolean(ItunesTopListLoader.PREF_KEY_HIDDEN_DISCOVERY_COUNTRY, hidden).apply()
EventBus.getDefault().post(DiscoveryDefaultUpdateEvent())
loadToplist(countryCode)
@ -211,7 +206,7 @@ class DiscoveryFragment : Fragment(), Toolbar.OnMenuItemClickListener {
val builder = MaterialAlertDialogBuilder(requireContext())
builder.setView(selectCountryDialogView)
val countryCodeArray: List<String> = ArrayList(Arrays.asList(*Locale.getISOCountries()))
val countryCodeArray: List<String> = listOf(*Locale.getISOCountries())
val countryCodeNames: MutableMap<String?, String> = HashMap()
val countryNameCodes: MutableMap<String, String> = HashMap()
for (code in countryCodeArray) {
@ -221,8 +216,8 @@ class DiscoveryFragment : Fragment(), Toolbar.OnMenuItemClickListener {
countryNameCodes[countryName] = code
}
val countryNamesSort: List<String> = ArrayList(countryCodeNames.values)
Collections.sort(countryNamesSort)
val countryNamesSort: MutableList<String> = ArrayList(countryCodeNames.values)
countryNamesSort.sort()
val dataAdapter =
ArrayAdapter(this.requireContext(), android.R.layout.simple_list_item_1, countryNamesSort)
@ -231,7 +226,7 @@ class DiscoveryFragment : Fragment(), Toolbar.OnMenuItemClickListener {
editText!!.setAdapter(dataAdapter)
editText.setText(countryCodeNames[countryCode])
editText.setOnClickListener { view: View? ->
if (editText.text.length != 0) {
if (editText.text.isNotEmpty()) {
editText.setText("")
editText.postDelayed({ editText.showDropDown() }, 100)
}
@ -247,13 +242,13 @@ class DiscoveryFragment : Fragment(), Toolbar.OnMenuItemClickListener {
val countryName = editText.text.toString()
if (countryNameCodes.containsKey(countryName)) {
countryCode = countryNameCodes[countryName]
val discoverHideItem = toolbar!!.menu.findItem(R.id.discover_hide_item)
val discoverHideItem = toolbar.menu.findItem(R.id.discover_hide_item)
discoverHideItem.setChecked(false)
hidden = false
}
prefs!!.edit().putBoolean(ItunesTopListLoader.PREF_KEY_HIDDEN_DISCOVERY_COUNTRY, hidden).apply()
prefs!!.edit().putString(ItunesTopListLoader.PREF_KEY_COUNTRY_CODE, countryCode).apply()
prefs.edit().putBoolean(ItunesTopListLoader.PREF_KEY_HIDDEN_DISCOVERY_COUNTRY, hidden).apply()
prefs.edit().putString(ItunesTopListLoader.PREF_KEY_COUNTRY_CODE, countryCode).apply()
EventBus.getDefault().post(DiscoveryDefaultUpdateEvent())
loadToplist(countryCode)

View File

@ -28,10 +28,11 @@ import org.greenrobot.eventbus.Subscribe
* Shows the download log
*/
class DownloadLogFragment : BottomSheetDialogFragment(), OnItemClickListener, Toolbar.OnMenuItemClickListener {
private lateinit var viewBinding: DownloadLogFragmentBinding
private lateinit var adapter: DownloadLogAdapter
private var downloadLog: List<DownloadResult> = ArrayList()
private var adapter: DownloadLogAdapter? = null
private var disposable: Disposable? = null
private var viewBinding: DownloadLogFragmentBinding? = null
override fun onStart() {
super.onStart()
@ -40,30 +41,28 @@ class DownloadLogFragment : BottomSheetDialogFragment(), OnItemClickListener, To
override fun onStop() {
super.onStop()
if (disposable != null) {
disposable!!.dispose()
}
disposable?.dispose()
}
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?, savedInstanceState: Bundle?
): View {
viewBinding = DownloadLogFragmentBinding.inflate(inflater)
viewBinding!!.toolbar.inflateMenu(R.menu.download_log)
viewBinding!!.toolbar.setOnMenuItemClickListener(this)
viewBinding.toolbar.inflateMenu(R.menu.download_log)
viewBinding.toolbar.setOnMenuItemClickListener(this)
val emptyView = EmptyViewHandler(activity)
emptyView.setIcon(R.drawable.ic_download)
emptyView.setTitle(R.string.no_log_downloads_head_label)
emptyView.setMessage(R.string.no_log_downloads_label)
emptyView.attachToListView(viewBinding!!.list)
emptyView.attachToListView(viewBinding.list)
adapter = DownloadLogAdapter(requireActivity())
viewBinding!!.list.adapter = adapter
viewBinding!!.list.onItemClickListener = this
viewBinding!!.list.isNestedScrollingEnabled = true
viewBinding.list.adapter = adapter
viewBinding.list.onItemClickListener = this
viewBinding.list.isNestedScrollingEnabled = true
EventBus.getDefault().register(this)
return viewBinding!!.root
return viewBinding.root
}
override fun onDestroyView() {
@ -72,7 +71,7 @@ class DownloadLogFragment : BottomSheetDialogFragment(), OnItemClickListener, To
}
override fun onItemClick(parent: AdapterView<*>?, view: View, position: Int, id: Long) {
val item = adapter!!.getItem(position)
val item = adapter.getItem(position)
if (item is DownloadResult) {
DownloadLogDetailsDialog(requireContext(), item).show()
}
@ -98,16 +97,15 @@ class DownloadLogFragment : BottomSheetDialogFragment(), OnItemClickListener, To
}
private fun loadDownloadLog() {
if (disposable != null) {
disposable!!.dispose()
}
disposable?.dispose()
disposable = Observable.fromCallable { DBReader.getDownloadLog() }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ result: List<DownloadResult>? ->
if (result != null) {
downloadLog = result
adapter!!.setDownloadLog(downloadLog)
adapter.setDownloadLog(downloadLog)
}
}, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
}

View File

@ -56,23 +56,20 @@ abstract class EpisodesListFragment : Fragment(), SelectableAdapter.OnSelectMode
protected var hasMoreItems: Boolean = false
private var displayUpArrow = false
var recyclerView: EpisodeItemListRecyclerView? = null
var listAdapter: EpisodeItemListAdapter? = null
@JvmField
var emptyView: EmptyViewHandler? = null
@JvmField
var speedDialView: SpeedDialView? = null
@JvmField
var toolbar: MaterialToolbar? = null
var swipeRefreshLayout: SwipeRefreshLayout? = null
var swipeActions: SwipeActions? = null
private var progressBar: ProgressBar? = null
lateinit var recyclerView: EpisodeItemListRecyclerView
lateinit var emptyView: EmptyViewHandler
lateinit var speedDialView: SpeedDialView
lateinit var toolbar: MaterialToolbar
lateinit var swipeRefreshLayout: SwipeRefreshLayout
lateinit var swipeActions: SwipeActions
private lateinit var progressBar: ProgressBar
lateinit var listAdapter: EpisodeItemListAdapter
@JvmField
var episodes: MutableList<FeedItem> = ArrayList()
protected var disposable: Disposable? = null
protected var txtvInformation: TextView? = null
protected lateinit var txtvInformation: TextView
override fun onStart() {
super.onStart()
@ -82,24 +79,22 @@ abstract class EpisodesListFragment : Fragment(), SelectableAdapter.OnSelectMode
override fun onResume() {
super.onResume()
if (recyclerView != null) registerForContextMenu(recyclerView!!)
registerForContextMenu(recyclerView)
}
override fun onPause() {
super.onPause()
if (getPrefName() != null) recyclerView?.saveScrollPosition(getPrefName())
if (recyclerView != null) unregisterForContextMenu(recyclerView!!)
recyclerView.saveScrollPosition(getPrefName())
unregisterForContextMenu(recyclerView)
}
override fun onStop() {
super.onStop()
EventBus.getDefault().unregister(this)
if (disposable != null) {
disposable?.dispose()
}
disposable?.dispose()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
@UnstableApi override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (super.onOptionsItemSelected(item)) {
return true
}
@ -116,17 +111,17 @@ abstract class EpisodesListFragment : Fragment(), SelectableAdapter.OnSelectMode
override fun onContextItemSelected(item: MenuItem): Boolean {
Log.d(TAG, "onContextItemSelected() called with: item = [$item]")
if (listAdapter == null || !userVisibleHint || !isVisible || !isMenuVisible) {
if (!userVisibleHint || !isVisible || !isMenuVisible) {
// The method is called on all fragments in a ViewPager, so this needs to be ignored in invisible ones.
// Apparently, none of the visibility check method works reliably on its own, so we just use all.
return false
} else if (listAdapter?.longPressedItem == null) {
} else if (listAdapter.longPressedItem == null) {
Log.i(TAG, "Selected item or listAdapter was null, ignoring selection")
return super.onContextItemSelected(item)
} else if (listAdapter != null && listAdapter!!.onContextItemSelected(item)) {
} else if (listAdapter.onContextItemSelected(item)) {
return true
}
val selectedItem: FeedItem = listAdapter!!.longPressedItem ?: return false
val selectedItem: FeedItem = listAdapter.longPressedItem ?: return false
return FeedItemMenuHandler.onMenuItemClicked(this, item.itemId, selectedItem)
}
@ -135,36 +130,34 @@ abstract class EpisodesListFragment : Fragment(), SelectableAdapter.OnSelectMode
val root: View = inflater.inflate(R.layout.episodes_list_fragment, container, false)
txtvInformation = root.findViewById(R.id.txtvInformation)
toolbar = root.findViewById(R.id.toolbar)
toolbar?.setOnMenuItemClickListener(this)
toolbar?.setOnLongClickListener { v: View? ->
recyclerView?.scrollToPosition(5)
recyclerView?.post { recyclerView?.smoothScrollToPosition(0) }
toolbar.setOnMenuItemClickListener(this)
toolbar.setOnLongClickListener { v: View? ->
recyclerView.scrollToPosition(5)
recyclerView.post { recyclerView.smoothScrollToPosition(0) }
false
}
displayUpArrow = parentFragmentManager.backStackEntryCount != 0
if (savedInstanceState != null) {
displayUpArrow = savedInstanceState.getBoolean(KEY_UP_ARROW)
}
if (toolbar != null) (activity as MainActivity).setupToolbarToggle(toolbar!!, displayUpArrow)
(activity as MainActivity).setupToolbarToggle(toolbar, displayUpArrow)
recyclerView = root.findViewById(R.id.recyclerView)
recyclerView?.setRecycledViewPool((activity as MainActivity).recycledViewPool)
recyclerView.setRecycledViewPool((activity as MainActivity).recycledViewPool)
setupLoadMoreScrollListener()
recyclerView?.addOnScrollListener(LiftOnScrollListener(root.findViewById(R.id.appbar)))
recyclerView.addOnScrollListener(LiftOnScrollListener(root.findViewById(R.id.appbar)))
swipeActions = SwipeActions(this, getFragmentTag()).attachTo(recyclerView)
swipeActions?.setFilter(getFilter())
if (recyclerView != null) {
val animator: RecyclerView.ItemAnimator? = recyclerView!!.itemAnimator
if (animator is SimpleItemAnimator) {
animator.supportsChangeAnimations = false
}
swipeActions.setFilter(getFilter())
val animator: RecyclerView.ItemAnimator? = recyclerView.itemAnimator
if (animator is SimpleItemAnimator) {
animator.supportsChangeAnimations = false
}
swipeRefreshLayout = root.findViewById(R.id.swipeRefresh)
swipeRefreshLayout?.setDistanceToTriggerSync(resources.getInteger(R.integer.swipe_refresh_distance))
swipeRefreshLayout?.setOnRefreshListener {
swipeRefreshLayout.setDistanceToTriggerSync(resources.getInteger(R.integer.swipe_refresh_distance))
swipeRefreshLayout.setOnRefreshListener {
FeedUpdateManager.runOnceOrAsk(requireContext())
}
@ -180,38 +173,38 @@ abstract class EpisodesListFragment : Fragment(), SelectableAdapter.OnSelectMode
}
}
}
listAdapter?.setOnSelectModeListener(this)
recyclerView?.adapter = listAdapter
listAdapter.setOnSelectModeListener(this)
recyclerView.adapter = listAdapter
progressBar = root.findViewById(R.id.progressBar)
progressBar?.visibility = View.VISIBLE
progressBar.visibility = View.VISIBLE
emptyView = EmptyViewHandler(context)
if (recyclerView != null) emptyView?.attachToRecyclerView(recyclerView!!)
emptyView?.setIcon(R.drawable.ic_feed)
emptyView?.setTitle(R.string.no_all_episodes_head_label)
emptyView?.setMessage(R.string.no_all_episodes_label)
emptyView?.updateAdapter(listAdapter)
emptyView?.hide()
emptyView.attachToRecyclerView(recyclerView)
emptyView.setIcon(R.drawable.ic_feed)
emptyView.setTitle(R.string.no_all_episodes_head_label)
emptyView.setMessage(R.string.no_all_episodes_label)
emptyView.updateAdapter(listAdapter)
emptyView.hide()
speedDialView = root.findViewById(R.id.fabSD)
speedDialView?.overlayLayout = root.findViewById(R.id.fabSDOverlay)
speedDialView?.inflate(R.menu.episodes_apply_action_speeddial)
speedDialView?.setOnChangeListener(object : SpeedDialView.OnChangeListener {
speedDialView.overlayLayout = root.findViewById(R.id.fabSDOverlay)
speedDialView.inflate(R.menu.episodes_apply_action_speeddial)
speedDialView.setOnChangeListener(object : SpeedDialView.OnChangeListener {
override fun onMainActionSelected(): Boolean {
return false
}
override fun onToggleChanged(open: Boolean) {
if (open && listAdapter != null && listAdapter!!.selectedCount == 0) {
if (open && listAdapter.selectedCount == 0) {
(activity as MainActivity).showSnackbarAbovePlayer(R.string.no_items_selected,
Snackbar.LENGTH_SHORT)
speedDialView?.close()
speedDialView.close()
}
}
})
speedDialView?.setOnActionSelectedListener { actionItem: SpeedDialActionItem ->
speedDialView.setOnActionSelectedListener { actionItem: SpeedDialActionItem ->
var confirmationString = 0
if (listAdapter != null && (listAdapter!!.selectedItems.size >= 25 || listAdapter!!.shouldSelectLazyLoadedItems())) {
if (listAdapter.selectedItems.size >= 25 || listAdapter.shouldSelectLazyLoadedItems()) {
// Should ask for confirmation
if (actionItem.id == R.id.mark_read_batch) {
confirmationString = R.string.multi_select_mark_played_confirmation
@ -234,34 +227,32 @@ abstract class EpisodesListFragment : Fragment(), SelectableAdapter.OnSelectMode
return root
}
private fun performMultiSelectAction(actionItemId: Int) {
@UnstableApi private fun performMultiSelectAction(actionItemId: Int) {
val handler =
EpisodeMultiSelectActionHandler((activity as MainActivity), actionItemId)
Completable.fromAction {
if (listAdapter != null) {
handler.handleAction(listAdapter!!.selectedItems.filterIsInstance<FeedItem>())
if (listAdapter!!.shouldSelectLazyLoadedItems()) {
var applyPage = page + 1
var nextPage: List<FeedItem>
do {
nextPage = loadMoreData(applyPage)
handler.handleAction(nextPage)
applyPage++
} while (nextPage.size == EPISODES_PER_PAGE)
}
handler.handleAction(listAdapter.selectedItems.filterIsInstance<FeedItem>())
if (listAdapter.shouldSelectLazyLoadedItems()) {
var applyPage = page + 1
var nextPage: List<FeedItem>
do {
nextPage = loadMoreData(applyPage)
handler.handleAction(nextPage)
applyPage++
} while (nextPage.size == EPISODES_PER_PAGE)
}
}
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ listAdapter?.endSelectMode() },
.subscribe({ listAdapter.endSelectMode() },
{ error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
}
private fun setupLoadMoreScrollListener() {
recyclerView?.addOnScrollListener(object : RecyclerView.OnScrollListener() {
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(view: RecyclerView, deltaX: Int, deltaY: Int) {
super.onScrolled(view, deltaX, deltaY)
if (!isLoadingMore && hasMoreItems && recyclerView!!.isScrolledToBottom) {
if (!isLoadingMore && hasMoreItems && recyclerView.isScrolledToBottom) {
/* The end of the list has been reached. Load more data. */
page++
loadMoreItems()
@ -272,12 +263,11 @@ abstract class EpisodesListFragment : Fragment(), SelectableAdapter.OnSelectMode
}
private fun loadMoreItems() {
if (disposable != null) {
disposable?.dispose()
}
disposable?.dispose()
isLoadingMore = true
listAdapter?.setDummyViews(1)
listAdapter?.notifyItemInserted(listAdapter!!.itemCount - 1)
listAdapter.setDummyViews(1)
listAdapter.notifyItemInserted(listAdapter.itemCount - 1)
disposable = Observable.fromCallable { loadMoreData(page) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
@ -287,33 +277,33 @@ abstract class EpisodesListFragment : Fragment(), SelectableAdapter.OnSelectMode
hasMoreItems = false
}
episodes.addAll(data)
listAdapter?.setDummyViews(0)
listAdapter?.updateItems(episodes)
if (listAdapter != null && listAdapter!!.shouldSelectLazyLoadedItems()) {
listAdapter!!.setSelected(episodes.size - data.size, episodes.size, true)
listAdapter.setDummyViews(0)
listAdapter.updateItems(episodes)
if (listAdapter.shouldSelectLazyLoadedItems()) {
listAdapter.setSelected(episodes.size - data.size, episodes.size, true)
}
}, { error: Throwable? ->
listAdapter?.setDummyViews(0)
listAdapter?.updateItems(emptyList())
listAdapter.setDummyViews(0)
listAdapter.updateItems(emptyList())
Log.e(TAG, Log.getStackTraceString(error))
}, {
// Make sure to not always load 2 pages at once
recyclerView?.post { isLoadingMore = false }
recyclerView.post { isLoadingMore = false }
})
}
override fun onDestroyView() {
super.onDestroyView()
listAdapter?.endSelectMode()
listAdapter.endSelectMode()
}
override fun onStartSelectMode() {
speedDialView?.visibility = View.VISIBLE
speedDialView.visibility = View.VISIBLE
}
override fun onEndSelectMode() {
speedDialView?.close()
speedDialView?.visibility = View.GONE
speedDialView.close()
speedDialView.visibility = View.GONE
}
@Subscribe(threadMode = ThreadMode.MAIN)
@ -325,9 +315,9 @@ abstract class EpisodesListFragment : Fragment(), SelectableAdapter.OnSelectMode
episodes.removeAt(pos)
if (getFilter().matches(item)) {
episodes.add(pos, item)
listAdapter?.notifyItemChangedCompat(pos)
listAdapter.notifyItemChangedCompat(pos)
} else {
listAdapter?.notifyItemRemoved(pos)
listAdapter.notifyItemRemoved(pos)
}
}
}
@ -335,11 +325,9 @@ abstract class EpisodesListFragment : Fragment(), SelectableAdapter.OnSelectMode
@UnstableApi @Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(event: PlaybackPositionEvent) {
if (listAdapter == null) return
for (i in 0 until listAdapter!!.itemCount) {
val holder: EpisodeItemViewHolder =
recyclerView?.findViewHolderForAdapterPosition(i) as EpisodeItemViewHolder
if (holder != null && holder.isCurrentlyPlayingItem) {
for (i in 0 until listAdapter.itemCount) {
val holder: EpisodeItemViewHolder = recyclerView.findViewHolderForAdapterPosition(i) as EpisodeItemViewHolder
if (holder.isCurrentlyPlayingItem) {
holder.notifyPlaybackPositionUpdated(event)
break
}
@ -352,8 +340,8 @@ abstract class EpisodesListFragment : Fragment(), SelectableAdapter.OnSelectMode
return
}
when (event.keyCode) {
KeyEvent.KEYCODE_T -> recyclerView?.smoothScrollToPosition(0)
KeyEvent.KEYCODE_B -> recyclerView?.smoothScrollToPosition(listAdapter?.itemCount ?: (0 - 1))
KeyEvent.KEYCODE_T -> recyclerView.smoothScrollToPosition(0)
KeyEvent.KEYCODE_B -> recyclerView.smoothScrollToPosition(listAdapter.itemCount)
else -> {}
}
}
@ -363,7 +351,7 @@ abstract class EpisodesListFragment : Fragment(), SelectableAdapter.OnSelectMode
for (downloadUrl in event.urls) {
val pos: Int = FeedItemUtil.indexOfItemWithDownloadUrl(episodes, downloadUrl)
if (pos >= 0) {
listAdapter?.notifyItemChangedCompat(pos)
listAdapter.notifyItemChangedCompat(pos)
}
}
}
@ -397,17 +385,17 @@ abstract class EpisodesListFragment : Fragment(), SelectableAdapter.OnSelectMode
val restoreScrollPosition = episodes.isEmpty()
episodes = data.first
hasMoreItems = !(page == 1 && episodes.size < EPISODES_PER_PAGE)
progressBar?.visibility = View.GONE
listAdapter?.setDummyViews(0)
listAdapter?.updateItems(episodes)
listAdapter?.setTotalNumberOfItems(data.second)
if (restoreScrollPosition && getPrefName() != null) {
recyclerView?.restoreScrollPosition(getPrefName())
progressBar.visibility = View.GONE
listAdapter.setDummyViews(0)
listAdapter.updateItems(episodes)
listAdapter.setTotalNumberOfItems(data.second)
if (restoreScrollPosition) {
recyclerView.restoreScrollPosition(getPrefName())
}
updateToolbar()
}, { error: Throwable? ->
listAdapter?.setDummyViews(0)
listAdapter?.updateItems(emptyList())
listAdapter.setDummyViews(0)
listAdapter.updateItems(emptyList())
Log.e(TAG, Log.getStackTraceString(error))
})
}
@ -429,7 +417,7 @@ abstract class EpisodesListFragment : Fragment(), SelectableAdapter.OnSelectMode
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
fun onEventMainThread(event: FeedUpdateRunningEvent) {
swipeRefreshLayout?.isRefreshing = event.isFeedUpdateRunning
swipeRefreshLayout.isRefreshing = event.isFeedUpdateRunning
}
override fun onSaveInstanceState(outState: Bundle) {

View File

@ -37,11 +37,12 @@ import org.greenrobot.eventbus.ThreadMode
* Fragment which is supposed to be displayed outside of the MediaplayerActivity.
*/
class ExternalPlayerFragment : Fragment() {
private var imgvCover: ImageView? = null
private var txtvTitle: TextView? = null
private var butPlay: PlayButton? = null
private var feedName: TextView? = null
private var progressBar: ProgressBar? = null
private lateinit var imgvCover: ImageView
private lateinit var txtvTitle: TextView
private lateinit var butPlay: PlayButton
private lateinit var feedName: TextView
private lateinit var progressBar: ProgressBar
private var controller: PlaybackController? = null
private var disposable: Disposable? = null
@ -73,7 +74,7 @@ class ExternalPlayerFragment : Fragment() {
@UnstableApi
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
butPlay!!.setOnClickListener { v: View? ->
butPlay.setOnClickListener { v: View? ->
if (controller == null) {
return@setOnClickListener
}
@ -91,7 +92,7 @@ class ExternalPlayerFragment : Fragment() {
private fun setupPlaybackController(): PlaybackController {
return object : PlaybackController(activity) {
override fun updatePlayButtonShowsPlay(showPlay: Boolean) {
butPlay!!.setIsShowPlay(showPlay)
butPlay.setIsShowPlay(showPlay)
}
override fun loadMediaInfo() {
@ -116,10 +117,9 @@ class ExternalPlayerFragment : Fragment() {
@UnstableApi
override fun onStop() {
super.onStop()
if (controller != null) {
controller!!.release()
controller = null
}
controller?.release()
controller = null
EventBus.getDefault().unregister(this)
}
@ -132,10 +132,10 @@ class ExternalPlayerFragment : Fragment() {
|| controller!!.duration == Playable.INVALID_TIME) {
return
}
progressBar!!.progress = (controller!!.position.toDouble() / controller!!.duration * 100).toInt()
progressBar.progress = (controller!!.position.toDouble() / controller!!.duration * 100).toInt()
}
@Subscribe(threadMode = ThreadMode.MAIN)
@UnstableApi @Subscribe(threadMode = ThreadMode.MAIN)
fun onPlaybackServiceChanged(event: PlaybackServiceEvent) {
if (event.action == PlaybackServiceEvent.Action.SERVICE_SHUT_DOWN) {
(activity as MainActivity).setPlayerVisible(false)
@ -145,17 +145,14 @@ class ExternalPlayerFragment : Fragment() {
override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "Fragment is about to be destroyed")
if (disposable != null) {
disposable!!.dispose()
}
disposable?.dispose()
}
@UnstableApi
override fun onPause() {
super.onPause()
if (controller != null) {
controller!!.pause()
}
controller?.pause()
}
@UnstableApi
@ -166,9 +163,7 @@ class ExternalPlayerFragment : Fragment() {
return
}
if (disposable != null) {
disposable!!.dispose()
}
disposable?.dispose()
disposable = Maybe.fromCallable<Playable?> { controller!!.getMedia() }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
@ -183,8 +178,8 @@ class ExternalPlayerFragment : Fragment() {
return
}
(activity as MainActivity).setPlayerVisible(true)
txtvTitle!!.text = media.getEpisodeTitle()
feedName!!.text = media.getFeedTitle()
txtvTitle.text = media.getEpisodeTitle()
feedName.text = media.getFeedTitle()
onPositionObserverUpdate(PlaybackPositionEvent(media.getPosition(), media.getDuration()))
val options = RequestOptions()
@ -199,13 +194,13 @@ class ExternalPlayerFragment : Fragment() {
.load(getFallbackImageLocation(media))
.apply(options))
.apply(options)
.into(imgvCover!!)
.into(imgvCover)
if (controller != null && controller!!.isPlayingVideoLocally) {
(activity as MainActivity).bottomSheet?.setLocked(true)
(activity as MainActivity).bottomSheet?.setState(BottomSheetBehavior.STATE_COLLAPSED)
} else {
butPlay!!.visibility = View.VISIBLE
butPlay.visibility = View.VISIBLE
(activity as MainActivity).bottomSheet?.setLocked(false)
}
}

View File

@ -62,17 +62,18 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
private var feed: Feed? = null
private var disposable: Disposable? = null
private var imgvCover: ImageView? = null
private var txtvTitle: TextView? = null
private var txtvDescription: TextView? = null
private var txtvFundingUrl: TextView? = null
private var lblSupport: TextView? = null
private var txtvUrl: TextView? = null
private var txtvAuthorHeader: TextView? = null
private var imgvBackground: ImageView? = null
private var infoContainer: View? = null
private var header: View? = null
private var toolbar: MaterialToolbar? = null
private lateinit var imgvCover: ImageView
private lateinit var txtvTitle: TextView
private lateinit var txtvDescription: TextView
private lateinit var txtvFundingUrl: TextView
private lateinit var lblSupport: TextView
private lateinit var txtvUrl: TextView
private lateinit var txtvAuthorHeader: TextView
private lateinit var imgvBackground: ImageView
private lateinit var infoContainer: View
private lateinit var header: View
private lateinit var toolbar: MaterialToolbar
private val copyUrlToClipboard = View.OnClickListener {
if (feed != null && feed!!.download_url != null) {
@ -91,28 +92,26 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
): View {
val root: View = inflater.inflate(R.layout.feedinfo, null)
toolbar = root.findViewById(R.id.toolbar)
toolbar?.title = ""
toolbar?.inflateMenu(R.menu.feedinfo)
toolbar?.setNavigationOnClickListener { v: View? -> parentFragmentManager.popBackStack() }
toolbar?.setOnMenuItemClickListener(this)
toolbar.title = ""
toolbar.inflateMenu(R.menu.feedinfo)
toolbar.setNavigationOnClickListener { v: View? -> parentFragmentManager.popBackStack() }
toolbar.setOnMenuItemClickListener(this)
refreshToolbarState()
val appBar: AppBarLayout = root.findViewById(R.id.appBar)
val collapsingToolbar: CollapsingToolbarLayout =
root.findViewById(R.id.collapsing_toolbar)
if (toolbar != null) {
val iconTintManager: ToolbarIconTintManager =
object : ToolbarIconTintManager(requireContext(), toolbar!!, collapsingToolbar) {
override fun doTint(themedContext: Context?) {
toolbar!!.menu.findItem(R.id.visit_website_item)
.setIcon(AppCompatResources.getDrawable(themedContext!!, R.drawable.ic_web))
toolbar!!.menu.findItem(R.id.share_item)
.setIcon(AppCompatResources.getDrawable(themedContext, R.drawable.ic_share))
}
val collapsingToolbar: CollapsingToolbarLayout = root.findViewById(R.id.collapsing_toolbar)
val iconTintManager: ToolbarIconTintManager =
object : ToolbarIconTintManager(requireContext(), toolbar, collapsingToolbar) {
override fun doTint(themedContext: Context?) {
toolbar.menu.findItem(R.id.visit_website_item)
.setIcon(AppCompatResources.getDrawable(themedContext!!, R.drawable.ic_web))
toolbar.menu.findItem(R.id.share_item)
.setIcon(AppCompatResources.getDrawable(themedContext, R.drawable.ic_share))
}
iconTintManager.updateTint()
appBar.addOnOffsetChangedListener(iconTintManager)
}
}
iconTintManager.updateTint()
appBar.addOnOffsetChangedListener(iconTintManager)
imgvCover = root.findViewById(R.id.imgvCover)
txtvTitle = root.findViewById(R.id.txtvTitle)
txtvAuthorHeader = root.findViewById(R.id.txtvAuthor)
@ -123,14 +122,14 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
root.findViewById<View>(R.id.butShowSettings).visibility = View.INVISIBLE
root.findViewById<View>(R.id.butFilter).visibility = View.INVISIBLE
// https://github.com/bumptech/glide/issues/529
imgvBackground?.colorFilter = LightingColorFilter(-0x7d7d7e, 0x000000)
imgvBackground.colorFilter = LightingColorFilter(-0x7d7d7e, 0x000000)
txtvDescription = root.findViewById(R.id.txtvDescription)
txtvUrl = root.findViewById(R.id.txtvUrl)
lblSupport = root.findViewById(R.id.lblSupport)
txtvFundingUrl = root.findViewById(R.id.txtvFundingUrl)
txtvUrl?.setOnClickListener(copyUrlToClipboard)
txtvUrl.setOnClickListener(copyUrlToClipboard)
val feedId = requireArguments().getLong(EXTRA_FEED_ID)
parentFragmentManager.beginTransaction().replace(R.id.statisticsFragmentContainer,
@ -165,13 +164,10 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
if (header == null || infoContainer == null) {
return
}
val horizontalSpacing = resources.getDimension(R.dimen.additional_horizontal_spacing).toInt()
header!!.setPadding(horizontalSpacing, header!!.paddingTop, horizontalSpacing, header!!.paddingBottom)
infoContainer!!.setPadding(horizontalSpacing, infoContainer!!.paddingTop,
horizontalSpacing, infoContainer!!.paddingBottom)
header.setPadding(horizontalSpacing, header.paddingTop, horizontalSpacing, header.paddingBottom)
infoContainer.setPadding(horizontalSpacing, infoContainer.paddingTop,
horizontalSpacing, infoContainer.paddingBottom)
}
private fun showFeed() {
@ -179,42 +175,42 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
Log.d(TAG, "Language is " + feed!!.language)
Log.d(TAG, "Author is " + feed!!.author)
Log.d(TAG, "URL is " + feed!!.download_url)
if (imgvCover != null) Glide.with(this)
Glide.with(this)
.load(feed!!.imageUrl)
.apply(RequestOptions()
.placeholder(R.color.light_gray)
.error(R.color.light_gray)
.fitCenter()
.dontAnimate())
.into(imgvCover!!)
if (imgvBackground != null) Glide.with(this)
.into(imgvCover)
Glide.with(this)
.load(feed!!.imageUrl)
.apply(RequestOptions()
.placeholder(R.color.image_readability_tint)
.error(R.color.image_readability_tint)
.transform(FastBlurTransformation())
.dontAnimate())
.into(imgvBackground!!)
.into(imgvBackground)
txtvTitle?.text = feed!!.title
txtvTitle?.setMaxLines(6)
txtvTitle.text = feed!!.title
txtvTitle.setMaxLines(6)
val description: String = HtmlToPlainText.getPlainText(feed!!.description)?:""
txtvDescription?.text = description
txtvDescription.text = description
if (!TextUtils.isEmpty(feed!!.author)) {
txtvAuthorHeader?.text = feed!!.author
if (!feed!!.author.isNullOrEmpty()) {
txtvAuthorHeader.text = feed!!.author
}
txtvUrl?.text = feed!!.download_url
txtvUrl?.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_paperclip, 0)
txtvUrl.text = feed!!.download_url
txtvUrl.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_paperclip, 0)
if (feed!!.paymentLinks.isEmpty()) {
lblSupport?.visibility = View.GONE
txtvFundingUrl?.visibility = View.GONE
lblSupport.visibility = View.GONE
txtvFundingUrl.visibility = View.GONE
} else {
lblSupport?.visibility = View.VISIBLE
lblSupport.visibility = View.VISIBLE
val fundingList: ArrayList<FeedFunding> = feed!!.paymentLinks
// Filter for duplicates, but keep items in the order that they have in the feed.
@ -238,7 +234,7 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
str.append("\n")
}
str = StringBuilder(StringUtils.trim(str.toString()))
txtvFundingUrl?.text = str.toString()
txtvFundingUrl.text = str.toString()
}
refreshToolbarState()
@ -250,12 +246,12 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
}
private fun refreshToolbarState() {
toolbar?.menu?.findItem(R.id.reconnect_local_folder)?.setVisible(feed != null && feed!!.isLocalFeed)
toolbar?.menu?.findItem(R.id.share_item)?.setVisible(feed != null && !feed!!.isLocalFeed)
toolbar?.menu?.findItem(R.id.visit_website_item)
toolbar.menu?.findItem(R.id.reconnect_local_folder)?.setVisible(feed != null && feed!!.isLocalFeed)
toolbar.menu?.findItem(R.id.share_item)?.setVisible(feed != null && !feed!!.isLocalFeed)
toolbar.menu?.findItem(R.id.visit_website_item)
?.setVisible(feed != null && feed!!.link != null && IntentUtils.isCallable(requireContext(),
Intent(Intent.ACTION_VIEW, Uri.parse(feed!!.link))))
toolbar?.menu?.findItem(R.id.edit_feed_url_item)?.setVisible(feed != null && !feed!!.isLocalFeed)
toolbar.menu?.findItem(R.id.edit_feed_url_item)?.setVisible(feed != null && !feed!!.isLocalFeed)
}
override fun onMenuItemClick(item: MenuItem): Boolean {
@ -264,33 +260,39 @@ class FeedInfoFragment : Fragment(), Toolbar.OnMenuItemClickListener {
R.string.please_wait_for_data, Toast.LENGTH_LONG)
return false
}
if (item.itemId == R.id.visit_website_item) {
if (feed!!.link != null) IntentUtils.openInBrowser(requireContext(), feed!!.link!!)
} else if (item.itemId == R.id.share_item) {
ShareUtils.shareFeedLink(requireContext(), feed!!)
} else if (item.itemId == R.id.reconnect_local_folder) {
val alert = MaterialAlertDialogBuilder(requireContext())
alert.setMessage(R.string.reconnect_local_folder_warning)
alert.setPositiveButton(string.ok
) { dialog: DialogInterface?, which: Int ->
try {
addLocalFolderLauncher.launch(null)
} catch (e: ActivityNotFoundException) {
Log.e(TAG, "No activity found. Should never happen...")
}
when (item.itemId) {
R.id.visit_website_item -> {
if (feed!!.link != null) IntentUtils.openInBrowser(requireContext(), feed!!.link!!)
}
alert.setNegativeButton(string.cancel, null)
alert.show()
} else if (item.itemId == R.id.edit_feed_url_item) {
object : EditUrlSettingsDialog(activity as Activity, feed!!) {
override fun setUrl(url: String?) {
feed!!.download_url = url
txtvUrl?.text = feed!!.download_url
txtvUrl?.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_paperclip, 0)
R.id.share_item -> {
ShareUtils.shareFeedLink(requireContext(), feed!!)
}
R.id.reconnect_local_folder -> {
val alert = MaterialAlertDialogBuilder(requireContext())
alert.setMessage(R.string.reconnect_local_folder_warning)
alert.setPositiveButton(string.ok
) { dialog: DialogInterface?, which: Int ->
try {
addLocalFolderLauncher.launch(null)
} catch (e: ActivityNotFoundException) {
Log.e(TAG, "No activity found. Should never happen...")
}
}
}.show()
} else {
return false
alert.setNegativeButton(string.cancel, null)
alert.show()
}
R.id.edit_feed_url_item -> {
object : EditUrlSettingsDialog(activity as Activity, feed!!) {
override fun setUrl(url: String?) {
feed!!.download_url = url
txtvUrl.text = feed!!.download_url
txtvUrl.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, R.drawable.ic_paperclip, 0)
}
}.show()
}
else -> {
return false
}
}
return true
}

View File

@ -69,16 +69,18 @@ import java.util.concurrent.ExecutionException
*/
class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolbar.OnMenuItemClickListener,
SelectableAdapter.OnSelectModeListener {
private var adapter: FeedItemListAdapter? = null
private var swipeActions: SwipeActions? = null
private var nextPageLoader: MoreContentListFooterUtil? = null
private lateinit var adapter: FeedItemListAdapter
private lateinit var swipeActions: SwipeActions
private lateinit var viewBinding: FeedItemListFragmentBinding
private lateinit var speedDialBinding: MultiSelectSpeedDialBinding
private lateinit var nextPageLoader: MoreContentListFooterUtil
private var displayUpArrow = false
private var feedID: Long = 0
private var feed: Feed? = null
private var headerCreated = false
private var disposable: Disposable? = null
private var viewBinding: FeedItemListFragmentBinding? = null
private var speedDialBinding: MultiSelectSpeedDialBinding? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -91,65 +93,65 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
savedInstanceState: Bundle?
): View {
viewBinding = FeedItemListFragmentBinding.inflate(inflater)
speedDialBinding = MultiSelectSpeedDialBinding.bind(viewBinding!!.root)
viewBinding!!.toolbar.inflateMenu(R.menu.feedlist)
viewBinding!!.toolbar.setOnMenuItemClickListener(this)
viewBinding!!.toolbar.setOnLongClickListener { v: View? ->
viewBinding!!.recyclerView.scrollToPosition(5)
viewBinding!!.recyclerView.post { viewBinding!!.recyclerView.smoothScrollToPosition(0) }
viewBinding!!.appBar.setExpanded(true)
speedDialBinding = MultiSelectSpeedDialBinding.bind(viewBinding.root)
viewBinding.toolbar.inflateMenu(R.menu.feedlist)
viewBinding.toolbar.setOnMenuItemClickListener(this)
viewBinding.toolbar.setOnLongClickListener { v: View? ->
viewBinding.recyclerView.scrollToPosition(5)
viewBinding.recyclerView.post { viewBinding.recyclerView.smoothScrollToPosition(0) }
viewBinding.appBar.setExpanded(true)
false
}
displayUpArrow = parentFragmentManager.backStackEntryCount != 0
if (savedInstanceState != null) {
displayUpArrow = savedInstanceState.getBoolean(KEY_UP_ARROW)
}
(activity as MainActivity).setupToolbarToggle(viewBinding!!.toolbar, displayUpArrow)
(activity as MainActivity).setupToolbarToggle(viewBinding.toolbar, displayUpArrow)
updateToolbar()
viewBinding!!.recyclerView.setRecycledViewPool((activity as MainActivity).recycledViewPool)
viewBinding.recyclerView.setRecycledViewPool((activity as MainActivity).recycledViewPool)
adapter = FeedItemListAdapter(activity as MainActivity)
adapter?.setOnSelectModeListener(this)
viewBinding!!.recyclerView.adapter = adapter
swipeActions = SwipeActions(this, TAG).attachTo(viewBinding!!.recyclerView)
viewBinding!!.progressBar.visibility = View.VISIBLE
adapter.setOnSelectModeListener(this)
viewBinding.recyclerView.adapter = adapter
swipeActions = SwipeActions(this, TAG).attachTo(viewBinding.recyclerView)
viewBinding.progressBar.visibility = View.VISIBLE
val iconTintManager: ToolbarIconTintManager = object : ToolbarIconTintManager(
requireContext(), viewBinding!!.toolbar, viewBinding!!.collapsingToolbar) {
requireContext(), viewBinding.toolbar, viewBinding.collapsingToolbar) {
override fun doTint(themedContext: Context?) {
viewBinding!!.toolbar.menu.findItem(R.id.refresh_item)
viewBinding.toolbar.menu.findItem(R.id.refresh_item)
.setIcon(AppCompatResources.getDrawable(themedContext!!, R.drawable.ic_refresh))
viewBinding!!.toolbar.menu.findItem(R.id.action_search)
viewBinding.toolbar.menu.findItem(R.id.action_search)
.setIcon(AppCompatResources.getDrawable(themedContext, R.drawable.ic_search))
}
}
iconTintManager.updateTint()
viewBinding!!.appBar.addOnOffsetChangedListener(iconTintManager)
viewBinding.appBar.addOnOffsetChangedListener(iconTintManager)
nextPageLoader = MoreContentListFooterUtil(viewBinding!!.moreContent.moreContentListFooter)
nextPageLoader?.setClickListener(object : MoreContentListFooterUtil.Listener {
nextPageLoader = MoreContentListFooterUtil(viewBinding.moreContent.moreContentListFooter)
nextPageLoader.setClickListener(object : MoreContentListFooterUtil.Listener {
override fun onClick() {
if (feed != null) {
FeedUpdateManager.runOnce(context, feed, true)
}
}
})
viewBinding!!.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
viewBinding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrolled(view: RecyclerView, deltaX: Int, deltaY: Int) {
super.onScrolled(view, deltaX, deltaY)
val hasMorePages = feed != null && feed!!.isPaged && feed!!.nextPageLink != null
val pageLoaderVisible = viewBinding!!.recyclerView.isScrolledToBottom && hasMorePages
nextPageLoader?.root?.visibility = if (pageLoaderVisible) View.VISIBLE else View.GONE
viewBinding!!.recyclerView.setPadding(
viewBinding!!.recyclerView.paddingLeft, 0, viewBinding!!.recyclerView.paddingRight,
if (pageLoaderVisible && nextPageLoader != null) nextPageLoader!!.root.measuredHeight else 0)
val pageLoaderVisible = viewBinding.recyclerView.isScrolledToBottom && hasMorePages
nextPageLoader.root.visibility = if (pageLoaderVisible) View.VISIBLE else View.GONE
viewBinding.recyclerView.setPadding(
viewBinding.recyclerView.paddingLeft, 0, viewBinding.recyclerView.paddingRight,
if (pageLoaderVisible) nextPageLoader.root.measuredHeight else 0)
}
})
EventBus.getDefault().register(this)
viewBinding!!.swipeRefresh.setDistanceToTriggerSync(resources.getInteger(R.integer.swipe_refresh_distance))
viewBinding!!.swipeRefresh.setOnRefreshListener {
viewBinding.swipeRefresh.setDistanceToTriggerSync(resources.getInteger(R.integer.swipe_refresh_distance))
viewBinding.swipeRefresh.setOnRefreshListener {
FeedUpdateManager.runOnceOrAsk(requireContext(),
feed)
}
@ -157,28 +159,28 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
loadItems()
// Init action UI (via a FAB Speed Dial)
speedDialBinding!!.fabSD.overlayLayout = speedDialBinding!!.fabSDOverlay
speedDialBinding!!.fabSD.inflate(R.menu.episodes_apply_action_speeddial)
speedDialBinding!!.fabSD.setOnChangeListener(object : SpeedDialView.OnChangeListener {
speedDialBinding.fabSD.overlayLayout = speedDialBinding.fabSDOverlay
speedDialBinding.fabSD.inflate(R.menu.episodes_apply_action_speeddial)
speedDialBinding.fabSD.setOnChangeListener(object : SpeedDialView.OnChangeListener {
override fun onMainActionSelected(): Boolean {
return false
}
override fun onToggleChanged(open: Boolean) {
if (open && adapter!!.selectedCount == 0) {
if (open && adapter.selectedCount == 0) {
(activity as MainActivity).showSnackbarAbovePlayer(R.string.no_items_selected,
Snackbar.LENGTH_SHORT)
speedDialBinding!!.fabSD.close()
speedDialBinding.fabSD.close()
}
}
})
speedDialBinding!!.fabSD.setOnActionSelectedListener { actionItem: SpeedDialActionItem ->
speedDialBinding.fabSD.setOnActionSelectedListener { actionItem: SpeedDialActionItem ->
EpisodeMultiSelectActionHandler((activity as MainActivity), actionItem.id)
.handleAction(adapter!!.selectedItems.filterIsInstance<FeedItem>())
adapter?.endSelectMode()
.handleAction(adapter.selectedItems.filterIsInstance<FeedItem>())
adapter.endSelectMode()
true
}
return viewBinding!!.root
return viewBinding.root
}
override fun onDestroyView() {
@ -186,7 +188,7 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
EventBus.getDefault().unregister(this)
disposable?.dispose()
adapter?.endSelectMode()
adapter.endSelectMode()
}
override fun onSaveInstanceState(outState: Bundle) {
@ -198,22 +200,22 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
if (feed == null) {
return
}
viewBinding!!.toolbar.menu.findItem(R.id.visit_website_item).setVisible(feed!!.link != null)
viewBinding!!.toolbar.menu.findItem(R.id.refresh_complete_item).setVisible(feed!!.isPaged)
viewBinding.toolbar.menu.findItem(R.id.visit_website_item).setVisible(feed!!.link != null)
viewBinding.toolbar.menu.findItem(R.id.refresh_complete_item).setVisible(feed!!.isPaged)
if (StringUtils.isBlank(feed!!.link)) {
viewBinding!!.toolbar.menu.findItem(R.id.visit_website_item).setVisible(false)
viewBinding.toolbar.menu.findItem(R.id.visit_website_item).setVisible(false)
}
if (feed!!.isLocalFeed) {
viewBinding!!.toolbar.menu.findItem(R.id.share_item).setVisible(false)
viewBinding.toolbar.menu.findItem(R.id.share_item).setVisible(false)
}
}
override fun onConfigurationChanged(newConfig: Configuration) {
super.onConfigurationChanged(newConfig)
val horizontalSpacing = resources.getDimension(R.dimen.additional_horizontal_spacing).toInt()
viewBinding!!.header.headerContainer.setPadding(
horizontalSpacing, viewBinding!!.header.headerContainer.paddingTop,
horizontalSpacing, viewBinding!!.header.headerContainer.paddingBottom)
viewBinding.header.headerContainer.setPadding(
horizontalSpacing, viewBinding.header.headerContainer.paddingTop,
horizontalSpacing, viewBinding.header.headerContainer.paddingBottom)
}
@UnstableApi override fun onMenuItemClick(item: MenuItem): Boolean {
@ -270,12 +272,12 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
}
override fun onContextItemSelected(item: MenuItem): Boolean {
val selectedItem: FeedItem? = adapter!!.longPressedItem
val selectedItem: FeedItem? = adapter.longPressedItem
if (selectedItem == null) {
Log.i(TAG, "Selected item at current position was null, ignoring selection")
return super.onContextItemSelected(item)
}
if (adapter!!.onContextItemSelected(item)) {
if (adapter.onContextItemSelected(item)) {
return true
}
return FeedItemMenuHandler.onMenuItemClicked(this, item.itemId, selectedItem)
@ -311,7 +313,7 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
if (pos >= 0) {
feed?.items?.removeAt(pos)
feed?.items?.add(pos, item)
adapter?.notifyItemChangedCompat(pos)
adapter.notifyItemChangedCompat(pos)
}
i++
}
@ -325,16 +327,16 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
for (downloadUrl in event.urls) {
val pos: Int = FeedItemUtil.indexOfItemWithDownloadUrl(feed!!.items, downloadUrl)
if (pos >= 0) {
adapter?.notifyItemChangedCompat(pos)
adapter.notifyItemChangedCompat(pos)
}
}
}
@UnstableApi @Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(event: PlaybackPositionEvent) {
for (i in 0 until adapter!!.itemCount) {
for (i in 0 until adapter.itemCount) {
val holder: EpisodeItemViewHolder? =
viewBinding!!.recyclerView.findViewHolderForAdapterPosition(i) as EpisodeItemViewHolder?
viewBinding.recyclerView.findViewHolderForAdapterPosition(i) as EpisodeItemViewHolder?
if (holder != null && holder.isCurrentlyPlayingItem) {
holder.notifyPlaybackPositionUpdated(event)
break
@ -355,19 +357,19 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
}
override fun onStartSelectMode() {
swipeActions?.detach()
swipeActions.detach()
if (feed != null && feed!!.isLocalFeed) {
speedDialBinding!!.fabSD.removeActionItemById(R.id.download_batch)
speedDialBinding.fabSD.removeActionItemById(R.id.download_batch)
}
speedDialBinding!!.fabSD.removeActionItemById(R.id.remove_all_inbox_item)
speedDialBinding!!.fabSD.visibility = View.VISIBLE
speedDialBinding.fabSD.removeActionItemById(R.id.remove_all_inbox_item)
speedDialBinding.fabSD.visibility = View.VISIBLE
updateToolbar()
}
override fun onEndSelectMode() {
speedDialBinding!!.fabSD.close()
speedDialBinding!!.fabSD.visibility = View.GONE
swipeActions?.attachTo(viewBinding!!.recyclerView)
speedDialBinding.fabSD.close()
speedDialBinding.fabSD.visibility = View.GONE
swipeActions.attachTo(viewBinding.recyclerView)
}
@UnstableApi private fun updateUi() {
@ -396,51 +398,51 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
fun onEventMainThread(event: FeedUpdateRunningEvent) {
nextPageLoader?.setLoadingState(event.isFeedUpdateRunning)
nextPageLoader.setLoadingState(event.isFeedUpdateRunning)
if (!event.isFeedUpdateRunning) {
nextPageLoader?.root?.visibility = View.GONE
nextPageLoader.root.visibility = View.GONE
}
viewBinding!!.swipeRefresh.isRefreshing = event.isFeedUpdateRunning
viewBinding.swipeRefresh.isRefreshing = event.isFeedUpdateRunning
}
@UnstableApi private fun refreshHeaderView() {
setupHeaderView()
if (viewBinding == null || feed == null) {
if (feed == null) {
Log.e(TAG, "Unable to refresh header view")
return
}
loadFeedImage()
if (feed != null && feed!!.hasLastUpdateFailed()) {
viewBinding!!.header.txtvFailure.visibility = View.VISIBLE
if (feed!!.hasLastUpdateFailed()) {
viewBinding.header.txtvFailure.visibility = View.VISIBLE
} else {
viewBinding!!.header.txtvFailure.visibility = View.GONE
viewBinding.header.txtvFailure.visibility = View.GONE
}
if (feed != null && feed!!.preferences != null && !feed!!.preferences!!.keepUpdated) {
viewBinding!!.header.txtvUpdatesDisabled.text = ("{md-pause-circle-outline} "
if (feed!!.preferences != null && !feed!!.preferences!!.keepUpdated) {
viewBinding.header.txtvUpdatesDisabled.text = ("{md-pause-circle-outline} "
+ this.getString(R.string.updates_disabled_label))
Iconify.addIcons(viewBinding!!.header.txtvUpdatesDisabled)
viewBinding!!.header.txtvUpdatesDisabled.visibility = View.VISIBLE
Iconify.addIcons(viewBinding.header.txtvUpdatesDisabled)
viewBinding.header.txtvUpdatesDisabled.visibility = View.VISIBLE
} else {
viewBinding!!.header.txtvUpdatesDisabled.visibility = View.GONE
viewBinding.header.txtvUpdatesDisabled.visibility = View.GONE
}
viewBinding!!.header.txtvTitle.text = feed!!.title
viewBinding!!.header.txtvAuthor.text = feed!!.author
viewBinding.header.txtvTitle.text = feed!!.title
viewBinding.header.txtvAuthor.text = feed!!.author
if (feed != null && feed!!.itemFilter != null) {
val filter: FeedItemFilter? = feed!!.itemFilter
if (filter != null && filter.values.isNotEmpty()) {
viewBinding!!.header.txtvInformation.text = ("{md-info-outline} "
viewBinding.header.txtvInformation.text = ("{md-info-outline} "
+ this.getString(R.string.filtered_label))
Iconify.addIcons(viewBinding!!.header.txtvInformation)
viewBinding!!.header.txtvInformation.setOnClickListener { l: View? ->
Iconify.addIcons(viewBinding.header.txtvInformation)
viewBinding.header.txtvInformation.setOnClickListener { l: View? ->
FeedItemFilterDialog.newInstance(feed!!).show(
childFragmentManager, null)
}
viewBinding!!.header.txtvInformation.visibility = View.VISIBLE
viewBinding.header.txtvInformation.visibility = View.VISIBLE
} else {
viewBinding!!.header.txtvInformation.visibility = View.GONE
viewBinding.header.txtvInformation.visibility = View.GONE
}
} else {
viewBinding!!.header.txtvInformation.visibility = View.GONE
viewBinding.header.txtvInformation.visibility = View.GONE
}
}
@ -450,19 +452,19 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
}
// https://github.com/bumptech/glide/issues/529
viewBinding!!.imgvBackground.colorFilter = LightingColorFilter(-0x99999a, 0x000000)
viewBinding!!.header.butShowInfo.setOnClickListener { v: View? -> showFeedInfo() }
viewBinding!!.header.imgvCover.setOnClickListener { v: View? -> showFeedInfo() }
viewBinding!!.header.butShowSettings.setOnClickListener { v: View? ->
viewBinding.imgvBackground.colorFilter = LightingColorFilter(-0x99999a, 0x000000)
viewBinding.header.butShowInfo.setOnClickListener { v: View? -> showFeedInfo() }
viewBinding.header.imgvCover.setOnClickListener { v: View? -> showFeedInfo() }
viewBinding.header.butShowSettings.setOnClickListener { v: View? ->
if (feed != null) {
val fragment = FeedSettingsFragment.newInstance(feed!!)
(activity as MainActivity).loadChildFragment(fragment, TransitionEffect.SLIDE)
}
}
viewBinding!!.header.butFilter.setOnClickListener { v: View? ->
viewBinding.header.butFilter.setOnClickListener { v: View? ->
if (feed != null) FeedItemFilterDialog.newInstance(feed!!).show(childFragmentManager, null)
}
viewBinding!!.header.txtvFailure.setOnClickListener { v: View? -> showErrorDetails() }
viewBinding.header.txtvFailure.setOnClickListener { v: View? -> showErrorDetails() }
headerCreated = true
}
@ -502,7 +504,7 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
.error(R.color.image_readability_tint)
.transform(FastBlurTransformation())
.dontAnimate())
.into(viewBinding!!.imgvBackground)
.into(viewBinding.imgvBackground)
Glide.with(this)
.load(feed!!.imageUrl)
@ -511,7 +513,7 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
.error(R.color.light_gray)
.fitCenter()
.dontAnimate())
.into(viewBinding!!.header.imgvCover)
.into(viewBinding.header.imgvCover)
}
@UnstableApi private fun loadItems() {
@ -524,17 +526,17 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
{ result: Feed? ->
feed = result
Log.d(TAG, "loadItems subscribe called ${feed?.title}")
swipeActions?.setFilter(feed?.itemFilter)
swipeActions.setFilter(feed?.itemFilter)
refreshHeaderView()
viewBinding!!.progressBar.visibility = View.GONE
adapter?.setDummyViews(0)
if (feed != null && feed!!.items.isNotEmpty()) adapter?.updateItems(feed!!.items)
viewBinding.progressBar.visibility = View.GONE
adapter.setDummyViews(0)
if (feed != null && feed!!.items.isNotEmpty()) adapter.updateItems(feed!!.items)
updateToolbar()
}, { error: Throwable? ->
feed = null
refreshHeaderView()
adapter?.setDummyViews(0)
adapter?.updateItems(emptyList())
adapter.setDummyViews(0)
adapter.updateItems(emptyList())
updateToolbar()
Log.e(TAG, Log.getStackTraceString(error))
})
@ -560,8 +562,8 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
return
}
when (event.keyCode) {
KeyEvent.KEYCODE_T -> viewBinding!!.recyclerView.smoothScrollToPosition(0)
KeyEvent.KEYCODE_B -> viewBinding!!.recyclerView.smoothScrollToPosition(adapter!!.itemCount - 1)
KeyEvent.KEYCODE_T -> viewBinding.recyclerView.smoothScrollToPosition(0)
KeyEvent.KEYCODE_B -> viewBinding.recyclerView.smoothScrollToPosition(adapter.itemCount - 1)
else -> {}
}
}
@ -576,8 +578,7 @@ class FeedItemlistFragment : Fragment(), AdapterView.OnItemClickListener, Toolba
if (!inActionMode()) {
menu.findItem(R.id.multi_select).setVisible(true)
}
MenuItemUtils.setOnClickListeners(menu
) { item: MenuItem ->
MenuItemUtils.setOnClickListeners(menu) { item: MenuItem ->
this@FeedItemlistFragment.onContextItemSelected(item)
}
}

View File

@ -43,6 +43,8 @@ import ac.mdiq.podvinci.model.feed.FeedPreferences.AutoDeleteAction
import ac.mdiq.podvinci.model.feed.FeedPreferences.NewEpisodesAction
import ac.mdiq.podvinci.model.feed.VolumeAdaptionSetting
import ac.mdiq.podvinci.storage.preferences.UserPreferences.isEnableAutodownload
import androidx.annotation.OptIn
import androidx.media3.common.util.UnstableApi
import io.reactivex.Maybe
import io.reactivex.MaybeEmitter
import io.reactivex.MaybeOnSubscribe
@ -100,7 +102,7 @@ class FeedSettingsFragment : Fragment() {
var notificationPermissionDenied: Boolean = false
private val requestPermissionLauncher =
registerForActivityResult<String, Boolean>(ActivityResultContracts.RequestPermission()) { isGranted: Boolean ->
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean ->
if (isGranted) {
return@registerForActivityResult
}
@ -123,7 +125,7 @@ class FeedSettingsFragment : Fragment() {
return view
}
override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
@OptIn(UnstableApi::class) override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
addPreferencesFromResource(R.xml.feed_settings)
// To prevent displaying partially loaded data
findPreference<Preference>(PREF_SCREEN)!!.isVisible = false
@ -181,7 +183,7 @@ class FeedSettingsFragment : Fragment() {
object : FeedPreferenceSkipDialog(context,
feedPreferences!!.feedSkipIntro,
feedPreferences!!.feedSkipEnding) {
override fun onConfirmed(skipIntro: Int, skipEnding: Int) {
@UnstableApi override fun onConfirmed(skipIntro: Int, skipEnding: Int) {
feedPreferences!!.feedSkipIntro = skipIntro
feedPreferences!!.feedSkipEnding = skipEnding
DBWriter.setFeedPreferences(feedPreferences!!)
@ -195,7 +197,7 @@ class FeedSettingsFragment : Fragment() {
}
}
private fun setupPlaybackSpeedPreference() {
@UnstableApi private fun setupPlaybackSpeedPreference() {
val feedPlaybackSpeedPreference = findPreference<Preference>(PREF_FEED_PLAYBACK_SPEED)
feedPlaybackSpeedPreference!!.onPreferenceClickListener =
Preference.OnPreferenceClickListener { preference: Preference ->
@ -237,7 +239,7 @@ class FeedSettingsFragment : Fragment() {
findPreference<Preference>(PREF_EPISODE_FILTER)!!.onPreferenceClickListener =
Preference.OnPreferenceClickListener { preference: Preference ->
object : EpisodeFilterDialog(context, feedPreferences!!.filter) {
override fun onConfirmed(filter: FeedFilter) {
@UnstableApi override fun onConfirmed(filter: FeedFilter) {
feedPreferences!!.filter = filter
DBWriter.setFeedPreferences(feedPreferences!!)
}
@ -253,7 +255,7 @@ class FeedSettingsFragment : Fragment() {
object : AuthenticationDialog(context,
R.string.authentication_label, true,
feedPreferences!!.username, feedPreferences!!.password) {
override fun onConfirmed(username: String, password: String) {
@UnstableApi override fun onConfirmed(username: String, password: String) {
feedPreferences!!.username = username
feedPreferences!!.password = password
val setPreferencesFuture = DBWriter.setFeedPreferences(feedPreferences!!)
@ -274,7 +276,7 @@ class FeedSettingsFragment : Fragment() {
}
}
private fun setupAutoDeletePreference() {
@UnstableApi private fun setupAutoDeletePreference() {
if (feedPreferences == null) return
findPreference<Preference>(PREF_AUTO_DELETE)!!.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { preference: Preference?, newValue: Any? ->
@ -310,7 +312,7 @@ class FeedSettingsFragment : Fragment() {
}
}
private fun setupVolumeAdaptationPreferences() {
@UnstableApi private fun setupVolumeAdaptationPreferences() {
if (feedPreferences == null) return
val volumeAdaptationPreference = findPreference<ListPreference>("volumeReduction")
volumeAdaptationPreference!!.onPreferenceChangeListener =
@ -348,7 +350,7 @@ class FeedSettingsFragment : Fragment() {
}
}
private fun setupNewEpisodesAction() {
@OptIn(UnstableApi::class) private fun setupNewEpisodesAction() {
if (feedPreferences == null) return
findPreference<Preference>(PREF_NEW_EPISODES_ACTION)!!.onPreferenceChangeListener =
@ -374,14 +376,14 @@ class FeedSettingsFragment : Fragment() {
}
}
private fun setupKeepUpdatedPreference() {
@OptIn(UnstableApi::class) private fun setupKeepUpdatedPreference() {
if (feedPreferences == null) return
val pref = findPreference<SwitchPreferenceCompat>("keepUpdated")
pref!!.isChecked = feedPreferences!!.keepUpdated
pref.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { preference: Preference?, newValue: Any ->
val checked = newValue === java.lang.Boolean.TRUE
val checked = newValue == true
feedPreferences!!.keepUpdated = checked
DBWriter.setFeedPreferences(feedPreferences!!)
pref.isChecked = checked
@ -399,7 +401,7 @@ class FeedSettingsFragment : Fragment() {
}
}
private fun setupAutoDownloadPreference() {
@OptIn(UnstableApi::class) private fun setupAutoDownloadPreference() {
if (feedPreferences == null) return
val pref = findPreference<SwitchPreferenceCompat>("autoDownload")
@ -413,7 +415,7 @@ class FeedSettingsFragment : Fragment() {
pref.onPreferenceChangeListener =
Preference.OnPreferenceChangeListener { preference: Preference?, newValue: Any ->
val checked = newValue === java.lang.Boolean.TRUE
val checked = newValue == true
feedPreferences!!.autoDownload = checked
DBWriter.setFeedPreferences(feedPreferences!!)
updateAutoDownloadEnabled()
@ -439,7 +441,7 @@ class FeedSettingsFragment : Fragment() {
}
}
private fun setupEpisodeNotificationPreference() {
@OptIn(UnstableApi::class) private fun setupEpisodeNotificationPreference() {
val pref = findPreference<SwitchPreferenceCompat>("episodeNotification")
pref!!.isChecked = feedPreferences!!.showEpisodeNotification
@ -451,7 +453,7 @@ class FeedSettingsFragment : Fragment() {
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
return@OnPreferenceChangeListener false
}
val checked = newValue === java.lang.Boolean.TRUE
val checked = newValue == true
feedPreferences!!.showEpisodeNotification = checked
if (feedPreferences != null) DBWriter.setFeedPreferences(feedPreferences!!)
pref.isChecked = checked

View File

@ -22,6 +22,7 @@ import ac.mdiq.podvinci.model.feed.FeedItem
import ac.mdiq.podvinci.model.feed.FeedItemFilter
import ac.mdiq.podvinci.model.feed.SortOrder
import ac.mdiq.podvinci.storage.preferences.UserPreferences
import androidx.annotation.OptIn
import org.greenrobot.eventbus.EventBus
/**
@ -39,16 +40,16 @@ class InboxFragment : EpisodesListFragment() {
@UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
val root = super.onCreateView(inflater, container, savedInstanceState)
toolbar?.inflateMenu(R.menu.inbox)
toolbar?.setTitle(R.string.inbox_label)
toolbar.inflateMenu(R.menu.inbox)
toolbar.setTitle(R.string.inbox_label)
prefs = requireActivity().getSharedPreferences(getPrefName(), Context.MODE_PRIVATE)
updateToolbar()
emptyView?.setIcon(R.drawable.ic_inbox)
emptyView?.setTitle(R.string.no_inbox_head_label)
emptyView?.setMessage(R.string.no_inbox_label)
speedDialView?.removeActionItemById(R.id.mark_unread_batch)
speedDialView?.removeActionItemById(R.id.remove_from_queue_batch)
speedDialView?.removeActionItemById(R.id.delete_batch)
emptyView.setIcon(R.drawable.ic_inbox)
emptyView.setTitle(R.string.no_inbox_head_label)
emptyView.setMessage(R.string.no_inbox_label)
speedDialView.removeActionItemById(R.id.mark_unread_batch)
speedDialView.removeActionItemById(R.id.remove_from_queue_batch)
speedDialView.removeActionItemById(R.id.delete_batch)
return root
}
@ -56,7 +57,7 @@ class InboxFragment : EpisodesListFragment() {
return FeedItemFilter(FeedItemFilter.NEW)
}
override fun onMenuItemClick(item: MenuItem): Boolean {
@OptIn(UnstableApi::class) override fun onMenuItemClick(item: MenuItem): Boolean {
if (super.onOptionsItemSelected(item)) {
return true
}
@ -88,7 +89,7 @@ class InboxFragment : EpisodesListFragment() {
return DBReader.getTotalEpisodeCount(FeedItemFilter(FeedItemFilter.NEW))
}
private fun removeAllFromInbox() {
@OptIn(UnstableApi::class) private fun removeAllFromInbox() {
DBWriter.removeAllNewFlags()
(activity as MainActivity).showSnackbarAbovePlayer(R.string.removed_all_inbox_msg, Toast.LENGTH_SHORT)
}
@ -102,8 +103,7 @@ class InboxFragment : EpisodesListFragment() {
val checkNeverAskAgain: CheckBox = view.findViewById(R.id.checkbox_do_not_show_again)
builder.setView(view)
builder.setPositiveButton(R.string.confirm_label
) { dialog: DialogInterface, which: Int ->
builder.setPositiveButton(R.string.confirm_label) { dialog: DialogInterface, which: Int ->
dialog.dismiss()
removeAllFromInbox()
prefs?.edit()?.putBoolean(PREF_DO_NOT_PROMPT_REMOVE_ALL_FROM_INBOX, checkNeverAskAgain.isChecked)

View File

@ -27,7 +27,7 @@ import io.reactivex.schedulers.Schedulers
*/
@UnstableApi
class ItemDescriptionFragment : Fragment() {
private var webvDescription: ShownotesWebView? = null
private lateinit var webvDescription: ShownotesWebView
private var webViewLoader: Disposable? = null
private var controller: PlaybackController? = null
@ -35,41 +35,39 @@ class ItemDescriptionFragment : Fragment() {
Log.d(TAG, "Creating view")
val root = inflater.inflate(R.layout.item_description_fragment, container, false)
webvDescription = root.findViewById(R.id.webview)
webvDescription?.setTimecodeSelectedListener { time: Int? ->
webvDescription.setTimecodeSelectedListener { time: Int? ->
if (controller != null) {
controller!!.seekTo(time!!)
}
}
webvDescription?.setPageFinishedListener {
webvDescription.setPageFinishedListener {
// Restoring the scroll position might not always work
webvDescription!!.postDelayed({ this@ItemDescriptionFragment.restoreFromPreference() }, 50)
webvDescription.postDelayed({ this@ItemDescriptionFragment.restoreFromPreference() }, 50)
}
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 (root.measuredHeight != webvDescription?.minimumHeight) {
webvDescription?.setMinimumHeight(root.measuredHeight)
if (root.measuredHeight != webvDescription.minimumHeight) {
webvDescription.setMinimumHeight(root.measuredHeight)
}
root.removeOnLayoutChangeListener(this)
}
})
if (webvDescription != null) registerForContextMenu(webvDescription!!)
registerForContextMenu(webvDescription)
return root
}
override fun onDestroy() {
super.onDestroy()
Log.d(TAG, "Fragment destroyed")
if (webvDescription != null) {
webvDescription!!.removeAllViews()
webvDescription!!.destroy()
}
webvDescription.removeAllViews()
webvDescription.destroy()
}
override fun onContextItemSelected(item: MenuItem): Boolean {
return webvDescription!!.onContextItemSelected(item)
return webvDescription.onContextItemSelected(item)
}
@UnstableApi private fun load() {
@ -85,11 +83,10 @@ class ItemDescriptionFragment : Fragment() {
return@create
}
if (media is FeedMedia) {
val feedMedia = media
if (feedMedia.getItem() == null) {
feedMedia.setItem(DBReader.getFeedItem(feedMedia.itemId))
if (media.getItem() == null) {
media.setItem(DBReader.getFeedItem(media.itemId))
}
if (feedMedia.getItem() != null) DBReader.loadDescriptionOfFeedItem(feedMedia.getItem()!!)
if (media.getItem() != null) DBReader.loadDescriptionOfFeedItem(media.getItem()!!)
}
val shownotesCleaner = ShownotesCleaner(context, media.getDescription()?:"", media.getDuration())
emitter.onSuccess(shownotesCleaner.processShownotes())
@ -97,7 +94,7 @@ class ItemDescriptionFragment : Fragment() {
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ data: String? ->
webvDescription!!.loadDataWithBaseURL("https://127.0.0.1", data!!, "text/html",
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)) })
@ -112,9 +109,9 @@ class ItemDescriptionFragment : Fragment() {
Log.d(TAG, "Saving preferences")
val prefs = requireActivity().getSharedPreferences(PREF, Activity.MODE_PRIVATE)
val editor = prefs.edit()
if (controller != null && controller!!.getMedia() != null && webvDescription != null) {
Log.d(TAG, "Saving scroll position: " + webvDescription!!.scrollY)
editor.putInt(PREF_SCROLL_Y, webvDescription!!.scrollY)
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 {
@ -132,9 +129,9 @@ class ItemDescriptionFragment : Fragment() {
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() && webvDescription != null) {
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)
webvDescription.scrollTo(webvDescription.scrollX, scrollY)
return true
}
}
@ -142,7 +139,7 @@ class ItemDescriptionFragment : Fragment() {
}
fun scrollToTop() {
webvDescription!!.scrollTo(0, 0)
webvDescription.scrollTo(0, 0)
savePreference()
}

View File

@ -46,6 +46,7 @@ import ac.mdiq.podvinci.storage.preferences.UserPreferences
import ac.mdiq.podvinci.ui.common.CircularProgressBar
import ac.mdiq.podvinci.ui.common.ThemeUtils
import ac.mdiq.podvinci.view.ShownotesWebView
import androidx.annotation.OptIn
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
@ -65,24 +66,25 @@ class ItemFragment : Fragment() {
private var item: FeedItem? = null
private var webviewData: String? = null
private var root: ViewGroup? = null
private var webvDescription: ShownotesWebView? = null
private var txtvPodcast: TextView? = null
private var txtvTitle: TextView? = null
private var txtvDuration: TextView? = null
private var txtvPublished: TextView? = null
private var imgvCover: ImageView? = null
private var progbarDownload: CircularProgressBar? = null
private var progbarLoading: ProgressBar? = null
private var butAction1Text: TextView? = null
private var butAction2Text: TextView? = null
private var butAction1Icon: ImageView? = null
private var butAction2Icon: ImageView? = null
private var butAction1: View? = null
private var butAction2: View? = null
private lateinit var root: ViewGroup
private lateinit var webvDescription: ShownotesWebView
private lateinit var txtvPodcast: TextView
private lateinit var txtvTitle: TextView
private lateinit var txtvDuration: TextView
private lateinit var txtvPublished: TextView
private lateinit var imgvCover: ImageView
private lateinit var progbarDownload: CircularProgressBar
private lateinit var progbarLoading: ProgressBar
private lateinit var butAction1Text: TextView
private lateinit var butAction2Text: TextView
private lateinit var butAction1Icon: ImageView
private lateinit var butAction2Icon: ImageView
private lateinit var butAction1: View
private lateinit var butAction2: View
private lateinit var noMediaLabel: View
private var actionButton1: ItemActionButton? = null
private var actionButton2: ItemActionButton? = null
private var noMediaLabel: View? = null
private var disposable: Disposable? = null
private var controller: PlaybackController? = null
@ -100,16 +102,16 @@ class ItemFragment : Fragment() {
root = layout.findViewById(R.id.content_root)
txtvPodcast = layout.findViewById(R.id.txtvPodcast)
txtvPodcast?.setOnClickListener { v: View? -> openPodcast() }
txtvPodcast.setOnClickListener { v: View? -> openPodcast() }
txtvTitle = layout.findViewById(R.id.txtvTitle)
if (Build.VERSION.SDK_INT >= 23) {
txtvTitle?.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL)
txtvTitle.setHyphenationFrequency(Layout.HYPHENATION_FREQUENCY_FULL)
}
txtvDuration = layout.findViewById(R.id.txtvDuration)
txtvPublished = layout.findViewById(R.id.txtvPublished)
txtvTitle?.ellipsize = TextUtils.TruncateAt.END
txtvTitle.ellipsize = TextUtils.TruncateAt.END
webvDescription = layout.findViewById(R.id.webvDescription)
webvDescription?.setTimecodeSelectedListener { time: Int? ->
webvDescription.setTimecodeSelectedListener { time: Int? ->
if (controller != null && item != null && item!!.media != null && controller!!.getMedia() != null &&
item!!.media!!.getIdentifier() == controller!!.getMedia()!!.getIdentifier()) {
controller!!.seekTo(time ?: 0)
@ -118,10 +120,10 @@ class ItemFragment : Fragment() {
Snackbar.LENGTH_LONG)
}
}
if (webvDescription != null) registerForContextMenu(webvDescription!!)
registerForContextMenu(webvDescription)
imgvCover = layout.findViewById(R.id.imgvCover)
imgvCover?.setOnClickListener { v: View? -> openPodcast() }
imgvCover.setOnClickListener { v: View? -> openPodcast() }
progbarDownload = layout.findViewById(R.id.circularProgressBar)
progbarLoading = layout.findViewById(R.id.progbarLoading)
butAction1 = layout.findViewById(R.id.butAction1)
@ -132,7 +134,7 @@ class ItemFragment : Fragment() {
butAction2Text = layout.findViewById(R.id.butAction2Text)
noMediaLabel = layout.findViewById(R.id.noMediaLabel)
butAction1?.setOnClickListener(View.OnClickListener { v: View? ->
butAction1.setOnClickListener(View.OnClickListener { v: View? ->
if (actionButton1 is StreamActionButton && !UserPreferences.isStreamOverDownload
&& UsageStatistics.hasSignificantBiasTo(UsageStatistics.ACTION_STREAM)) {
showOnDemandConfigBalloon(true)
@ -142,7 +144,7 @@ class ItemFragment : Fragment() {
}
actionButton1?.onClick(requireContext())
})
butAction2?.setOnClickListener(View.OnClickListener { v: View? ->
butAction2.setOnClickListener(View.OnClickListener { v: View? ->
if (actionButton2 is DownloadActionButton && UserPreferences.isStreamOverDownload
&& UsageStatistics.hasSignificantBiasTo(UsageStatistics.ACTION_DOWNLOAD)) {
showOnDemandConfigBalloon(false)
@ -155,7 +157,7 @@ class ItemFragment : Fragment() {
return layout
}
private fun showOnDemandConfigBalloon(offerStreaming: Boolean) {
@OptIn(UnstableApi::class) private fun showOnDemandConfigBalloon(offerStreaming: Boolean) {
val isLocaleRtl = (TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) == View.LAYOUT_DIRECTION_RTL)
val balloon: Balloon = Balloon.Builder(requireContext())
.setArrowOrientation(ArrowOrientation.TOP)
@ -186,7 +188,7 @@ class ItemFragment : Fragment() {
UsageStatistics.doNotAskAgain(UsageStatistics.ACTION_STREAM) // Type does not matter. Both are silenced.
balloon.dismiss()
}
balloon.showAlignBottom(butAction1!!, 0, (-12 * resources.displayMetrics.density).toInt())
balloon.showAlignBottom(butAction1, 0, (-12 * resources.displayMetrics.density).toInt())
}
@UnstableApi override fun onStart() {
@ -204,7 +206,7 @@ class ItemFragment : Fragment() {
@UnstableApi override fun onResume() {
super.onResume()
if (itemsLoaded) {
progbarLoading?.visibility = View.GONE
progbarLoading.visibility = View.GONE
updateAppearance()
}
}
@ -218,16 +220,13 @@ class ItemFragment : Fragment() {
override fun onDestroyView() {
super.onDestroyView()
disposable?.dispose()
if (webvDescription != null && root != null) {
root!!.removeView(webvDescription)
webvDescription!!.destroy()
}
root.removeView(webvDescription)
webvDescription.destroy()
}
@UnstableApi private fun onFragmentLoaded() {
if (webviewData != null && !itemsLoaded) {
webvDescription?.loadDataWithBaseURL("https://127.0.0.1", webviewData!!, "text/html", "utf-8", "about:blank")
webvDescription.loadDataWithBaseURL("https://127.0.0.1", webviewData!!, "text/html", "utf-8", "about:blank")
}
updateAppearance()
}
@ -237,13 +236,13 @@ class ItemFragment : Fragment() {
Log.d(TAG, "updateAppearance item is null")
return
}
if (item!!.feed != null) txtvPodcast?.text = item!!.feed!!.title
txtvTitle?.text = item!!.title
if (item!!.feed != null) txtvPodcast.text = item!!.feed!!.title
txtvTitle.text = item!!.title
if (item?.pubDate != null) {
val pubDateStr = DateFormatter.formatAbbrev(activity, item!!.pubDate)
txtvPublished?.text = pubDateStr
txtvPublished?.setContentDescription(DateFormatter.formatForAccessibility(item!!.pubDate))
txtvPublished.text = pubDateStr
txtvPublished.setContentDescription(DateFormatter.formatForAccessibility(item!!.pubDate))
}
val options: RequestOptions = RequestOptions()
@ -252,25 +251,25 @@ class ItemFragment : Fragment() {
RoundedCorners((8 * resources.displayMetrics.density).toInt()))
.dontAnimate()
if (imgvCover != null) Glide.with(this)
Glide.with(this)
.load(item!!.imageLocation)
.error(Glide.with(this)
.load(ImageResourceUtils.getFallbackImageLocation(item!!))
.apply(options))
.apply(options)
.into(imgvCover!!)
.into(imgvCover)
updateButtons()
}
@UnstableApi private fun updateButtons() {
progbarDownload?.visibility = View.GONE
progbarDownload.visibility = View.GONE
val dls = DownloadServiceInterface.get()
if (item != null && item!!.hasMedia() && item!!.media!!.download_url != null) {
val url = item!!.media!!.download_url!!
if (dls != null && dls.isDownloadingEpisode(url)) {
progbarDownload?.visibility = View.VISIBLE
progbarDownload?.setPercentage(0.01f * max(1.0, dls.getProgress(url).toDouble()).toFloat(), item)
progbarDownload?.setIndeterminate(dls.isEpisodeQueued(url))
progbarDownload.visibility = View.VISIBLE
progbarDownload.setPercentage(0.01f * max(1.0, dls.getProgress(url).toDouble()).toFloat(), item)
progbarDownload.setIndeterminate(dls.isEpisodeQueued(url))
}
}
@ -280,12 +279,12 @@ class ItemFragment : Fragment() {
actionButton1 = MarkAsPlayedActionButton(item!!)
actionButton2 = VisitWebsiteActionButton(item!!)
}
noMediaLabel!!.visibility = View.VISIBLE
noMediaLabel.visibility = View.VISIBLE
} else {
noMediaLabel!!.visibility = View.GONE
noMediaLabel.visibility = View.GONE
if (media.getDuration() > 0) {
txtvDuration?.text = Converter.getDurationStringLong(media.getDuration())
txtvDuration?.setContentDescription(
txtvDuration.text = Converter.getDurationStringLong(media.getDuration())
txtvDuration.setContentDescription(
Converter.getDurationStringLocalized(requireContext(), media.getDuration().toLong()))
}
if (item != null) {
@ -309,25 +308,25 @@ class ItemFragment : Fragment() {
}
if (actionButton1 != null) {
butAction1Text?.setText(actionButton1!!.getLabel())
butAction1Icon?.setImageResource(actionButton1!!.getDrawable())
butAction1Text.setText(actionButton1!!.getLabel())
butAction1Icon.setImageResource(actionButton1!!.getDrawable())
}
butAction1Text?.transformationMethod = null
if (actionButton1 != null) butAction1!!.visibility = actionButton1!!.visibility
butAction1Text.transformationMethod = null
if (actionButton1 != null) butAction1.visibility = actionButton1!!.visibility
if (actionButton2 != null) {
butAction2Text?.setText(actionButton2!!.getLabel())
butAction2Icon?.setImageResource(actionButton2!!.getDrawable())
butAction2Text.setText(actionButton2!!.getLabel())
butAction2Icon.setImageResource(actionButton2!!.getDrawable())
}
butAction2Text?.transformationMethod = null
if (actionButton2 != null) butAction2!!.visibility = actionButton2!!.visibility
butAction2Text.transformationMethod = null
if (actionButton2 != null) butAction2.visibility = actionButton2!!.visibility
}
override fun onContextItemSelected(item: MenuItem): Boolean {
return webvDescription?.onContextItemSelected(item)?: false
return webvDescription.onContextItemSelected(item)
}
private fun openPodcast() {
@OptIn(UnstableApi::class) private fun openPodcast() {
if (item == null) {
return
}
@ -374,13 +373,13 @@ class ItemFragment : Fragment() {
disposable?.dispose()
if (!itemsLoaded) {
progbarLoading?.visibility = View.VISIBLE
progbarLoading.visibility = View.VISIBLE
}
disposable = Observable.fromCallable<FeedItem?> { this.loadInBackground() }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ result: FeedItem? ->
progbarLoading?.visibility = View.GONE
progbarLoading.visibility = View.GONE
item = result
onFragmentLoaded()
itemsLoaded = true

View File

@ -30,22 +30,22 @@ import kotlin.math.max
* Displays information about a list of FeedItems.
*/
class ItemPagerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
private var pager: ViewPager2? = null
private lateinit var pager: ViewPager2
private lateinit var toolbar: MaterialToolbar
private var feedItems: LongArray? = null
private var item: FeedItem? = null
private var disposable: Disposable? = null
private var toolbar: MaterialToolbar? = null
@UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
super.onCreateView(inflater, container, savedInstanceState)
val layout: View = inflater.inflate(R.layout.feeditem_pager_fragment, container, false)
toolbar = layout.findViewById(R.id.toolbar)
toolbar?.title = ""
toolbar?.inflateMenu(R.menu.feeditem_options)
toolbar?.setNavigationOnClickListener { v: View? -> parentFragmentManager.popBackStack() }
toolbar?.setOnMenuItemClickListener(this)
toolbar.title = ""
toolbar.inflateMenu(R.menu.feeditem_options)
toolbar.setNavigationOnClickListener { v: View? -> parentFragmentManager.popBackStack() }
toolbar.setOnMenuItemClickListener(this)
feedItems = requireArguments().getLongArray(ARG_FEEDITEMS)
val feedItemPos = max(0.0, requireArguments().getInt(ARG_FEEDITEM_POS).toDouble())
@ -61,16 +61,18 @@ class ItemPagerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
// Restore state by using the same ID as before. ID collisions are prevented in MainActivity.
newId = savedInstanceState.getInt(KEY_PAGER_ID, 0)
}
pager?.setId(newId)
pager?.adapter = ItemPagerAdapter(this)
pager?.setCurrentItem(feedItemPos, false)
pager?.offscreenPageLimit = 1
loadItem(feedItems!![feedItemPos])
pager?.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
loadItem(feedItems!![position])
}
})
pager.setId(newId)
pager.adapter = ItemPagerAdapter(this)
pager.setCurrentItem(feedItemPos, false)
pager.offscreenPageLimit = 1
if (feedItems != null && feedItems!!.isNotEmpty()) {
loadItem(feedItems!![feedItemPos])
pager.registerOnPageChangeCallback(object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
loadItem(feedItems!![position])
}
})
}
EventBus.getDefault().register(this)
return layout
@ -78,7 +80,7 @@ class ItemPagerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
if (pager != null) outState.putInt(KEY_PAGER_ID, pager!!.id)
outState.putInt(KEY_PAGER_ID, pager.id)
}
override fun onDestroyView() {
@ -100,19 +102,19 @@ class ItemPagerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
}
@UnstableApi fun refreshToolbarState() {
if (item == null || toolbar == null) {
if (item == null) {
return
}
if (item!!.hasMedia()) {
FeedItemMenuHandler.onPrepareMenu(toolbar!!.menu, item)
FeedItemMenuHandler.onPrepareMenu(toolbar.menu, item)
} else {
// these are already available via button1 and button2
FeedItemMenuHandler.onPrepareMenu(toolbar!!.menu, item,
FeedItemMenuHandler.onPrepareMenu(toolbar.menu, item,
R.id.mark_read_item, R.id.visit_website_item)
}
}
override fun onMenuItemClick(menuItem: MenuItem): Boolean {
@UnstableApi override fun onMenuItemClick(menuItem: MenuItem): Boolean {
if (menuItem.itemId == R.id.open_podcast) {
openPodcast()
return true
@ -132,7 +134,7 @@ class ItemPagerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
}
}
private fun openPodcast() {
@UnstableApi private fun openPodcast() {
if (item == null) {
return
}
@ -142,11 +144,11 @@ class ItemPagerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
private inner class ItemPagerAdapter(fragment: Fragment) : FragmentStateAdapter(fragment) {
override fun createFragment(position: Int): Fragment {
return ItemFragment.newInstance(feedItems!![position])
return ItemFragment.newInstance(if (feedItems!= null) feedItems!![position] else 0L)
}
override fun getItemCount(): Int {
return feedItems!!.size
return feedItems?.size?:0
}
}
@ -165,7 +167,7 @@ class ItemPagerFragment : Fragment(), Toolbar.OnMenuItemClickListener {
fun newInstance(feeditems: LongArray?, feedItemPos: Int): ItemPagerFragment {
val fragment = ItemPagerFragment()
val args = Bundle()
args.putLongArray(ARG_FEEDITEMS, feeditems)
if (feeditems != null) args.putLongArray(ARG_FEEDITEMS, feeditems)
args.putInt(ARG_FEEDITEM_POS, max(0.0, feedItemPos.toDouble()).toInt())
fragment.arguments = args
return fragment

View File

@ -42,6 +42,8 @@ import ac.mdiq.podvinci.storage.preferences.UserPreferences
import ac.mdiq.podvinci.ui.appstartintent.MainActivityStarter
import ac.mdiq.podvinci.ui.common.ThemeUtils
import ac.mdiq.podvinci.ui.home.HomeFragment
import androidx.annotation.OptIn
import androidx.media3.common.util.UnstableApi
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
@ -56,9 +58,11 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange
private var navDrawerData: NavDrawerData? = null
private var flatItemList: List<NavDrawerData.DrawerItem>? = null
private var contextPressedItem: NavDrawerData.DrawerItem? = null
private var navAdapter: NavListAdapter? = null
private var disposable: Disposable? = null
private var progressBar: ProgressBar? = null
private lateinit var navAdapter: NavListAdapter
private lateinit var progressBar: ProgressBar
private var openFolders: MutableSet<String> = HashSet()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
@ -83,12 +87,13 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange
}
val preferences: SharedPreferences = requireContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
// TODO: what is this?
openFolders = HashSet(preferences.getStringSet(PREF_OPEN_FOLDERS, HashSet<String>())) // Must not modify
progressBar = root.findViewById(R.id.progressBar)
val navList = root.findViewById<RecyclerView>(R.id.nav_list)
navAdapter = NavListAdapter(itemAccess, requireActivity())
navAdapter?.setHasStableIds(true)
navAdapter.setHasStableIds(true)
navList.adapter = navAdapter
navList.layoutManager = LinearLayoutManager(context)
@ -161,14 +166,14 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange
}
}
private fun onFeedContextMenuClicked(feed: Feed, item: MenuItem): Boolean {
@OptIn(UnstableApi::class) private fun onFeedContextMenuClicked(feed: Feed, item: MenuItem): Boolean {
val itemId = item.itemId
when (itemId) {
R.id.remove_all_inbox_item -> {
val removeAllNewFlagsConfirmationDialog: ConfirmationDialog = object : ConfirmationDialog(requireContext(),
R.string.remove_all_inbox_label,
R.string.remove_all_inbox_confirmation_msg) {
override fun onConfirmButtonPressed(dialog: DialogInterface) {
@OptIn(UnstableApi::class) override fun onConfirmButtonPressed(dialog: DialogInterface) {
dialog.dismiss()
DBWriter.removeFeedNewFlag(feed.id)
}
@ -253,12 +258,12 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange
override fun isSelected(position: Int): Boolean {
val lastNavFragment = getLastNavFragment(context)
if (position < navAdapter!!.subscriptionOffset) {
return navAdapter!!.getFragmentTags()[position] == lastNavFragment
if (position < navAdapter.subscriptionOffset) {
return navAdapter.getFragmentTags()[position] == lastNavFragment
} else if (StringUtils.isNumeric(lastNavFragment)) { // last fragment was not a list, but a feed
val feedId = lastNavFragment.toLong()
if (navDrawerData != null) {
val itemToCheck: NavDrawerData.DrawerItem = flatItemList!![position - navAdapter!!.subscriptionOffset]
val itemToCheck: NavDrawerData.DrawerItem = flatItemList!![position - navAdapter.subscriptionOffset]
if (itemToCheck.type == NavDrawerData.DrawerItem.Type.FEED) {
// When the same feed is displayed multiple times, it should be highlighted multiple times.
return (itemToCheck as NavDrawerData.FeedDrawerItem).feed.id == feedId
@ -292,15 +297,15 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange
return sum
}
override fun onItemClick(position: Int) {
val viewType: Int = navAdapter!!.getItemViewType(position)
@OptIn(UnstableApi::class) override fun onItemClick(position: Int) {
val viewType: Int = navAdapter.getItemViewType(position)
if (viewType != NavListAdapter.VIEW_TYPE_SECTION_DIVIDER) {
if (position < navAdapter!!.subscriptionOffset) {
val tag: String = navAdapter!!.getFragmentTags()[position] ?:""
if (position < navAdapter.subscriptionOffset) {
val tag: String = navAdapter.getFragmentTags()[position] ?:""
(activity as MainActivity).loadFragment(tag, null)
(activity as MainActivity).bottomSheet?.setState(BottomSheetBehavior.STATE_COLLAPSED)
} else {
val pos: Int = position - navAdapter!!.subscriptionOffset
val pos: Int = position - navAdapter.subscriptionOffset
val clickedItem: NavDrawerData.DrawerItem = flatItemList!![pos]
if (clickedItem.type == NavDrawerData.DrawerItem.Type.FEED) {
@ -327,20 +332,20 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange
.subscribe(
{ result: List<NavDrawerData.DrawerItem>? ->
flatItemList = result
navAdapter?.notifyDataSetChanged()
navAdapter.notifyDataSetChanged()
}, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
}
}
} else if (UserPreferences.subscriptionsFilter.isEnabled
&& navAdapter!!.showSubscriptionList) {
&& navAdapter.showSubscriptionList) {
SubscriptionsFilterDialog().show(childFragmentManager, "filter")
}
}
override fun onItemLongClick(position: Int): Boolean {
if (position < navAdapter!!.getFragmentTags().size) {
if (position < navAdapter.getFragmentTags().size) {
DrawerPreferencesDialog.show(context!!) {
navAdapter?.notifyDataSetChanged()
navAdapter.notifyDataSetChanged()
if (UserPreferences.hiddenDrawerItems != null && UserPreferences.hiddenDrawerItems!!.contains(
getLastNavFragment(context))) {
MainActivityStarter(context!!)
@ -351,7 +356,7 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange
}
return true
} else {
contextPressedItem = flatItemList!![position - navAdapter!!.subscriptionOffset]
contextPressedItem = flatItemList!![position - navAdapter.subscriptionOffset]
return false
}
}
@ -372,11 +377,11 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange
{ result: Pair<NavDrawerData, List<NavDrawerData.DrawerItem>> ->
navDrawerData = result.first
flatItemList = result.second
navAdapter?.notifyDataSetChanged()
progressBar?.visibility = View.GONE // Stays hidden once there is something in the list
navAdapter.notifyDataSetChanged()
progressBar.visibility = View.GONE // Stays hidden once there is something in the list
}, { error: Throwable? ->
Log.e(TAG, Log.getStackTraceString(error))
progressBar?.visibility = View.GONE
progressBar.visibility = View.GONE
})
}
@ -398,7 +403,7 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange
override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) {
if (PREF_LAST_FRAGMENT_TAG == key) {
navAdapter?.notifyDataSetChanged() // Update selection
navAdapter.notifyDataSetChanged() // Update selection
}
}
@ -414,6 +419,7 @@ class NavDrawerFragment : Fragment(), SharedPreferences.OnSharedPreferenceChange
const val TAG: String = "NavDrawerFragment"
@JvmField
@UnstableApi
val NAV_DRAWER_TAGS: Array<String> = arrayOf(HomeFragment.TAG,
QueueFragment.TAG,
InboxFragment.TAG,

View File

@ -33,11 +33,12 @@ class OnlineSearchFragment
*/
private var adapter: ItunesAdapter? = null
private var searchProvider: PodcastSearcher? = null
private var gridView: GridView? = null
private var progressBar: ProgressBar? = null
private var txtvError: TextView? = null
private var butRetry: Button? = null
private var txtvEmpty: TextView? = null
private lateinit var gridView: GridView
private lateinit var progressBar: ProgressBar
private lateinit var txtvError: TextView
private lateinit var butRetry: Button
private lateinit var txtvEmpty: TextView
/**
* List of podcasts retreived from the search
@ -65,10 +66,10 @@ class OnlineSearchFragment
val root: View = inflater.inflate(R.layout.fragment_itunes_search, container, false)
gridView = root.findViewById(R.id.gridView)
adapter = ItunesAdapter(requireContext(), ArrayList())
gridView?.setAdapter(adapter)
gridView.setAdapter(adapter)
//Show information about the podcast when the list item is clicked
gridView?.onItemClickListener =
gridView.onItemClickListener =
AdapterView.OnItemClickListener { parent: AdapterView<*>?, view1: View?, position: Int, id: Long ->
val podcast = searchResults!![position]
if (podcast != null) {
@ -86,7 +87,7 @@ class OnlineSearchFragment
if (searchProvider != null) txtvPoweredBy.text = getString(R.string.search_powered_by, searchProvider!!.name)
setupToolbar(root.findViewById(R.id.toolbar))
gridView?.setOnScrollListener(object : AbsListView.OnScrollListener {
gridView.setOnScrollListener(object : AbsListView.OnScrollListener {
override fun onScrollStateChanged(view: AbsListView, scrollState: Int) {
if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL) {
val imm = activity!!.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
@ -158,29 +159,29 @@ class OnlineSearchFragment
disposable = searchProvider?.search(query)
?.subscribe({ result: List<PodcastSearchResult?>? ->
searchResults = result
progressBar?.visibility = View.GONE
progressBar.visibility = View.GONE
adapter?.clear()
if (searchResults != null) adapter?.addAll(searchResults!!)
adapter?.notifyDataSetInvalidated()
gridView?.visibility = if (searchResults!!.isNotEmpty()) View.VISIBLE else View.GONE
txtvEmpty?.visibility = if (searchResults!!.isEmpty()) View.VISIBLE else View.GONE
txtvEmpty?.text = getString(R.string.no_results_for_query) + query
gridView.visibility = if (searchResults!!.isNotEmpty()) View.VISIBLE else View.GONE
txtvEmpty.visibility = if (searchResults!!.isEmpty()) View.VISIBLE else View.GONE
txtvEmpty.text = getString(R.string.no_results_for_query) + query
}, { error: Throwable ->
Log.e(TAG, Log.getStackTraceString(error))
progressBar?.visibility = View.GONE
txtvError?.text = error.toString()
txtvError?.visibility = View.VISIBLE
butRetry?.setOnClickListener { v: View? -> search(query) }
butRetry!!.visibility = View.VISIBLE
progressBar.visibility = View.GONE
txtvError.text = error.toString()
txtvError.visibility = View.VISIBLE
butRetry.setOnClickListener { v: View? -> search(query) }
butRetry.visibility = View.VISIBLE
})
}
private fun showOnlyProgressBar() {
gridView?.visibility = View.GONE
txtvError?.visibility = View.GONE
butRetry!!.visibility = View.GONE
txtvEmpty?.visibility = View.GONE
progressBar?.visibility = View.VISIBLE
gridView.visibility = View.GONE
txtvError.visibility = View.GONE
butRetry.visibility = View.GONE
txtvEmpty.visibility = View.GONE
progressBar.visibility = View.VISIBLE
}
private fun showInputMethod(view: View) {

View File

@ -14,6 +14,7 @@ import ac.mdiq.podvinci.core.storage.DBWriter
import ac.mdiq.podvinci.event.playback.PlaybackHistoryEvent
import ac.mdiq.podvinci.model.feed.FeedItem
import ac.mdiq.podvinci.model.feed.FeedItemFilter
import androidx.annotation.OptIn
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
@ -27,19 +28,19 @@ class PlaybackHistoryFragment : EpisodesListFragment() {
@UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
val root = super.onCreateView(inflater, container, savedInstanceState)
toolbar!!.inflateMenu(R.menu.playback_history)
toolbar!!.setTitle(R.string.playback_history_label)
toolbar.inflateMenu(R.menu.playback_history)
toolbar.setTitle(R.string.playback_history_label)
updateToolbar()
emptyView!!.setIcon(R.drawable.ic_history)
emptyView!!.setTitle(R.string.no_history_head_label)
emptyView!!.setMessage(R.string.no_history_label)
emptyView.setIcon(R.drawable.ic_history)
emptyView.setTitle(R.string.no_history_head_label)
emptyView.setMessage(R.string.no_history_label)
return root
}
override fun getFilter(): FeedItemFilter {
return FeedItemFilter.unfiltered()
}
override fun onMenuItemClick(item: MenuItem): Boolean {
@OptIn(UnstableApi::class) override fun onMenuItemClick(item: MenuItem): Boolean {
if (super.onOptionsItemSelected(item)) {
return true
}
@ -62,7 +63,7 @@ class PlaybackHistoryFragment : EpisodesListFragment() {
override fun updateToolbar() {
// Not calling super, as we do not have a refresh button that could be updated
toolbar!!.menu.findItem(R.id.clear_history_item).setVisible(episodes.isNotEmpty())
toolbar.menu.findItem(R.id.clear_history_item).setVisible(episodes.isNotEmpty())
}
@Subscribe(threadMode = ThreadMode.MAIN)

View File

@ -60,22 +60,21 @@ import java.util.*
* Shows all items in the queue.
*/
class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAdapter.OnSelectModeListener {
private var infoBar: TextView? = null
private var recyclerView: EpisodeItemListRecyclerView? = null
private var recyclerAdapter: QueueRecyclerAdapter? = null
private var emptyView: EmptyViewHandler? = null
private var toolbar: MaterialToolbar? = null
private var swipeRefreshLayout: SwipeRefreshLayout? = null
private lateinit var infoBar: TextView
private lateinit var recyclerView: EpisodeItemListRecyclerView
private lateinit var emptyView: EmptyViewHandler
private lateinit var toolbar: MaterialToolbar
private lateinit var swipeRefreshLayout: SwipeRefreshLayout
private lateinit var swipeActions: SwipeActions
private lateinit var prefs: SharedPreferences
private lateinit var speedDialView: SpeedDialView
private lateinit var progressBar: ProgressBar
private var displayUpArrow = false
private var queue: MutableList<FeedItem> = mutableListOf()
private var queue: MutableList<FeedItem>? = null
private var recyclerAdapter: QueueRecyclerAdapter? = null
private var disposable: Disposable? = null
private var swipeActions: SwipeActions? = null
private var prefs: SharedPreferences? = null
private var speedDialView: SpeedDialView? = null
private var progressBar: ProgressBar? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -83,10 +82,96 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda
prefs = requireActivity().getSharedPreferences(PREFS, Context.MODE_PRIVATE)
}
@UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
super.onCreateView(inflater, container, savedInstanceState)
val root: View = inflater.inflate(R.layout.queue_fragment, container, false)
toolbar = root.findViewById(R.id.toolbar)
toolbar.setOnMenuItemClickListener(this)
toolbar.setOnLongClickListener { v: View? ->
recyclerView.scrollToPosition(5)
recyclerView.post { recyclerView.smoothScrollToPosition(0) }
false
}
displayUpArrow = parentFragmentManager.backStackEntryCount != 0
if (savedInstanceState != null) {
displayUpArrow = savedInstanceState.getBoolean(KEY_UP_ARROW)
}
(activity as MainActivity).setupToolbarToggle(toolbar, displayUpArrow)
toolbar.inflateMenu(R.menu.queue)
refreshToolbarState()
progressBar = root.findViewById(R.id.progressBar)
progressBar.visibility = View.VISIBLE
infoBar = root.findViewById(R.id.info_bar)
recyclerView = root.findViewById(R.id.recyclerView)
val animator: RecyclerView.ItemAnimator? = recyclerView.itemAnimator
if (animator != null && animator is SimpleItemAnimator) {
animator.supportsChangeAnimations = false
}
recyclerView.setRecycledViewPool((activity as MainActivity).recycledViewPool)
registerForContextMenu(recyclerView)
recyclerView.addOnScrollListener(LiftOnScrollListener(root.findViewById(R.id.appbar)))
swipeActions = QueueSwipeActions()
swipeActions.setFilter(FeedItemFilter(FeedItemFilter.QUEUED))
swipeActions.attachTo(recyclerView)
recyclerAdapter = object : QueueRecyclerAdapter(activity as MainActivity, swipeActions) {
override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) {
super.onCreateContextMenu(menu, v, menuInfo)
MenuItemUtils.setOnClickListeners(menu
) { item: MenuItem -> this@QueueFragment.onContextItemSelected(item) }
}
}
recyclerAdapter?.setOnSelectModeListener(this)
recyclerView.adapter = recyclerAdapter
swipeRefreshLayout = root.findViewById(R.id.swipeRefresh)
swipeRefreshLayout.setDistanceToTriggerSync(resources.getInteger(R.integer.swipe_refresh_distance))
swipeRefreshLayout.setOnRefreshListener {
FeedUpdateManager.runOnceOrAsk(requireContext())
}
emptyView = EmptyViewHandler(context)
emptyView.attachToRecyclerView(recyclerView)
emptyView.setIcon(R.drawable.ic_playlist_play)
emptyView.setTitle(R.string.no_items_header_label)
emptyView.setMessage(R.string.no_items_label)
emptyView.updateAdapter(recyclerAdapter)
speedDialView = root.findViewById(R.id.fabSD)
speedDialView.overlayLayout = root.findViewById(R.id.fabSDOverlay)
speedDialView.inflate(R.menu.episodes_apply_action_speeddial)
speedDialView.removeActionItemById(R.id.mark_read_batch)
speedDialView.removeActionItemById(R.id.mark_unread_batch)
speedDialView.removeActionItemById(R.id.add_to_queue_batch)
speedDialView.removeActionItemById(R.id.remove_all_inbox_item)
speedDialView.setOnChangeListener(object : SpeedDialView.OnChangeListener {
override fun onMainActionSelected(): Boolean {
return false
}
override fun onToggleChanged(open: Boolean) {
if (open && recyclerAdapter!!.selectedCount == 0) {
(activity as MainActivity).showSnackbarAbovePlayer(R.string.no_items_selected,
Snackbar.LENGTH_SHORT)
speedDialView.close()
}
}
})
speedDialView.setOnActionSelectedListener { actionItem: SpeedDialActionItem ->
EpisodeMultiSelectActionHandler((activity as MainActivity), actionItem.id)
.handleAction(recyclerAdapter!!.selectedItems.filterIsInstance<FeedItem>())
recyclerAdapter?.endSelectMode()
true
}
return root
}
override fun onStart() {
super.onStart()
if (queue != null) {
recyclerView?.restoreScrollPosition(TAG)
if (queue.isNotEmpty()) {
recyclerView.restoreScrollPosition(TAG)
}
loadItems(true)
EventBus.getDefault().register(this)
@ -94,7 +179,7 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda
override fun onPause() {
super.onPause()
recyclerView?.saveScrollPosition(TAG)
recyclerView.saveScrollPosition(TAG)
}
override fun onStop() {
@ -106,15 +191,13 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(event: QueueEvent) {
Log.d(TAG, "onEventMainThread() called with: event = [$event]")
if (queue == null) {
return
} else if (recyclerAdapter == null) {
if (recyclerAdapter == null) {
loadItems(true)
return
}
when (event.action) {
QueueEvent.Action.ADDED -> {
if (event.item != null) queue!!.add(event.position, event.item!!)
if (event.item != null) queue.add(event.position, event.item!!)
recyclerAdapter?.notifyItemInserted(event.position)
}
QueueEvent.Action.SET_QUEUE, QueueEvent.Action.SORTED -> {
@ -125,14 +208,14 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda
}
QueueEvent.Action.REMOVED, QueueEvent.Action.IRREVERSIBLE_REMOVED -> {
if (event.item != null) {
val position: Int = FeedItemUtil.indexOfItemWithId(queue!!.toList(), event.item!!.id)
queue!!.removeAt(position)
val position: Int = FeedItemUtil.indexOfItemWithId(queue.toList(), event.item!!.id)
queue.removeAt(position)
recyclerAdapter?.notifyItemRemoved(position)
}
}
QueueEvent.Action.CLEARED -> {
queue!!.clear()
recyclerAdapter?.updateItems(queue!!)
queue.clear()
recyclerAdapter?.updateItems(queue)
}
QueueEvent.Action.MOVED -> return
QueueEvent.Action.ADDED_ITEMS -> return
@ -140,16 +223,14 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda
}
recyclerAdapter?.updateDragDropEnabled()
refreshToolbarState()
recyclerView?.saveScrollPosition(TAG)
recyclerView.saveScrollPosition(TAG)
refreshInfoBar()
}
@Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(event: FeedItemEvent) {
Log.d(TAG, "onEventMainThread() called with: event = [$event]")
if (queue == null) {
return
} else if (recyclerAdapter == null) {
if (recyclerAdapter == null) {
loadItems(true)
return
}
@ -157,10 +238,10 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda
val size: Int = event.items.size
while (i < size) {
val item: FeedItem = event.items[i]
val pos: Int = FeedItemUtil.indexOfItemWithId(queue!!, item.id)
val pos: Int = FeedItemUtil.indexOfItemWithId(queue, item.id)
if (pos >= 0) {
queue!!.removeAt(pos)
queue!!.add(pos, item)
queue.removeAt(pos)
queue.add(pos, item)
recyclerAdapter?.notifyItemChangedCompat(pos)
refreshInfoBar()
}
@ -170,11 +251,8 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
fun onEventMainThread(event: EpisodeDownloadEvent) {
if (queue == null) {
return
}
for (downloadUrl in event.urls) {
val pos: Int = FeedItemUtil.indexOfItemWithDownloadUrl(queue!!.toList(), downloadUrl)
val pos: Int = FeedItemUtil.indexOfItemWithDownloadUrl(queue.toList(), downloadUrl)
if (pos >= 0) {
recyclerAdapter?.notifyItemChangedCompat(pos)
}
@ -185,7 +263,7 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda
fun onEventMainThread(event: PlaybackPositionEvent) {
if (recyclerAdapter != null) {
for (i in 0 until recyclerAdapter!!.itemCount) {
val holder: EpisodeItemViewHolder? = recyclerView?.findViewHolderForAdapterPosition(i) as? EpisodeItemViewHolder
val holder: EpisodeItemViewHolder? = recyclerView.findViewHolderForAdapterPosition(i) as? EpisodeItemViewHolder
if (holder != null && holder.isCurrentlyPlayingItem) {
holder.notifyPlaybackPositionUpdated(event)
break
@ -213,8 +291,8 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda
return
}
when (event.keyCode) {
KeyEvent.KEYCODE_T -> recyclerView!!.smoothScrollToPosition(0)
KeyEvent.KEYCODE_B -> recyclerView!!.smoothScrollToPosition(recyclerAdapter!!.itemCount - 1)
KeyEvent.KEYCODE_T -> recyclerView.smoothScrollToPosition(0)
KeyEvent.KEYCODE_B -> recyclerView.smoothScrollToPosition(recyclerAdapter!!.itemCount - 1)
else -> {}
}
}
@ -224,19 +302,19 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda
recyclerAdapter?.endSelectMode()
recyclerAdapter = null
toolbar?.setOnMenuItemClickListener(null)
toolbar?.setOnLongClickListener(null)
toolbar.setOnMenuItemClickListener(null)
toolbar.setOnLongClickListener(null)
}
private fun refreshToolbarState() {
val keepSorted: Boolean = UserPreferences.isQueueKeepSorted
toolbar?.menu?.findItem(R.id.queue_lock)?.setChecked(UserPreferences.isQueueLocked)
toolbar?.menu?.findItem(R.id.queue_lock)?.setVisible(!keepSorted)
toolbar.menu?.findItem(R.id.queue_lock)?.setChecked(UserPreferences.isQueueLocked)
toolbar.menu?.findItem(R.id.queue_lock)?.setVisible(!keepSorted)
}
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
fun onEventMainThread(event: FeedUpdateRunningEvent) {
swipeRefreshLayout?.isRefreshing = event.isFeedUpdateRunning
swipeRefreshLayout.isRefreshing = event.isFeedUpdateRunning
}
@UnstableApi override fun onMenuItemClick(item: MenuItem): Boolean {
@ -282,7 +360,7 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda
if (isLocked) {
setQueueLocked(false)
} else {
val shouldShowLockWarning: Boolean = prefs!!.getBoolean(PREF_SHOW_LOCK_WARNING, true)
val shouldShowLockWarning: Boolean = prefs.getBoolean(PREF_SHOW_LOCK_WARNING, true)
if (!shouldShowLockWarning) {
setQueueLocked(true)
} else {
@ -296,7 +374,7 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda
builder.setPositiveButton(R.string.lock_queue
) { dialog: DialogInterface?, which: Int ->
prefs!!.edit().putBoolean(PREF_SHOW_LOCK_WARNING, !checkDoNotShowAgain.isChecked).apply()
prefs.edit().putBoolean(PREF_SHOW_LOCK_WARNING, !checkDoNotShowAgain.isChecked).apply()
setQueueLocked(true)
}
builder.setNegativeButton(R.string.cancel_label, null)
@ -311,7 +389,7 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda
if (recyclerAdapter != null) {
recyclerAdapter?.updateDragDropEnabled()
}
if (queue!!.size == 0) {
if (queue.size == 0) {
if (locked) {
(activity as MainActivity).showSnackbarAbovePlayer(R.string.queue_locked, Snackbar.LENGTH_SHORT)
} else {
@ -334,126 +412,38 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda
return true
}
if (queue != null) {
val position: Int = FeedItemUtil.indexOfItemWithId(queue!!.toList(), selectedItem.id)
if (position < 0) {
Log.i(TAG, "Selected item no longer exist, ignoring selection")
return super.onContextItemSelected(item)
}
val itemId = item.itemId
if (itemId == R.id.move_to_top_item) {
queue!!.add(0, queue!!.removeAt(position))
recyclerAdapter?.notifyItemMoved(position, 0)
DBWriter.moveQueueItemToTop(selectedItem.id, true)
return true
} else if (itemId == R.id.move_to_bottom_item) {
queue!!.add(queue!!.size - 1, queue!!.removeAt(position))
recyclerAdapter?.notifyItemMoved(position, queue!!.size - 1)
DBWriter.moveQueueItemToBottom(selectedItem.id, true)
return true
}
val position: Int = FeedItemUtil.indexOfItemWithId(queue.toList(), selectedItem.id)
if (position < 0) {
Log.i(TAG, "Selected item no longer exist, ignoring selection")
return super.onContextItemSelected(item)
}
val itemId = item.itemId
if (itemId == R.id.move_to_top_item) {
queue.add(0, queue.removeAt(position))
recyclerAdapter?.notifyItemMoved(position, 0)
DBWriter.moveQueueItemToTop(selectedItem.id, true)
return true
} else if (itemId == R.id.move_to_bottom_item) {
queue.add(queue.size - 1, queue.removeAt(position))
recyclerAdapter?.notifyItemMoved(position, queue.size - 1)
DBWriter.moveQueueItemToBottom(selectedItem.id, true)
return true
}
return FeedItemMenuHandler.onMenuItemClicked(this, item.itemId, selectedItem)
}
@UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
super.onCreateView(inflater, container, savedInstanceState)
val root: View = inflater.inflate(R.layout.queue_fragment, container, false)
toolbar = root.findViewById(R.id.toolbar)
toolbar?.setOnMenuItemClickListener(this)
toolbar?.setOnLongClickListener { v: View? ->
recyclerView?.scrollToPosition(5)
recyclerView?.post { recyclerView?.smoothScrollToPosition(0) }
false
}
displayUpArrow = parentFragmentManager.backStackEntryCount != 0
if (savedInstanceState != null) {
displayUpArrow = savedInstanceState.getBoolean(KEY_UP_ARROW)
}
if (toolbar != null) (activity as MainActivity).setupToolbarToggle(toolbar!!, displayUpArrow)
toolbar?.inflateMenu(R.menu.queue)
refreshToolbarState()
progressBar = root.findViewById(R.id.progressBar)
progressBar?.visibility = View.VISIBLE
infoBar = root.findViewById(R.id.info_bar)
recyclerView = root.findViewById(R.id.recyclerView)
val animator: RecyclerView.ItemAnimator? = recyclerView!!.itemAnimator
if (animator != null && animator is SimpleItemAnimator) {
animator.supportsChangeAnimations = false
}
recyclerView?.setRecycledViewPool((activity as MainActivity).recycledViewPool)
registerForContextMenu(recyclerView!!)
recyclerView?.addOnScrollListener(LiftOnScrollListener(root.findViewById(R.id.appbar)))
swipeActions = QueueSwipeActions()
swipeActions?.setFilter(FeedItemFilter(FeedItemFilter.QUEUED))
swipeActions?.attachTo(recyclerView)
recyclerAdapter = object : QueueRecyclerAdapter(activity as MainActivity, swipeActions!!) {
override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) {
super.onCreateContextMenu(menu, v, menuInfo)
MenuItemUtils.setOnClickListeners(menu
) { item: MenuItem -> this@QueueFragment.onContextItemSelected(item) }
}
}
recyclerAdapter?.setOnSelectModeListener(this)
recyclerView?.adapter = recyclerAdapter
swipeRefreshLayout = root.findViewById(R.id.swipeRefresh)
swipeRefreshLayout?.setDistanceToTriggerSync(resources.getInteger(R.integer.swipe_refresh_distance))
swipeRefreshLayout?.setOnRefreshListener {
FeedUpdateManager.runOnceOrAsk(requireContext())
}
emptyView = EmptyViewHandler(context)
emptyView?.attachToRecyclerView(recyclerView!!)
emptyView?.setIcon(R.drawable.ic_playlist_play)
emptyView?.setTitle(R.string.no_items_header_label)
emptyView?.setMessage(R.string.no_items_label)
emptyView?.updateAdapter(recyclerAdapter)
speedDialView = root.findViewById(R.id.fabSD)
speedDialView?.overlayLayout = root.findViewById(R.id.fabSDOverlay)
speedDialView?.inflate(R.menu.episodes_apply_action_speeddial)
speedDialView?.removeActionItemById(R.id.mark_read_batch)
speedDialView?.removeActionItemById(R.id.mark_unread_batch)
speedDialView?.removeActionItemById(R.id.add_to_queue_batch)
speedDialView?.removeActionItemById(R.id.remove_all_inbox_item)
speedDialView?.setOnChangeListener(object : SpeedDialView.OnChangeListener {
override fun onMainActionSelected(): Boolean {
return false
}
override fun onToggleChanged(open: Boolean) {
if (open && recyclerAdapter!!.selectedCount == 0) {
(activity as MainActivity).showSnackbarAbovePlayer(R.string.no_items_selected,
Snackbar.LENGTH_SHORT)
speedDialView?.close()
}
}
})
speedDialView?.setOnActionSelectedListener { actionItem: SpeedDialActionItem ->
EpisodeMultiSelectActionHandler((activity as MainActivity), actionItem.id)
.handleAction(recyclerAdapter!!.selectedItems.filterIsInstance<FeedItem>())
recyclerAdapter?.endSelectMode()
true
}
return root
}
override fun onSaveInstanceState(outState: Bundle) {
outState.putBoolean(KEY_UP_ARROW, displayUpArrow)
super.onSaveInstanceState(outState)
}
private fun refreshInfoBar() {
if (queue == null) return
var info = String.format(Locale.getDefault(), "%d%s", queue!!.size, getString(R.string.episodes_suffix))
if (queue!!.size > 0) {
var info = String.format(Locale.getDefault(), "%d%s", queue.size, getString(R.string.episodes_suffix))
if (queue.size > 0) {
var timeLeft: Long = 0
for (item in queue!!) {
for (item in queue) {
var playbackSpeed = 1f
if (UserPreferences.timeRespectsSpeed()) {
playbackSpeed = PlaybackSpeedUtils.getCurrentPlaybackSpeed(item.media)
@ -467,44 +457,44 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda
info += getString(R.string.time_left_label)
info += Converter.getDurationStringLocalized(requireActivity(), timeLeft)
}
infoBar?.text = info
infoBar.text = info
}
private fun loadItems(restoreScrollPosition: Boolean) {
Log.d(TAG, "loadItems()")
disposable?.dispose()
if (queue == null) {
emptyView?.hide()
if (queue.isEmpty()) {
emptyView.hide()
}
disposable =
Observable.fromCallable { DBReader.getQueue().toMutableList() }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ items: MutableList<FeedItem>? ->
.subscribe({ items: MutableList<FeedItem> ->
queue = items
progressBar?.visibility = View.GONE
progressBar.visibility = View.GONE
recyclerAdapter?.setDummyViews(0)
if (queue != null) recyclerAdapter?.updateItems(queue!!)
recyclerAdapter?.updateItems(queue)
if (restoreScrollPosition) {
recyclerView?.restoreScrollPosition(TAG)
recyclerView.restoreScrollPosition(TAG)
}
refreshInfoBar()
}, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
}
override fun onStartSelectMode() {
swipeActions?.detach()
speedDialView?.visibility = View.VISIBLE
swipeActions.detach()
speedDialView.visibility = View.VISIBLE
refreshToolbarState()
infoBar?.visibility = View.GONE
infoBar.visibility = View.GONE
}
override fun onEndSelectMode() {
speedDialView?.close()
speedDialView?.visibility = View.GONE
infoBar?.visibility = View.VISIBLE
swipeActions?.attachTo(recyclerView)
speedDialView.close()
speedDialView.visibility = View.GONE
infoBar.visibility = View.VISIBLE
swipeActions.attachTo(recyclerView)
}
class QueueSortDialog : ItemSortDialog() {
@ -565,10 +555,10 @@ class QueueFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAda
val from = viewHolder.bindingAdapterPosition
val to = target.bindingAdapterPosition
Log.d(TAG, "move($from, $to) in memory")
if (queue == null || from >= queue!!.size || to >= queue!!.size || from < 0 || to < 0) {
if (from >= queue.size || to >= queue.size || from < 0 || to < 0) {
return false
}
queue!!.add(to, queue!!.removeAt(from))
queue.add(to, queue.removeAt(from))
recyclerAdapter?.notifyItemMoved(from, to)
return true
}

View File

@ -1,26 +1,27 @@
package ac.mdiq.podvinci.fragment
import ac.mdiq.podvinci.activity.MainActivity
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.text.TextUtils
import android.util.DisplayMetrics
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.*
import androidx.fragment.app.Fragment
import ac.mdiq.podvinci.BuildConfig
import ac.mdiq.podvinci.R
import ac.mdiq.podvinci.activity.MainActivity
import ac.mdiq.podvinci.activity.OnlineFeedViewActivity
import ac.mdiq.podvinci.adapter.FeedDiscoverAdapter
import ac.mdiq.podvinci.core.storage.DBReader
import ac.mdiq.podvinci.event.DiscoveryDefaultUpdateEvent
import ac.mdiq.podvinci.net.discovery.ItunesTopListLoader
import ac.mdiq.podvinci.net.discovery.PodcastSearchResult
import android.content.Context
import android.content.Intent
import android.content.SharedPreferences
import android.os.Bundle
import android.util.DisplayMetrics
import android.util.Log
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.*
import androidx.annotation.OptIn
import androidx.fragment.app.Fragment
import androidx.media3.common.util.UnstableApi
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
@ -32,14 +33,15 @@ import java.util.*
class QuickFeedDiscoveryFragment : Fragment(), AdapterView.OnItemClickListener {
private var disposable: Disposable? = null
private var adapter: FeedDiscoverAdapter? = null
private var discoverGridLayout: GridView? = null
private var errorTextView: TextView? = null
private var poweredByTextView: TextView? = null
private var errorView: LinearLayout? = null
private var errorRetry: Button? = null
private lateinit var adapter: FeedDiscoverAdapter
private lateinit var discoverGridLayout: GridView
private lateinit var errorTextView: TextView
private lateinit var poweredByTextView: TextView
private lateinit var errorView: LinearLayout
private lateinit var errorRetry: Button
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
@OptIn(UnstableApi::class) override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
super.onCreateView(inflater, container, savedInstanceState)
val root: View = inflater.inflate(R.layout.quick_feed_discovery, container, false)
val discoverMore = root.findViewById<View>(R.id.discover_more)
@ -52,15 +54,15 @@ class QuickFeedDiscoveryFragment : Fragment(), AdapterView.OnItemClickListener {
poweredByTextView = root.findViewById(R.id.discover_powered_by_itunes)
adapter = FeedDiscoverAdapter(activity as MainActivity)
discoverGridLayout?.setAdapter(adapter)
discoverGridLayout?.onItemClickListener = this
discoverGridLayout.setAdapter(adapter)
discoverGridLayout.onItemClickListener = this
val displayMetrics: DisplayMetrics = requireContext().resources.displayMetrics
val screenWidthDp: Float = displayMetrics.widthPixels / displayMetrics.density
if (screenWidthDp > 600) {
discoverGridLayout?.numColumns = 6
discoverGridLayout.numColumns = 6
} else {
discoverGridLayout?.numColumns = 4
discoverGridLayout.numColumns = 4
}
// Fill with dummy elements to have a fixed height and
@ -70,7 +72,7 @@ class QuickFeedDiscoveryFragment : Fragment(), AdapterView.OnItemClickListener {
dummies.add(PodcastSearchResult.dummy())
}
adapter?.updateData(dummies)
adapter.updateData(dummies)
loadToplist()
EventBus.getDefault().register(this)
@ -90,31 +92,31 @@ class QuickFeedDiscoveryFragment : Fragment(), AdapterView.OnItemClickListener {
}
private fun loadToplist() {
errorView?.visibility = View.GONE
errorRetry!!.visibility = View.INVISIBLE
errorRetry?.setText(R.string.retry_label)
poweredByTextView?.visibility = View.VISIBLE
errorView.visibility = View.GONE
errorRetry.visibility = View.INVISIBLE
errorRetry.setText(R.string.retry_label)
poweredByTextView.visibility = View.VISIBLE
val loader = ItunesTopListLoader(requireContext())
val prefs: SharedPreferences = requireActivity().getSharedPreferences(ItunesTopListLoader.PREFS, Context.MODE_PRIVATE)
val countryCode: String = prefs.getString(ItunesTopListLoader.PREF_KEY_COUNTRY_CODE,
Locale.getDefault().country)!!
if (prefs.getBoolean(ItunesTopListLoader.PREF_KEY_HIDDEN_DISCOVERY_COUNTRY, false)) {
errorTextView?.setText(R.string.discover_is_hidden)
errorView?.visibility = View.VISIBLE
discoverGridLayout?.visibility = View.GONE
errorRetry!!.visibility = View.GONE
poweredByTextView?.visibility = View.GONE
errorTextView.setText(R.string.discover_is_hidden)
errorView.visibility = View.VISIBLE
discoverGridLayout.visibility = View.GONE
errorRetry.visibility = View.GONE
poweredByTextView.visibility = View.GONE
return
}
if (BuildConfig.FLAVOR == "free" && prefs.getBoolean(ItunesTopListLoader.PREF_KEY_NEEDS_CONFIRM, true)) {
errorTextView?.text = ""
errorView?.visibility = View.VISIBLE
discoverGridLayout?.visibility = View.VISIBLE
errorRetry!!.visibility = View.VISIBLE
errorRetry?.setText(R.string.discover_confirm)
poweredByTextView?.visibility = View.VISIBLE
errorRetry!!.setOnClickListener { v: View? ->
errorTextView.text = ""
errorView.visibility = View.VISIBLE
discoverGridLayout.visibility = View.VISIBLE
errorRetry.visibility = View.VISIBLE
errorRetry.setText(R.string.discover_confirm)
poweredByTextView.visibility = View.VISIBLE
errorRetry.setOnClickListener { v: View? ->
prefs.edit().putBoolean(ItunesTopListLoader.PREF_KEY_NEEDS_CONFIRM, false).apply()
loadToplist()
}
@ -130,32 +132,32 @@ class QuickFeedDiscoveryFragment : Fragment(), AdapterView.OnItemClickListener {
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ podcasts: List<PodcastSearchResult> ->
errorView?.visibility = View.GONE
errorView.visibility = View.GONE
if (podcasts.isEmpty()) {
errorTextView?.text = resources.getText(R.string.search_status_no_results)
errorView?.visibility = View.VISIBLE
discoverGridLayout?.visibility = View.INVISIBLE
errorTextView.text = resources.getText(R.string.search_status_no_results)
errorView.visibility = View.VISIBLE
discoverGridLayout.visibility = View.INVISIBLE
} else {
discoverGridLayout?.visibility = View.VISIBLE
adapter?.updateData(podcasts)
discoverGridLayout.visibility = View.VISIBLE
adapter.updateData(podcasts)
}
}, { error: Throwable ->
Log.e(TAG, Log.getStackTraceString(error))
errorTextView?.text = error.localizedMessage
errorView?.visibility = View.VISIBLE
discoverGridLayout?.visibility = View.INVISIBLE
errorRetry!!.visibility = View.VISIBLE
errorRetry?.setOnClickListener { v: View? -> loadToplist() }
errorTextView.text = error.localizedMessage
errorView.visibility = View.VISIBLE
discoverGridLayout.visibility = View.INVISIBLE
errorRetry.visibility = View.VISIBLE
errorRetry.setOnClickListener { v: View? -> loadToplist() }
})
}
override fun onItemClick(parent: AdapterView<*>?, view: View, position: Int, id: Long) {
val podcast: PodcastSearchResult = adapter!!.getItem(position)
if (TextUtils.isEmpty(podcast.feedUrl)) {
val podcast: PodcastSearchResult? = adapter.getItem(position)
if (podcast?.feedUrl.isNullOrEmpty()) {
return
}
val intent = Intent(activity, OnlineFeedViewActivity::class.java)
intent.putExtra(OnlineFeedViewActivity.ARG_FEEDURL, podcast.feedUrl)
intent.putExtra(OnlineFeedViewActivity.ARG_FEEDURL, podcast!!.feedUrl)
startActivity(intent)
}

View File

@ -55,18 +55,20 @@ import org.greenrobot.eventbus.ThreadMode
* Performs a search operation on all feeds or one specific feed and displays the search result.
*/
class SearchFragment : Fragment(), SelectableAdapter.OnSelectModeListener {
private var adapter: EpisodeItemListAdapter? = null
private var adapterFeeds: HorizontalFeedListAdapter? = null
private lateinit var adapter: EpisodeItemListAdapter
private lateinit var adapterFeeds: HorizontalFeedListAdapter
private lateinit var progressBar: ProgressBar
private lateinit var emptyViewHandler: EmptyViewHandler
private lateinit var recyclerView: EpisodeItemListRecyclerView
private lateinit var searchView: SearchView
private lateinit var speedDialBinding: MultiSelectSpeedDialBinding
private lateinit var chip: Chip
private lateinit var automaticSearchDebouncer: Handler
private var results: MutableList<FeedItem> = mutableListOf()
private var disposable: Disposable? = null
private var progressBar: ProgressBar? = null
private var emptyViewHandler: EmptyViewHandler? = null
private var recyclerView: EpisodeItemListRecyclerView? = null
private var results: MutableList<FeedItem>? = null
private var chip: Chip? = null
private var searchView: SearchView? = null
private var automaticSearchDebouncer: Handler? = null
private var lastQueryChange: Long = 0
private var speedDialBinding: MultiSelectSpeedDialBinding? = null
private var isOtherViewInFoucus = false
@ -89,8 +91,8 @@ class SearchFragment : Fragment(), SelectableAdapter.OnSelectModeListener {
speedDialBinding = MultiSelectSpeedDialBinding.bind(layout)
progressBar = layout.findViewById(R.id.progressBar)
recyclerView = layout.findViewById(R.id.recyclerView)
recyclerView?.setRecycledViewPool((activity as MainActivity).recycledViewPool)
registerForContextMenu(recyclerView!!)
recyclerView.setRecycledViewPool((activity as MainActivity).recycledViewPool)
registerForContextMenu(recyclerView)
adapter = object : EpisodeItemListAdapter(activity as MainActivity) {
override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) {
super.onCreateContextMenu(menu, v, menuInfo)
@ -101,9 +103,9 @@ class SearchFragment : Fragment(), SelectableAdapter.OnSelectModeListener {
) { item: MenuItem -> this@SearchFragment.onContextItemSelected(item) }
}
}
adapter?.setOnSelectModeListener(this)
recyclerView?.adapter = adapter
recyclerView?.addOnScrollListener(LiftOnScrollListener(layout.findViewById(R.id.appbar)))
adapter.setOnSelectModeListener(this)
recyclerView.adapter = adapter
recyclerView.addOnScrollListener(LiftOnScrollListener(layout.findViewById(R.id.appbar)))
val recyclerViewFeeds = layout.findViewById<RecyclerView>(R.id.recyclerViewFeeds)
val layoutManagerFeeds = LinearLayoutManager(activity)
@ -121,28 +123,28 @@ class SearchFragment : Fragment(), SelectableAdapter.OnSelectModeListener {
recyclerViewFeeds.adapter = adapterFeeds
emptyViewHandler = EmptyViewHandler(context)
emptyViewHandler?.attachToRecyclerView(recyclerView!!)
emptyViewHandler?.setIcon(R.drawable.ic_search)
emptyViewHandler?.setTitle(R.string.search_status_no_results)
emptyViewHandler?.setMessage(R.string.type_to_search)
emptyViewHandler.attachToRecyclerView(recyclerView)
emptyViewHandler.setIcon(R.drawable.ic_search)
emptyViewHandler.setTitle(R.string.search_status_no_results)
emptyViewHandler.setMessage(R.string.type_to_search)
EventBus.getDefault().register(this)
chip = layout.findViewById(R.id.feed_title_chip)
chip?.setOnCloseIconClickListener { v: View? ->
chip.setOnCloseIconClickListener { v: View? ->
requireArguments().putLong(ARG_FEED, 0)
searchWithProgressBar()
}
chip?.visibility = if ((requireArguments().getLong(ARG_FEED, 0) == 0L)) View.GONE else View.VISIBLE
chip?.text = requireArguments().getString(ARG_FEED_NAME, "")
chip.visibility = if ((requireArguments().getLong(ARG_FEED, 0) == 0L)) View.GONE else View.VISIBLE
chip.text = requireArguments().getString(ARG_FEED_NAME, "")
if (requireArguments().getString(ARG_QUERY, null) != null) {
search()
}
searchView!!.setOnQueryTextFocusChangeListener { view: View, hasFocus: Boolean ->
searchView.setOnQueryTextFocusChangeListener { view: View, hasFocus: Boolean ->
if (hasFocus && !isOtherViewInFoucus) {
showInputMethod(view.findFocus())
}
}
recyclerView?.addOnScrollListener(object : RecyclerView.OnScrollListener() {
recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
super.onScrollStateChanged(recyclerView, newState)
if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
@ -151,24 +153,24 @@ class SearchFragment : Fragment(), SelectableAdapter.OnSelectModeListener {
}
}
})
speedDialBinding!!.fabSD.overlayLayout = speedDialBinding!!.fabSDOverlay
speedDialBinding!!.fabSD.inflate(R.menu.episodes_apply_action_speeddial)
speedDialBinding!!.fabSD.setOnChangeListener(object : SpeedDialView.OnChangeListener {
speedDialBinding.fabSD.overlayLayout = speedDialBinding.fabSDOverlay
speedDialBinding.fabSD.inflate(R.menu.episodes_apply_action_speeddial)
speedDialBinding.fabSD.setOnChangeListener(object : SpeedDialView.OnChangeListener {
override fun onMainActionSelected(): Boolean {
return false
}
override fun onToggleChanged(open: Boolean) {
if (open && adapter!!.selectedCount == 0) {
if (open && adapter.selectedCount == 0) {
(activity as MainActivity).showSnackbarAbovePlayer(R.string.no_items_selected, Snackbar.LENGTH_SHORT)
speedDialBinding!!.fabSD.close()
speedDialBinding.fabSD.close()
}
}
})
speedDialBinding!!.fabSD.setOnActionSelectedListener { actionItem: SpeedDialActionItem ->
speedDialBinding.fabSD.setOnActionSelectedListener { actionItem: SpeedDialActionItem ->
EpisodeMultiSelectActionHandler(activity as MainActivity, actionItem.id)
.handleAction(adapter!!.selectedItems.filterIsInstance<FeedItem>())
adapter?.endSelectMode()
.handleAction(adapter.selectedItems.filterIsInstance<FeedItem>())
adapter.endSelectMode()
true
}
@ -187,24 +189,24 @@ class SearchFragment : Fragment(), SelectableAdapter.OnSelectModeListener {
val item: MenuItem = toolbar.menu.findItem(R.id.action_search)
item.expandActionView()
searchView = item.actionView as SearchView?
searchView!!.queryHint = getString(R.string.search_label)
searchView!!.setQuery(requireArguments().getString(ARG_QUERY), true)
searchView!!.requestFocus()
searchView!!.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
searchView = item.actionView as SearchView
searchView.queryHint = getString(R.string.search_label)
searchView.setQuery(requireArguments().getString(ARG_QUERY), true)
searchView.requestFocus()
searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {
@UnstableApi override fun onQueryTextSubmit(s: String): Boolean {
searchView!!.clearFocus()
searchView.clearFocus()
searchWithProgressBar()
return true
}
@UnstableApi override fun onQueryTextChange(s: String): Boolean {
automaticSearchDebouncer!!.removeCallbacksAndMessages(null)
automaticSearchDebouncer.removeCallbacksAndMessages(null)
if (s.isEmpty() || s.endsWith(" ") || (lastQueryChange != 0L
&& System.currentTimeMillis() > lastQueryChange + SEARCH_DEBOUNCE_INTERVAL)) {
search()
} else {
automaticSearchDebouncer!!.postDelayed({
automaticSearchDebouncer.postDelayed({
search()
lastQueryChange = 0 // Don't search instantly with first symbol after some pause
}, (SEARCH_DEBOUNCE_INTERVAL / 2).toLong())
@ -226,14 +228,14 @@ class SearchFragment : Fragment(), SelectableAdapter.OnSelectModeListener {
}
override fun onContextItemSelected(item: MenuItem): Boolean {
val selectedFeedItem: Feed? = adapterFeeds?.longPressedItem
val selectedFeedItem: Feed? = adapterFeeds.longPressedItem
if (selectedFeedItem != null
&& FeedMenuHandler.onMenuItemClicked(this, item.itemId, selectedFeedItem) {}) {
return true
}
val selectedItem: FeedItem? = adapter?.longPressedItem
val selectedItem: FeedItem? = adapter.longPressedItem
if (selectedItem != null) {
if (adapter!!.onContextItemSelected(item)) {
if (adapter.onContextItemSelected(item)) {
return true
}
if (FeedItemMenuHandler.onMenuItemClicked(this, item.itemId, selectedItem)) {
@ -256,21 +258,16 @@ class SearchFragment : Fragment(), SelectableAdapter.OnSelectModeListener {
@UnstableApi @Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(event: FeedItemEvent) {
Log.d(TAG, "onEventMainThread() called with: event = [$event]")
if (results == null) {
return
} else if (adapter == null) {
search()
return
}
var i = 0
val size: Int = event.items.size
while (i < size) {
val item: FeedItem = event.items[i]
val pos: Int = FeedItemUtil.indexOfItemWithId(results!!, item.id)
val pos: Int = FeedItemUtil.indexOfItemWithId(results, item.id)
if (pos >= 0) {
results!!.removeAt(pos)
results!!.add(pos, item)
adapter!!.notifyItemChangedCompat(pos)
results.removeAt(pos)
results.add(pos, item)
adapter.notifyItemChangedCompat(pos)
}
i++
}
@ -278,27 +275,22 @@ class SearchFragment : Fragment(), SelectableAdapter.OnSelectModeListener {
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
fun onEventMainThread(event: EpisodeDownloadEvent) {
if (results == null) {
return
}
for (downloadUrl in event.urls) {
val pos: Int = FeedItemUtil.indexOfItemWithDownloadUrl(results!!, downloadUrl)
val pos: Int = FeedItemUtil.indexOfItemWithDownloadUrl(results, downloadUrl)
if (pos >= 0) {
adapter?.notifyItemChangedCompat(pos)
adapter.notifyItemChangedCompat(pos)
}
}
}
@UnstableApi @Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(event: PlaybackPositionEvent) {
if (adapter != null) {
for (i in 0 until adapter!!.itemCount) {
val holder: EpisodeItemViewHolder =
recyclerView!!.findViewHolderForAdapterPosition(i) as EpisodeItemViewHolder
if (holder.isCurrentlyPlayingItem) {
holder.notifyPlaybackPositionUpdated(event)
break
}
for (i in 0 until adapter.itemCount) {
val holder: EpisodeItemViewHolder =
recyclerView.findViewHolderForAdapterPosition(i) as EpisodeItemViewHolder
if (holder.isCurrentlyPlayingItem) {
holder.notifyPlaybackPositionUpdated(event)
break
}
}
}
@ -309,40 +301,40 @@ class SearchFragment : Fragment(), SelectableAdapter.OnSelectModeListener {
}
@UnstableApi private fun searchWithProgressBar() {
progressBar?.visibility = View.VISIBLE
emptyViewHandler?.hide()
progressBar.visibility = View.VISIBLE
emptyViewHandler.hide()
search()
}
@UnstableApi private fun search() {
disposable?.dispose()
adapterFeeds?.setEndButton(R.string.search_online) { this.searchOnline() }
chip?.visibility = if ((requireArguments().getLong(ARG_FEED, 0) == 0L)) View.GONE else View.VISIBLE
adapterFeeds.setEndButton(R.string.search_online) { this.searchOnline() }
chip.visibility = if ((requireArguments().getLong(ARG_FEED, 0) == 0L)) View.GONE else View.VISIBLE
disposable = Observable.fromCallable { this.performSearch() }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ results: Pair<List<FeedItem>?, List<Feed?>?> ->
progressBar?.visibility = View.GONE
progressBar.visibility = View.GONE
if (results.first != null) {
this.results = results.first!!.toMutableList()
adapter?.updateItems(results.first!!)
adapter.updateItems(results.first!!)
}
if (requireArguments().getLong(ARG_FEED, 0) == 0L) {
if (results.second != null) adapterFeeds?.updateData(results.second!!.filterNotNull())
if (results.second != null) adapterFeeds.updateData(results.second!!.filterNotNull())
} else {
adapterFeeds?.updateData(emptyList())
adapterFeeds.updateData(emptyList())
}
if (searchView!!.query.toString().isEmpty()) {
emptyViewHandler?.setMessage(R.string.type_to_search)
if (searchView.query.toString().isEmpty()) {
emptyViewHandler.setMessage(R.string.type_to_search)
} else {
emptyViewHandler?.setMessage(getString(R.string.no_results_for_query) + searchView!!.query)
emptyViewHandler.setMessage(getString(R.string.no_results_for_query) + searchView.query)
}
}, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
}
@UnstableApi private fun performSearch(): Pair<List<FeedItem>?, List<Feed?>?> {
val query = searchView!!.query.toString()
val query = searchView.query.toString()
if (query.isEmpty()) {
return Pair<List<FeedItem>?, List<Feed?>?>(emptyList(), emptyList<Feed>())
}
@ -358,10 +350,10 @@ class SearchFragment : Fragment(), SelectableAdapter.OnSelectModeListener {
}
@UnstableApi private fun searchOnline() {
searchView!!.clearFocus()
searchView.clearFocus()
val inVal = requireActivity().getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
inVal.hideSoftInputFromWindow(searchView!!.windowToken, 0)
val query = searchView!!.query.toString()
inVal.hideSoftInputFromWindow(searchView.windowToken, 0)
val query = searchView.query.toString()
if (query.matches("http[s]?://.*".toRegex())) {
val intent = Intent(activity, OnlineFeedViewActivity::class.java)
intent.putExtra(OnlineFeedViewActivity.ARG_FEEDURL, query)
@ -374,26 +366,26 @@ class SearchFragment : Fragment(), SelectableAdapter.OnSelectModeListener {
override fun onStartSelectMode() {
searchViewFocusOff()
speedDialBinding!!.fabSD.removeActionItemById(R.id.remove_from_inbox_batch)
speedDialBinding!!.fabSD.removeActionItemById(R.id.remove_from_queue_batch)
speedDialBinding!!.fabSD.removeActionItemById(R.id.delete_batch)
speedDialBinding!!.fabSD.visibility = View.VISIBLE
speedDialBinding.fabSD.removeActionItemById(R.id.remove_from_inbox_batch)
speedDialBinding.fabSD.removeActionItemById(R.id.remove_from_queue_batch)
speedDialBinding.fabSD.removeActionItemById(R.id.delete_batch)
speedDialBinding.fabSD.visibility = View.VISIBLE
}
override fun onEndSelectMode() {
speedDialBinding!!.fabSD.close()
speedDialBinding!!.fabSD.visibility = View.GONE
speedDialBinding.fabSD.close()
speedDialBinding.fabSD.visibility = View.GONE
searchViewFocusOn()
}
private fun searchViewFocusOff() {
isOtherViewInFoucus = true
searchView!!.clearFocus()
searchView.clearFocus()
}
private fun searchViewFocusOn() {
isOtherViewInFoucus = false
searchView!!.requestFocus()
searchView.requestFocus()
}
companion object {

View File

@ -52,22 +52,21 @@ import java.util.*
* Fragment for displaying feed subscriptions
*/
class SubscriptionFragment : Fragment(), Toolbar.OnMenuItemClickListener, SelectableAdapter.OnSelectModeListener {
private var subscriptionRecycler: RecyclerView? = null
private var subscriptionAdapter: SubscriptionsRecyclerAdapter? = null
private var emptyView: EmptyViewHandler? = null
private var feedsFilteredMsg: LinearLayout? = null
private var toolbar: MaterialToolbar? = null
private var swipeRefreshLayout: SwipeRefreshLayout? = null
private var progressBar: ProgressBar? = null
private var displayedFolder: String? = null
private lateinit var subscriptionRecycler: RecyclerView
private lateinit var subscriptionAdapter: SubscriptionsRecyclerAdapter
private lateinit var emptyView: EmptyViewHandler
private lateinit var feedsFilteredMsg: LinearLayout
private lateinit var toolbar: MaterialToolbar
private lateinit var swipeRefreshLayout: SwipeRefreshLayout
private lateinit var progressBar: ProgressBar
private lateinit var displayedFolder: String
private lateinit var prefs: SharedPreferences
private lateinit var speedDialView: SpeedDialView
private var displayUpArrow = false
private var disposable: Disposable? = null
private var prefs: SharedPreferences? = null
private var speedDialView: SpeedDialView? = null
private var listItems: List<NavDrawerData.DrawerItem?>? = null
private var listItems: List<NavDrawerData.DrawerItem> = mutableListOf()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -81,36 +80,34 @@ class SubscriptionFragment : Fragment(), Toolbar.OnMenuItemClickListener, Select
): View {
val root: View = inflater.inflate(R.layout.fragment_subscriptions, container, false)
toolbar = root.findViewById(R.id.toolbar)
toolbar?.setOnMenuItemClickListener(this)
toolbar?.setOnLongClickListener { v: View? ->
subscriptionRecycler!!.scrollToPosition(5)
subscriptionRecycler!!.post { subscriptionRecycler!!.smoothScrollToPosition(0) }
toolbar.setOnMenuItemClickListener(this)
toolbar.setOnLongClickListener { v: View? ->
subscriptionRecycler.scrollToPosition(5)
subscriptionRecycler.post { subscriptionRecycler.smoothScrollToPosition(0) }
false
}
displayUpArrow = parentFragmentManager.backStackEntryCount != 0
if (savedInstanceState != null) {
displayUpArrow = savedInstanceState.getBoolean(KEY_UP_ARROW)
}
if (toolbar != null) (activity as MainActivity).setupToolbarToggle(toolbar!!, displayUpArrow)
toolbar?.inflateMenu(R.menu.subscriptions)
(activity as MainActivity).setupToolbarToggle(toolbar, displayUpArrow)
toolbar.inflateMenu(R.menu.subscriptions)
for (i in COLUMN_CHECKBOX_IDS.indices) {
// Do this in Java to localize numbers
toolbar?.menu?.findItem(COLUMN_CHECKBOX_IDS[i])
toolbar.menu?.findItem(COLUMN_CHECKBOX_IDS[i])
?.setTitle(String.format(Locale.getDefault(), "%d", i + MIN_NUM_COLUMNS))
}
refreshToolbarState()
if (arguments != null) {
displayedFolder = requireArguments().getString(ARGUMENT_FOLDER, null)
if (displayedFolder != null) {
toolbar?.title = displayedFolder
}
toolbar.title = displayedFolder
}
subscriptionRecycler = root.findViewById(R.id.subscriptions_grid)
subscriptionRecycler?.addItemDecoration(SubscriptionsRecyclerAdapter.GridDividerItemDecorator())
if (subscriptionRecycler != null) registerForContextMenu(subscriptionRecycler!!)
subscriptionRecycler?.addOnScrollListener(LiftOnScrollListener(root.findViewById(R.id.appbar)))
subscriptionRecycler.addItemDecoration(SubscriptionsRecyclerAdapter.GridDividerItemDecorator())
registerForContextMenu(subscriptionRecycler)
subscriptionRecycler.addOnScrollListener(LiftOnScrollListener(root.findViewById(R.id.appbar)))
subscriptionAdapter = object : SubscriptionsRecyclerAdapter(activity as MainActivity) {
override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) {
super.onCreateContextMenu(menu, v, menuInfo)
@ -120,13 +117,13 @@ class SubscriptionFragment : Fragment(), Toolbar.OnMenuItemClickListener, Select
}
}
}
setColumnNumber(prefs!!.getInt(PREF_NUM_COLUMNS, defaultNumOfColumns))
subscriptionAdapter?.setOnSelectModeListener(this)
subscriptionRecycler?.adapter = subscriptionAdapter
setColumnNumber(prefs.getInt(PREF_NUM_COLUMNS, defaultNumOfColumns))
subscriptionAdapter.setOnSelectModeListener(this)
subscriptionRecycler.adapter = subscriptionAdapter
setupEmptyView()
progressBar = root.findViewById(R.id.progressBar)
progressBar?.visibility = View.VISIBLE
progressBar.visibility = View.VISIBLE
val subscriptionAddButton: FloatingActionButton =
root.findViewById(R.id.subscriptions_add)
@ -137,21 +134,21 @@ class SubscriptionFragment : Fragment(), Toolbar.OnMenuItemClickListener, Select
}
feedsFilteredMsg = root.findViewById(R.id.feeds_filtered_message)
feedsFilteredMsg?.setOnClickListener { l: View? ->
feedsFilteredMsg.setOnClickListener { l: View? ->
SubscriptionsFilterDialog().show(
childFragmentManager, "filter")
}
swipeRefreshLayout = root.findViewById(R.id.swipeRefresh)
swipeRefreshLayout?.setDistanceToTriggerSync(resources.getInteger(R.integer.swipe_refresh_distance))
swipeRefreshLayout?.setOnRefreshListener {
swipeRefreshLayout.setDistanceToTriggerSync(resources.getInteger(R.integer.swipe_refresh_distance))
swipeRefreshLayout.setOnRefreshListener {
FeedUpdateManager.runOnceOrAsk(requireContext())
}
speedDialView = root.findViewById(R.id.fabSD)
speedDialView?.overlayLayout = root.findViewById(R.id.fabSDOverlay)
speedDialView?.inflate(R.menu.nav_feed_action_speeddial)
speedDialView?.setOnChangeListener(object : SpeedDialView.OnChangeListener {
speedDialView.overlayLayout = root.findViewById(R.id.fabSDOverlay)
speedDialView.inflate(R.menu.nav_feed_action_speeddial)
speedDialView.setOnChangeListener(object : SpeedDialView.OnChangeListener {
override fun onMainActionSelected(): Boolean {
return false
}
@ -159,9 +156,9 @@ class SubscriptionFragment : Fragment(), Toolbar.OnMenuItemClickListener, Select
override fun onToggleChanged(isOpen: Boolean) {
}
})
speedDialView?.setOnActionSelectedListener { actionItem: SpeedDialActionItem ->
speedDialView.setOnActionSelectedListener { actionItem: SpeedDialActionItem ->
FeedMultiSelectActionHandler(activity as MainActivity,
subscriptionAdapter!!.selectedItems.filterIsInstance<Feed>()).handleAction(actionItem.id)
subscriptionAdapter.selectedItems.filterIsInstance<Feed>()).handleAction(actionItem.id)
true
}
@ -174,13 +171,13 @@ class SubscriptionFragment : Fragment(), Toolbar.OnMenuItemClickListener, Select
}
private fun refreshToolbarState() {
val columns: Int = prefs!!.getInt(PREF_NUM_COLUMNS, defaultNumOfColumns)
toolbar?.menu?.findItem(COLUMN_CHECKBOX_IDS[columns - MIN_NUM_COLUMNS])?.setChecked(true)
val columns: Int = prefs.getInt(PREF_NUM_COLUMNS, defaultNumOfColumns)
toolbar.menu?.findItem(COLUMN_CHECKBOX_IDS[columns - MIN_NUM_COLUMNS])?.setChecked(true)
}
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
fun onEventMainThread(event: FeedUpdateRunningEvent) {
swipeRefreshLayout?.isRefreshing = event.isFeedUpdateRunning
swipeRefreshLayout.isRefreshing = event.isFeedUpdateRunning
}
@UnstableApi override fun onMenuItemClick(item: MenuItem): Boolean {
@ -229,18 +226,18 @@ class SubscriptionFragment : Fragment(), Toolbar.OnMenuItemClickListener, Select
private fun setColumnNumber(columns: Int) {
val gridLayoutManager = GridLayoutManager(context,
columns, RecyclerView.VERTICAL, false)
subscriptionAdapter?.setColumnCount(columns)
subscriptionRecycler!!.layoutManager = gridLayoutManager
prefs!!.edit().putInt(PREF_NUM_COLUMNS, columns).apply()
subscriptionAdapter.setColumnCount(columns)
subscriptionRecycler.layoutManager = gridLayoutManager
prefs.edit().putInt(PREF_NUM_COLUMNS, columns).apply()
refreshToolbarState()
}
private fun setupEmptyView() {
emptyView = EmptyViewHandler(context)
emptyView?.setIcon(R.drawable.ic_subscriptions)
emptyView?.setTitle(R.string.no_subscriptions_head_label)
emptyView?.setMessage(R.string.no_subscriptions_label)
if (subscriptionRecycler != null) emptyView?.attachToRecyclerView(subscriptionRecycler!!)
emptyView.setIcon(R.drawable.ic_subscriptions)
emptyView.setTitle(R.string.no_subscriptions_head_label)
emptyView.setMessage(R.string.no_subscriptions_label)
emptyView.attachToRecyclerView(subscriptionRecycler)
}
override fun onStart() {
@ -253,13 +250,12 @@ class SubscriptionFragment : Fragment(), Toolbar.OnMenuItemClickListener, Select
super.onStop()
EventBus.getDefault().unregister(this)
disposable?.dispose()
subscriptionAdapter?.endSelectMode()
subscriptionAdapter.endSelectMode()
}
private fun loadSubscriptions() {
disposable?.dispose()
emptyView?.hide()
emptyView.hide()
disposable = Observable.fromCallable {
val data: NavDrawerData = DBReader.getNavDrawerData(UserPreferences.subscriptionsFilter)
val items: List<NavDrawerData.DrawerItem> = data.items
@ -273,23 +269,23 @@ class SubscriptionFragment : Fragment(), Toolbar.OnMenuItemClickListener, Select
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ result: List<NavDrawerData.DrawerItem?> ->
if (listItems != null && listItems!!.size > result.size) {
{ result: List<NavDrawerData.DrawerItem> ->
if ( listItems.size > result.size) {
// We have fewer items. This can result in items being selected that are no longer visible.
subscriptionAdapter?.endSelectMode()
subscriptionAdapter.endSelectMode()
}
listItems = result
progressBar?.visibility = View.GONE
subscriptionAdapter?.setItems(result.filterNotNull())
emptyView?.updateVisibility()
progressBar.visibility = View.GONE
subscriptionAdapter.setItems(result)
emptyView.updateVisibility()
}, { error: Throwable? ->
Log.e(TAG, Log.getStackTraceString(error))
})
if (UserPreferences.subscriptionsFilter.isEnabled) {
feedsFilteredMsg?.visibility = View.VISIBLE
feedsFilteredMsg.visibility = View.VISIBLE
} else {
feedsFilteredMsg?.visibility = View.GONE
feedsFilteredMsg.visibility = View.GONE
}
}
@ -297,7 +293,7 @@ class SubscriptionFragment : Fragment(), Toolbar.OnMenuItemClickListener, Select
get() = resources.getInteger(R.integer.subscriptions_default_num_of_columns)
override fun onContextItemSelected(item: MenuItem): Boolean {
val drawerItem: NavDrawerData.DrawerItem = subscriptionAdapter!!.getSelectedItem() ?: return false
val drawerItem: NavDrawerData.DrawerItem = subscriptionAdapter.getSelectedItem() ?: return false
val itemId = item.itemId
if (drawerItem.type == NavDrawerData.DrawerItem.Type.TAG && itemId == R.id.rename_folder_item) {
RenameItemDialog(activity as Activity, drawerItem).show()
@ -306,8 +302,8 @@ class SubscriptionFragment : Fragment(), Toolbar.OnMenuItemClickListener, Select
val feed: Feed = (drawerItem as NavDrawerData.FeedDrawerItem).feed
if (itemId == R.id.multi_select) {
speedDialView?.visibility = View.VISIBLE
return subscriptionAdapter!!.onContextItemSelected(item)
speedDialView.visibility = View.VISIBLE
return subscriptionAdapter.onContextItemSelected(item)
}
return FeedMenuHandler.onMenuItemClicked(this, item.itemId, feed) { this.loadSubscriptions() }
}
@ -323,20 +319,19 @@ class SubscriptionFragment : Fragment(), Toolbar.OnMenuItemClickListener, Select
}
override fun onEndSelectMode() {
speedDialView?.close()
speedDialView?.visibility = View.GONE
if (listItems != null) subscriptionAdapter?.setItems(listItems!!.filterNotNull())
speedDialView.close()
speedDialView.visibility = View.GONE
subscriptionAdapter.setItems(listItems)
}
override fun onStartSelectMode() {
val feedsOnly: MutableList<NavDrawerData.DrawerItem> = ArrayList<NavDrawerData.DrawerItem>()
if (listItems != null) for (item in listItems!!) {
if (item == null) continue
for (item in listItems) {
if (item.type == NavDrawerData.DrawerItem.Type.FEED) {
feedsOnly.add(item)
}
}
subscriptionAdapter?.setItems(feedsOnly)
subscriptionAdapter.setItems(feedsOnly)
}
companion object {

View File

@ -10,7 +10,9 @@ import ac.mdiq.podvinci.core.util.LongList
import ac.mdiq.podvinci.model.feed.FeedItem
import ac.mdiq.podvinci.net.download.serviceinterface.DownloadServiceInterface
import ac.mdiq.podvinci.view.LocalDeleteModal
import androidx.media3.common.util.UnstableApi
@UnstableApi
class EpisodeMultiSelectActionHandler(private val activity: MainActivity, private val actionId: Int) {
private var totalNumItems = 0
private var snackbar: Snackbar? = null

View File

@ -17,8 +17,10 @@ import ac.mdiq.podvinci.fragment.preferences.dialog.PreferenceListDialog
import ac.mdiq.podvinci.fragment.preferences.dialog.PreferenceSwitchDialog
import ac.mdiq.podvinci.model.feed.Feed
import ac.mdiq.podvinci.model.feed.FeedPreferences
import androidx.media3.common.util.UnstableApi
import java.util.*
@UnstableApi
class FeedMultiSelectActionHandler(private val activity: MainActivity, private val selectedItems: List<Feed>) {
fun handleAction(id: Int) {
@ -56,7 +58,7 @@ class FeedMultiSelectActionHandler(private val activity: MainActivity, private v
activity.getString(R.string.episode_notification_summary))
preferenceSwitchDialog.setOnPreferenceChangedListener(object: PreferenceSwitchDialog.OnPreferenceChangedListener {
override fun preferenceChanged(enabled: Boolean) {
@UnstableApi override fun preferenceChanged(enabled: Boolean) {
saveFeedPreferences { feedPreferences: FeedPreferences ->
feedPreferences.showEpisodeNotification = enabled
}
@ -69,7 +71,7 @@ class FeedMultiSelectActionHandler(private val activity: MainActivity, private v
val preferenceSwitchDialog = PreferenceSwitchDialog(activity,
activity.getString(R.string.auto_download_settings_label),
activity.getString(R.string.auto_download_label))
preferenceSwitchDialog.setOnPreferenceChangedListener(object: PreferenceSwitchDialog.OnPreferenceChangedListener {
preferenceSwitchDialog.setOnPreferenceChangedListener(@UnstableApi object: PreferenceSwitchDialog.OnPreferenceChangedListener {
override fun preferenceChanged(enabled: Boolean) {
saveFeedPreferences { feedPreferences: FeedPreferences -> feedPreferences.autoDownload = enabled }
}
@ -77,7 +79,7 @@ class FeedMultiSelectActionHandler(private val activity: MainActivity, private v
preferenceSwitchDialog.openDialog()
}
private fun playbackSpeedPrefHandler() {
@UnstableApi private fun playbackSpeedPrefHandler() {
val viewBinding =
PlaybackSpeedFeedSettingDialogBinding.inflate(activity.layoutInflater)
viewBinding.seekBar.setProgressChangedListener { speed: Float? ->
@ -110,7 +112,7 @@ class FeedMultiSelectActionHandler(private val activity: MainActivity, private v
val items: Array<String> = activity.resources.getStringArray(R.array.spnAutoDeleteItems)
preferenceListDialog.openDialog(items)
preferenceListDialog.setOnPreferenceChangedListener(object: PreferenceListDialog.OnPreferenceChangedListener {
override fun preferenceChanged(which: Int) {
@UnstableApi override fun preferenceChanged(which: Int) {
val autoDeleteAction: FeedPreferences.AutoDeleteAction = FeedPreferences.AutoDeleteAction.fromCode(which)
saveFeedPreferences { feedPreferences: FeedPreferences ->
feedPreferences.currentAutoDelete = autoDeleteAction
@ -124,7 +126,7 @@ class FeedMultiSelectActionHandler(private val activity: MainActivity, private v
activity.getString(R.string.kept_updated),
activity.getString(R.string.keep_updated_summary))
preferenceSwitchDialog.setOnPreferenceChangedListener(object: PreferenceSwitchDialog.OnPreferenceChangedListener {
override fun preferenceChanged(keepUpdated: Boolean) {
@UnstableApi override fun preferenceChanged(keepUpdated: Boolean) {
saveFeedPreferences { feedPreferences: FeedPreferences ->
feedPreferences.keepUpdated = keepUpdated
}
@ -133,12 +135,12 @@ class FeedMultiSelectActionHandler(private val activity: MainActivity, private v
preferenceSwitchDialog.openDialog()
}
private fun showMessage(@PluralsRes msgId: Int, numItems: Int) {
@UnstableApi private fun showMessage(@PluralsRes msgId: Int, numItems: Int) {
activity.showSnackbarAbovePlayer(activity.resources
.getQuantityString(msgId, numItems, numItems), Snackbar.LENGTH_LONG)
}
private fun saveFeedPreferences(preferencesConsumer: Consumer<FeedPreferences>) {
@UnstableApi private fun saveFeedPreferences(preferencesConsumer: Consumer<FeedPreferences>) {
for (feed in selectedItems) {
if (feed.preferences == null) continue
preferencesConsumer.accept(feed.preferences)

View File

@ -30,9 +30,8 @@ class DeleteSwipeAction : SwipeAction {
if (!item.isDownloaded && !item.feed!!.isLocalFeed) {
return
}
showLocalFeedDeleteWarningIfNecessary(
fragment.requireContext(), listOf(item)
) { DBWriter.deleteFeedMediaOfItem(fragment.requireContext(), item.media!!.id) }
showLocalFeedDeleteWarningIfNecessary(fragment.requireContext(), listOf(item)) {
DBWriter.deleteFeedMediaOfItem(fragment.requireContext(), item.media!!.id) }
}
override fun willRemove(filter: FeedItemFilter, item: FeedItem): Boolean {

View File

@ -21,11 +21,13 @@ import ac.mdiq.podvinci.model.feed.FeedItem
import ac.mdiq.podvinci.model.feed.FeedMedia
import ac.mdiq.podvinci.net.sync.model.EpisodeAction
import ac.mdiq.podvinci.view.LocalDeleteModal
import androidx.annotation.OptIn
import kotlin.math.ceil
/**
* Handles interactions with the FeedItemMenu.
*/
@OptIn(UnstableApi::class)
object FeedItemMenuHandler {
private const val TAG = "FeedItemMenuHandler"
@ -115,7 +117,7 @@ object FeedItemMenuHandler {
return false
}
val rc = onPrepareMenu(menu, selectedItem)
if (rc && excludeIds != null) {
if (rc && excludeIds.isNotEmpty()) {
for (id in excludeIds) {
setItemVisibility(menu, id, false)
}

View File

@ -29,6 +29,7 @@ import ac.mdiq.podvinci.storage.preferences.UserPreferences
import ac.mdiq.podvinci.ui.echo.EchoActivity
import ac.mdiq.podvinci.ui.home.sections.*
import ac.mdiq.podvinci.view.LiftOnScrollListener
import androidx.media3.common.util.UnstableApi
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
@ -43,33 +44,32 @@ import java.util.*
*/
class HomeFragment : Fragment(), Toolbar.OnMenuItemClickListener {
private var displayUpArrow = false
private var viewBinding: HomeFragmentBinding? = null
private var disposable: Disposable? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
private lateinit var viewBinding: HomeFragmentBinding
@UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
super.onCreateView(inflater, container, savedInstanceState)
viewBinding = HomeFragmentBinding.inflate(inflater)
viewBinding!!.toolbar.inflateMenu(R.menu.home)
viewBinding!!.toolbar.setOnMenuItemClickListener(this)
if (savedInstanceState != null) {
displayUpArrow = savedInstanceState.getBoolean(KEY_UP_ARROW)
}
viewBinding!!.homeScrollView.setOnScrollChangeListener(LiftOnScrollListener(
viewBinding!!.appbar))
(requireActivity() as MainActivity).setupToolbarToggle(viewBinding!!.toolbar, displayUpArrow)
viewBinding.toolbar.inflateMenu(R.menu.home)
viewBinding.toolbar.setOnMenuItemClickListener(this)
displayUpArrow = savedInstanceState?.getBoolean(KEY_UP_ARROW)?:false
viewBinding.homeScrollView.setOnScrollChangeListener(LiftOnScrollListener(viewBinding.appbar))
(requireActivity() as MainActivity).setupToolbarToggle(viewBinding.toolbar, displayUpArrow)
populateSectionList()
updateWelcomeScreenVisibility()
viewBinding!!.swipeRefresh.setDistanceToTriggerSync(resources.getInteger(R.integer.swipe_refresh_distance))
viewBinding!!.swipeRefresh.setOnRefreshListener {
viewBinding.swipeRefresh.setDistanceToTriggerSync(resources.getInteger(R.integer.swipe_refresh_distance))
viewBinding.swipeRefresh.setOnRefreshListener {
FeedUpdateManager.runOnceOrAsk(requireContext())
}
return viewBinding!!.root
return viewBinding.root
}
private fun populateSectionList() {
viewBinding!!.homeContainer.removeAllViews()
viewBinding.homeContainer.removeAllViews()
val prefs: SharedPreferences = requireContext().getSharedPreferences(PREF_NAME, Context.MODE_PRIVATE)
if (Build.VERSION.SDK_INT >= 33 && ContextCompat.checkSelfPermission(requireContext(),
@ -78,8 +78,10 @@ class HomeFragment : Fragment(), Toolbar.OnMenuItemClickListener {
addSection(AllowNotificationsSection())
}
}
if (Calendar.getInstance()[Calendar.YEAR] == EchoActivity.RELEASE_YEAR && Calendar.getInstance()[Calendar.MONTH] == Calendar.DECEMBER && Calendar.getInstance()[Calendar.DAY_OF_MONTH] >= 10 && prefs.getInt(
PREF_HIDE_ECHO, 0) != EchoActivity.RELEASE_YEAR) {
if (Calendar.getInstance()[Calendar.YEAR] == EchoActivity.RELEASE_YEAR &&
Calendar.getInstance()[Calendar.MONTH] == Calendar.DECEMBER &&
Calendar.getInstance()[Calendar.DAY_OF_MONTH] >= 10 &&
prefs.getInt(PREF_HIDE_ECHO, 0) != EchoActivity.RELEASE_YEAR) {
addSection(EchoSection())
}
@ -96,8 +98,8 @@ class HomeFragment : Fragment(), Toolbar.OnMenuItemClickListener {
private fun addSection(section: Fragment?) {
val containerView = FragmentContainerView(requireContext())
containerView.id = View.generateViewId()
viewBinding!!.homeContainer.addView(containerView)
childFragmentManager.beginTransaction().add(containerView.id, section!!).commit()
viewBinding.homeContainer.addView(containerView)
if (section != null) childFragmentManager.beginTransaction().add(containerView.id, section).commit()
}
private fun getSection(tag: String): Fragment? {
@ -113,10 +115,10 @@ class HomeFragment : Fragment(), Toolbar.OnMenuItemClickListener {
@Subscribe(sticky = true, threadMode = ThreadMode.MAIN)
fun onEventMainThread(event: FeedUpdateRunningEvent) {
viewBinding!!.swipeRefresh.isRefreshing = event.isFeedUpdateRunning
viewBinding.swipeRefresh.isRefreshing = event.isFeedUpdateRunning
}
override fun onMenuItemClick(item: MenuItem): Boolean {
@UnstableApi override fun onMenuItemClick(item: MenuItem): Boolean {
when (item.itemId) {
R.id.homesettings_items -> {
HomeSectionsSettingsDialog.open(requireContext()
@ -128,7 +130,7 @@ class HomeFragment : Fragment(), Toolbar.OnMenuItemClickListener {
return true
}
R.id.action_search -> {
(activity as MainActivity?)?.loadChildFragment(SearchFragment.newInstance())
(activity as MainActivity).loadChildFragment(SearchFragment.newInstance())
return true
}
else -> return super.onOptionsItemSelected(item)
@ -156,16 +158,14 @@ class HomeFragment : Fragment(), Toolbar.OnMenuItemClickListener {
}
private fun updateWelcomeScreenVisibility() {
if (disposable != null) {
disposable?.dispose()
}
disposable =
Observable.fromCallable { DBReader.getNavDrawerData(UserPreferences.subscriptionsFilter).items.size }
disposable?.dispose()
disposable = Observable.fromCallable { DBReader.getNavDrawerData(UserPreferences.subscriptionsFilter).items.size }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ numSubscriptions: Int ->
viewBinding!!.welcomeContainer.visibility = if (numSubscriptions == 0) View.VISIBLE else View.GONE
viewBinding!!.homeContainer.visibility = if (numSubscriptions == 0) View.GONE else View.VISIBLE
viewBinding.welcomeContainer.visibility = if (numSubscriptions == 0) View.VISIBLE else View.GONE
viewBinding.homeContainer.visibility = if (numSubscriptions == 0) View.GONE else View.VISIBLE
}, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
}

View File

@ -24,28 +24,25 @@ import java.util.*
* Section on the HomeFragment
*/
abstract class HomeSection : Fragment(), OnCreateContextMenuListener {
@JvmField
protected var viewBinding: HomeSectionBinding? = null
protected lateinit var viewBinding: HomeSectionBinding
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?, savedInstanceState: Bundle?
): View {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
viewBinding = HomeSectionBinding.inflate(inflater)
viewBinding!!.titleLabel.text = sectionTitle
viewBinding.titleLabel.text = sectionTitle
if (TextUtils.getLayoutDirectionFromLocale(Locale.getDefault()) == View.LAYOUT_DIRECTION_LTR) {
viewBinding!!.moreButton.text = "$moreLinkTitle\u00A0»"
viewBinding.moreButton.text = "$moreLinkTitle\u00A0»"
} else {
viewBinding!!.moreButton.text = "«\u00A0$moreLinkTitle"
viewBinding.moreButton.text = "«\u00A0$moreLinkTitle"
}
viewBinding!!.moreButton.setOnClickListener { view: View? -> handleMoreClick() }
viewBinding.moreButton.setOnClickListener { view: View? -> handleMoreClick() }
if (TextUtils.isEmpty(moreLinkTitle)) {
viewBinding!!.moreButton.visibility = View.INVISIBLE
viewBinding.moreButton.visibility = View.INVISIBLE
}
// Dummies are necessary to ensure height, but do not animate them
viewBinding!!.recyclerView.itemAnimator = null
viewBinding!!.recyclerView.postDelayed(
{ viewBinding!!.recyclerView.itemAnimator = DefaultItemAnimator() }, 500)
return viewBinding!!.root
viewBinding.recyclerView.itemAnimator = null
viewBinding.recyclerView.postDelayed(
{ viewBinding.recyclerView.itemAnimator = DefaultItemAnimator() }, 500)
return viewBinding.root
}
override fun onContextItemSelected(item: MenuItem): Boolean {
@ -54,19 +51,19 @@ abstract class HomeSection : Fragment(), OnCreateContextMenuListener {
// Apparently, none of the visibility check method works reliably on its own, so we just use all.
return false
}
if (viewBinding!!.recyclerView.adapter is HorizontalFeedListAdapter) {
val adapter = viewBinding!!.recyclerView.adapter as HorizontalFeedListAdapter?
val selectedFeed = adapter!!.longPressedItem
return (selectedFeed != null
&& FeedMenuHandler.onMenuItemClicked(this, item.itemId, selectedFeed) {})
if (viewBinding.recyclerView.adapter is HorizontalFeedListAdapter) {
val adapter = viewBinding.recyclerView.adapter as? HorizontalFeedListAdapter
val selectedFeed = adapter?.longPressedItem
return (selectedFeed != null && FeedMenuHandler.onMenuItemClicked(this, item.itemId, selectedFeed) {})
}
var longPressedItem: FeedItem? = null
if (viewBinding!!.recyclerView.adapter is EpisodeItemListAdapter) {
val adapter = viewBinding!!.recyclerView.adapter as EpisodeItemListAdapter?
if (adapter != null) longPressedItem = adapter.longPressedItem
} else if (viewBinding!!.recyclerView.adapter is HorizontalItemListAdapter) {
val adapter = viewBinding!!.recyclerView.adapter as HorizontalItemListAdapter?
if (adapter != null) longPressedItem = adapter.longPressedItem
if (viewBinding.recyclerView.adapter is EpisodeItemListAdapter) {
val adapter = viewBinding.recyclerView.adapter as? EpisodeItemListAdapter
longPressedItem = adapter?.longPressedItem
} else if (viewBinding.recyclerView.adapter is HorizontalItemListAdapter) {
val adapter = viewBinding.recyclerView.adapter as HorizontalItemListAdapter?
longPressedItem = adapter?.longPressedItem
} else {
return false
}
@ -81,13 +78,13 @@ abstract class HomeSection : Fragment(), OnCreateContextMenuListener {
override fun onStart() {
super.onStart()
EventBus.getDefault().register(this)
registerForContextMenu(viewBinding!!.recyclerView)
registerForContextMenu(viewBinding.recyclerView)
}
override fun onStop() {
super.onStop()
EventBus.getDefault().unregister(this)
unregisterForContextMenu(viewBinding!!.recyclerView)
unregisterForContextMenu(viewBinding.recyclerView)
}
protected abstract val sectionTitle: String?

View File

@ -22,8 +22,7 @@ object HomeSectionsSettingsDialog {
val builder = MaterialAlertDialogBuilder(context)
builder.setTitle(R.string.configure_home)
builder.setMultiChoiceItems(sectionLabels,
checked) { dialog: DialogInterface?, which: Int, isChecked: Boolean ->
builder.setMultiChoiceItems(sectionLabels, checked) { dialog: DialogInterface?, which: Int, isChecked: Boolean ->
if (isChecked) {
hiddenSections.remove(sectionTags[which])
} else {

View File

@ -19,37 +19,39 @@ import com.google.android.material.dialog.MaterialAlertDialogBuilder
import ac.mdiq.podvinci.R
import ac.mdiq.podvinci.databinding.HomeSectionNotificationBinding
import ac.mdiq.podvinci.ui.home.HomeFragment
import androidx.media3.common.util.UnstableApi
@UnstableApi
class AllowNotificationsSection : Fragment() {
var viewBinding: HomeSectionNotificationBinding? = null
lateinit var viewBinding: HomeSectionNotificationBinding
private val requestPermissionLauncher =
registerForActivityResult(ActivityResultContracts.RequestPermission()) { isGranted: Boolean ->
if (isGranted) {
(activity as MainActivity).loadFragment(HomeFragment.TAG, null)
} else {
viewBinding!!.openSettingsButton.visibility = View.VISIBLE
viewBinding!!.allowButton.visibility = View.GONE
viewBinding.openSettingsButton.visibility = View.VISIBLE
viewBinding.allowButton.visibility = View.GONE
Toast.makeText(context, R.string.notification_permission_denied, Toast.LENGTH_LONG).show()
}
}
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?, savedInstanceState: Bundle?
@UnstableApi override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?, savedInstanceState: Bundle?
): View {
viewBinding = HomeSectionNotificationBinding.inflate(inflater)
viewBinding!!.allowButton.setOnClickListener { v: View? ->
viewBinding.allowButton.setOnClickListener { v: View? ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
requestPermissionLauncher.launch(Manifest.permission.POST_NOTIFICATIONS)
}
}
viewBinding!!.openSettingsButton.setOnClickListener { view: View? ->
viewBinding.openSettingsButton.setOnClickListener { view: View? ->
val intent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)
val uri = Uri.fromParts("package", requireContext().packageName, null)
intent.setData(uri)
startActivity(intent)
}
viewBinding!!.denyButton.setOnClickListener { v: View? ->
viewBinding.denyButton.setOnClickListener { v: View? ->
val builder = MaterialAlertDialogBuilder(requireContext())
builder.setMessage(R.string.notification_permission_deny_warning)
builder.setPositiveButton(R.string.deny_label
@ -61,6 +63,6 @@ class AllowNotificationsSection : Fragment() {
builder.setNegativeButton(R.string.cancel_label, null)
builder.show()
}
return viewBinding!!.root
return viewBinding.root
}
}

View File

@ -32,17 +32,15 @@ import org.greenrobot.eventbus.ThreadMode
class DownloadsSection : HomeSection() {
private var adapter: EpisodeItemListAdapter? = null
private var items: List<FeedItem>? = null
private var disposable: Disposable? = null
@UnstableApi override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?, savedInstanceState: Bundle?
@UnstableApi override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?
): View {
val view: View = super.onCreateView(inflater, container, savedInstanceState)
viewBinding?.recyclerView?.setPadding(0, 0, 0, 0)
viewBinding?.recyclerView?.setOverScrollMode(RecyclerView.OVER_SCROLL_NEVER)
viewBinding?.recyclerView?.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
viewBinding?.recyclerView?.setRecycledViewPool((requireActivity() as MainActivity).recycledViewPool)
viewBinding.recyclerView.setPadding(0, 0, 0, 0)
viewBinding.recyclerView.setOverScrollMode(RecyclerView.OVER_SCROLL_NEVER)
viewBinding.recyclerView.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
viewBinding.recyclerView.setRecycledViewPool((requireActivity() as MainActivity).recycledViewPool)
adapter = object : EpisodeItemListAdapter(requireActivity() as MainActivity) {
override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) {
super.onCreateContextMenu(menu, v, menuInfo)
@ -53,10 +51,10 @@ class DownloadsSection : HomeSection() {
}
}
adapter?.setDummyViews(NUM_EPISODES)
if (adapter != null) viewBinding?.recyclerView?.adapter = adapter
viewBinding.recyclerView.adapter = adapter
val swipeActions = SwipeActions(this, CompletedDownloadsFragment.TAG)
if (viewBinding != null) swipeActions.attachTo(viewBinding!!.recyclerView)
swipeActions.attachTo(viewBinding.recyclerView)
swipeActions.setFilter(FeedItemFilter(FeedItemFilter.DOWNLOADED))
return view
}
@ -66,7 +64,7 @@ class DownloadsSection : HomeSection() {
loadItems()
}
override fun handleMoreClick() {
@UnstableApi override fun handleMoreClick() {
(requireActivity() as MainActivity).loadChildFragment(CompletedDownloadsFragment())
}
@ -77,11 +75,11 @@ class DownloadsSection : HomeSection() {
@UnstableApi @Subscribe(threadMode = ThreadMode.MAIN)
fun onEventMainThread(event: PlaybackPositionEvent) {
if (viewBinding == null || adapter == null) {
if (adapter == null) {
return
}
for (i in 0 until adapter!!.itemCount) {
val holder: EpisodeItemViewHolder? = viewBinding!!.recyclerView.findViewHolderForAdapterPosition(i) as? EpisodeItemViewHolder
val holder: EpisodeItemViewHolder? = viewBinding.recyclerView.findViewHolderForAdapterPosition(i) as? EpisodeItemViewHolder
if (holder != null && holder.isCurrentlyPlayingItem) {
holder.notifyPlaybackPositionUpdated(event)
break
@ -119,9 +117,8 @@ class DownloadsSection : HomeSection() {
if (downloads.size > NUM_EPISODES) {
downloads = downloads.subList(0, NUM_EPISODES)
}
items = downloads
adapter?.setDummyViews(0)
if (items != null) adapter?.updateItems(items!!)
adapter?.updateItems(downloads)
}, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
}

View File

@ -13,28 +13,30 @@ import ac.mdiq.podvinci.core.storage.DBReader
import ac.mdiq.podvinci.databinding.HomeSectionEchoBinding
import ac.mdiq.podvinci.ui.echo.EchoActivity
import ac.mdiq.podvinci.ui.home.HomeFragment
import androidx.media3.common.util.UnstableApi
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import java.util.*
@UnstableApi
class EchoSection : Fragment() {
private var viewBinding: HomeSectionEchoBinding? = null
private lateinit var viewBinding: HomeSectionEchoBinding
private var disposable: Disposable? = null
override fun onCreateView(inflater: LayoutInflater,
container: ViewGroup?, savedInstanceState: Bundle?
): View {
viewBinding = HomeSectionEchoBinding.inflate(inflater)
viewBinding!!.titleLabel.text = getString(R.string.podvinci_echo_year, EchoActivity.RELEASE_YEAR)
viewBinding!!.echoButton.setOnClickListener { v: View? ->
viewBinding.titleLabel.text = getString(R.string.podvinci_echo_year, EchoActivity.RELEASE_YEAR)
viewBinding.echoButton.setOnClickListener { v: View? ->
startActivity(Intent(context,
EchoActivity::class.java))
}
viewBinding!!.closeButton.setOnClickListener { v: View? -> hideThisYear() }
viewBinding.closeButton.setOnClickListener { v: View? -> hideThisYear() }
updateVisibility()
return viewBinding!!.root
return viewBinding.root
}
private fun jan1(): Long {
@ -64,10 +66,9 @@ class EchoSection : Fragment() {
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ totalTime: Long ->
val shouldShow = (totalTime >= 3600 * 10)
viewBinding!!.root.visibility = if (shouldShow) View.VISIBLE else View.GONE
if (!shouldShow) {
hideThisYear()
}
viewBinding.root.visibility = if (shouldShow) View.VISIBLE else View.GONE
if (!shouldShow) hideThisYear()
}, { obj: Throwable -> obj.printStackTrace() })
}

View File

@ -37,10 +37,10 @@ class EpisodesSurpriseSection : HomeSection() {
container: ViewGroup?, savedInstanceState: Bundle?
): View {
val view: View = super.onCreateView(inflater, container, savedInstanceState)
viewBinding?.shuffleButton?.setVisibility(View.VISIBLE)
viewBinding?.shuffleButton?.setOnClickListener { v: View? ->
viewBinding.shuffleButton.setVisibility(View.VISIBLE)
viewBinding.shuffleButton.setOnClickListener { v: View? ->
seed = Random().nextInt()
viewBinding?.recyclerView?.scrollToPosition(0)
viewBinding.recyclerView.scrollToPosition(0)
loadItems()
}
listAdapter = object : HorizontalItemListAdapter(activity as MainActivity) {
@ -53,13 +53,12 @@ class EpisodesSurpriseSection : HomeSection() {
}
}
listAdapter?.setDummyViews(NUM_EPISODES)
viewBinding?.recyclerView?.layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
viewBinding?.recyclerView?.adapter = listAdapter
viewBinding.recyclerView.layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
viewBinding.recyclerView.adapter = listAdapter
val paddingHorizontal: Int = (12 * resources.displayMetrics.density).toInt()
viewBinding?.recyclerView?.setPadding(paddingHorizontal, 0, paddingHorizontal, 0)
if (seed == 0) {
seed = Random().nextInt()
}
viewBinding.recyclerView.setPadding(paddingHorizontal, 0, paddingHorizontal, 0)
if (seed == 0) seed = Random().nextInt()
return view
}
@ -68,7 +67,7 @@ class EpisodesSurpriseSection : HomeSection() {
loadItems()
}
override fun handleMoreClick() {
@UnstableApi override fun handleMoreClick() {
(requireActivity() as MainActivity).loadChildFragment(AllEpisodesFragment())
}
@ -105,9 +104,8 @@ class EpisodesSurpriseSection : HomeSection() {
fun onEventMainThread(event: EpisodeDownloadEvent) {
for (downloadUrl in event.urls) {
val pos: Int = FeedItemUtil.indexOfItemWithDownloadUrl(episodes, downloadUrl)
if (pos >= 0) {
listAdapter?.notifyItemChangedCompat(pos)
}
if (pos >= 0) listAdapter?.notifyItemChangedCompat(pos)
}
}
@ -117,7 +115,7 @@ class EpisodesSurpriseSection : HomeSection() {
return
}
for (i in 0 until listAdapter!!.itemCount) {
val holder: HorizontalItemViewHolder? = viewBinding?.recyclerView?.findViewHolderForAdapterPosition(i) as? HorizontalItemViewHolder
val holder: HorizontalItemViewHolder? = viewBinding.recyclerView.findViewHolderForAdapterPosition(i) as? HorizontalItemViewHolder
if (holder != null && holder.isCurrentlyPlayingItem) {
holder.notifyPlaybackPositionUpdated(event)
break
@ -128,10 +126,7 @@ class EpisodesSurpriseSection : HomeSection() {
private fun loadItems() {
disposable?.dispose()
disposable = Observable.fromCallable {
DBReader.getRandomEpisodes(
NUM_EPISODES, seed)
}
disposable = Observable.fromCallable { DBReader.getRandomEpisodes(NUM_EPISODES, seed) }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ episodes: List<FeedItem> ->

View File

@ -40,10 +40,10 @@ class InboxSection : HomeSection() {
container: ViewGroup?, savedInstanceState: Bundle?
): View {
val view: View = super.onCreateView(inflater, container, savedInstanceState)
viewBinding?.recyclerView?.setPadding(0, 0, 0, 0)
viewBinding?.recyclerView?.setOverScrollMode(RecyclerView.OVER_SCROLL_NEVER)
viewBinding?.recyclerView?.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
viewBinding?.recyclerView?.setRecycledViewPool((requireActivity() as MainActivity).recycledViewPool)
viewBinding.recyclerView.setPadding(0, 0, 0, 0)
viewBinding.recyclerView.setOverScrollMode(RecyclerView.OVER_SCROLL_NEVER)
viewBinding.recyclerView.layoutManager = LinearLayoutManager(context, RecyclerView.VERTICAL, false)
viewBinding.recyclerView.setRecycledViewPool((requireActivity() as MainActivity).recycledViewPool)
adapter = object : EpisodeItemListAdapter(requireActivity() as MainActivity) {
override fun onCreateContextMenu(menu: ContextMenu, v: View, menuInfo: ContextMenu.ContextMenuInfo?) {
super.onCreateContextMenu(menu, v, menuInfo)
@ -52,10 +52,10 @@ class InboxSection : HomeSection() {
}
}
adapter?.setDummyViews(NUM_EPISODES)
if (adapter != null) viewBinding?.recyclerView?.adapter = adapter
viewBinding.recyclerView.adapter = adapter
val swipeActions = SwipeActions(this, InboxFragment.TAG)
if (viewBinding != null) swipeActions.attachTo(viewBinding!!.recyclerView)
swipeActions.attachTo(viewBinding.recyclerView)
swipeActions.setFilter(FeedItemFilter(FeedItemFilter.NEW))
return view
}
@ -65,7 +65,7 @@ class InboxSection : HomeSection() {
loadItems()
}
override fun handleMoreClick() {
@UnstableApi override fun handleMoreClick() {
(requireActivity() as MainActivity).loadChildFragment(InboxFragment())
}
@ -114,11 +114,11 @@ class InboxSection : HomeSection() {
items = data.first
adapter?.setDummyViews(0)
adapter?.updateItems(items)
viewBinding?.numNewItemsLabel?.visibility = View.VISIBLE
viewBinding.numNewItemsLabel.visibility = View.VISIBLE
if (data.second >= 100) {
viewBinding?.numNewItemsLabel?.text = String.format(Locale.getDefault(), "%d+", 99)
viewBinding.numNewItemsLabel.text = String.format(Locale.getDefault(), "%d+", 99)
} else {
viewBinding?.numNewItemsLabel?.text = String.format(Locale.getDefault(), "%d", data.second)
viewBinding.numNewItemsLabel.text = String.format(Locale.getDefault(), "%d", data.second)
}
}, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) })
}

View File

@ -1,13 +1,7 @@
package ac.mdiq.podvinci.ui.home.sections
import ac.mdiq.podvinci.activity.MainActivity
import android.os.Bundle
import android.util.Log
import android.view.*
import androidx.media3.common.util.UnstableApi
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import ac.mdiq.podvinci.R
import ac.mdiq.podvinci.activity.MainActivity
import ac.mdiq.podvinci.adapter.HorizontalItemListAdapter
import ac.mdiq.podvinci.core.menuhandler.MenuItemUtils
import ac.mdiq.podvinci.core.storage.DBReader
@ -21,13 +15,18 @@ import ac.mdiq.podvinci.fragment.QueueFragment
import ac.mdiq.podvinci.model.feed.FeedItem
import ac.mdiq.podvinci.ui.home.HomeSection
import ac.mdiq.podvinci.view.viewholder.HorizontalItemViewHolder
import android.os.Bundle
import android.util.Log
import android.view.*
import androidx.media3.common.util.UnstableApi
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import io.reactivex.Observable
import io.reactivex.android.schedulers.AndroidSchedulers
import io.reactivex.disposables.Disposable
import io.reactivex.schedulers.Schedulers
import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode
import java.util.concurrent.Callable
class QueueSection : HomeSection() {
private var listAdapter: HorizontalItemListAdapter? = null
@ -46,10 +45,10 @@ class QueueSection : HomeSection() {
}
}
listAdapter?.setDummyViews(NUM_EPISODES)
viewBinding?.recyclerView?.layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
viewBinding?.recyclerView?.adapter = listAdapter
viewBinding.recyclerView.layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false)
viewBinding.recyclerView.adapter = listAdapter
val paddingHorizontal: Int = (12 * resources.displayMetrics.density).toInt()
viewBinding?.recyclerView?.setPadding(paddingHorizontal, 0, paddingHorizontal, 0)
viewBinding.recyclerView.setPadding(paddingHorizontal, 0, paddingHorizontal, 0)
return view
}
@ -58,7 +57,7 @@ class QueueSection : HomeSection() {
loadItems()
}
override fun handleMoreClick() {
@UnstableApi override fun handleMoreClick() {
(requireActivity() as MainActivity).loadChildFragment(QueueFragment())
}
@ -112,7 +111,7 @@ class QueueSection : HomeSection() {
var currentlyPlayingItemIsFirst = true
for (i in 0 until listAdapter!!.itemCount) {
val holder: HorizontalItemViewHolder =
viewBinding?.recyclerView?.findViewHolderForAdapterPosition(i) as? HorizontalItemViewHolder ?: continue
viewBinding.recyclerView.findViewHolderForAdapterPosition(i) as? HorizontalItemViewHolder ?: continue
if (holder.isCurrentlyPlayingItem) {
holder.notifyPlaybackPositionUpdated(event)
foundCurrentlyPlayingItem = true

View File

@ -34,7 +34,7 @@ class SubscriptionsSection : HomeSection() {
container: ViewGroup?, savedInstanceState: Bundle?
): View {
val view: View = super.onCreateView(inflater, container, savedInstanceState)
viewBinding?.recyclerView?.layoutManager = LinearLayoutManager(activity, RecyclerView.HORIZONTAL, false)
viewBinding.recyclerView.layoutManager = LinearLayoutManager(activity, RecyclerView.HORIZONTAL, false)
listAdapter = object : HorizontalFeedListAdapter(activity as MainActivity) {
override fun onCreateContextMenu(contextMenu: ContextMenu, view: View, contextMenuInfo: ContextMenu.ContextMenuInfo?
) {
@ -46,9 +46,9 @@ class SubscriptionsSection : HomeSection() {
}
}
listAdapter?.setDummyViews(NUM_FEEDS)
viewBinding?.recyclerView?.adapter = listAdapter
viewBinding.recyclerView.adapter = listAdapter
val paddingHorizontal: Int = (12 * resources.displayMetrics.density).toInt()
viewBinding?.recyclerView?.setPadding(paddingHorizontal, 0, paddingHorizontal, 0)
viewBinding.recyclerView.setPadding(paddingHorizontal, 0, paddingHorizontal, 0)
return view
}
@ -78,11 +78,8 @@ class SubscriptionsSection : HomeSection() {
val prefs: SharedPreferences =
requireContext().getSharedPreferences(StatisticsFragment.PREF_NAME, Context.MODE_PRIVATE)
val includeMarkedAsPlayed: Boolean = prefs.getBoolean(StatisticsFragment.PREF_INCLUDE_MARKED_PLAYED, false)
disposable = Observable.fromCallable<List<StatisticsItem>> {
DBReader.getStatistics(includeMarkedAsPlayed,
0,
Long.MAX_VALUE).feedTime
}
disposable = Observable.fromCallable<List<StatisticsItem>>
{ DBReader.getStatistics(includeMarkedAsPlayed, 0, Long.MAX_VALUE).feedTime }
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ statisticsData: List<StatisticsItem> ->

View File

@ -9,6 +9,7 @@ class AspectRatioVideoView @JvmOverloads constructor(context: Context?,
attrs: AttributeSet? = null,
defStyle: Int = 0
) : VideoView(context, attrs, defStyle) {
private var mVideoWidth = 0
private var mVideoHeight = 0
private var mAvailableWidth = -1f

View File

@ -53,7 +53,7 @@ class ChapterSeekBar : AppCompatSeekBar {
if (dividerPos != null) {
this.dividerPos = FloatArray(dividerPos.size + 2)
this.dividerPos!![0] = 0f
System.arraycopy(dividerPos, 0, this.dividerPos, 1, dividerPos.size)
System.arraycopy(dividerPos, 0, this.dividerPos!!, 1, dividerPos.size)
this.dividerPos!![this.dividerPos!!.size - 1] = 1f
} else {
this.dividerPos = null
@ -104,30 +104,32 @@ class ChapterSeekBar : AppCompatSeekBar {
canvas.translate(paddingLeft.toFloat(), paddingTop.toFloat())
for (i in 1 until dividerPos!!.size) {
val right = dividerPos!![i] * width - chapterMargin
val left = dividerPos!![i - 1] * width
val rightCurr = dividerPos!![currChapter] * width - chapterMargin
val leftCurr = dividerPos!![currChapter - 1] * width
if (dividerPos != null && dividerPos!!.isNotEmpty()) {
for (i in 1 until dividerPos!!.size) {
val right = dividerPos!![i] * width - chapterMargin
val left = dividerPos!![i - 1] * width
val rightCurr = dividerPos!![currChapter] * width - chapterMargin
val leftCurr = dividerPos!![currChapter - 1] * width
canvas.drawRect(left, top, right, bottom, paintBackground)
canvas.drawRect(left, top, right, bottom, paintBackground)
if (progressSecondary > 0 && progressSecondary < width) {
if (right < progressSecondary) {
canvas.drawRect(left, top, right, bottom, paintBackground)
} else if (progressSecondary > left) {
canvas.drawRect(left, top, progressSecondary, bottom, paintBackground)
if (progressSecondary > 0 && progressSecondary < width) {
if (right < progressSecondary) {
canvas.drawRect(left, top, right, bottom, paintBackground)
} else if (progressSecondary > left) {
canvas.drawRect(left, top, progressSecondary, bottom, paintBackground)
}
}
}
if (right < progressPrimary) {
currChapter = i + 1
canvas.drawRect(left, top, right, bottom, paintProgressPrimary)
} else if (isHighlighted || isPressed) {
canvas.drawRect(leftCurr, topExpanded, rightCurr, bottomExpanded, paintBackground)
canvas.drawRect(leftCurr, topExpanded, progressPrimary, bottomExpanded, paintProgressPrimary)
} else {
canvas.drawRect(leftCurr, top, progressPrimary, bottom, paintProgressPrimary)
if (right < progressPrimary) {
currChapter = i + 1
canvas.drawRect(left, top, right, bottom, paintProgressPrimary)
} else if (isHighlighted || isPressed) {
canvas.drawRect(leftCurr, topExpanded, rightCurr, bottomExpanded, paintBackground)
canvas.drawRect(leftCurr, topExpanded, progressPrimary, bottomExpanded, paintProgressPrimary)
} else {
canvas.drawRect(leftCurr, top, progressPrimary, bottom, paintProgressPrimary)
}
}
}
canvas.restoreToCount(saveCount)

View File

@ -58,38 +58,41 @@ class EmptyViewHandler(context: Context?) {
}
private fun addToParentView(view: View) {
var parent = (view.parent as ViewGroup)
var parent = view.parent as? ViewGroup
while (parent != null) {
if (parent is RelativeLayout) {
parent.addView(emptyView)
val layoutParams =
emptyView.layoutParams as RelativeLayout.LayoutParams
layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE)
emptyView.layoutParams = layoutParams
break
} else if (parent is FrameLayout) {
parent.addView(emptyView)
val layoutParams =
emptyView.layoutParams as FrameLayout.LayoutParams
layoutParams.gravity = Gravity.CENTER
emptyView.layoutParams = layoutParams
break
} else if (parent is CoordinatorLayout) {
parent.addView(emptyView)
val layoutParams =
emptyView.layoutParams as CoordinatorLayout.LayoutParams
layoutParams.gravity = Gravity.CENTER
emptyView.layoutParams = layoutParams
break
when (parent) {
is RelativeLayout -> {
parent.addView(emptyView)
val layoutParams =
emptyView.layoutParams as RelativeLayout.LayoutParams
layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT, RelativeLayout.TRUE)
emptyView.layoutParams = layoutParams
break
}
is FrameLayout -> {
parent.addView(emptyView)
val layoutParams =
emptyView.layoutParams as FrameLayout.LayoutParams
layoutParams.gravity = Gravity.CENTER
emptyView.layoutParams = layoutParams
break
}
is CoordinatorLayout -> {
parent.addView(emptyView)
val layoutParams =
emptyView.layoutParams as CoordinatorLayout.LayoutParams
layoutParams.gravity = Gravity.CENTER
emptyView.layoutParams = layoutParams
break
}
}
parent = parent.parent as ViewGroup
parent = parent.parent as? ViewGroup
}
}
fun updateAdapter(adapter: RecyclerView.Adapter<*>?) {
if (this.recyclerAdapter != null) {
recyclerAdapter!!.unregisterAdapterDataObserver(adapterObserver)
}
recyclerAdapter?.unregisterAdapterDataObserver(adapterObserver)
this.recyclerAdapter = adapter
adapter?.registerAdapterDataObserver(adapterObserver)
updateVisibility()

View File

@ -10,28 +10,28 @@ import androidx.recyclerview.widget.RecyclerView
import ac.mdiq.podvinci.R
class EpisodeItemListRecyclerView : RecyclerView {
private var layoutManager: LinearLayoutManager? = null
private lateinit var layoutManager: LinearLayoutManager
constructor(context: Context?) : super(ContextThemeWrapper(context, R.style.FastScrollRecyclerView)) {
constructor(context: Context) : super(ContextThemeWrapper(context, R.style.FastScrollRecyclerView)) {
setup()
}
constructor(context: Context?, attrs: AttributeSet?) : super(ContextThemeWrapper(context,
constructor(context: Context, attrs: AttributeSet?) : super(ContextThemeWrapper(context,
R.style.FastScrollRecyclerView), attrs) {
setup()
}
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(ContextThemeWrapper(context,
constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(ContextThemeWrapper(context,
R.style.FastScrollRecyclerView), attrs, defStyleAttr) {
setup()
}
private fun setup() {
layoutManager = LinearLayoutManager(context)
layoutManager!!.recycleChildrenOnDetach = true
layoutManager.recycleChildrenOnDetach = true
setLayoutManager(layoutManager)
setHasFixedSize(true)
addItemDecoration(DividerItemDecoration(context, layoutManager!!.orientation))
addItemDecoration(DividerItemDecoration(context, layoutManager.orientation))
clipToPadding = false
}
@ -42,8 +42,8 @@ class EpisodeItemListRecyclerView : RecyclerView {
}
fun saveScrollPosition(tag: String) {
val firstItem = layoutManager!!.findFirstVisibleItemPosition()
val firstItemView = layoutManager!!.findViewByPosition(firstItem)
val firstItem = layoutManager.findFirstVisibleItemPosition()
val firstItemView = layoutManager.findViewByPosition(firstItem)
val topOffset = firstItemView?.top?.toFloat() ?: 0f
context.getSharedPreferences(TAG, Context.MODE_PRIVATE).edit()
@ -57,15 +57,15 @@ class EpisodeItemListRecyclerView : RecyclerView {
val position = prefs.getInt(PREF_PREFIX_SCROLL_POSITION + tag, 0)
val offset = prefs.getInt(PREF_PREFIX_SCROLL_OFFSET + tag, 0)
if (position > 0 || offset > 0) {
layoutManager!!.scrollToPositionWithOffset(position, offset)
layoutManager.scrollToPositionWithOffset(position, offset)
}
}
val isScrolledToBottom: Boolean
get() {
val visibleEpisodeCount = childCount
val totalEpisodeCount = layoutManager!!.itemCount
val firstVisibleEpisode = layoutManager!!.findFirstVisibleItemPosition()
val totalEpisodeCount = layoutManager.itemCount
val firstVisibleEpisode = layoutManager.findFirstVisibleItemPosition()
return (totalEpisodeCount - visibleEpisodeCount) <= (firstVisibleEpisode + 3)
}

View File

@ -22,14 +22,13 @@ class LiftOnScrollListener(appBar: View) : RecyclerView.OnScrollListener(), Nest
}
private fun isScrolled(recyclerView: RecyclerView): Boolean {
val firstItem =
(recyclerView.layoutManager as LinearLayoutManager?)!!.findFirstVisibleItemPosition()
val firstItem = (recyclerView.layoutManager as? LinearLayoutManager)?.findFirstVisibleItemPosition()?:-1
if (firstItem < 0) {
return false
} else if (firstItem > 0) {
return true
}
val firstItemView = recyclerView.layoutManager!!.findViewByPosition(firstItem)
val firstItemView = recyclerView.layoutManager?.findViewByPosition(firstItem)
return if (firstItemView == null) {
false
} else {

View File

@ -7,9 +7,8 @@ import ac.mdiq.podvinci.model.feed.FeedItem
import ac.mdiq.podvinci.ui.i18n.R
object LocalDeleteModal {
fun showLocalFeedDeleteWarningIfNecessary(context: Context?, items: Iterable<FeedItem>,
deleteCommand: Runnable
) {
fun showLocalFeedDeleteWarningIfNecessary(context: Context, items: Iterable<FeedItem>,
deleteCommand: Runnable) {
var anyLocalFeed = false
for (item in items) {
if (item.feed?.isLocalFeed == true) {
@ -23,7 +22,7 @@ object LocalDeleteModal {
return
}
MaterialAlertDialogBuilder(context!!)
MaterialAlertDialogBuilder(context)
.setTitle(R.string.delete_episode_label)
.setMessage(R.string.delete_local_feed_warning_body)
.setPositiveButton(R.string.delete_label) { dialog: DialogInterface?, which: Int -> deleteCommand.run() }

View File

@ -69,10 +69,7 @@ class NestedScrollableHost : FrameLayout {
}
private fun setAttributes(context: Context, attrs: AttributeSet?) {
val a = context.theme.obtainStyledAttributes(
attrs,
R.styleable.NestedScrollableHost,
0, 0)
val a = context.theme.obtainStyledAttributes(attrs, R.styleable.NestedScrollableHost, 0, 0)
try {
preferHorizontal = a.getInteger(R.styleable.NestedScrollableHost_preferHorizontal, 1)
@ -89,11 +86,11 @@ class NestedScrollableHost : FrameLayout {
viewTreeObserver.addOnPreDrawListener(object : ViewTreeObserver.OnPreDrawListener {
override fun onPreDraw(): Boolean {
var v = parent as View
var v = parent as? View
while (v != null && v !is ViewPager2 || isntSameDirection(v)) {
v = v.parent as View
v = v!!.parent as? View
}
parentViewPager = v as ViewPager2
parentViewPager = v as? ViewPager2
viewTreeObserver.removeOnPreDrawListener(this)
return false
@ -146,7 +143,6 @@ class NestedScrollableHost : FrameLayout {
return
}
if (e.action == MotionEvent.ACTION_DOWN) {
initialX = e.x
initialY = e.y

View File

@ -24,20 +24,23 @@ class PlayButton : AppCompatImageButton {
if (this.isShowPlay != showPlay) {
this.isShowPlay = showPlay
contentDescription = context.getString(if (showPlay) R.string.play_label else R.string.pause_label)
if (isVideoScreen) {
setImageResource(if (showPlay) R.drawable.ic_play_video_white else R.drawable.ic_pause_video_white)
} else if (!isShown) {
setImageResource(if (showPlay) R.drawable.ic_play_48dp else R.drawable.ic_pause)
} else if (showPlay) {
val drawable = AnimatedVectorDrawableCompat.create(
context, R.drawable.ic_animate_pause_play)
setImageDrawable(drawable)
drawable!!.start()
} else {
val drawable = AnimatedVectorDrawableCompat.create(
context, R.drawable.ic_animate_play_pause)
setImageDrawable(drawable)
drawable!!.start()
when {
isVideoScreen -> {
setImageResource(if (showPlay) R.drawable.ic_play_video_white else R.drawable.ic_pause_video_white)
}
!isShown -> {
setImageResource(if (showPlay) R.drawable.ic_play_48dp else R.drawable.ic_pause)
}
showPlay -> {
val drawable = AnimatedVectorDrawableCompat.create(context, R.drawable.ic_animate_pause_play)
setImageDrawable(drawable)
drawable?.start()
}
else -> {
val drawable = AnimatedVectorDrawableCompat.create(context, R.drawable.ic_animate_play_pause)
setImageDrawable(drawable)
drawable?.start()
}
}
}
}

View File

@ -10,7 +10,7 @@ import androidx.core.util.Consumer
import ac.mdiq.podvinci.R
class PlaybackSpeedSeekBar : FrameLayout {
private var seekBar: SeekBar? = null
private lateinit var seekBar: SeekBar
private var progressChangedListener: Consumer<Float>? = null
constructor(context: Context) : super(context) {
@ -28,10 +28,10 @@ class PlaybackSpeedSeekBar : FrameLayout {
private fun setup() {
inflate(context, R.layout.playback_speed_seek_bar, this)
seekBar = findViewById(R.id.playback_speed)
findViewById<View>(R.id.butDecSpeed).setOnClickListener { v: View? -> seekBar?.progress = (seekBar?.progress ?: 0) - 2 }
findViewById<View>(R.id.butIncSpeed).setOnClickListener { v: View? -> seekBar?.progress = (seekBar?.progress ?: 0) + 2 }
findViewById<View>(R.id.butDecSpeed).setOnClickListener { v: View? -> seekBar.progress -= 2 }
findViewById<View>(R.id.butIncSpeed).setOnClickListener { v: View? -> seekBar.progress += 2 }
seekBar?.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
seekBar.setOnSeekBarChangeListener(object : OnSeekBarChangeListener {
override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {
val playbackSpeed = (progress + 10) / 20.0f
if (progressChangedListener != null) {
@ -48,7 +48,7 @@ class PlaybackSpeedSeekBar : FrameLayout {
}
fun updateSpeed(speedMultiplier: Float) {
seekBar!!.progress = Math.round((20 * speedMultiplier) - 10)
seekBar.progress = Math.round((20 * speedMultiplier) - 10)
}
fun setProgressChangedListener(progressChangedListener: Consumer<Float>?) {
@ -56,11 +56,11 @@ class PlaybackSpeedSeekBar : FrameLayout {
}
val currentSpeed: Float
get() = (seekBar!!.progress + 10) / 20.0f
get() = (seekBar.progress + 10) / 20.0f
override fun setEnabled(enabled: Boolean) {
super.setEnabled(enabled)
seekBar!!.isEnabled = enabled
seekBar.isEnabled = enabled
findViewById<View>(R.id.butDecSpeed).isEnabled = enabled
findViewById<View>(R.id.butIncSpeed).isEnabled = enabled
}

View File

@ -27,6 +27,7 @@ import ac.mdiq.podvinci.core.util.IntentUtils
import ac.mdiq.podvinci.core.util.NetworkUtils
import ac.mdiq.podvinci.core.util.ShareUtils
import ac.mdiq.podvinci.core.util.gui.ShownotesCleaner
import androidx.media3.common.util.UnstableApi
import kotlin.math.max
class ShownotesWebView : WebView, View.OnLongClickListener {
@ -80,27 +81,28 @@ class ShownotesWebView : WebView, View.OnLongClickListener {
})
}
override fun onLongClick(v: View): Boolean {
@UnstableApi override fun onLongClick(v: View): Boolean {
val r: HitTestResult = getHitTestResult()
if (r.type == HitTestResult.SRC_ANCHOR_TYPE) {
Log.d(TAG, "Link of webview was long-pressed. Extra: " + r.extra)
selectedUrl = r.extra
showContextMenu()
return true
} else if (r.type == HitTestResult.EMAIL_TYPE) {
Log.d(TAG, "E-Mail of webview was long-pressed. Extra: " + r.extra)
ContextCompat.getSystemService(
context,
ClipboardManager::class.java)?.setPrimaryClip(ClipData.newPlainText("PodVinci", r.extra))
if (Build.VERSION.SDK_INT <= 32 && this.context is MainActivity) {
(this.context as MainActivity).showSnackbarAbovePlayer(
resources.getString(R.string.copied_to_clipboard),
Snackbar.LENGTH_SHORT)
when (r.type) {
HitTestResult.SRC_ANCHOR_TYPE -> {
Log.d(TAG, "Link of webview was long-pressed. Extra: " + r.extra)
selectedUrl = r.extra
showContextMenu()
return true
}
HitTestResult.EMAIL_TYPE -> {
Log.d(TAG, "E-Mail of webview was long-pressed. Extra: " + r.extra)
ContextCompat.getSystemService(context, ClipboardManager::class.java)?.setPrimaryClip(ClipData.newPlainText("PodVinci", r.extra))
if (Build.VERSION.SDK_INT <= 32 && this.context is MainActivity) {
(this.context as MainActivity).showSnackbarAbovePlayer(resources.getString(R.string.copied_to_clipboard), Snackbar.LENGTH_SHORT)
}
return true
}
else -> {
selectedUrl = null
return false
}
return true
}
selectedUrl = null
return false
}
fun onContextItemSelected(item: MenuItem): Boolean {
@ -109,29 +111,34 @@ class ShownotesWebView : WebView, View.OnLongClickListener {
}
val itemId = item.itemId
if (itemId == R.id.open_in_browser_item) {
if (selectedUrl != null) IntentUtils.openInBrowser(context, selectedUrl!!)
} else if (itemId == R.id.share_url_item) {
if (selectedUrl != null) ShareUtils.shareLink(context, selectedUrl!!)
} else if (itemId == R.id.copy_url_item) {
val clipData: ClipData = ClipData.newPlainText(selectedUrl, selectedUrl)
val cm = context
.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
cm.setPrimaryClip(clipData)
if (Build.VERSION.SDK_INT < 32) {
val s: Snackbar = Snackbar.make(this, R.string.copied_to_clipboard, Snackbar.LENGTH_LONG)
s.view.elevation = 100f
s.show()
when (itemId) {
R.id.open_in_browser_item -> {
if (selectedUrl != null) IntentUtils.openInBrowser(context, selectedUrl!!)
}
} else if (itemId == R.id.go_to_position_item) {
if (ShownotesCleaner.isTimecodeLink(selectedUrl) && timecodeSelectedListener != null) {
timecodeSelectedListener!!.accept(ShownotesCleaner.getTimecodeLinkTime(selectedUrl))
} else {
Log.e(TAG, "Selected go_to_position_item, but URL was no timecode link: $selectedUrl")
R.id.share_url_item -> {
if (selectedUrl != null) ShareUtils.shareLink(context, selectedUrl!!)
}
R.id.copy_url_item -> {
val clipData: ClipData = ClipData.newPlainText(selectedUrl, selectedUrl)
val cm = context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
cm.setPrimaryClip(clipData)
if (Build.VERSION.SDK_INT < 32) {
val s: Snackbar = Snackbar.make(this, R.string.copied_to_clipboard, Snackbar.LENGTH_LONG)
s.view.elevation = 100f
s.show()
}
}
R.id.go_to_position_item -> {
if (ShownotesCleaner.isTimecodeLink(selectedUrl) && timecodeSelectedListener != null) {
timecodeSelectedListener!!.accept(ShownotesCleaner.getTimecodeLinkTime(selectedUrl))
} else {
Log.e(TAG, "Selected go_to_position_item, but URL was no timecode link: $selectedUrl")
}
}
else -> {
selectedUrl = null
return false
}
} else {
selectedUrl = null
return false
}
selectedUrl = null
return true

View File

@ -40,9 +40,7 @@ abstract class ToolbarIconTintManager(private val context: Context,
}
private fun safeSetColorFilter(icon: Drawable?, filter: PorterDuffColorFilter?) {
if (icon != null) {
icon.colorFilter = filter
}
icon?.colorFilter = filter
}
/**

View File

@ -15,6 +15,7 @@ import ac.mdiq.podvinci.ui.common.CircularProgressBar
class DownloadLogItemViewHolder(context: Context?, parent: ViewGroup?) :
RecyclerView.ViewHolder(LayoutInflater.from(context).inflate(R.layout.downloadlog_item, parent, false)) {
@JvmField
val secondaryActionButton: View = itemView.findViewById(R.id.secondaryActionButton)
@JvmField

View File

@ -41,8 +41,9 @@ import kotlin.math.max
* Holds the view which shows FeedItems.
*/
@UnstableApi
class EpisodeItemViewHolder(activity: MainActivity, parent: ViewGroup?) :
class EpisodeItemViewHolder(private val activity: MainActivity, parent: ViewGroup?) :
RecyclerView.ViewHolder(LayoutInflater.from(activity).inflate(R.layout.feeditemlist_item, parent, false)) {
private val container: View = itemView.findViewById(R.id.container)
@JvmField
val dragHandle: ImageView = itemView.findViewById(R.id.drag_handle)
@ -69,7 +70,6 @@ class EpisodeItemViewHolder(activity: MainActivity, parent: ViewGroup?) :
@JvmField
val coverHolder: CardView
private val activity: MainActivity = activity
private var item: FeedItem? = null
init {
@ -269,8 +269,11 @@ class EpisodeItemViewHolder(activity: MainActivity, parent: ViewGroup?) :
* Hides the separator dot between icons and text if there are no icons.
*/
fun hideSeparatorIfNecessary() {
val hasIcons =
isInbox.visibility == View.VISIBLE || isInQueue.visibility == View.VISIBLE || isVideo.visibility == View.VISIBLE || isFavorite.visibility == View.VISIBLE || isInbox.visibility == View.VISIBLE
val hasIcons = isInbox.visibility == View.VISIBLE ||
isInQueue.visibility == View.VISIBLE ||
isVideo.visibility == View.VISIBLE ||
isFavorite.visibility == View.VISIBLE ||
isInbox.visibility == View.VISIBLE
separatorIcons.visibility = if (hasIcons) View.VISIBLE else View.GONE
}

View File

@ -29,6 +29,7 @@ import kotlin.math.max
class HorizontalItemViewHolder(private val activity: MainActivity, parent: ViewGroup?) :
RecyclerView.ViewHolder(LayoutInflater.from(activity).inflate(R.layout.horizontal_itemlist_item, parent, false)) {
@JvmField
val card: CardView = itemView.findViewById(R.id.card)
@ -110,7 +111,7 @@ class HorizontalItemViewHolder(private val activity: MainActivity, parent: ViewG
}
val isCurrentlyPlayingItem: Boolean
@UnstableApi get() = item != null && item!!.media != null && PlaybackStatus.isCurrentlyPlaying(item!!.media)
@UnstableApi get() = item?.media != null && PlaybackStatus.isCurrentlyPlaying(item!!.media)
fun notifyPlaybackPositionUpdated(event: PlaybackPositionEvent) {
setProgressBar(true, 100.0f * event.position / event.duration)

View File

@ -9,6 +9,8 @@
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK"/>
<uses-permission android:name="android.permission.BLUETOOTH" />
<uses-permission android:name="android.permission.VIBRATE" />
@ -18,10 +20,11 @@
android:supportsRtl="true">
<service android:name=".service.playback.PlaybackService"
android:label="@string/app_name"
android:enabled="true"
android:exported="true"
tools:ignore="ExportedService">
android:foregroundServiceType="mediaPlayback"
android:label="@string/app_name"
android:enabled="true"
android:exported="false"
tools:ignore="ExportedService">
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService"/>

View File

@ -223,6 +223,7 @@ class ExoPlayerWrapper internal constructor(private val context: Context) {
}
fun setPlaybackParams(speed: Float, skipSilence: Boolean) {
Log.d(TAG, "setPlaybackParams speed=$speed pitch=${playbackParameters.pitch} skipSilence=$skipSilence")
playbackParameters = PlaybackParameters(speed, playbackParameters.pitch)
exoPlayer.skipSilenceEnabled = skipSilence
exoPlayer.playbackParameters = playbackParameters

View File

@ -166,16 +166,16 @@ class PlaybackService : MediaBrowserServiceCompat() {
notificationBuilder = PlaybackServiceNotificationBuilder(this)
// TODO: this shit doesn't work
// if (Build.VERSION.SDK_INT >= VERSION_CODES.TIRAMISU) {
// registerReceiver(autoStateUpdated, IntentFilter("com.google.android.gms.car.media.STATUS"), RECEIVER_EXPORTED)
// registerReceiver(shutdownReceiver, IntentFilter(PlaybackServiceInterface.ACTION_SHUTDOWN_PLAYBACK_SERVICE), RECEIVER_EXPORTED)
// } else {
// ContextCompat.registerReceiver(applicationContext, autoStateUpdated, IntentFilter("com.google.android.gms.car.media.STATUS"), ContextCompat.RECEIVER_EXPORTED)
// ContextCompat.registerReceiver(applicationContext, shutdownReceiver, IntentFilter(PlaybackServiceInterface.ACTION_SHUTDOWN_PLAYBACK_SERVICE), ContextCompat.RECEIVER_EXPORTED)
// }
if (Build.VERSION.SDK_INT >= VERSION_CODES.TIRAMISU) {
registerReceiver(autoStateUpdated, IntentFilter("com.google.android.gms.car.media.STATUS"), RECEIVER_NOT_EXPORTED)
registerReceiver(shutdownReceiver, IntentFilter(PlaybackServiceInterface.ACTION_SHUTDOWN_PLAYBACK_SERVICE), RECEIVER_NOT_EXPORTED)
} else {
registerReceiver(autoStateUpdated, IntentFilter("com.google.android.gms.car.media.STATUS"))
registerReceiver(shutdownReceiver, IntentFilter(PlaybackServiceInterface.ACTION_SHUTDOWN_PLAYBACK_SERVICE))
}
registerReceiver(autoStateUpdated, IntentFilter("com.google.android.gms.car.media.STATUS"))
registerReceiver(shutdownReceiver, IntentFilter(PlaybackServiceInterface.ACTION_SHUTDOWN_PLAYBACK_SERVICE))
// registerReceiver(autoStateUpdated, IntentFilter("com.google.android.gms.car.media.STATUS"))
// registerReceiver(shutdownReceiver, IntentFilter(PlaybackServiceInterface.ACTION_SHUTDOWN_PLAYBACK_SERVICE))
registerReceiver(headsetDisconnected, IntentFilter(Intent.ACTION_HEADSET_PLUG))
registerReceiver(bluetoothStateUpdated, IntentFilter(BluetoothA2dp.ACTION_CONNECTION_STATE_CHANGED))
@ -272,13 +272,13 @@ class PlaybackService : MediaBrowserServiceCompat() {
cancelPositionObserver()
mediaSession?.release()
mediaSession = null
mediaPlayer?.shutdown()
unregisterReceiver(autoStateUpdated)
unregisterReceiver(headsetDisconnected)
unregisterReceiver(shutdownReceiver)
unregisterReceiver(bluetoothStateUpdated)
unregisterReceiver(audioBecomingNoisy)
mediaPlayer?.shutdown()
taskManager.shutdown()
EventBus.getDefault().unregister(this)
}
@ -699,7 +699,7 @@ class PlaybackService : MediaBrowserServiceCompat() {
}
else -> {
Log.d(TAG, "Unhandled key code: $keycode")
if (info?.playable != null && info?.playerStatus == PlayerStatus.PLAYING) { // only notify the user about an unknown key event if it is actually doing something
if (info?.playable != null && info.playerStatus == PlayerStatus.PLAYING) { // only notify the user about an unknown key event if it is actually doing something
val message = String.format(resources.getString(R.string.unknown_media_key), keycode)
Toast.makeText(this, message, Toast.LENGTH_SHORT).show()
}

View File

@ -2,6 +2,8 @@ package ac.mdiq.podvinci.core.service.playback
import android.annotation.SuppressLint
import android.app.Notification
import android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK
import android.os.Build
import android.util.Log
import androidx.core.app.ServiceCompat
import kotlin.concurrent.Volatile
@ -13,11 +15,13 @@ internal class PlaybackServiceStateManager(private val playbackService: Playback
@Volatile
private var hasReceivedValidStartCommand = false
@SuppressLint("ForegroundServiceType")
fun startForeground(notificationId: Int, notification: Notification?) {
fun startForeground(notificationId: Int, notification: Notification) {
Log.d(TAG, "startForeground")
// TODO: need to add declaration in manifest
playbackService.startForeground(notificationId, notification)
if (Build.VERSION.SDK_INT >= 29) {
playbackService.startForeground(notificationId, notification, FOREGROUND_SERVICE_TYPE_MEDIA_PLAYBACK)
} else {
playbackService.startForeground(notificationId, notification)
}
isInForeground = true
}

View File

@ -242,10 +242,8 @@ class PlaybackServiceTaskManager(private val context: Context,
cancelWidgetUpdater()
disableSleepTimer()
if (chapterLoaderFuture != null) {
chapterLoaderFuture!!.dispose()
chapterLoaderFuture = null
}
chapterLoaderFuture?.dispose()
chapterLoaderFuture = null
}
/**

View File

@ -646,7 +646,11 @@ object DBReader {
@JvmStatic
fun loadChaptersOfFeedItem(item: FeedItem): List<Chapter>? {
Log.d(TAG, "loadChaptersOfFeedItem() called with: item = [$item]")
// TODO: need to find out who are often calling this
// val stackTraceElements = Thread.currentThread().stackTrace
// stackTraceElements.forEach { element ->
// println(element)
// }
val adapter = getInstance()
adapter!!.open()
try {

View File

@ -205,7 +205,7 @@ import java.util.concurrent.TimeUnit
removedFromQueue.add(item)
}
if (item.media != null) {
if (item.media!!.id == currentlyPlayingFeedMediaId) {
if (item.media?.id == currentlyPlayingFeedMediaId) {
// Applies to both downloaded and streamed media
writeNoMediaPlaying()
sendLocalBroadcast(context, PlaybackServiceInterface.ACTION_SHUTDOWN_PLAYBACK_SERVICE)

View File

@ -10,7 +10,7 @@ object FeedItemUtil {
fun indexOfItemWithId(items: List<FeedItem?>, id: Long): Int {
for (i in items.indices) {
val item = items[i]
if (item != null && item.id == id) {
if (item?.id == id) {
return i
}
}
@ -21,7 +21,7 @@ object FeedItemUtil {
fun indexOfItemWithDownloadUrl(items: List<FeedItem?>, downloadUrl: String): Int {
for (i in items.indices) {
val item = items[i]
if (item?.media != null && item.media!!.download_url == downloadUrl) {
if (item?.media?.download_url == downloadUrl) {
return i
}
}
@ -30,7 +30,7 @@ object FeedItemUtil {
@JvmStatic
fun getIds(items: List<FeedItem>?): LongArray {
if (items == null || items.isEmpty()) {
if (items.isNullOrEmpty()) {
return LongArray(0)
}
val result = LongArray(items.size)

View File

@ -74,19 +74,17 @@ abstract class PlaybackController(private val activity: FragmentActivity?) {
initialized = true
// TODO: this shit doesn't work
// if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
// activity?.registerReceiver(statusUpdate, IntentFilter(
// PlaybackService.ACTION_PLAYER_STATUS_CHANGED), Context.RECEIVER_NOT_EXPORTED)
// activity?.registerReceiver(notificationReceiver, IntentFilter(
// PlaybackServiceInterface.ACTION_PLAYER_NOTIFICATION), Context.RECEIVER_NOT_EXPORTED)
// } else {
// ContextCompat.registerReceiver(activity!!, statusUpdate, IntentFilter(
// PlaybackService.ACTION_PLAYER_STATUS_CHANGED), ContextCompat.RECEIVER_EXPORTED)
// ContextCompat.registerReceiver(activity, notificationReceiver, IntentFilter(
// PlaybackServiceInterface.ACTION_PLAYER_NOTIFICATION), ContextCompat.RECEIVER_EXPORTED)
// }
activity?.registerReceiver(statusUpdate, IntentFilter(PlaybackService.ACTION_PLAYER_STATUS_CHANGED))
activity?.registerReceiver(notificationReceiver, IntentFilter(PlaybackServiceInterface.ACTION_PLAYER_NOTIFICATION))
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
activity?.registerReceiver(statusUpdate, IntentFilter(
PlaybackService.ACTION_PLAYER_STATUS_CHANGED), Context.RECEIVER_NOT_EXPORTED)
activity?.registerReceiver(notificationReceiver, IntentFilter(
PlaybackServiceInterface.ACTION_PLAYER_NOTIFICATION), Context.RECEIVER_NOT_EXPORTED)
} else {
activity?.registerReceiver(statusUpdate, IntentFilter(PlaybackService.ACTION_PLAYER_STATUS_CHANGED))
activity?.registerReceiver(notificationReceiver, IntentFilter(PlaybackServiceInterface.ACTION_PLAYER_NOTIFICATION))
}
// activity?.registerReceiver(statusUpdate, IntentFilter(PlaybackService.ACTION_PLAYER_STATUS_CHANGED))
// activity?.registerReceiver(notificationReceiver, IntentFilter(PlaybackServiceInterface.ACTION_PLAYER_NOTIFICATION))
if (!released) {
bindToService()
@ -390,7 +388,7 @@ abstract class PlaybackController(private val activity: FragmentActivity?) {
if (playbackService == null || playbackService!!.audioTracks.isNullOrEmpty()) {
return emptyList()
}
return playbackService!!.audioTracks!!.filterNotNull().map { it }
return playbackService!!.audioTracks.filterNotNull().map { it }
}
val selectedAudioTrack: Int

View File

@ -31,6 +31,6 @@ class Chapter : FeedComponent {
}
override fun toString(): String {
return "ID3Chapter [title=" + title + ", start=" + start + ", url=" + link + "]"
return "ID3Chapter [title=$title, start=$start, url=$link]"
}
}