From 45abbd34b9e4464bc0ec7431c77ede5a0e4cca7c Mon Sep 17 00:00:00 2001 From: Xilin Jia <6257601+XilinJia@users.noreply.github.com> Date: Sat, 17 Feb 2024 23:46:14 +0100 Subject: [PATCH] null safety tuning and bug fixes --- README.md | 2 +- app/src/main/AndroidManifest.xml | 8 +- .../activity/OnlineFeedViewActivity.kt | 112 +++--- .../podvinci/activity/OpmlImportActivity.kt | 81 +++-- .../podvinci/activity/PreferenceActivity.kt | 10 +- .../activity/SelectSubscriptionActivity.kt | 49 ++- .../podvinci/activity/VideoplayerActivity.kt | 277 ++++++++------- .../podvinci/activity/WidgetConfigActivity.kt | 106 +++--- .../podvinci/adapter/ChaptersListAdapter.kt | 30 +- .../ac/mdiq/podvinci/adapter/CoverLoader.kt | 9 +- .../podvinci/adapter/DataFolderAdapter.kt | 9 +- .../podvinci/adapter/DownloadLogAdapter.kt | 9 +- .../adapter/EpisodeItemListAdapter.kt | 47 +-- .../podvinci/adapter/FeedDiscoverAdapter.kt | 15 +- .../adapter/FeedItemlistDescriptionAdapter.kt | 31 +- .../adapter/HorizontalFeedListAdapter.kt | 2 +- .../adapter/HorizontalItemListAdapter.kt | 13 +- .../mdiq/podvinci/adapter/NavListAdapter.kt | 81 +++-- .../podvinci/adapter/QueueRecyclerAdapter.kt | 10 +- .../podvinci/adapter/SelectableAdapter.kt | 11 +- .../podvinci/adapter/SimpleChipAdapter.kt | 2 +- .../podvinci/adapter/SimpleIconListAdapter.kt | 4 +- .../adapter/SubscriptionsRecyclerAdapter.kt | 4 +- .../actionbutton/DeleteActionButton.kt | 6 +- .../adapter/actionbutton/ItemActionButton.kt | 36 +- .../actionbutton/MarkAsPlayedActionButton.kt | 5 +- .../actionbutton/VisitWebsiteActionButton.kt | 2 +- .../podvinci/adapter/itunes/ItunesAdapter.kt | 8 +- .../asynctask/DocumentFileExportWorker.kt | 10 +- .../mdiq/podvinci/dialog/RemoveFeedDialog.kt | 4 +- .../mdiq/podvinci/dialog/TagSettingsDialog.kt | 2 +- .../mdiq/podvinci/fragment/AddFeedFragment.kt | 46 ++- .../podvinci/fragment/AudioPlayerFragment.kt | 172 +++++---- .../podvinci/fragment/ChaptersFragment.kt | 66 ++-- .../fragment/CompletedDownloadsFragment.kt | 180 +++++----- .../mdiq/podvinci/fragment/CoverFragment.kt | 125 ++++--- .../podvinci/fragment/DiscoveryFragment.kt | 133 ++++--- .../podvinci/fragment/DownloadLogFragment.kt | 32 +- .../podvinci/fragment/EpisodesListFragment.kt | 198 +++++------ .../fragment/ExternalPlayerFragment.kt | 47 ++- .../podvinci/fragment/FeedInfoFragment.kt | 164 ++++----- .../podvinci/fragment/FeedItemlistFragment.kt | 201 +++++------ .../podvinci/fragment/FeedSettingsFragment.kt | 32 +- .../mdiq/podvinci/fragment/InboxFragment.kt | 24 +- .../fragment/ItemDescriptionFragment.kt | 43 ++- .../ac/mdiq/podvinci/fragment/ItemFragment.kt | 117 +++--- .../podvinci/fragment/ItemPagerFragment.kt | 54 +-- .../podvinci/fragment/NavDrawerFragment.kt | 50 +-- .../podvinci/fragment/OnlineSearchFragment.kt | 45 +-- .../fragment/PlaybackHistoryFragment.kt | 15 +- .../mdiq/podvinci/fragment/QueueFragment.kt | 334 +++++++++--------- .../fragment/QuickFeedDiscoveryFragment.kt | 112 +++--- .../mdiq/podvinci/fragment/SearchFragment.kt | 170 +++++---- .../podvinci/fragment/SubscriptionFragment.kt | 131 ++++--- .../EpisodeMultiSelectActionHandler.kt | 2 + .../actions/FeedMultiSelectActionHandler.kt | 16 +- .../swipeactions/DeleteSwipeAction.kt | 5 +- .../menuhandler/FeedItemMenuHandler.kt | 4 +- .../ac/mdiq/podvinci/ui/home/HomeFragment.kt | 56 +-- .../ac/mdiq/podvinci/ui/home/HomeSection.kt | 51 ++- .../ui/home/HomeSectionsSettingsDialog.kt | 3 +- .../sections/AllowNotificationsSection.kt | 20 +- .../ui/home/sections/DownloadsSection.kt | 25 +- .../podvinci/ui/home/sections/EchoSection.kt | 19 +- .../home/sections/EpisodesSurpriseSection.kt | 31 +- .../podvinci/ui/home/sections/InboxSection.kt | 20 +- .../podvinci/ui/home/sections/QueueSection.kt | 25 +- .../ui/home/sections/SubscriptionsSection.kt | 13 +- .../podvinci/view/AspectRatioVideoView.kt | 1 + .../ac/mdiq/podvinci/view/ChapterSeekBar.kt | 44 +-- .../ac/mdiq/podvinci/view/EmptyViewHandler.kt | 55 +-- .../view/EpisodeItemListRecyclerView.kt | 22 +- .../podvinci/view/LiftOnScrollListener.kt | 5 +- .../ac/mdiq/podvinci/view/LocalDeleteModal.kt | 7 +- .../podvinci/view/NestedScrollableHost.kt | 12 +- .../java/ac/mdiq/podvinci/view/PlayButton.kt | 31 +- .../podvinci/view/PlaybackSpeedSeekBar.kt | 14 +- .../ac/mdiq/podvinci/view/ShownotesWebView.kt | 85 +++-- .../podvinci/view/ToolbarIconTintManager.kt | 4 +- .../viewholder/DownloadLogItemViewHolder.kt | 1 + .../view/viewholder/EpisodeItemViewHolder.kt | 11 +- .../viewholder/HorizontalItemViewHolder.kt | 3 +- core/src/main/AndroidManifest.xml | 11 +- .../core/service/playback/ExoPlayerWrapper.kt | 1 + .../core/service/playback/PlaybackService.kt | 22 +- .../playback/PlaybackServiceStateManager.kt | 12 +- .../playback/PlaybackServiceTaskManager.kt | 6 +- .../ac/mdiq/podvinci/core/storage/DBReader.kt | 6 +- .../ac/mdiq/podvinci/core/storage/DBWriter.kt | 2 +- .../mdiq/podvinci/core/util/FeedItemUtil.kt | 6 +- .../core/util/playback/PlaybackController.kt | 26 +- .../ac/mdiq/podvinci/model/feed/Chapter.kt | 2 +- 92 files changed, 2063 insertions(+), 2102 deletions(-) diff --git a/README.md b/README.md index c7cfdeaf..16aa28cb 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ce1f72b3..9f6ff86f 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -8,7 +8,6 @@ tools:ignore="ScopedStorage" /> - + + + + + + + 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) { - 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 = ArrayList() val alternateUrlsTitleList: MutableList = 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? + val urlsMap: Map try { urlsMap = fd.findLinks(feedFile, baseUrl) if (urlsMap.isEmpty()) { diff --git a/app/src/main/java/ac/mdiq/podvinci/activity/OpmlImportActivity.kt b/app/src/main/java/ac/mdiq/podvinci/activity/OpmlImportActivity.kt index 48ef81e1..881a38f1 100644 --- a/app/src/main/java/ac/mdiq/podvinci/activity/OpmlImportActivity.kt +++ b/app/src/main/java/ac/mdiq/podvinci/activity/OpmlImportActivity.kt @@ -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? = null - private var selectAll: MenuItem? = null - private var deselectAll: MenuItem? = null private var readElements: ArrayList? = 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 get() { val result: MutableList = 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? -> - 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) diff --git a/app/src/main/java/ac/mdiq/podvinci/activity/PreferenceActivity.kt b/app/src/main/java/ac/mdiq/podvinci/activity/PreferenceActivity.kt index f624f837..4473ede6 100644 --- a/app/src/main/java/ac/mdiq/podvinci/activity/PreferenceActivity.kt +++ b/app/src/main/java/ac/mdiq/podvinci/activity/PreferenceActivity.kt @@ -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) } } diff --git a/app/src/main/java/ac/mdiq/podvinci/activity/SelectSubscriptionActivity.kt b/app/src/main/java/ac/mdiq/podvinci/activity/SelectSubscriptionActivity.kt index 9a8eb0f8..6d3ada7e 100644 --- a/app/src/main/java/ac/mdiq/podvinci/activity/SelectSubscriptionActivity.kt +++ b/app/src/main/java/ac/mdiq/podvinci/activity/SelectSubscriptionActivity.kt @@ -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? = 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(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 { - override fun onLoadFailed(e: GlideException?, model: Any?, - target: Target, isFirstResource: Boolean + @UnstableApi override fun onLoadFailed(e: GlideException?, model: Any?, + target: Target, isFirstResource: Boolean ): Boolean { addShortcut(feed, null) return true } - override fun onResourceReady(resource: Bitmap, model: Any, - target: Target, dataSource: DataSource, isFirstResource: Boolean + @UnstableApi override fun onResourceReady(resource: Bitmap, model: Any, + target: Target, 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 = ArrayAdapter(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)) }) } diff --git a/app/src/main/java/ac/mdiq/podvinci/activity/VideoplayerActivity.kt b/app/src/main/java/ac/mdiq/podvinci/activity/VideoplayerActivity.kt index f7283a52..be5cd8d8 100644 --- a/app/src/main/java/ac/mdiq/podvinci/activity/VideoplayerActivity.kt +++ b/app/src/main/java/ac/mdiq/podvinci/activity/VideoplayerActivity.kt @@ -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) diff --git a/app/src/main/java/ac/mdiq/podvinci/activity/WidgetConfigActivity.kt b/app/src/main/java/ac/mdiq/podvinci/activity/WidgetConfigActivity.kt index b5da376b..d4bb46b0 100644 --- a/app/src/main/java/ac/mdiq/podvinci/activity/WidgetConfigActivity.kt +++ b/app/src/main/java/ac/mdiq/podvinci/activity/WidgetConfigActivity.kt @@ -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(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(R.id.txtNoPlaying).visibility = View.GONE + val title = widgetPreview.findViewById(R.id.txtvTitle) + title.visibility = View.VISIBLE + title.setText(R.string.app_name) + val progress = widgetPreview.findViewById(R.id.txtvProgress) + progress.visibility = View.VISIBLE + progress.setText(R.string.position_default_label) - override fun onStopTrackingTouch(seekBar: SeekBar) { - } - }) - } - if (widgetPreview != null) { - widgetPreview!!.findViewById(R.id.txtNoPlaying).visibility = View.GONE - val title = widgetPreview!!.findViewById(R.id.txtvTitle) - title.visibility = View.VISIBLE - title.setText(R.string.app_name) - val progress = widgetPreview!!.findViewById(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(R.id.extendedButtonsContainer).visibility = + ckPlaybackSpeed.isChecked || ckRewind.isChecked || ckFastForward.isChecked || ckSkip.isChecked + widgetPreview.findViewById(R.id.extendedButtonsContainer).visibility = if (showExtendedPreview) View.VISIBLE else View.GONE - widgetPreview!!.findViewById(R.id.butPlay).visibility = + widgetPreview.findViewById(R.id.butPlay).visibility = if (showExtendedPreview) View.GONE else View.VISIBLE - widgetPreview!!.findViewById(R.id.butPlaybackSpeed).visibility = - if (ckPlaybackSpeed!!.isChecked) View.VISIBLE else View.GONE - widgetPreview!!.findViewById(R.id.butFastForward).visibility = - if (ckFastForward!!.isChecked) View.VISIBLE else View.GONE - widgetPreview!!.findViewById(R.id.butSkip).visibility = - if (ckSkip!!.isChecked) View.VISIBLE else View.GONE - widgetPreview!!.findViewById(R.id.butRew).visibility = - if (ckRewind!!.isChecked) View.VISIBLE else View.GONE + widgetPreview.findViewById(R.id.butPlaybackSpeed).visibility = + if (ckPlaybackSpeed.isChecked) View.VISIBLE else View.GONE + widgetPreview.findViewById(R.id.butFastForward).visibility = + if (ckFastForward.isChecked) View.VISIBLE else View.GONE + widgetPreview.findViewById(R.id.butSkip).visibility = + if (ckSkip.isChecked) View.VISIBLE else View.GONE + widgetPreview.findViewById(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() diff --git a/app/src/main/java/ac/mdiq/podvinci/adapter/ChaptersListAdapter.kt b/app/src/main/java/ac/mdiq/podvinci/adapter/ChaptersListAdapter.kt index d4a7c650..d44a75e7 100644 --- a/app/src/main/java/ac/mdiq/podvinci/adapter/ChaptersListAdapter.kt +++ b/app/src/main/java/ac/mdiq/podvinci/adapter/ChaptersListAdapter.kt @@ -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() { +class ChaptersListAdapter(private val context: Context, private val callback: Callback?) : RecyclerView.Adapter() { + 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 { diff --git a/app/src/main/java/ac/mdiq/podvinci/adapter/CoverLoader.kt b/app/src/main/java/ac/mdiq/podvinci/adapter/CoverLoader.kt index baff4b1b..8925b933 100644 --- a/app/src/main/java/ac/mdiq/podvinci/adapter/CoverLoader.kt +++ b/app/src/main/java/ac/mdiq/podvinci/adapter/CoverLoader.kt @@ -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) } - internal class CoverTarget(fallbackTitle: TextView, + internal class CoverTarget(fallbackTitle: TextView?, coverImage: ImageView, private val textAndImageCombined: Boolean ) : CustomViewTarget(coverImage) { - private val fallbackTitle: WeakReference = WeakReference(fallbackTitle) + + private val fallbackTitle: WeakReference = WeakReference(fallbackTitle) private val cover: WeakReference = WeakReference(coverImage) override fun onLoadFailed(errorDrawable: Drawable?) { diff --git a/app/src/main/java/ac/mdiq/podvinci/adapter/DataFolderAdapter.kt b/app/src/main/java/ac/mdiq/podvinci/adapter/DataFolderAdapter.kt index b43b0eae..3968f803 100644 --- a/app/src/main/java/ac/mdiq/podvinci/adapter/DataFolderAdapter.kt +++ b/app/src/main/java/ac/mdiq/podvinci/adapter/DataFolderAdapter.kt @@ -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) : - RecyclerView.Adapter() { +class DataFolderAdapter(context: Context, selectionHandler: Consumer) : RecyclerView.Adapter() { + private val selectionHandler: Consumer private val currentPath: String? private val entries: List @@ -63,10 +63,7 @@ class DataFolderAdapter(context: Context, selectionHandler: Consumer) : 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 { diff --git a/app/src/main/java/ac/mdiq/podvinci/adapter/DownloadLogAdapter.kt b/app/src/main/java/ac/mdiq/podvinci/adapter/DownloadLogAdapter.kt index ad009914..6968595e 100644 --- a/app/src/main/java/ac/mdiq/podvinci/adapter/DownloadLogAdapter.kt +++ b/app/src/main/java/ac/mdiq/podvinci/adapter/DownloadLogAdapter.kt @@ -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 diff --git a/app/src/main/java/ac/mdiq/podvinci/adapter/EpisodeItemListAdapter.kt b/app/src/main/java/ac/mdiq/podvinci/adapter/EpisodeItemListAdapter.kt index e20414ce..812131b3 100644 --- a/app/src/main/java/ac/mdiq/podvinci/adapter/EpisodeItemListAdapter.kt +++ b/app/src/main/java/ac/mdiq/podvinci/adapter/EpisodeItemListAdapter.kt @@ -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 = 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 diff --git a/app/src/main/java/ac/mdiq/podvinci/adapter/FeedDiscoverAdapter.kt b/app/src/main/java/ac/mdiq/podvinci/adapter/FeedDiscoverAdapter.kt index 6850fbb5..cbd9ce5e 100644 --- a/app/src/main/java/ac/mdiq/podvinci/adapter/FeedDiscoverAdapter.kt +++ b/app/src/main/java/ac/mdiq/podvinci/adapter/FeedDiscoverAdapter.kt @@ -17,9 +17,9 @@ class FeedDiscoverAdapter(mainActivity: MainActivity) : BaseAdapter() { private val mainActivityRef: WeakReference = WeakReference(mainActivity) private val data: MutableList = ArrayList() - fun updateData(newData: List?) { + fun updateData(newData: List) { 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())) diff --git a/app/src/main/java/ac/mdiq/podvinci/adapter/FeedItemlistDescriptionAdapter.kt b/app/src/main/java/ac/mdiq/podvinci/adapter/FeedItemlistDescriptionAdapter.kt index b574c315..d2998bf5 100644 --- a/app/src/main/java/ac/mdiq/podvinci/adapter/FeedItemlistDescriptionAdapter.kt +++ b/app/src/main/java/ac/mdiq/podvinci/adapter/FeedItemlistDescriptionAdapter.kt @@ -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?) : - ArrayAdapter( - context!!, resource, objects!!) { +class FeedItemlistDescriptionAdapter(context: Context, resource: Int, objects: List?) : + ArrayAdapter(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) diff --git a/app/src/main/java/ac/mdiq/podvinci/adapter/HorizontalFeedListAdapter.kt b/app/src/main/java/ac/mdiq/podvinci/adapter/HorizontalFeedListAdapter.kt index f1170524..f81de721 100644 --- a/app/src/main/java/ac/mdiq/podvinci/adapter/HorizontalFeedListAdapter.kt +++ b/app/src/main/java/ac/mdiq/podvinci/adapter/HorizontalFeedListAdapter.kt @@ -21,7 +21,7 @@ import java.lang.ref.WeakReference open class HorizontalFeedListAdapter(mainActivity: MainActivity) : RecyclerView.Adapter(), View.OnCreateContextMenuListener { - private val mainActivityRef: WeakReference = WeakReference(mainActivity) + private val mainActivityRef: WeakReference = WeakReference(mainActivity) private val data: MutableList = ArrayList() private var dummyViews = 0 var longPressedItem: Feed? = null diff --git a/app/src/main/java/ac/mdiq/podvinci/adapter/HorizontalItemListAdapter.kt b/app/src/main/java/ac/mdiq/podvinci/adapter/HorizontalItemListAdapter.kt index 691ce051..2f44ff2c 100644 --- a/app/src/main/java/ac/mdiq/podvinci/adapter/HorizontalItemListAdapter.kt +++ b/app/src/main/java/ac/mdiq/podvinci/adapter/HorizontalItemListAdapter.kt @@ -18,6 +18,7 @@ import java.lang.ref.WeakReference open class HorizontalItemListAdapter(mainActivity: MainActivity) : RecyclerView.Adapter(), View.OnCreateContextMenuListener { + private val mainActivityRef: WeakReference = WeakReference(mainActivity) private var data: List = 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) diff --git a/app/src/main/java/ac/mdiq/podvinci/adapter/NavListAdapter.kt b/app/src/main/java/ac/mdiq/podvinci/adapter/NavListAdapter.kt index 4aa7a355..928eae2c 100644 --- a/app/src/main/java/ac/mdiq/podvinci/adapter/NavListAdapter.kt +++ b/app/src/main/java/ac/mdiq/podvinci/adapter/NavListAdapter.kt @@ -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(), OnSharedPreferenceChangeListener { + private val fragmentTags: MutableList = ArrayList() private val titles: Array = 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() + } } } } diff --git a/app/src/main/java/ac/mdiq/podvinci/adapter/QueueRecyclerAdapter.kt b/app/src/main/java/ac/mdiq/podvinci/adapter/QueueRecyclerAdapter.kt index 72fe8e36..ce10ca91 100644 --- a/app/src/main/java/ac/mdiq/podvinci/adapter/QueueRecyclerAdapter.kt +++ b/app/src/main/java/ac/mdiq/podvinci/adapter/QueueRecyclerAdapter.kt @@ -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 { diff --git a/app/src/main/java/ac/mdiq/podvinci/adapter/SelectableAdapter.kt b/app/src/main/java/ac/mdiq/podvinci/adapter/SelectableAdapter.kt index 113167df..6eec24ed 100644 --- a/app/src/main/java/ac/mdiq/podvinci/adapter/SelectableAdapter.kt +++ b/app/src/main/java/ac/mdiq/podvinci/adapter/SelectableAdapter.kt @@ -23,10 +23,7 @@ abstract class SelectableAdapter(private val activ if (inActionMode()) { endSelectMode() } - - if (onSelectModeListener != null) { - onSelectModeListener!!.onStartSelectMode() - } + onSelectModeListener?.onStartSelectMode() shouldSelectLazyLoadedItems = false selectedIds.clear() @@ -75,7 +72,7 @@ abstract class SelectableAdapter(private val activ fun endSelectMode() { if (inActionMode()) { callOnEndSelectMode() - actionMode!!.finish() + actionMode?.finish() } } @@ -164,9 +161,7 @@ abstract class SelectableAdapter(private val activ } private fun callOnEndSelectMode() { - if (onSelectModeListener != null) { - onSelectModeListener!!.onEndSelectMode() - } + onSelectModeListener?.onEndSelectMode() } fun shouldSelectLazyLoadedItems(): Boolean { diff --git a/app/src/main/java/ac/mdiq/podvinci/adapter/SimpleChipAdapter.kt b/app/src/main/java/ac/mdiq/podvinci/adapter/SimpleChipAdapter.kt index 5b06abaa..392f72ad 100644 --- a/app/src/main/java/ac/mdiq/podvinci/adapter/SimpleChipAdapter.kt +++ b/app/src/main/java/ac/mdiq/podvinci/adapter/SimpleChipAdapter.kt @@ -12,7 +12,7 @@ abstract class SimpleChipAdapter(private val context: Context) : RecyclerView.Ad setHasStableIds(true) } - protected abstract fun getChips(): List + protected abstract fun getChips(): List protected abstract fun onRemoveClicked(position: Int) diff --git a/app/src/main/java/ac/mdiq/podvinci/adapter/SimpleIconListAdapter.kt b/app/src/main/java/ac/mdiq/podvinci/adapter/SimpleIconListAdapter.kt index f9c368b4..9c687be5 100644 --- a/app/src/main/java/ac/mdiq/podvinci/adapter/SimpleIconListAdapter.kt +++ b/app/src/main/java/ac/mdiq/podvinci/adapter/SimpleIconListAdapter.kt @@ -16,8 +16,8 @@ import ac.mdiq.podvinci.R */ class SimpleIconListAdapter(private val context: Context, private val listItems: List -) : ArrayAdapter( - context, R.layout.simple_icon_list_item, listItems) { +) : ArrayAdapter(context, R.layout.simple_icon_list_item, listItems) { + override fun getView(position: Int, view: View?, parent: ViewGroup): View { var view = view if (view == null) { diff --git a/app/src/main/java/ac/mdiq/podvinci/adapter/SubscriptionsRecyclerAdapter.kt b/app/src/main/java/ac/mdiq/podvinci/adapter/SubscriptionsRecyclerAdapter.kt index 5f1e2ba2..c4ae49bb 100644 --- a/app/src/main/java/ac/mdiq/podvinci/adapter/SubscriptionsRecyclerAdapter.kt +++ b/app/src/main/java/ac/mdiq/podvinci/adapter/SubscriptionsRecyclerAdapter.kt @@ -28,6 +28,7 @@ import java.text.NumberFormat open class SubscriptionsRecyclerAdapter(mainActivity: MainActivity) : SelectableAdapter(mainActivity), View.OnCreateContextMenuListener { + private val mainActivityRef: WeakReference = WeakReference(mainActivity) private var listItems: List 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(R.id.titleLabel).visibility = - if (viewType == COVER_WITH_TITLE) View.VISIBLE else View.GONE + itemView.findViewById(R.id.titleLabel).visibility = if (viewType == COVER_WITH_TITLE) View.VISIBLE else View.GONE return SubscriptionViewHolder(itemView) } diff --git a/app/src/main/java/ac/mdiq/podvinci/adapter/actionbutton/DeleteActionButton.kt b/app/src/main/java/ac/mdiq/podvinci/adapter/actionbutton/DeleteActionButton.kt index 37755ad0..5604193a 100644 --- a/app/src/main/java/ac/mdiq/podvinci/adapter/actionbutton/DeleteActionButton.kt +++ b/app/src/main/java/ac/mdiq/podvinci/adapter/actionbutton/DeleteActionButton.kt @@ -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 diff --git a/app/src/main/java/ac/mdiq/podvinci/adapter/actionbutton/ItemActionButton.kt b/app/src/main/java/ac/mdiq/podvinci/adapter/actionbutton/ItemActionButton.kt index 4360882c..739a344c 100644 --- a/app/src/main/java/ac/mdiq/podvinci/adapter/actionbutton/ItemActionButton.kt +++ b/app/src/main/java/ac/mdiq/podvinci/adapter/actionbutton/ItemActionButton.kt @@ -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) + } } } } diff --git a/app/src/main/java/ac/mdiq/podvinci/adapter/actionbutton/MarkAsPlayedActionButton.kt b/app/src/main/java/ac/mdiq/podvinci/adapter/actionbutton/MarkAsPlayedActionButton.kt index 9b461424..6703562c 100644 --- a/app/src/main/java/ac/mdiq/podvinci/adapter/actionbutton/MarkAsPlayedActionButton.kt +++ b/app/src/main/java/ac/mdiq/podvinci/adapter/actionbutton/MarkAsPlayedActionButton.kt @@ -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 } diff --git a/app/src/main/java/ac/mdiq/podvinci/adapter/actionbutton/VisitWebsiteActionButton.kt b/app/src/main/java/ac/mdiq/podvinci/adapter/actionbutton/VisitWebsiteActionButton.kt index 5353c3f2..8e20e634 100644 --- a/app/src/main/java/ac/mdiq/podvinci/adapter/actionbutton/VisitWebsiteActionButton.kt +++ b/app/src/main/java/ac/mdiq/podvinci/adapter/actionbutton/VisitWebsiteActionButton.kt @@ -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 } diff --git a/app/src/main/java/ac/mdiq/podvinci/adapter/itunes/ItunesAdapter.kt b/app/src/main/java/ac/mdiq/podvinci/adapter/itunes/ItunesAdapter.kt index bc63b53f..f46732f7 100644 --- a/app/src/main/java/ac/mdiq/podvinci/adapter/itunes/ItunesAdapter.kt +++ b/app/src/main/java/ac/mdiq/podvinci/adapter/itunes/ItunesAdapter.kt @@ -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 = 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(R.id.txtvTitle) + val titleView: TextView = view.findViewById(R.id.txtvTitle) val authorView: TextView = view.findViewById(R.id.txtvAuthor) } diff --git a/app/src/main/java/ac/mdiq/podvinci/asynctask/DocumentFileExportWorker.kt b/app/src/main/java/ac/mdiq/podvinci/asynctask/DocumentFileExportWorker.kt index 2ba24d13..1268827d 100644 --- a/app/src/main/java/ac/mdiq/podvinci/asynctask/DocumentFileExportWorker.kt +++ b/app/src/main/java/ac/mdiq/podvinci/asynctask/DocumentFileExportWorker.kt @@ -20,17 +20,15 @@ class DocumentFileExportWorker(private val exportWriter: ExportWriter, private val outputFileUri: Uri ) { fun exportObservable(): Observable { - val output = DocumentFile.fromSingleUri( - context, outputFileUri) + val output = DocumentFile.fromSingleUri(context, outputFileUri) return Observable.create { subscriber: ObservableEmitter -> 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) diff --git a/app/src/main/java/ac/mdiq/podvinci/dialog/RemoveFeedDialog.kt b/app/src/main/java/ac/mdiq/podvinci/dialog/RemoveFeedDialog.kt index da29a064..e8b6d012 100644 --- a/app/src/main/java/ac/mdiq/podvinci/dialog/RemoveFeedDialog.kt +++ b/app/src/main/java/ac/mdiq/podvinci/dialog/RemoveFeedDialog.kt @@ -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, 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() diff --git a/app/src/main/java/ac/mdiq/podvinci/dialog/TagSettingsDialog.kt b/app/src/main/java/ac/mdiq/podvinci/dialog/TagSettingsDialog.kt index dcb63687..d6165aef 100644 --- a/app/src/main/java/ac/mdiq/podvinci/dialog/TagSettingsDialog.kt +++ b/app/src/main/java/ac/mdiq/podvinci/dialog/TagSettingsDialog.kt @@ -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 { + override fun getChips(): List { return displayedTags?: listOf() } diff --git a/app/src/main/java/ac/mdiq/podvinci/fragment/AddFeedFragment.kt b/app/src/main/java/ac/mdiq/podvinci/fragment/AddFeedFragment.kt index 5256fe55..9043e3f0 100644 --- a/app/src/main/java/ac/mdiq/podvinci/fragment/AddFeedFragment.kt +++ b/app/src/main/java/ac/mdiq/podvinci/fragment/AddFeedFragment.kt @@ -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) } } diff --git a/app/src/main/java/ac/mdiq/podvinci/fragment/AudioPlayerFragment.kt b/app/src/main/java/ac/mdiq/podvinci/fragment/AudioPlayerFragment.kt index 7dc6d5f2..0d6f41fb 100644 --- a/app/src/main/java/ac/mdiq/podvinci/fragment/AudioPlayerFragment.kt +++ b/app/src/main/java/ac/mdiq/podvinci/fragment/AudioPlayerFragment.kt @@ -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) { diff --git a/app/src/main/java/ac/mdiq/podvinci/fragment/ChaptersFragment.kt b/app/src/main/java/ac/mdiq/podvinci/fragment/ChaptersFragment.kt index 7f9ae775..d64ab2ab 100644 --- a/app/src/main/java/ac/mdiq/podvinci/fragment/ChaptersFragment.kt +++ b/app/src/main/java/ac/mdiq/podvinci/fragment/ChaptersFragment.kt @@ -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 -> 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) } } } diff --git a/app/src/main/java/ac/mdiq/podvinci/fragment/CompletedDownloadsFragment.kt b/app/src/main/java/ac/mdiq/podvinci/fragment/CompletedDownloadsFragment.kt index cbd5f4db..30d1bc1e 100644 --- a/app/src/main/java/ac/mdiq/podvinci/fragment/CompletedDownloadsFragment.kt +++ b/app/src/main/java/ac/mdiq/podvinci/fragment/CompletedDownloadsFragment.kt @@ -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? = HashSet() - private var items: MutableList? = ArrayList() - private var adapter: CompletedDownloadsListAdapter? = null - private var recyclerView: EpisodeItemListRecyclerView? = null + private var items: MutableList = 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()) } - 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 = DBReader.getEpisodes(0, Int.MAX_VALUE, @@ -306,35 +311,35 @@ class CompletedDownloadsFragment : Fragment(), SelectableAdapter.OnSelectModeLis .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( - { result: List? -> - items = result?.toMutableList() - adapter?.setDummyViews(0) - progressBar?.visibility = View.GONE - if (result != null) adapter?.updateItems(result) + { result: List -> + 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) } } diff --git a/app/src/main/java/ac/mdiq/podvinci/fragment/CoverFragment.kt b/app/src/main/java/ac/mdiq/podvinci/fragment/CoverFragment.kt index 05388da3..12ab601f 100644 --- a/app/src/main/java/ac/mdiq/podvinci/fragment/CoverFragment.kt +++ b/app/src/main/java/ac/mdiq/podvinci/fragment/CoverFragment.kt @@ -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 { emitter: MaybeEmitter -> 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) { diff --git a/app/src/main/java/ac/mdiq/podvinci/fragment/DiscoveryFragment.kt b/app/src/main/java/ac/mdiq/podvinci/fragment/DiscoveryFragment.kt index 3ad12842..9387f26b 100644 --- a/app/src/main/java/ac/mdiq/podvinci/fragment/DiscoveryFragment.kt +++ b/app/src/main/java/ac/mdiq/podvinci/fragment/DiscoveryFragment.kt @@ -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?) { 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? -> - 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 = ArrayList(Arrays.asList(*Locale.getISOCountries())) + val countryCodeArray: List = listOf(*Locale.getISOCountries()) val countryCodeNames: MutableMap = HashMap() val countryNameCodes: MutableMap = HashMap() for (code in countryCodeArray) { @@ -221,8 +216,8 @@ class DiscoveryFragment : Fragment(), Toolbar.OnMenuItemClickListener { countryNameCodes[countryName] = code } - val countryNamesSort: List = ArrayList(countryCodeNames.values) - Collections.sort(countryNamesSort) + val countryNamesSort: MutableList = 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) diff --git a/app/src/main/java/ac/mdiq/podvinci/fragment/DownloadLogFragment.kt b/app/src/main/java/ac/mdiq/podvinci/fragment/DownloadLogFragment.kt index 2057655a..b6835665 100644 --- a/app/src/main/java/ac/mdiq/podvinci/fragment/DownloadLogFragment.kt +++ b/app/src/main/java/ac/mdiq/podvinci/fragment/DownloadLogFragment.kt @@ -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 = 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? -> if (result != null) { downloadLog = result - adapter!!.setDownloadLog(downloadLog) + adapter.setDownloadLog(downloadLog) } }, { error: Throwable? -> Log.e(TAG, Log.getStackTraceString(error)) }) } diff --git a/app/src/main/java/ac/mdiq/podvinci/fragment/EpisodesListFragment.kt b/app/src/main/java/ac/mdiq/podvinci/fragment/EpisodesListFragment.kt index 195675e1..f2c314aa 100644 --- a/app/src/main/java/ac/mdiq/podvinci/fragment/EpisodesListFragment.kt +++ b/app/src/main/java/ac/mdiq/podvinci/fragment/EpisodesListFragment.kt @@ -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 = 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()) - if (listAdapter!!.shouldSelectLazyLoadedItems()) { - var applyPage = page + 1 - var nextPage: List - do { - nextPage = loadMoreData(applyPage) - handler.handleAction(nextPage) - applyPage++ - } while (nextPage.size == EPISODES_PER_PAGE) - } + handler.handleAction(listAdapter.selectedItems.filterIsInstance()) + if (listAdapter.shouldSelectLazyLoadedItems()) { + var applyPage = page + 1 + var nextPage: List + 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) { diff --git a/app/src/main/java/ac/mdiq/podvinci/fragment/ExternalPlayerFragment.kt b/app/src/main/java/ac/mdiq/podvinci/fragment/ExternalPlayerFragment.kt index c1ac81a5..4bf2a243 100644 --- a/app/src/main/java/ac/mdiq/podvinci/fragment/ExternalPlayerFragment.kt +++ b/app/src/main/java/ac/mdiq/podvinci/fragment/ExternalPlayerFragment.kt @@ -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 { 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) } } diff --git a/app/src/main/java/ac/mdiq/podvinci/fragment/FeedInfoFragment.kt b/app/src/main/java/ac/mdiq/podvinci/fragment/FeedInfoFragment.kt index 61f8e068..6d082bb1 100644 --- a/app/src/main/java/ac/mdiq/podvinci/fragment/FeedInfoFragment.kt +++ b/app/src/main/java/ac/mdiq/podvinci/fragment/FeedInfoFragment.kt @@ -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(R.id.butShowSettings).visibility = View.INVISIBLE root.findViewById(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 = 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 } diff --git a/app/src/main/java/ac/mdiq/podvinci/fragment/FeedItemlistFragment.kt b/app/src/main/java/ac/mdiq/podvinci/fragment/FeedItemlistFragment.kt index 6b95cc88..67cb4567 100644 --- a/app/src/main/java/ac/mdiq/podvinci/fragment/FeedItemlistFragment.kt +++ b/app/src/main/java/ac/mdiq/podvinci/fragment/FeedItemlistFragment.kt @@ -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()) - adapter?.endSelectMode() + .handleAction(adapter.selectedItems.filterIsInstance()) + 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) } } diff --git a/app/src/main/java/ac/mdiq/podvinci/fragment/FeedSettingsFragment.kt b/app/src/main/java/ac/mdiq/podvinci/fragment/FeedSettingsFragment.kt index c12f3cbf..77da53bb 100644 --- a/app/src/main/java/ac/mdiq/podvinci/fragment/FeedSettingsFragment.kt +++ b/app/src/main/java/ac/mdiq/podvinci/fragment/FeedSettingsFragment.kt @@ -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(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(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(PREF_FEED_PLAYBACK_SPEED) feedPlaybackSpeedPreference!!.onPreferenceClickListener = Preference.OnPreferenceClickListener { preference: Preference -> @@ -237,7 +239,7 @@ class FeedSettingsFragment : Fragment() { findPreference(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(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("volumeReduction") volumeAdaptationPreference!!.onPreferenceChangeListener = @@ -348,7 +350,7 @@ class FeedSettingsFragment : Fragment() { } } - private fun setupNewEpisodesAction() { + @OptIn(UnstableApi::class) private fun setupNewEpisodesAction() { if (feedPreferences == null) return findPreference(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("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("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("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 diff --git a/app/src/main/java/ac/mdiq/podvinci/fragment/InboxFragment.kt b/app/src/main/java/ac/mdiq/podvinci/fragment/InboxFragment.kt index 5a06b6d7..803f61b5 100644 --- a/app/src/main/java/ac/mdiq/podvinci/fragment/InboxFragment.kt +++ b/app/src/main/java/ac/mdiq/podvinci/fragment/InboxFragment.kt @@ -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) diff --git a/app/src/main/java/ac/mdiq/podvinci/fragment/ItemDescriptionFragment.kt b/app/src/main/java/ac/mdiq/podvinci/fragment/ItemDescriptionFragment.kt index 686858e0..ea66dd22 100644 --- a/app/src/main/java/ac/mdiq/podvinci/fragment/ItemDescriptionFragment.kt +++ b/app/src/main/java/ac/mdiq/podvinci/fragment/ItemDescriptionFragment.kt @@ -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() } diff --git a/app/src/main/java/ac/mdiq/podvinci/fragment/ItemFragment.kt b/app/src/main/java/ac/mdiq/podvinci/fragment/ItemFragment.kt index 16133a59..58e65f8b 100644 --- a/app/src/main/java/ac/mdiq/podvinci/fragment/ItemFragment.kt +++ b/app/src/main/java/ac/mdiq/podvinci/fragment/ItemFragment.kt @@ -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 { this.loadInBackground() } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ result: FeedItem? -> - progbarLoading?.visibility = View.GONE + progbarLoading.visibility = View.GONE item = result onFragmentLoaded() itemsLoaded = true diff --git a/app/src/main/java/ac/mdiq/podvinci/fragment/ItemPagerFragment.kt b/app/src/main/java/ac/mdiq/podvinci/fragment/ItemPagerFragment.kt index e09dc053..6813aef3 100644 --- a/app/src/main/java/ac/mdiq/podvinci/fragment/ItemPagerFragment.kt +++ b/app/src/main/java/ac/mdiq/podvinci/fragment/ItemPagerFragment.kt @@ -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 diff --git a/app/src/main/java/ac/mdiq/podvinci/fragment/NavDrawerFragment.kt b/app/src/main/java/ac/mdiq/podvinci/fragment/NavDrawerFragment.kt index 29949df8..55507661 100644 --- a/app/src/main/java/ac/mdiq/podvinci/fragment/NavDrawerFragment.kt +++ b/app/src/main/java/ac/mdiq/podvinci/fragment/NavDrawerFragment.kt @@ -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? = 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 = 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())) // Must not modify progressBar = root.findViewById(R.id.progressBar) val navList = root.findViewById(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? -> 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 = 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 = arrayOf(HomeFragment.TAG, QueueFragment.TAG, InboxFragment.TAG, diff --git a/app/src/main/java/ac/mdiq/podvinci/fragment/OnlineSearchFragment.kt b/app/src/main/java/ac/mdiq/podvinci/fragment/OnlineSearchFragment.kt index ceec72eb..494bb608 100644 --- a/app/src/main/java/ac/mdiq/podvinci/fragment/OnlineSearchFragment.kt +++ b/app/src/main/java/ac/mdiq/podvinci/fragment/OnlineSearchFragment.kt @@ -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? -> 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) { diff --git a/app/src/main/java/ac/mdiq/podvinci/fragment/PlaybackHistoryFragment.kt b/app/src/main/java/ac/mdiq/podvinci/fragment/PlaybackHistoryFragment.kt index 415b7aca..38647c25 100644 --- a/app/src/main/java/ac/mdiq/podvinci/fragment/PlaybackHistoryFragment.kt +++ b/app/src/main/java/ac/mdiq/podvinci/fragment/PlaybackHistoryFragment.kt @@ -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) diff --git a/app/src/main/java/ac/mdiq/podvinci/fragment/QueueFragment.kt b/app/src/main/java/ac/mdiq/podvinci/fragment/QueueFragment.kt index 1e98e9d5..2d812153 100644 --- a/app/src/main/java/ac/mdiq/podvinci/fragment/QueueFragment.kt +++ b/app/src/main/java/ac/mdiq/podvinci/fragment/QueueFragment.kt @@ -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 = mutableListOf() - private var queue: MutableList? = 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()) + 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()) - 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? -> + .subscribe({ items: MutableList -> 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 } diff --git a/app/src/main/java/ac/mdiq/podvinci/fragment/QuickFeedDiscoveryFragment.kt b/app/src/main/java/ac/mdiq/podvinci/fragment/QuickFeedDiscoveryFragment.kt index 53bf2c31..75dd8038 100644 --- a/app/src/main/java/ac/mdiq/podvinci/fragment/QuickFeedDiscoveryFragment.kt +++ b/app/src/main/java/ac/mdiq/podvinci/fragment/QuickFeedDiscoveryFragment.kt @@ -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(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 -> - 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) } diff --git a/app/src/main/java/ac/mdiq/podvinci/fragment/SearchFragment.kt b/app/src/main/java/ac/mdiq/podvinci/fragment/SearchFragment.kt index 83f47d42..62932f6c 100644 --- a/app/src/main/java/ac/mdiq/podvinci/fragment/SearchFragment.kt +++ b/app/src/main/java/ac/mdiq/podvinci/fragment/SearchFragment.kt @@ -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 = 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? = 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(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()) - adapter?.endSelectMode() + .handleAction(adapter.selectedItems.filterIsInstance()) + 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?> -> - 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?> { - val query = searchView!!.query.toString() + val query = searchView.query.toString() if (query.isEmpty()) { return Pair?, List?>(emptyList(), emptyList()) } @@ -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 { diff --git a/app/src/main/java/ac/mdiq/podvinci/fragment/SubscriptionFragment.kt b/app/src/main/java/ac/mdiq/podvinci/fragment/SubscriptionFragment.kt index b9b5f4b3..c64fb89b 100644 --- a/app/src/main/java/ac/mdiq/podvinci/fragment/SubscriptionFragment.kt +++ b/app/src/main/java/ac/mdiq/podvinci/fragment/SubscriptionFragment.kt @@ -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? = null + private var listItems: List = 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()).handleAction(actionItem.id) + subscriptionAdapter.selectedItems.filterIsInstance()).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 = data.items @@ -273,23 +269,23 @@ class SubscriptionFragment : Fragment(), Toolbar.OnMenuItemClickListener, Select .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe( - { result: List -> - if (listItems != null && listItems!!.size > result.size) { + { result: List -> + 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 = ArrayList() - 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 { diff --git a/app/src/main/java/ac/mdiq/podvinci/fragment/actions/EpisodeMultiSelectActionHandler.kt b/app/src/main/java/ac/mdiq/podvinci/fragment/actions/EpisodeMultiSelectActionHandler.kt index b0095bb1..27e6faea 100644 --- a/app/src/main/java/ac/mdiq/podvinci/fragment/actions/EpisodeMultiSelectActionHandler.kt +++ b/app/src/main/java/ac/mdiq/podvinci/fragment/actions/EpisodeMultiSelectActionHandler.kt @@ -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 diff --git a/app/src/main/java/ac/mdiq/podvinci/fragment/actions/FeedMultiSelectActionHandler.kt b/app/src/main/java/ac/mdiq/podvinci/fragment/actions/FeedMultiSelectActionHandler.kt index 81c073b2..794322d0 100644 --- a/app/src/main/java/ac/mdiq/podvinci/fragment/actions/FeedMultiSelectActionHandler.kt +++ b/app/src/main/java/ac/mdiq/podvinci/fragment/actions/FeedMultiSelectActionHandler.kt @@ -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) { 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 = 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) { + @UnstableApi private fun saveFeedPreferences(preferencesConsumer: Consumer) { for (feed in selectedItems) { if (feed.preferences == null) continue preferencesConsumer.accept(feed.preferences) diff --git a/app/src/main/java/ac/mdiq/podvinci/fragment/swipeactions/DeleteSwipeAction.kt b/app/src/main/java/ac/mdiq/podvinci/fragment/swipeactions/DeleteSwipeAction.kt index f88f28aa..08dfee50 100644 --- a/app/src/main/java/ac/mdiq/podvinci/fragment/swipeactions/DeleteSwipeAction.kt +++ b/app/src/main/java/ac/mdiq/podvinci/fragment/swipeactions/DeleteSwipeAction.kt @@ -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 { diff --git a/app/src/main/java/ac/mdiq/podvinci/menuhandler/FeedItemMenuHandler.kt b/app/src/main/java/ac/mdiq/podvinci/menuhandler/FeedItemMenuHandler.kt index 24b59ad4..e40a3a98 100644 --- a/app/src/main/java/ac/mdiq/podvinci/menuhandler/FeedItemMenuHandler.kt +++ b/app/src/main/java/ac/mdiq/podvinci/menuhandler/FeedItemMenuHandler.kt @@ -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) } diff --git a/app/src/main/java/ac/mdiq/podvinci/ui/home/HomeFragment.kt b/app/src/main/java/ac/mdiq/podvinci/ui/home/HomeFragment.kt index 55ae0a7c..efce2e89 100644 --- a/app/src/main/java/ac/mdiq/podvinci/ui/home/HomeFragment.kt +++ b/app/src/main/java/ac/mdiq/podvinci/ui/home/HomeFragment.kt @@ -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)) }) } diff --git a/app/src/main/java/ac/mdiq/podvinci/ui/home/HomeSection.kt b/app/src/main/java/ac/mdiq/podvinci/ui/home/HomeSection.kt index 138ebe71..6365adb8 100644 --- a/app/src/main/java/ac/mdiq/podvinci/ui/home/HomeSection.kt +++ b/app/src/main/java/ac/mdiq/podvinci/ui/home/HomeSection.kt @@ -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? diff --git a/app/src/main/java/ac/mdiq/podvinci/ui/home/HomeSectionsSettingsDialog.kt b/app/src/main/java/ac/mdiq/podvinci/ui/home/HomeSectionsSettingsDialog.kt index 8fa85617..783a1c5d 100644 --- a/app/src/main/java/ac/mdiq/podvinci/ui/home/HomeSectionsSettingsDialog.kt +++ b/app/src/main/java/ac/mdiq/podvinci/ui/home/HomeSectionsSettingsDialog.kt @@ -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 { diff --git a/app/src/main/java/ac/mdiq/podvinci/ui/home/sections/AllowNotificationsSection.kt b/app/src/main/java/ac/mdiq/podvinci/ui/home/sections/AllowNotificationsSection.kt index 054443d6..af9b8b57 100644 --- a/app/src/main/java/ac/mdiq/podvinci/ui/home/sections/AllowNotificationsSection.kt +++ b/app/src/main/java/ac/mdiq/podvinci/ui/home/sections/AllowNotificationsSection.kt @@ -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 } } diff --git a/app/src/main/java/ac/mdiq/podvinci/ui/home/sections/DownloadsSection.kt b/app/src/main/java/ac/mdiq/podvinci/ui/home/sections/DownloadsSection.kt index 34213dbc..b1149693 100644 --- a/app/src/main/java/ac/mdiq/podvinci/ui/home/sections/DownloadsSection.kt +++ b/app/src/main/java/ac/mdiq/podvinci/ui/home/sections/DownloadsSection.kt @@ -32,17 +32,15 @@ import org.greenrobot.eventbus.ThreadMode class DownloadsSection : HomeSection() { private var adapter: EpisodeItemListAdapter? = null - private var items: List? = 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)) }) } diff --git a/app/src/main/java/ac/mdiq/podvinci/ui/home/sections/EchoSection.kt b/app/src/main/java/ac/mdiq/podvinci/ui/home/sections/EchoSection.kt index 0fc33594..64198fcd 100644 --- a/app/src/main/java/ac/mdiq/podvinci/ui/home/sections/EchoSection.kt +++ b/app/src/main/java/ac/mdiq/podvinci/ui/home/sections/EchoSection.kt @@ -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() }) } diff --git a/app/src/main/java/ac/mdiq/podvinci/ui/home/sections/EpisodesSurpriseSection.kt b/app/src/main/java/ac/mdiq/podvinci/ui/home/sections/EpisodesSurpriseSection.kt index 99402a62..1ac6f007 100644 --- a/app/src/main/java/ac/mdiq/podvinci/ui/home/sections/EpisodesSurpriseSection.kt +++ b/app/src/main/java/ac/mdiq/podvinci/ui/home/sections/EpisodesSurpriseSection.kt @@ -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 -> diff --git a/app/src/main/java/ac/mdiq/podvinci/ui/home/sections/InboxSection.kt b/app/src/main/java/ac/mdiq/podvinci/ui/home/sections/InboxSection.kt index 8dda016a..4c5f41c1 100644 --- a/app/src/main/java/ac/mdiq/podvinci/ui/home/sections/InboxSection.kt +++ b/app/src/main/java/ac/mdiq/podvinci/ui/home/sections/InboxSection.kt @@ -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)) }) } diff --git a/app/src/main/java/ac/mdiq/podvinci/ui/home/sections/QueueSection.kt b/app/src/main/java/ac/mdiq/podvinci/ui/home/sections/QueueSection.kt index 4fc10a63..e3aecf1e 100644 --- a/app/src/main/java/ac/mdiq/podvinci/ui/home/sections/QueueSection.kt +++ b/app/src/main/java/ac/mdiq/podvinci/ui/home/sections/QueueSection.kt @@ -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 diff --git a/app/src/main/java/ac/mdiq/podvinci/ui/home/sections/SubscriptionsSection.kt b/app/src/main/java/ac/mdiq/podvinci/ui/home/sections/SubscriptionsSection.kt index a954e9a6..c5f32807 100644 --- a/app/src/main/java/ac/mdiq/podvinci/ui/home/sections/SubscriptionsSection.kt +++ b/app/src/main/java/ac/mdiq/podvinci/ui/home/sections/SubscriptionsSection.kt @@ -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> { - DBReader.getStatistics(includeMarkedAsPlayed, - 0, - Long.MAX_VALUE).feedTime - } + disposable = Observable.fromCallable> + { DBReader.getStatistics(includeMarkedAsPlayed, 0, Long.MAX_VALUE).feedTime } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ statisticsData: List -> diff --git a/app/src/main/java/ac/mdiq/podvinci/view/AspectRatioVideoView.kt b/app/src/main/java/ac/mdiq/podvinci/view/AspectRatioVideoView.kt index e24357b3..e651bb82 100644 --- a/app/src/main/java/ac/mdiq/podvinci/view/AspectRatioVideoView.kt +++ b/app/src/main/java/ac/mdiq/podvinci/view/AspectRatioVideoView.kt @@ -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 diff --git a/app/src/main/java/ac/mdiq/podvinci/view/ChapterSeekBar.kt b/app/src/main/java/ac/mdiq/podvinci/view/ChapterSeekBar.kt index 09600783..d41dd336 100644 --- a/app/src/main/java/ac/mdiq/podvinci/view/ChapterSeekBar.kt +++ b/app/src/main/java/ac/mdiq/podvinci/view/ChapterSeekBar.kt @@ -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) diff --git a/app/src/main/java/ac/mdiq/podvinci/view/EmptyViewHandler.kt b/app/src/main/java/ac/mdiq/podvinci/view/EmptyViewHandler.kt index 45777ee0..ccbd2c39 100644 --- a/app/src/main/java/ac/mdiq/podvinci/view/EmptyViewHandler.kt +++ b/app/src/main/java/ac/mdiq/podvinci/view/EmptyViewHandler.kt @@ -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() diff --git a/app/src/main/java/ac/mdiq/podvinci/view/EpisodeItemListRecyclerView.kt b/app/src/main/java/ac/mdiq/podvinci/view/EpisodeItemListRecyclerView.kt index ae759b0d..2d1640b4 100644 --- a/app/src/main/java/ac/mdiq/podvinci/view/EpisodeItemListRecyclerView.kt +++ b/app/src/main/java/ac/mdiq/podvinci/view/EpisodeItemListRecyclerView.kt @@ -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) } diff --git a/app/src/main/java/ac/mdiq/podvinci/view/LiftOnScrollListener.kt b/app/src/main/java/ac/mdiq/podvinci/view/LiftOnScrollListener.kt index fc2c6935..ff8ced20 100644 --- a/app/src/main/java/ac/mdiq/podvinci/view/LiftOnScrollListener.kt +++ b/app/src/main/java/ac/mdiq/podvinci/view/LiftOnScrollListener.kt @@ -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 { diff --git a/app/src/main/java/ac/mdiq/podvinci/view/LocalDeleteModal.kt b/app/src/main/java/ac/mdiq/podvinci/view/LocalDeleteModal.kt index 59bf51f7..e6785839 100644 --- a/app/src/main/java/ac/mdiq/podvinci/view/LocalDeleteModal.kt +++ b/app/src/main/java/ac/mdiq/podvinci/view/LocalDeleteModal.kt @@ -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, - deleteCommand: Runnable - ) { + fun showLocalFeedDeleteWarningIfNecessary(context: Context, items: Iterable, + 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() } diff --git a/app/src/main/java/ac/mdiq/podvinci/view/NestedScrollableHost.kt b/app/src/main/java/ac/mdiq/podvinci/view/NestedScrollableHost.kt index 789250b0..9c405bae 100644 --- a/app/src/main/java/ac/mdiq/podvinci/view/NestedScrollableHost.kt +++ b/app/src/main/java/ac/mdiq/podvinci/view/NestedScrollableHost.kt @@ -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 diff --git a/app/src/main/java/ac/mdiq/podvinci/view/PlayButton.kt b/app/src/main/java/ac/mdiq/podvinci/view/PlayButton.kt index a4a5c70d..72f2faab 100644 --- a/app/src/main/java/ac/mdiq/podvinci/view/PlayButton.kt +++ b/app/src/main/java/ac/mdiq/podvinci/view/PlayButton.kt @@ -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() + } } } } diff --git a/app/src/main/java/ac/mdiq/podvinci/view/PlaybackSpeedSeekBar.kt b/app/src/main/java/ac/mdiq/podvinci/view/PlaybackSpeedSeekBar.kt index 0bc236ce..f9a09b79 100644 --- a/app/src/main/java/ac/mdiq/podvinci/view/PlaybackSpeedSeekBar.kt +++ b/app/src/main/java/ac/mdiq/podvinci/view/PlaybackSpeedSeekBar.kt @@ -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? = 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(R.id.butDecSpeed).setOnClickListener { v: View? -> seekBar?.progress = (seekBar?.progress ?: 0) - 2 } - findViewById(R.id.butIncSpeed).setOnClickListener { v: View? -> seekBar?.progress = (seekBar?.progress ?: 0) + 2 } + findViewById(R.id.butDecSpeed).setOnClickListener { v: View? -> seekBar.progress -= 2 } + findViewById(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?) { @@ -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(R.id.butDecSpeed).isEnabled = enabled findViewById(R.id.butIncSpeed).isEnabled = enabled } diff --git a/app/src/main/java/ac/mdiq/podvinci/view/ShownotesWebView.kt b/app/src/main/java/ac/mdiq/podvinci/view/ShownotesWebView.kt index 4242e5d9..dc8ace41 100644 --- a/app/src/main/java/ac/mdiq/podvinci/view/ShownotesWebView.kt +++ b/app/src/main/java/ac/mdiq/podvinci/view/ShownotesWebView.kt @@ -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 diff --git a/app/src/main/java/ac/mdiq/podvinci/view/ToolbarIconTintManager.kt b/app/src/main/java/ac/mdiq/podvinci/view/ToolbarIconTintManager.kt index b8953aea..54c945c5 100644 --- a/app/src/main/java/ac/mdiq/podvinci/view/ToolbarIconTintManager.kt +++ b/app/src/main/java/ac/mdiq/podvinci/view/ToolbarIconTintManager.kt @@ -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 } /** diff --git a/app/src/main/java/ac/mdiq/podvinci/view/viewholder/DownloadLogItemViewHolder.kt b/app/src/main/java/ac/mdiq/podvinci/view/viewholder/DownloadLogItemViewHolder.kt index 595ff24e..a861cd3d 100644 --- a/app/src/main/java/ac/mdiq/podvinci/view/viewholder/DownloadLogItemViewHolder.kt +++ b/app/src/main/java/ac/mdiq/podvinci/view/viewholder/DownloadLogItemViewHolder.kt @@ -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 diff --git a/app/src/main/java/ac/mdiq/podvinci/view/viewholder/EpisodeItemViewHolder.kt b/app/src/main/java/ac/mdiq/podvinci/view/viewholder/EpisodeItemViewHolder.kt index bcf2446b..93220007 100644 --- a/app/src/main/java/ac/mdiq/podvinci/view/viewholder/EpisodeItemViewHolder.kt +++ b/app/src/main/java/ac/mdiq/podvinci/view/viewholder/EpisodeItemViewHolder.kt @@ -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 } diff --git a/app/src/main/java/ac/mdiq/podvinci/view/viewholder/HorizontalItemViewHolder.kt b/app/src/main/java/ac/mdiq/podvinci/view/viewholder/HorizontalItemViewHolder.kt index 6d006e13..ce5780fa 100644 --- a/app/src/main/java/ac/mdiq/podvinci/view/viewholder/HorizontalItemViewHolder.kt +++ b/app/src/main/java/ac/mdiq/podvinci/view/viewholder/HorizontalItemViewHolder.kt @@ -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) diff --git a/core/src/main/AndroidManifest.xml b/core/src/main/AndroidManifest.xml index 2c9cbf88..5a5f22b4 100644 --- a/core/src/main/AndroidManifest.xml +++ b/core/src/main/AndroidManifest.xml @@ -9,6 +9,8 @@ + + @@ -18,10 +20,11 @@ android:supportsRtl="true"> + android:foregroundServiceType="mediaPlayback" + android:label="@string/app_name" + android:enabled="true" + android:exported="false" + tools:ignore="ExportedService"> diff --git a/core/src/main/java/ac/mdiq/podvinci/core/service/playback/ExoPlayerWrapper.kt b/core/src/main/java/ac/mdiq/podvinci/core/service/playback/ExoPlayerWrapper.kt index 75c0f2bb..a2cf110e 100644 --- a/core/src/main/java/ac/mdiq/podvinci/core/service/playback/ExoPlayerWrapper.kt +++ b/core/src/main/java/ac/mdiq/podvinci/core/service/playback/ExoPlayerWrapper.kt @@ -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 diff --git a/core/src/main/java/ac/mdiq/podvinci/core/service/playback/PlaybackService.kt b/core/src/main/java/ac/mdiq/podvinci/core/service/playback/PlaybackService.kt index 545c4783..4875e314 100644 --- a/core/src/main/java/ac/mdiq/podvinci/core/service/playback/PlaybackService.kt +++ b/core/src/main/java/ac/mdiq/podvinci/core/service/playback/PlaybackService.kt @@ -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() } diff --git a/core/src/main/java/ac/mdiq/podvinci/core/service/playback/PlaybackServiceStateManager.kt b/core/src/main/java/ac/mdiq/podvinci/core/service/playback/PlaybackServiceStateManager.kt index ee6de9ca..1851b944 100644 --- a/core/src/main/java/ac/mdiq/podvinci/core/service/playback/PlaybackServiceStateManager.kt +++ b/core/src/main/java/ac/mdiq/podvinci/core/service/playback/PlaybackServiceStateManager.kt @@ -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 } diff --git a/core/src/main/java/ac/mdiq/podvinci/core/service/playback/PlaybackServiceTaskManager.kt b/core/src/main/java/ac/mdiq/podvinci/core/service/playback/PlaybackServiceTaskManager.kt index 0f47a846..2b07b92f 100644 --- a/core/src/main/java/ac/mdiq/podvinci/core/service/playback/PlaybackServiceTaskManager.kt +++ b/core/src/main/java/ac/mdiq/podvinci/core/service/playback/PlaybackServiceTaskManager.kt @@ -242,10 +242,8 @@ class PlaybackServiceTaskManager(private val context: Context, cancelWidgetUpdater() disableSleepTimer() - if (chapterLoaderFuture != null) { - chapterLoaderFuture!!.dispose() - chapterLoaderFuture = null - } + chapterLoaderFuture?.dispose() + chapterLoaderFuture = null } /** diff --git a/core/src/main/java/ac/mdiq/podvinci/core/storage/DBReader.kt b/core/src/main/java/ac/mdiq/podvinci/core/storage/DBReader.kt index a0c5b53f..2522d338 100644 --- a/core/src/main/java/ac/mdiq/podvinci/core/storage/DBReader.kt +++ b/core/src/main/java/ac/mdiq/podvinci/core/storage/DBReader.kt @@ -646,7 +646,11 @@ object DBReader { @JvmStatic fun loadChaptersOfFeedItem(item: FeedItem): List? { 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 { diff --git a/core/src/main/java/ac/mdiq/podvinci/core/storage/DBWriter.kt b/core/src/main/java/ac/mdiq/podvinci/core/storage/DBWriter.kt index 09812798..2f960896 100644 --- a/core/src/main/java/ac/mdiq/podvinci/core/storage/DBWriter.kt +++ b/core/src/main/java/ac/mdiq/podvinci/core/storage/DBWriter.kt @@ -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) diff --git a/core/src/main/java/ac/mdiq/podvinci/core/util/FeedItemUtil.kt b/core/src/main/java/ac/mdiq/podvinci/core/util/FeedItemUtil.kt index 34f0e7c1..344be077 100644 --- a/core/src/main/java/ac/mdiq/podvinci/core/util/FeedItemUtil.kt +++ b/core/src/main/java/ac/mdiq/podvinci/core/util/FeedItemUtil.kt @@ -10,7 +10,7 @@ object FeedItemUtil { fun indexOfItemWithId(items: List, 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, 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?): LongArray { - if (items == null || items.isEmpty()) { + if (items.isNullOrEmpty()) { return LongArray(0) } val result = LongArray(items.size) diff --git a/core/src/main/java/ac/mdiq/podvinci/core/util/playback/PlaybackController.kt b/core/src/main/java/ac/mdiq/podvinci/core/util/playback/PlaybackController.kt index f5d48b1a..b0db8ae4 100644 --- a/core/src/main/java/ac/mdiq/podvinci/core/util/playback/PlaybackController.kt +++ b/core/src/main/java/ac/mdiq/podvinci/core/util/playback/PlaybackController.kt @@ -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 diff --git a/model/src/main/java/ac/mdiq/podvinci/model/feed/Chapter.kt b/model/src/main/java/ac/mdiq/podvinci/model/feed/Chapter.kt index 2f0175a6..70752a9f 100644 --- a/model/src/main/java/ac/mdiq/podvinci/model/feed/Chapter.kt +++ b/model/src/main/java/ac/mdiq/podvinci/model/feed/Chapter.kt @@ -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]" } }