diff --git a/README.md b/README.md index 887e883..c081ecc 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,9 @@ -This project is a full conversion of [NewPipe](https://github.com/TeamNewPipe/NewPipe) (cloned as of Feb 5 2024) to Kotlin. Most existing dependencies are updated. Built SDK is 34 and target SDK is 33. Code and null safety issues are resolved after the conversion. Other than that, there is no logic or structural change from the original project. +This project is a full conversion of [NewPipe](https://github.com/TeamNewPipe/NewPipe) (cloned as of Feb 5 2024) to Kotlin. Most existing dependencies are updated. ~Built SDK is 34 and target SDK is 33.~ Code and null safety issues are resolved after the conversion. ~Other than that, there is no logic or structural change from the original project.~ Introductory migration to androidx.media3 and removal of exoplayer2. Though not having been thoroughly tested, things appear to work. +Built SDK is 34 and target SDK is 34 + For non-intrusive testing, the appId and name is changed to NewPipeX. See more changes and issues in [changelog](changelog.md) diff --git a/app/build.gradle b/app/build.gradle index 0e2d710..e377a80 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -19,9 +19,10 @@ android { applicationId "org.schabi.newpipex" resValue "string", "app_name", "NewPipeX" minSdk 24 - targetSdk 33 - versionCode 998 - versionName "0.26.3" + //noinspection EditedTargetSdkVersion + targetSdk 34 + versionCode 999 + versionName "0.26.4" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/app/src/main/java/androidx/fragment/app/FragmentStatePagerAdapterMenuWorkaround.kt b/app/src/main/java/androidx/fragment/app/FragmentStatePagerAdapterMenuWorkaround.kt index 5a7ad89..30b5cac 100644 --- a/app/src/main/java/androidx/fragment/app/FragmentStatePagerAdapterMenuWorkaround.kt +++ b/app/src/main/java/androidx/fragment/app/FragmentStatePagerAdapterMenuWorkaround.kt @@ -68,6 +68,7 @@ abstract class FragmentStatePagerAdapterMenuWorkaround ) : PagerAdapter() { @Retention(AnnotationRetention.SOURCE) @IntDef(Behavior.BEHAVIOR_SET_USER_VISIBLE_HINT, Behavior.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) + protected annotation class Behavior { companion object { const val BEHAVIOR_SET_USER_VISIBLE_HINT = 0 @@ -96,7 +97,7 @@ abstract class FragmentStatePagerAdapterMenuWorkaround */ @Deprecated("""use {@link #FragmentStatePagerAdapterMenuWorkaround(FragmentManager, int)} with {@link #BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT}""") - constructor(fm: FragmentManager) : this(fm, BEHAVIOR_SET_USER_VISIBLE_HINT) + constructor(fm: FragmentManager) : this(fm, Behavior.BEHAVIOR_SET_USER_VISIBLE_HINT) /** * @param position the position of the item you want @@ -142,14 +143,14 @@ abstract class FragmentStatePagerAdapterMenuWorkaround mFragments.add(null) } fragment.setMenuVisibility(false) - if (mBehavior == BEHAVIOR_SET_USER_VISIBLE_HINT) { + if (mBehavior == Behavior.BEHAVIOR_SET_USER_VISIBLE_HINT) { fragment.userVisibleHint = false } mFragments[position] = fragment mCurTransaction!!.add(container.id, fragment) - if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { + if (mBehavior == Behavior.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { mCurTransaction!!.setMaxLifecycle(fragment, Lifecycle.State.STARTED) } @@ -189,7 +190,7 @@ abstract class FragmentStatePagerAdapterMenuWorkaround if (fragment !== mCurrentPrimaryItem) { if (mCurrentPrimaryItem != null) { mCurrentPrimaryItem!!.setMenuVisibility(false) - if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { + if (mBehavior == Behavior.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction() } @@ -199,7 +200,7 @@ abstract class FragmentStatePagerAdapterMenuWorkaround } } fragment.setMenuVisibility(true) - if (mBehavior == BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { + if (mBehavior == Behavior.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { if (mCurTransaction == null) { mCurTransaction = mFragmentManager.beginTransaction() } @@ -309,12 +310,12 @@ abstract class FragmentStatePagerAdapterMenuWorkaround * * @see .FragmentStatePagerAdapterMenuWorkaround */ - @Deprecated("""This behavior relies on the deprecated - {@link Fragment#setUserVisibleHint(boolean)} API. Use - {@link #BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT} to switch to its replacement, - {@link FragmentTransaction#setMaxLifecycle}. - """) - const val BEHAVIOR_SET_USER_VISIBLE_HINT: Int = 0 +// @Deprecated("""This behavior relies on the deprecated +// {@link Fragment#setUserVisibleHint(boolean)} API. Use +// {@link #BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT} to switch to its replacement, +// {@link FragmentTransaction#setMaxLifecycle}. +// """) +// const val BEHAVIOR_SET_USER_VISIBLE_HINT: Int = 0 /** * Indicates that only the current fragment will be in the [Lifecycle.State.RESUMED] @@ -322,6 +323,6 @@ abstract class FragmentStatePagerAdapterMenuWorkaround * * @see .FragmentStatePagerAdapterMenuWorkaround */ - const val BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT: Int = 1 +// const val BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT: Int = 1 } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.kt b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.kt index adbbdf4..7e5eca5 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/fragments/MainFragment.kt @@ -9,9 +9,11 @@ import android.util.Log import android.view.* import android.widget.RelativeLayout import androidx.annotation.ColorInt +import androidx.annotation.OptIn import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentManager import androidx.fragment.app.FragmentStatePagerAdapterMenuWorkaround +import androidx.media3.common.util.UnstableApi import androidx.preference.PreferenceManager import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayout.OnTabSelectedListener @@ -137,7 +139,7 @@ class MainFragment : BaseFragment(), OnTabSelectedListener { supportActionBar?.setDisplayHomeAsUpEnabled(false) } - override fun onOptionsItemSelected(item: MenuItem): Boolean { + @OptIn(UnstableApi::class) override fun onOptionsItemSelected(item: MenuItem): Boolean { if (item.itemId == R.id.action_search) { try { openSearchFragment(fM!!, getSelectedServiceId(requireActivity()), "") @@ -235,9 +237,7 @@ class MainFragment : BaseFragment(), OnTabSelectedListener { updateTitleForTab(tab.position) } - class SelectedTabsPagerAdapter(private val context: Context, - fragmentManager: FragmentManager, - tabsList: List) + class SelectedTabsPagerAdapter(private val context: Context, fragmentManager: FragmentManager, tabsList: List) : FragmentStatePagerAdapterMenuWorkaround(fragmentManager, Behavior.BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) { private val internalTabsList: List = ArrayList(tabsList) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.kt b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.kt index 0fde6e1..a7ca5cb 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.kt @@ -37,6 +37,7 @@ import androidx.core.content.ContextCompat import androidx.preference.PreferenceManager import androidx.media3.common.PlaybackException import androidx.media3.common.PlaybackParameters +import androidx.media3.common.util.UnstableApi import com.google.android.material.appbar.AppBarLayout import com.google.android.material.bottomsheet.BottomSheetBehavior import com.google.android.material.bottomsheet.BottomSheetBehavior.BottomSheetCallback @@ -140,7 +141,7 @@ import kotlin.math.abs import kotlin.math.max import kotlin.math.min -class VideoDetailFragment +@UnstableApi class VideoDetailFragment : BaseStateFragment(), BackPressable, PlayerServiceExtendedEventListener, OnKeyDownListener { // tabs private var showComments = false @@ -211,8 +212,8 @@ class VideoDetailFragment /*////////////////////////////////////////////////////////////////////////// // Views ////////////////////////////////////////////////////////////////////////// */ - private var binding: FragmentVideoDetailBinding? = null - + private var binding_: FragmentVideoDetailBinding? = null + private val binding get() = binding_!! private var pageAdapter: TabAdapter? = null private var settingsContentObserver: ContentObserver? = null @@ -291,9 +292,9 @@ class VideoDetailFragment } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { - binding = FragmentVideoDetailBinding.inflate(inflater, container, false) + binding_ = FragmentVideoDetailBinding.inflate(inflater, container, false) Log.d(TAG, "onCreateView") - return binding!!.root + return binding.root } override fun onPause() { @@ -303,7 +304,7 @@ class VideoDetailFragment restoreDefaultBrightness() PreferenceManager.getDefaultSharedPreferences(requireContext()) .edit() - .putString(getString(R.string.stream_info_selected_tab_key), pageAdapter!!.getItemTitle(binding!!.viewPager.currentItem)) + .putString(getString(R.string.stream_info_selected_tab_key), pageAdapter!!.getItemTitle(binding.viewPager.currentItem)) .apply() } @@ -372,7 +373,7 @@ class VideoDetailFragment override fun onDestroyView() { super.onDestroyView() - binding = null + binding_ = null } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { @@ -391,10 +392,10 @@ class VideoDetailFragment // OnClick ////////////////////////////////////////////////////////////////////////// */ private fun setOnClickListeners() { - binding!!.detailTitleRootLayout.setOnClickListener { v: View? -> toggleTitleAndSecondaryControls() } - binding!!.detailUploaderRootLayout.setOnClickListener(makeOnClickListener { info: StreamInfo -> - if (TextUtils.isEmpty(info.subChannelUrl)) { - if (!TextUtils.isEmpty(info.uploaderUrl)) { + binding.detailTitleRootLayout.setOnClickListener { v: View? -> toggleTitleAndSecondaryControls() } + binding.detailUploaderRootLayout.setOnClickListener(makeOnClickListener { info: StreamInfo -> + if (info.subChannelUrl.isEmpty()) { + if (info.uploaderUrl.isNotEmpty()) { openChannel(info.uploaderUrl, info.uploaderName) } @@ -405,7 +406,7 @@ class VideoDetailFragment openChannel(info.subChannelUrl, info.subChannelName) } }) - binding!!.detailThumbnailRootLayout.setOnClickListener { v: View? -> + binding.detailThumbnailRootLayout.setOnClickListener { v: View? -> autoPlayEnabled = true // forcefully start playing // FIXME Workaround #7427 if (isPlayerAvailable) { @@ -414,40 +415,42 @@ class VideoDetailFragment openVideoPlayerAutoFullscreen() } - binding!!.detailControlsBackground.setOnClickListener { v: View? -> openBackgroundPlayer(false) } - binding!!.detailControlsPopup.setOnClickListener { v: View? -> openPopupPlayer(false) } - binding!!.detailControlsPlaylistAppend.setOnClickListener(makeOnClickListener { info: StreamInfo? -> + binding.detailControlsBackground.setOnClickListener { v: View? -> openBackgroundPlayer(false) } + binding.detailControlsPopup.setOnClickListener { v: View? -> openPopupPlayer(false) } + binding.detailControlsPlaylistAppend.setOnClickListener(makeOnClickListener { info: StreamInfo? -> if (fM != null && currentInfo != null) { val fragment = parentFragmentManager.findFragmentById(R.id.fragment_holder) // commit previous pending changes to database - if (fragment is LocalPlaylistFragment) { - fragment.commitChanges() - } else if (fragment is MainFragment) { - fragment.commitPlaylistTabs() + when (fragment) { + is LocalPlaylistFragment -> { + fragment.commitChanges() + } + is MainFragment -> { + fragment.commitPlaylistTabs() + } } - disposables.add(PlaylistDialog.createCorrespondingDialog(requireContext(), - java.util.List.of(StreamEntity(info!!)) + disposables.add(PlaylistDialog.createCorrespondingDialog(requireContext(), listOf(StreamEntity(info!!)) ) { dialog -> dialog.show(parentFragmentManager, TAG) }) } }) - binding!!.detailControlsDownload.setOnClickListener { v: View? -> + binding.detailControlsDownload.setOnClickListener { v: View? -> if (checkStoragePermissions(requireActivity(), PermissionHelper.DOWNLOAD_DIALOG_REQUEST_CODE)) { openDownloadDialog() } } - binding!!.detailControlsShare.setOnClickListener(makeOnClickListener { info: StreamInfo -> + binding.detailControlsShare.setOnClickListener(makeOnClickListener { info: StreamInfo -> shareText(requireContext(), info.name, info.url, info.thumbnails) }) - binding!!.detailControlsOpenInBrowser.setOnClickListener(makeOnClickListener { info: StreamInfo -> + binding.detailControlsOpenInBrowser.setOnClickListener(makeOnClickListener { info: StreamInfo -> openUrlInBrowser(requireContext(), info.url) }) - binding!!.detailControlsPlayWithKodi.setOnClickListener(makeOnClickListener { info: StreamInfo -> + binding.detailControlsPlayWithKodi.setOnClickListener(makeOnClickListener { info: StreamInfo -> playWithKore(requireContext(), Uri.parse(info.url)) }) if (DEBUG) { - binding!!.detailControlsCrashThePlayer.setOnClickListener { v: View? -> + binding.detailControlsCrashThePlayer.setOnClickListener { v: View? -> VideoDetailPlayerCrasher.onCrashThePlayer(requireContext(), player) } } @@ -455,14 +458,15 @@ class VideoDetailFragment val overlayListener = View.OnClickListener { v: View? -> bottomSheetBehavior?.setState(BottomSheetBehavior.STATE_EXPANDED) } - binding!!.overlayThumbnail.setOnClickListener(overlayListener) - binding!!.overlayMetadataLayout.setOnClickListener(overlayListener) - binding!!.overlayButtonsLayout.setOnClickListener(overlayListener) - binding!!.overlayCloseButton.setOnClickListener { v: View? -> - bottomSheetBehavior?.setState(BottomSheetBehavior.STATE_HIDDEN) + binding.overlayThumbnail.setOnClickListener(overlayListener) + binding.overlayMetadataLayout.setOnClickListener(overlayListener) + binding.overlayButtonsLayout.setOnClickListener(overlayListener) + binding.overlayCloseButton.setOnClickListener { v: View? -> +// bottomSheetBehavior?.setState(BottomSheetBehavior.STATE_HIDDEN) + bottomSheetBehavior?.setState(BottomSheetBehavior.STATE_COLLAPSED) } - binding!!.overlayPlayQueueButton.setOnClickListener { v: View? -> openPlayQueue(requireContext()) } - binding!!.overlayPlayPauseButton.setOnClickListener { v: View? -> + binding.overlayPlayQueueButton.setOnClickListener { v: View? -> openPlayQueue(requireContext()) } + binding.overlayPlayPauseButton.setOnClickListener { v: View? -> if (playerIsNotStopped()) { player!!.playPause() player!!.UIs().get(VideoPlayerUi::class.java).ifPresent { ui: VideoPlayerUi -> ui.hideControls(0, 0) } @@ -484,38 +488,35 @@ class VideoDetailFragment } private fun setOnLongClickListeners() { - binding!!.detailTitleRootLayout.setOnLongClickListener(makeOnLongClickListener { info: StreamInfo? -> - copyToClipboard(requireContext(), - binding!!.detailVideoTitleView.text.toString()) + binding.detailTitleRootLayout.setOnLongClickListener(makeOnLongClickListener { info: StreamInfo? -> + copyToClipboard(requireContext(), binding.detailVideoTitleView.text.toString()) }) - binding!!.detailUploaderRootLayout.setOnLongClickListener(makeOnLongClickListener { info: StreamInfo -> - if (TextUtils.isEmpty(info.subChannelUrl)) { + binding.detailUploaderRootLayout.setOnLongClickListener(makeOnLongClickListener { info: StreamInfo -> + if (info.subChannelUrl.isEmpty()) { Log.w(TAG, "Can't open parent channel because we got no parent channel URL") } else { openChannel(info.uploaderUrl, info.uploaderName) } }) - binding!!.detailControlsBackground.setOnLongClickListener(makeOnLongClickListener { info: StreamInfo? -> + binding.detailControlsBackground.setOnLongClickListener(makeOnLongClickListener { info: StreamInfo? -> openBackgroundPlayer(true) }) - binding!!.detailControlsPopup.setOnLongClickListener(makeOnLongClickListener { info: StreamInfo? -> + binding.detailControlsPopup.setOnLongClickListener(makeOnLongClickListener { info: StreamInfo? -> openPopupPlayer(true) }) - binding!!.detailControlsDownload.setOnLongClickListener(makeOnLongClickListener { info: StreamInfo? -> + binding.detailControlsDownload.setOnLongClickListener(makeOnLongClickListener { info: StreamInfo? -> openDownloads(requireActivity()) }) val overlayListener = makeOnLongClickListener { info: StreamInfo -> openChannel(info.uploaderUrl, info.uploaderName) } - binding!!.overlayThumbnail.setOnLongClickListener(overlayListener) - binding!!.overlayMetadataLayout.setOnLongClickListener(overlayListener) + binding.overlayThumbnail.setOnLongClickListener(overlayListener) + binding.overlayMetadataLayout.setOnLongClickListener(overlayListener) } private fun makeOnLongClickListener(consumer: Consumer): OnLongClickListener { return OnLongClickListener { v: View? -> - if (isLoading.get() || currentInfo == null) { - return@OnLongClickListener false - } + if (isLoading.get() || currentInfo == null) return@OnLongClickListener false consumer.accept(currentInfo!!) true } @@ -523,24 +524,21 @@ class VideoDetailFragment private fun openChannel(subChannelUrl: String, subChannelName: String) { try { - openChannelFragment(fM!!, currentInfo!!.serviceId, - subChannelUrl, subChannelName) + openChannelFragment(fM!!, currentInfo!!.serviceId, subChannelUrl, subChannelName) } catch (e: Exception) { showUiErrorSnackbar(this, "Opening channel fragment", e) } } private fun toggleTitleAndSecondaryControls() { - if (binding!!.detailSecondaryControlPanel.visibility == View.GONE) { - binding!!.detailVideoTitleView.maxLines = 10 - binding!!.detailToggleSecondaryControlsView - .animateRotation(VideoPlayerUi.DEFAULT_CONTROLS_DURATION, 180) - binding!!.detailSecondaryControlPanel.visibility = View.VISIBLE + if (binding.detailSecondaryControlPanel.visibility == View.GONE) { + binding.detailVideoTitleView.maxLines = 10 + binding.detailToggleSecondaryControlsView.animateRotation(VideoPlayerUi.DEFAULT_CONTROLS_DURATION, 180) + binding.detailSecondaryControlPanel.visibility = View.VISIBLE } else { - binding!!.detailVideoTitleView.maxLines = 1 - binding!!.detailToggleSecondaryControlsView - .animateRotation(VideoPlayerUi.DEFAULT_CONTROLS_DURATION, 0) - binding!!.detailSecondaryControlPanel.visibility = View.GONE + binding.detailVideoTitleView.maxLines = 1 + binding.detailToggleSecondaryControlsView.animateRotation(VideoPlayerUi.DEFAULT_CONTROLS_DURATION, 0) + binding.detailSecondaryControlPanel.visibility = View.GONE } // view pager height has changed, update the tab layout updateTabLayoutVisibility() @@ -554,19 +552,17 @@ class VideoDetailFragment super.initViews(rootView, savedInstanceState) pageAdapter = TabAdapter(childFragmentManager) - binding!!.viewPager.adapter = pageAdapter - binding!!.tabLayout.setupWithViewPager(binding!!.viewPager) + binding.viewPager.adapter = pageAdapter + binding.tabLayout.setupWithViewPager(binding.viewPager) - binding!!.detailThumbnailRootLayout.requestFocus() + binding.detailThumbnailRootLayout.requestFocus() - binding!!.detailControlsPlayWithKodi.visibility = if (shouldShowPlayWithKodi(requireContext(), serviceId) + binding.detailControlsPlayWithKodi.visibility = if (shouldShowPlayWithKodi(requireContext(), serviceId) ) View.VISIBLE else View.GONE - binding!!.detailControlsCrashThePlayer.visibility = + binding.detailControlsCrashThePlayer.visibility = if (DEBUG && PreferenceManager.getDefaultSharedPreferences(requireContext()) - .getBoolean(getString(R.string.show_crash_the_player_key), false) - ) View.VISIBLE - else View.GONE + .getBoolean(getString(R.string.show_crash_the_player_key), false)) View.VISIBLE else View.GONE accommodateForTvAndDesktopMode() } @@ -578,19 +574,17 @@ class VideoDetailFragment setOnLongClickListeners() val controlsTouchListener = OnTouchListener { view: View?, motionEvent: MotionEvent -> - if (motionEvent.action == MotionEvent.ACTION_DOWN - && shouldShowHoldToAppendTip(requireActivity())) { - binding!!.touchAppendDetail.animate(true, - 250, - AnimationType.ALPHA, - 0) { binding!!.touchAppendDetail.animate(false, 1500, AnimationType.ALPHA, 1000) } + if (motionEvent.action == MotionEvent.ACTION_DOWN && shouldShowHoldToAppendTip(requireActivity())) { + binding.touchAppendDetail.animate(true, 250, AnimationType.ALPHA, 0) { + binding.touchAppendDetail.animate(false, 1500, AnimationType.ALPHA, 1000) + } } false } - binding!!.detailControlsBackground.setOnTouchListener(controlsTouchListener) - binding!!.detailControlsPopup.setOnTouchListener(controlsTouchListener) + binding.detailControlsBackground.setOnTouchListener(controlsTouchListener) + binding.detailControlsPopup.setOnTouchListener(controlsTouchListener) - binding!!.appBarLayout.addOnOffsetChangedListener { layout: AppBarLayout?, verticalOffset: Int -> + binding.appBarLayout.addOnOffsetChangedListener { layout: AppBarLayout?, verticalOffset: Int -> // prevent useless updates to tab layout visibility if nothing changed if (verticalOffset != lastAppBarVerticalOffset) { lastAppBarVerticalOffset = verticalOffset @@ -608,8 +602,7 @@ class VideoDetailFragment } override fun onKeyDown(keyCode: Int): Boolean { - return (isPlayerAvailable - && player!!.UIs().get(VideoPlayerUi::class.java) + return (isPlayerAvailable && player!!.UIs().get(VideoPlayerUi::class.java) .map { playerUi: VideoPlayerUi -> playerUi.onKeyDown(keyCode) }.orElse(false)) } @@ -629,8 +622,7 @@ class VideoDetailFragment } // If we have something in history of played items we replay it here - if (isPlayerAvailable && player!!.playQueue != null && player!!.videoPlayerSelected() - && player!!.playQueue!!.previous()) { + if (isPlayerAvailable && player!!.playQueue != null && player!!.videoPlayerSelected() && player!!.playQueue!!.previous()) { return true // no code here, as previous() was used in the if } @@ -692,7 +684,7 @@ class VideoDetailFragment Handler(Looper.getMainLooper()).postDelayed({ if (activity == null) return@postDelayed // Data can already be drawn, don't spend time twice - if (info!!.name == binding!!.detailVideoTitleView.text.toString()) return@postDelayed + if (info!!.name == binding.detailVideoTitleView.text.toString()) return@postDelayed prepareAndHandleInfo(info, scrollToTop) }, delay) } @@ -770,7 +762,7 @@ class VideoDetailFragment ////////////////////////////////////////////////////////////////////////// */ private fun initTabs() { if (pageAdapter!!.count != 0) { - selectedTabTag = pageAdapter!!.getItemTitle(binding!!.viewPager.currentItem) + selectedTabTag = pageAdapter!!.getItemTitle(binding.viewPager.currentItem) } pageAdapter!!.clearAllItems() tabIcons.clear() @@ -782,7 +774,7 @@ class VideoDetailFragment tabContentDescriptions.add(R.string.comments_tab_description) } - if (showRelatedItems && binding!!.relatedItemsLayout == null) { + if (showRelatedItems && binding.relatedItemsLayout == null) { // temp empty fragment. will be updated in handleResult pageAdapter!!.addFragment(newInstance(false), RELATED_TAB_TAG) tabIcons.add(R.drawable.ic_art_track) @@ -804,7 +796,7 @@ class VideoDetailFragment if (pageAdapter!!.count >= 2) { val position = pageAdapter!!.getItemPositionByTitle(selectedTabTag!!) if (position != -1) { - binding!!.viewPager.currentItem = position + binding.viewPager.currentItem = position } updateTabIconsAndContentDescriptions() } @@ -820,7 +812,7 @@ class VideoDetailFragment */ private fun updateTabIconsAndContentDescriptions() { for (i in tabIcons.indices) { - val tab = binding!!.tabLayout.getTabAt(i) + val tab = binding.tabLayout.getTabAt(i) tab?.setIcon(tabIcons[i]) tab?.setContentDescription(tabContentDescriptions[i]) } @@ -828,13 +820,13 @@ class VideoDetailFragment private fun updateTabs(info: StreamInfo) { if (showRelatedItems) { - if (binding!!.relatedItemsLayout == null) { // phone + if (binding.relatedItemsLayout == null) { // phone pageAdapter!!.updateItem(RELATED_TAB_TAG, RelatedItemsFragment.getInstance(info)) } else { // tablet + TV childFragmentManager.beginTransaction() .replace(R.id.relatedItemsLayout, RelatedItemsFragment.getInstance(info)) .commitAllowingStateLoss() - binding!!.relatedItemsLayout!!.visibility = if (isFullscreen) View.GONE else View.VISIBLE + binding.relatedItemsLayout!!.visibility = if (isFullscreen) View.GONE else View.VISIBLE } } @@ -842,7 +834,7 @@ class VideoDetailFragment pageAdapter!!.updateItem(DESCRIPTION_TAB_TAG, DescriptionFragment(info)) } - binding!!.viewPager.visibility = View.VISIBLE + binding.viewPager.visibility = View.VISIBLE // make sure the tab layout is visible updateTabLayoutVisibility() pageAdapter!!.notifyDataSetUpdate() @@ -864,17 +856,17 @@ class VideoDetailFragment //If binding is null we do not need to and should not do anything with its object(s) if (binding == null) return - if (pageAdapter!!.count < 2 || binding!!.viewPager.visibility != View.VISIBLE) { + if (pageAdapter!!.count < 2 || binding.viewPager.visibility != View.VISIBLE) { // hide tab layout if there is only one tab or if the view pager is also hidden - binding!!.tabLayout.visibility = View.GONE + binding.tabLayout.visibility = View.GONE } else { // call `post()` to be sure `viewPager.getHitRect()` // is up to date and not being currently recomputed - binding!!.tabLayout.post { + binding.tabLayout.post { val activity = getActivity() if (activity != null) { val pagerHitRect = Rect() - binding!!.viewPager.getHitRect(pagerHitRect) + binding.viewPager.getHitRect(pagerHitRect) val height = getWindowHeight(activity.windowManager) val viewPagerVisibleHeight = height - pagerHitRect.top @@ -883,12 +875,11 @@ class VideoDetailFragment if (viewPagerVisibleHeight > tabLayoutHeight * 2) { // no translation at all when viewPagerVisibleHeight > tabLayout.height * 3 - binding!!.tabLayout.translationY = - max(0.0, (tabLayoutHeight * 3 - viewPagerVisibleHeight).toDouble()).toFloat() - binding!!.tabLayout.visibility = View.VISIBLE + binding.tabLayout.translationY = max(0.0, (tabLayoutHeight * 3 - viewPagerVisibleHeight).toDouble()).toFloat() + binding.tabLayout.visibility = View.VISIBLE } else { // view pager is not visible enough - binding!!.tabLayout.visibility = View.GONE + binding.tabLayout.visibility = View.GONE } } } @@ -896,7 +887,7 @@ class VideoDetailFragment } fun scrollToTop() { - binding!!.appBarLayout.setExpanded(true, true) + binding.appBarLayout.setExpanded(true, true) // notify tab layout of scrolling updateTabLayoutVisibility() } @@ -907,8 +898,8 @@ class VideoDetailFragment // unexpand the app bar only if scrolling to the comment succeeded if (fragment.scrollToComment(comment)) { - binding!!.appBarLayout.setExpanded(false, false) - binding!!.viewPager.setCurrentItem(commentsTabPos, false) + binding.appBarLayout.setExpanded(false, false) + binding.viewPager.setCurrentItem(commentsTabPos, false) } } @@ -1117,7 +1108,7 @@ class VideoDetailFragment if (binding != null) { // prevent from re-adding a view multiple times playerUi.removeViewFromParent() - binding!!.playerPlaceholder.addView(playerUi.binding.root) + binding.playerPlaceholder.addView(playerUi.binding.root) playerUi.setupVideoSurfaceIfNeeded() } } @@ -1132,8 +1123,8 @@ class VideoDetailFragment private fun makeDefaultHeightForVideoPlaceholder() { if (view == null) return - binding!!.playerPlaceholder.layoutParams.height = FrameLayout.LayoutParams.MATCH_PARENT - binding!!.playerPlaceholder.requestLayout() + binding.playerPlaceholder.layoutParams.height = FrameLayout.LayoutParams.MATCH_PARENT + binding.playerPlaceholder.requestLayout() } private val preDrawListener: ViewTreeObserver.OnPreDrawListener = object : ViewTreeObserver.OnPreDrawListener { @@ -1178,8 +1169,8 @@ class VideoDetailFragment } private fun setHeightThumbnail(newHeight: Int, metrics: DisplayMetrics) { - binding!!.detailThumbnailImageView.layoutParams = FrameLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, newHeight) - binding!!.detailThumbnailImageView.minimumHeight = newHeight + binding.detailThumbnailImageView.layoutParams = FrameLayout.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, newHeight) + binding.detailThumbnailImageView.minimumHeight = newHeight if (isPlayerAvailable) { val maxHeight = (metrics.heightPixels * MAX_PLAYER_HEIGHT).toInt() player!!.UIs().get(VideoPlayerUi::class.java).ifPresent { ui: VideoPlayerUi -> @@ -1189,7 +1180,7 @@ class VideoDetailFragment } private fun showContent() { - binding!!.detailContentRootHiding.visibility = View.VISIBLE + binding.detailContentRootHiding.visibility = View.VISIBLE } protected fun setInitialData(newServiceId: Int, newUrl: String?, newTitle: String, newPlayQueue: PlayQueue?) { @@ -1202,9 +1193,9 @@ class VideoDetailFragment private fun setErrorImage(imageResource: Int) { if (binding == null || activity == null) return - binding!!.detailThumbnailImageView.setImageDrawable(AppCompatResources.getDrawable(requireContext(), imageResource)) - binding!!.detailThumbnailImageView.animate(false, 0, AnimationType.ALPHA, 0) { - binding!!.detailThumbnailImageView.animate(true, 500) + binding.detailThumbnailImageView.setImageDrawable(AppCompatResources.getDrawable(requireContext(), imageResource)) + binding.detailThumbnailImageView.animate(false, 0, AnimationType.ALPHA, 0) { + binding.detailThumbnailImageView.animate(true, 500) } } @@ -1213,11 +1204,11 @@ class VideoDetailFragment setErrorImage(R.drawable.not_available_monkey) // hide related streams for tablets - binding!!.relatedItemsLayout?.visibility = View.INVISIBLE + binding.relatedItemsLayout?.visibility = View.INVISIBLE // hide comments / related streams / description tabs - binding!!.viewPager.visibility = View.GONE - binding!!.tabLayout.visibility = View.GONE + binding.viewPager.visibility = View.GONE + binding.tabLayout.visibility = View.GONE } private fun hideAgeRestrictedContent() { @@ -1228,9 +1219,13 @@ class VideoDetailFragment private fun setupBroadcastReceiver() { broadcastReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context, intent: Intent) { + Log.d(TAG, "onReceive ${intent.action}") when (intent.action) { - ACTION_SHOW_MAIN_PLAYER -> bottomSheetBehavior!!.setState(BottomSheetBehavior.STATE_EXPANDED) - ACTION_HIDE_MAIN_PLAYER -> bottomSheetBehavior!!.setState(BottomSheetBehavior.STATE_HIDDEN) + ACTION_SHOW_MAIN_PLAYER -> { + bottomSheetBehavior!!.setState(BottomSheetBehavior.STATE_EXPANDED) + } +// ACTION_HIDE_MAIN_PLAYER -> bottomSheetBehavior!!.setState(BottomSheetBehavior.STATE_HIDDEN) + ACTION_HIDE_MAIN_PLAYER -> bottomSheetBehavior!!.setState(BottomSheetBehavior.STATE_COLLAPSED) ACTION_PLAYER_STARTED -> { // If the state is not hidden we don't need to show the mini player if (bottomSheetBehavior!!.state == BottomSheetBehavior.STATE_HIDDEN) { @@ -1282,33 +1277,33 @@ class VideoDetailFragment //if data is already cached, transition from VISIBLE -> INVISIBLE -> VISIBLE is not required if (!isCached(serviceId, url!!, InfoItem.InfoType.STREAM)) { - binding!!.detailContentRootHiding.visibility = View.INVISIBLE + binding.detailContentRootHiding.visibility = View.INVISIBLE } - binding!!.detailThumbnailPlayButton.animate(false, 50) - binding!!.detailDurationView.animate(false, 100) - binding!!.detailPositionView.visibility = View.GONE - binding!!.positionView.visibility = View.GONE + binding.detailThumbnailPlayButton.animate(false, 50) + binding.detailDurationView.animate(false, 100) + binding.detailPositionView.visibility = View.GONE + binding.positionView.visibility = View.GONE - binding!!.detailVideoTitleView.text = title - binding!!.detailVideoTitleView.maxLines = 1 - binding!!.detailVideoTitleView.animate(true, 0) + binding.detailVideoTitleView.text = title + binding.detailVideoTitleView.maxLines = 1 + binding.detailVideoTitleView.animate(true, 0) - binding!!.detailToggleSecondaryControlsView.visibility = View.GONE - binding!!.detailTitleRootLayout.isClickable = false - binding!!.detailSecondaryControlPanel.visibility = View.GONE + binding.detailToggleSecondaryControlsView.visibility = View.GONE + binding.detailTitleRootLayout.isClickable = false + binding.detailSecondaryControlPanel.visibility = View.GONE - if (binding!!.relatedItemsLayout != null) { + if (binding.relatedItemsLayout != null) { if (showRelatedItems) { - binding!!.relatedItemsLayout!!.visibility = if (isFullscreen) View.GONE else View.INVISIBLE + binding.relatedItemsLayout!!.visibility = if (isFullscreen) View.GONE else View.INVISIBLE } else { - binding!!.relatedItemsLayout!!.visibility = View.GONE + binding.relatedItemsLayout!!.visibility = View.GONE } } cancelTag(PICASSO_VIDEO_DETAILS_TAG) - binding!!.detailThumbnailImageView.setImageBitmap(null) - binding!!.detailSubChannelThumbnailView.setImageBitmap(null) + binding.detailThumbnailImageView.setImageBitmap(null) + binding.detailSubChannelThumbnailView.setImageBitmap(null) } override fun handleResult(info: StreamInfo) { @@ -1319,10 +1314,10 @@ class VideoDetailFragment updateTabs(info) - binding!!.detailThumbnailPlayButton.animate(true, 200) - binding!!.detailVideoTitleView.text = title + binding.detailThumbnailPlayButton.animate(true, 200) + binding.detailVideoTitleView.text = title - binding!!.detailSubChannelThumbnailView.visibility = View.GONE + binding.detailSubChannelThumbnailView.visibility = View.GONE if (!TextUtils.isEmpty(info.subChannelName)) { displayBothUploaderAndSubChannel(info) @@ -1333,72 +1328,72 @@ class VideoDetailFragment if (info.viewCount >= 0) { when (info.streamType) { StreamType.AUDIO_LIVE_STREAM -> { - binding!!.detailViewCountView.text = listeningCount(requireActivity(), info.viewCount) + binding.detailViewCountView.text = listeningCount(requireActivity(), info.viewCount) } StreamType.LIVE_STREAM -> { - binding!!.detailViewCountView.text = localizeWatchingCount(requireActivity(), info.viewCount) + binding.detailViewCountView.text = localizeWatchingCount(requireActivity(), info.viewCount) } else -> { - binding!!.detailViewCountView.text = localizeViewCount(requireActivity(), info.viewCount) + binding.detailViewCountView.text = localizeViewCount(requireActivity(), info.viewCount) } } - binding!!.detailViewCountView.visibility = View.VISIBLE + binding.detailViewCountView.visibility = View.VISIBLE } else { - binding!!.detailViewCountView.visibility = View.GONE + binding.detailViewCountView.visibility = View.GONE } if (info.dislikeCount == -1L && info.likeCount == -1L) { - binding!!.detailThumbsDownImgView.visibility = View.VISIBLE - binding!!.detailThumbsUpImgView.visibility = View.VISIBLE - binding!!.detailThumbsUpCountView.visibility = View.GONE - binding!!.detailThumbsDownCountView.visibility = View.GONE + binding.detailThumbsDownImgView.visibility = View.VISIBLE + binding.detailThumbsUpImgView.visibility = View.VISIBLE + binding.detailThumbsUpCountView.visibility = View.GONE + binding.detailThumbsDownCountView.visibility = View.GONE - binding!!.detailThumbsDisabledView.visibility = View.VISIBLE + binding.detailThumbsDisabledView.visibility = View.VISIBLE } else { if (info.dislikeCount >= 0) { - binding!!.detailThumbsDownCountView.text = shortCount(requireActivity(), info.dislikeCount) - binding!!.detailThumbsDownCountView.visibility = View.VISIBLE - binding!!.detailThumbsDownImgView.visibility = View.VISIBLE + binding.detailThumbsDownCountView.text = shortCount(requireActivity(), info.dislikeCount) + binding.detailThumbsDownCountView.visibility = View.VISIBLE + binding.detailThumbsDownImgView.visibility = View.VISIBLE } else { - binding!!.detailThumbsDownCountView.visibility = View.GONE - binding!!.detailThumbsDownImgView.visibility = View.GONE + binding.detailThumbsDownCountView.visibility = View.GONE + binding.detailThumbsDownImgView.visibility = View.GONE } if (info.likeCount >= 0) { - binding!!.detailThumbsUpCountView.text = shortCount(requireActivity(), info.likeCount) - binding!!.detailThumbsUpCountView.visibility = View.VISIBLE - binding!!.detailThumbsUpImgView.visibility = View.VISIBLE + binding.detailThumbsUpCountView.text = shortCount(requireActivity(), info.likeCount) + binding.detailThumbsUpCountView.visibility = View.VISIBLE + binding.detailThumbsUpImgView.visibility = View.VISIBLE } else { - binding!!.detailThumbsUpCountView.visibility = View.GONE - binding!!.detailThumbsUpImgView.visibility = View.GONE + binding.detailThumbsUpCountView.visibility = View.GONE + binding.detailThumbsUpImgView.visibility = View.GONE } - binding!!.detailThumbsDisabledView.visibility = View.GONE + binding.detailThumbsDisabledView.visibility = View.GONE } when { info.duration > 0 -> { - binding!!.detailDurationView.text = getDurationString(info.duration) - binding!!.detailDurationView.setBackgroundColor(ContextCompat.getColor(requireActivity(), R.color.duration_background_color)) - binding!!.detailDurationView.animate(true, 100) + binding.detailDurationView.text = getDurationString(info.duration) + binding.detailDurationView.setBackgroundColor(ContextCompat.getColor(requireActivity(), R.color.duration_background_color)) + binding.detailDurationView.animate(true, 100) } info.streamType == StreamType.LIVE_STREAM -> { - binding!!.detailDurationView.setText(R.string.duration_live) - binding!!.detailDurationView.setBackgroundColor(ContextCompat.getColor(requireActivity(), R.color.live_duration_background_color)) - binding!!.detailDurationView.animate(true, 100) + binding.detailDurationView.setText(R.string.duration_live) + binding.detailDurationView.setBackgroundColor(ContextCompat.getColor(requireActivity(), R.color.live_duration_background_color)) + binding.detailDurationView.animate(true, 100) } else -> { - binding!!.detailDurationView.visibility = View.GONE + binding.detailDurationView.visibility = View.GONE } } - binding!!.detailTitleRootLayout.isClickable = true - binding!!.detailToggleSecondaryControlsView.rotation = 0f - binding!!.detailToggleSecondaryControlsView.visibility = View.VISIBLE - binding!!.detailSecondaryControlPanel.visibility = View.GONE + binding.detailTitleRootLayout.isClickable = true + binding.detailToggleSecondaryControlsView.rotation = 0f + binding.detailToggleSecondaryControlsView.visibility = View.VISIBLE + binding.detailSecondaryControlPanel.visibility = View.GONE checkUpdateProgressInfo(info) - loadDetailsThumbnail(info.thumbnails).tag(PICASSO_VIDEO_DETAILS_TAG).into(binding!!.detailThumbnailImageView) - showMetaInfoInTextView(info.metaInfo, binding!!.detailMetaInfoTextView, binding!!.detailMetaInfoSeparator, disposables) + loadDetailsThumbnail(info.thumbnails).tag(PICASSO_VIDEO_DETAILS_TAG).into(binding.detailThumbnailImageView) + showMetaInfoInTextView(info.metaInfo, binding.detailMetaInfoTextView, binding.detailMetaInfoSeparator, disposables) if (!isPlayerAvailable || player!!.isStopped) { updateOverlayData(info.name, info.uploaderName, info.thumbnails) @@ -1418,37 +1413,37 @@ class VideoDetailFragment } } - binding!!.detailControlsDownload.visibility = if (isLiveStream(info.streamType)) View.GONE else View.VISIBLE - binding!!.detailControlsBackground.visibility = + binding.detailControlsDownload.visibility = if (isLiveStream(info.streamType)) View.GONE else View.VISIBLE + binding.detailControlsBackground.visibility = if (info.audioStreams.isEmpty() && info.videoStreams.isEmpty()) View.GONE else View.VISIBLE val noVideoStreams = info.videoStreams.isEmpty() && info.videoOnlyStreams.isEmpty() - binding!!.detailControlsPopup.visibility = if (noVideoStreams) View.GONE else View.VISIBLE - binding!!.detailThumbnailPlayButton.setImageResource( + binding.detailControlsPopup.visibility = if (noVideoStreams) View.GONE else View.VISIBLE + binding.detailThumbnailPlayButton.setImageResource( if (noVideoStreams) R.drawable.ic_headset_shadow else R.drawable.ic_play_arrow_shadow) } private fun displayUploaderAsSubChannel(info: StreamInfo) { - binding!!.detailSubChannelTextView.text = info.uploaderName - binding!!.detailSubChannelTextView.visibility = View.VISIBLE - binding!!.detailSubChannelTextView.isSelected = true + binding.detailSubChannelTextView.text = info.uploaderName + binding.detailSubChannelTextView.visibility = View.VISIBLE + binding.detailSubChannelTextView.isSelected = true if (info.uploaderSubscriberCount > -1) { - binding!!.detailUploaderTextView.text = shortSubscriberCount(requireActivity(), info.uploaderSubscriberCount) - binding!!.detailUploaderTextView.visibility = View.VISIBLE + binding.detailUploaderTextView.text = shortSubscriberCount(requireActivity(), info.uploaderSubscriberCount) + binding.detailUploaderTextView.visibility = View.VISIBLE } else { - binding!!.detailUploaderTextView.visibility = View.GONE + binding.detailUploaderTextView.visibility = View.GONE } - loadAvatar(info.uploaderAvatars).tag(PICASSO_VIDEO_DETAILS_TAG).into(binding!!.detailSubChannelThumbnailView) - binding!!.detailSubChannelThumbnailView.visibility = View.VISIBLE - binding!!.detailUploaderThumbnailView.visibility = View.GONE + loadAvatar(info.uploaderAvatars).tag(PICASSO_VIDEO_DETAILS_TAG).into(binding.detailSubChannelThumbnailView) + binding.detailSubChannelThumbnailView.visibility = View.VISIBLE + binding.detailUploaderThumbnailView.visibility = View.GONE } private fun displayBothUploaderAndSubChannel(info: StreamInfo) { - binding!!.detailSubChannelTextView.text = info.subChannelName - binding!!.detailSubChannelTextView.visibility = View.VISIBLE - binding!!.detailSubChannelTextView.isSelected = true + binding.detailSubChannelTextView.text = info.subChannelName + binding.detailSubChannelTextView.visibility = View.VISIBLE + binding.detailSubChannelTextView.isSelected = true val subText = StringBuilder() if (!TextUtils.isEmpty(info.uploaderName)) { @@ -1462,19 +1457,19 @@ class VideoDetailFragment } if (subText.isNotEmpty()) { - binding!!.detailUploaderTextView.text = subText - binding!!.detailUploaderTextView.visibility = View.VISIBLE - binding!!.detailUploaderTextView.isSelected = true + binding.detailUploaderTextView.text = subText + binding.detailUploaderTextView.visibility = View.VISIBLE + binding.detailUploaderTextView.isSelected = true } else { - binding!!.detailUploaderTextView.visibility = View.GONE + binding.detailUploaderTextView.visibility = View.GONE } loadAvatar(info.subChannelAvatars).tag(PICASSO_VIDEO_DETAILS_TAG) - .into(binding!!.detailSubChannelThumbnailView) - binding!!.detailSubChannelThumbnailView.visibility = View.VISIBLE + .into(binding.detailSubChannelThumbnailView) + binding.detailSubChannelThumbnailView.visibility = View.VISIBLE loadAvatar(info.uploaderAvatars).tag(PICASSO_VIDEO_DETAILS_TAG) - .into(binding!!.detailUploaderThumbnailView) - binding!!.detailUploaderThumbnailView.visibility = View.VISIBLE + .into(binding.detailUploaderThumbnailView) + binding.detailUploaderThumbnailView.visibility = View.VISIBLE } fun openDownloadDialog() { @@ -1495,8 +1490,8 @@ class VideoDetailFragment private fun checkUpdateProgressInfo(info: StreamInfo) { positionSubscriber?.dispose() if (!getResumePlaybackEnabled(requireActivity())) { - binding!!.positionView.visibility = View.GONE - binding!!.detailPositionView.visibility = View.GONE + binding.positionView.visibility = View.GONE + binding.detailPositionView.visibility = View.GONE return } val recordManager = HistoryRecordManager(requireContext()) @@ -1507,8 +1502,8 @@ class VideoDetailFragment .subscribe({ state: StreamStateEntity -> updatePlaybackProgress(state.progressMillis, info.duration * 1000) }, { e: Throwable? -> }, { - binding!!.positionView.visibility = View.GONE - binding!!.detailPositionView.visibility = View.GONE + binding.positionView.visibility = View.GONE + binding.detailPositionView.visibility = View.GONE }) } @@ -1519,20 +1514,20 @@ class VideoDetailFragment val durationSeconds = TimeUnit.MILLISECONDS.toSeconds(duration).toInt() // If the old and the new progress values have a big difference then use animation. // Otherwise don't because it affects CPU - val progressDifference = abs((binding!!.positionView.progress - progressSeconds).toDouble()).toInt() - binding!!.positionView.max = durationSeconds + val progressDifference = abs((binding.positionView.progress - progressSeconds).toDouble()).toInt() + binding.positionView.max = durationSeconds if (progressDifference > 2) { - binding!!.positionView.setProgressAnimated(progressSeconds) + binding.positionView.setProgressAnimated(progressSeconds) } else { - binding!!.positionView.progress = progressSeconds + binding.positionView.progress = progressSeconds } val position = getDurationString(progressSeconds.toLong()) - if (position !== binding!!.detailPositionView.text) { - binding!!.detailPositionView.text = position + if (position !== binding.detailPositionView.text) { + binding.detailPositionView.text = position } - if (binding!!.positionView.visibility != View.VISIBLE) { - binding!!.positionView.animate(true, 100) - binding!!.detailPositionView.animate(true, 100) + if (binding.positionView.visibility != View.VISIBLE) { + binding.positionView.animate(true, 100) + binding.detailPositionView.animate(true, 100) } } @@ -1580,9 +1575,9 @@ class VideoDetailFragment setOverlayPlayPauseImage(player != null && player!!.isPlaying) when (state) { - Player.STATE_PLAYING -> if (binding!!.positionView.alpha != 1.0f && player!!.playQueue?.item?.url == url) { - binding!!.positionView.animate(true, 100) - binding!!.detailPositionView.animate(true, 100) + Player.STATE_PLAYING -> if (binding.positionView.alpha != 1.0f && player!!.playQueue?.item?.url == url) { + binding.positionView.animate(true, 100) + binding.detailPositionView.animate(true, 100) } } } @@ -1647,12 +1642,12 @@ class VideoDetailFragment if (fullscreen) { hideSystemUiIfNeeded() - binding!!.overlayPlayPauseButton.requestFocus() + binding.overlayPlayPauseButton.requestFocus() } else { showSystemUi() } - binding!!.relatedItemsLayout?.visibility = if (fullscreen) View.GONE else View.VISIBLE + binding.relatedItemsLayout?.visibility = if (fullscreen) View.GONE else View.VISIBLE scrollToTop() @@ -1679,13 +1674,13 @@ class VideoDetailFragment * Will scroll down to description view after long click on moreOptionsButton * */ override fun onMoreOptionsLongClicked() { - val params = binding!!.appBarLayout.layoutParams as CoordinatorLayout.LayoutParams + val params = binding.appBarLayout.layoutParams as CoordinatorLayout.LayoutParams val behavior = params.behavior as AppBarLayout.Behavior? - val valueAnimator = ValueAnimator.ofInt(0, -binding!!.playerPlaceholder.height) + val valueAnimator = ValueAnimator.ofInt(0, -binding.playerPlaceholder.height) valueAnimator.interpolator = DecelerateInterpolator() valueAnimator.addUpdateListener { animation: ValueAnimator -> behavior!!.setTopAndBottomOffset(animation.animatedValue as Int) - binding!!.appBarLayout.requestLayout() + binding.appBarLayout.requestLayout() } valueAnimator.interpolator = DecelerateInterpolator() valueAnimator.setDuration(500) @@ -1798,18 +1793,18 @@ class VideoDetailFragment if (isTv(requireContext())) { // remove ripple effects from detail controls val transparent = ContextCompat.getColor(requireContext(), R.color.transparent_background_color) - binding!!.detailControlsPlaylistAppend.setBackgroundColor(transparent) - binding!!.detailControlsBackground.setBackgroundColor(transparent) - binding!!.detailControlsPopup.setBackgroundColor(transparent) - binding!!.detailControlsDownload.setBackgroundColor(transparent) - binding!!.detailControlsShare.setBackgroundColor(transparent) - binding!!.detailControlsOpenInBrowser.setBackgroundColor(transparent) - binding!!.detailControlsPlayWithKodi.setBackgroundColor(transparent) + binding.detailControlsPlaylistAppend.setBackgroundColor(transparent) + binding.detailControlsBackground.setBackgroundColor(transparent) + binding.detailControlsPopup.setBackgroundColor(transparent) + binding.detailControlsDownload.setBackgroundColor(transparent) + binding.detailControlsShare.setBackgroundColor(transparent) + binding.detailControlsOpenInBrowser.setBackgroundColor(transparent) + binding.detailControlsPlayWithKodi.setBackgroundColor(transparent) } if (isDesktopMode(requireContext())) { // Remove the "hover" overlay (since it is visible on all mouse events and interferes // with the video content being played) - binding!!.detailThumbnailRootLayout.foreground = null + binding.detailThumbnailRootLayout.foreground = null } } @@ -1992,8 +1987,8 @@ class VideoDetailFragment toolbar.descendantFocusability = blockDescendants (requireView() as ViewGroup).descendantFocusability = afterDescendants // Only focus the player if it not already has focus - if (!binding!!.root.hasFocus()) { - binding!!.detailThumbnailRootLayout.requestFocus() + if (!binding.root.hasFocus()) { + binding.detailThumbnailRootLayout.requestFocus() } } } @@ -2014,7 +2009,7 @@ class VideoDetailFragment } private fun setupBottomPlayer() { - val params = binding!!.appBarLayout.layoutParams as CoordinatorLayout.LayoutParams + val params = binding.appBarLayout.layoutParams as CoordinatorLayout.LayoutParams val behavior = params.behavior as AppBarLayout.Behavior? val bottomSheetLayout = requireActivity().findViewById(R.id.fragment_player_holder) @@ -2028,10 +2023,10 @@ class VideoDetailFragment bottomSheetBehavior!!.peekHeight = peekHeight when (bottomSheetState) { BottomSheetBehavior.STATE_COLLAPSED -> { - binding!!.overlayLayout.alpha = MAX_OVERLAY_ALPHA + binding.overlayLayout.alpha = MAX_OVERLAY_ALPHA } BottomSheetBehavior.STATE_EXPANDED -> { - binding!!.overlayLayout.alpha = 0f + binding.overlayLayout.alpha = 0f setOverlayElementsClickable(false) } } @@ -2040,6 +2035,7 @@ class VideoDetailFragment bottomSheetCallback = object : BottomSheetCallback() { override fun onStateChanged(bottomSheet: View, newState: Int) { updateBottomSheetState(newState) + Log.d(TAG, "onStateChanged: $newState") when (newState) { BottomSheetBehavior.STATE_HIDDEN -> { @@ -2062,7 +2058,7 @@ class VideoDetailFragment if (isLandscape(requireContext()) && isPlayerAvailable && player!!.isPlaying && !isFullscreen && !isTablet(requireActivity())) { player!!.UIs().get(MainPlayerUi::class.java).ifPresent { obj: MainPlayerUi -> obj.toggleFullscreen() } } - setOverlayLook(binding!!.appBarLayout, behavior, 1f) + setOverlayLook(binding.appBarLayout, behavior, 1f) } BottomSheetBehavior.STATE_COLLAPSED -> { moveFocusToMainFragment(true) @@ -2075,7 +2071,7 @@ class VideoDetailFragment if (isPlayerAvailable) { player!!.UIs().get(MainPlayerUi::class.java).ifPresent { obj: MainPlayerUi -> obj.closeItemsList() } } - setOverlayLook(binding!!.appBarLayout, behavior, 0f) + setOverlayLook(binding.appBarLayout, behavior, 0f) } BottomSheetBehavior.STATE_DRAGGING, BottomSheetBehavior.STATE_SETTLING -> { if (isFullscreen) { @@ -2094,7 +2090,7 @@ class VideoDetailFragment } override fun onSlide(bottomSheet: View, slideOffset: Float) { - setOverlayLook(binding!!.appBarLayout, behavior, slideOffset) + setOverlayLook(binding.appBarLayout, behavior, slideOffset) } } @@ -2112,20 +2108,20 @@ class VideoDetailFragment val isPlayQueueEmpty = player == null || player!!.playQueue == null || player!!.playQueue!!.isEmpty if (binding != null) { // binding is null when rotating the device... - binding!!.overlayPlayQueueButton.visibility = if (isPlayQueueEmpty) View.GONE else View.VISIBLE + binding.overlayPlayQueueButton.visibility = if (isPlayQueueEmpty) View.GONE else View.VISIBLE } } private fun updateOverlayData(overlayTitle: String?, uploader: String?, thumbnails: List) { - binding!!.overlayTitleTextView.text = if (TextUtils.isEmpty(overlayTitle)) "" else overlayTitle - binding!!.overlayChannelTextView.text = if (TextUtils.isEmpty(uploader)) "" else uploader - binding!!.overlayThumbnail.setImageDrawable(null) - loadDetailsThumbnail(thumbnails).tag(PICASSO_VIDEO_DETAILS_TAG).into(binding!!.overlayThumbnail) + binding.overlayTitleTextView.text = if (TextUtils.isEmpty(overlayTitle)) "" else overlayTitle + binding.overlayChannelTextView.text = if (TextUtils.isEmpty(uploader)) "" else uploader + binding.overlayThumbnail.setImageDrawable(null) + loadDetailsThumbnail(thumbnails).tag(PICASSO_VIDEO_DETAILS_TAG).into(binding.overlayThumbnail) } private fun setOverlayPlayPauseImage(playerIsPlaying: Boolean) { val drawable = if (playerIsPlaying) R.drawable.ic_pause else R.drawable.ic_play_arrow - binding!!.overlayPlayPauseButton.setImageResource(drawable) + binding.overlayPlayPauseButton.setImageResource(drawable) } private fun setOverlayLook(appBar: AppBarLayout, behavior: AppBarLayout.Behavior?, slideOffset: Float) { @@ -2133,21 +2129,21 @@ class VideoDetailFragment // Stop animation in this case if (behavior == null || slideOffset < 0) return - binding!!.overlayLayout.alpha = min(MAX_OVERLAY_ALPHA.toDouble(), (1 - slideOffset).toDouble()).toFloat() + binding.overlayLayout.alpha = min(MAX_OVERLAY_ALPHA.toDouble(), (1 - slideOffset).toDouble()).toFloat() // These numbers are not special. They just do a cool transition - behavior.setTopAndBottomOffset((-binding!!.detailThumbnailImageView.height * 2 * (1 - slideOffset) / 3).toInt()) + behavior.setTopAndBottomOffset((-binding.detailThumbnailImageView.height * 2 * (1 - slideOffset) / 3).toInt()) appBar.requestLayout() } private fun setOverlayElementsClickable(enable: Boolean) { - binding!!.overlayThumbnail.isClickable = enable - binding!!.overlayThumbnail.isLongClickable = enable - binding!!.overlayMetadataLayout.isClickable = enable - binding!!.overlayMetadataLayout.isLongClickable = enable - binding!!.overlayButtonsLayout.isClickable = enable - binding!!.overlayPlayQueueButton.isClickable = enable - binding!!.overlayPlayPauseButton.isClickable = enable - binding!!.overlayCloseButton.isClickable = enable + binding.overlayThumbnail.isClickable = enable + binding.overlayThumbnail.isLongClickable = enable + binding.overlayMetadataLayout.isClickable = enable + binding.overlayMetadataLayout.isLongClickable = enable + binding.overlayButtonsLayout.isClickable = enable + binding.overlayPlayQueueButton.isClickable = enable + binding.overlayPlayPauseButton.isClickable = enable + binding.overlayCloseButton.isClickable = enable } val isPlayerAvailable: Boolean diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.kt b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.kt index 1588bb6..417046a 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.kt @@ -8,6 +8,8 @@ import android.util.Log import android.view.Menu import android.view.MenuInflater import android.view.View +import androidx.annotation.OptIn +import androidx.media3.common.util.UnstableApi import androidx.preference.PreferenceManager import androidx.recyclerview.widget.GridLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -49,10 +51,9 @@ abstract class BaseListFragment /*////////////////////////////////////////////////////////////////////////// // Views ////////////////////////////////////////////////////////////////////////// */ + protected lateinit var itemsList: RecyclerView @JvmField protected var infoListAdapter: InfoListAdapter? = null - @JvmField - protected var itemsList: RecyclerView? = null private var focusedPosition = -1 /*////////////////////////////////////////////////////////////////////////// @@ -113,8 +114,8 @@ abstract class BaseListFragment private fun getFocusedPosition(): Int { try { - val focusedItem = itemsList!!.focusedChild - val itemHolder = itemsList!!.findContainingViewHolder(focusedItem) + val focusedItem = itemsList.focusedChild + val itemHolder = itemsList.findContainingViewHolder(focusedItem) return itemHolder!!.bindingAdapterPosition } catch (e: NullPointerException) { return -1 @@ -140,8 +141,8 @@ abstract class BaseListFragment private fun restoreFocus(position: Int?) { if (position == null || position < 0) return - itemsList!!.post { - val focusedHolder = itemsList!!.findViewHolderForAdapterPosition(position) + itemsList.post { + val focusedHolder = itemsList.findViewHolderForAdapterPosition(position) focusedHolder?.itemView?.requestFocus() } } @@ -195,7 +196,7 @@ abstract class BaseListFragment */ private fun refreshItemViewMode() { val itemViewMode = itemViewMode - itemsList!!.layoutManager = if ((itemViewMode == ItemViewMode.GRID)) gridLayoutManager else listLayoutManager + itemsList.layoutManager = if ((itemViewMode == ItemViewMode.GRID)) gridLayoutManager else listLayoutManager infoListAdapter!!.setItemViewMode(itemViewMode) infoListAdapter!!.notifyDataSetChanged() } @@ -203,7 +204,7 @@ abstract class BaseListFragment override fun initViews(rootView: View, savedInstanceState: Bundle?) { super.initViews(rootView, savedInstanceState) - itemsList = rootView!!.findViewById(R.id.items_list) + itemsList = rootView.findViewById(R.id.items_list) refreshItemViewMode() val listHeaderSupplier = listHeaderSupplier @@ -211,7 +212,7 @@ abstract class BaseListFragment infoListAdapter!!.setHeaderSupplier(listHeaderSupplier) } - itemsList?.setAdapter(infoListAdapter) + itemsList.setAdapter(infoListAdapter) } protected open fun onItemSelected(selectedItem: InfoItem) { @@ -220,7 +221,7 @@ abstract class BaseListFragment } } - override fun initListeners() { + @OptIn(UnstableApi::class) override fun initListeners() { super.initListeners() infoListAdapter!!.setOnStreamSelectedListener(object : OnClickGesture { override fun selected(selectedItem: StreamInfoItem) { @@ -263,8 +264,8 @@ abstract class BaseListFragment if (DEBUG) { Log.d(TAG, "useNormalItemListScrollListener called") } - itemsList!!.clearOnScrollListeners() - itemsList!!.addOnScrollListener(DefaultItemListOnScrolledDownListener()) + itemsList.clearOnScrollListeners() + itemsList.addOnScrollListener(DefaultItemListOnScrolledDownListener()) } /** @@ -288,8 +289,8 @@ abstract class BaseListFragment if (DEBUG) { Log.d(TAG, "useInitialItemListLoadScrollListener called") } - itemsList!!.clearOnScrollListeners() - itemsList!!.addOnScrollListener(object : DefaultItemListOnScrolledDownListener() { + itemsList.clearOnScrollListeners() + itemsList.addOnScrollListener(object : DefaultItemListOnScrolledDownListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { super.onScrolled(recyclerView, dx, dy) if (dy != 0) { @@ -306,7 +307,7 @@ abstract class BaseListFragment useNormalItemListScrollListener() return } - if (itemsList!!.canScrollVertically(1) || itemsList!!.canScrollVertically(-1)) { + if (itemsList.canScrollVertically(1) || itemsList.canScrollVertically(-1)) { log("View is scrollable") useNormalItemListScrollListener() return @@ -330,12 +331,11 @@ abstract class BaseListFragment } } - private fun onStreamSelected(selectedItem: StreamInfoItem) { + @OptIn(UnstableApi::class) private fun onStreamSelected(selectedItem: StreamInfoItem) { Log.d(TAG, "onStreamSelected: ${selectedItem.name}") onItemSelected(selectedItem) - Log.d(TAG, "onItemClick: ${selectedItem.serviceId}, ${selectedItem.url}, ${selectedItem.name}") - openVideoDetailFragment(requireContext(), fM!!, selectedItem.serviceId, selectedItem.url, selectedItem.name, - null, false) + Log.d(TAG, "onItemClick: ${selectedItem.serviceId}, ${selectedItem.url}") + openVideoDetailFragment(requireContext(), fM!!, selectedItem.serviceId, selectedItem.url, selectedItem.name, null, false) } protected fun onScrollToBottom() { @@ -355,12 +355,9 @@ abstract class BaseListFragment /*////////////////////////////////////////////////////////////////////////// // Menu ////////////////////////////////////////////////////////////////////////// */ - override fun onCreateOptionsMenu(menu: Menu, - inflater: MenuInflater - ) { + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { if (DEBUG) { - Log.d(TAG, "onCreateOptionsMenu() called with: " - + "menu = [" + menu + "], inflater = [" + inflater + "]") + Log.d(TAG, "onCreateOptionsMenu() called with: menu = [$menu], inflater = [$inflater]") } super.onCreateOptionsMenu(menu, inflater) val supportActionBar = activity?.supportActionBar @@ -385,26 +382,22 @@ abstract class BaseListFragment ////////////////////////////////////////////////////////////////////////// */ override fun showLoading() { super.showLoading() - itemsList!!.animateHideRecyclerViewAllowingScrolling() + itemsList.animateHideRecyclerViewAllowingScrolling() } override fun hideLoading() { super.hideLoading() - itemsList!!.animate(true, 300) + itemsList.animate(true, 300) } override fun showEmptyState() { super.showEmptyState() showListFooter(false) - itemsList!!.animateHideRecyclerViewAllowingScrolling() + itemsList.animateHideRecyclerViewAllowingScrolling() } override fun showListFooter(show: Boolean) { - itemsList!!.post { - if (infoListAdapter != null && itemsList != null) { - infoListAdapter!!.showFooter(show) - } - } + itemsList.post { infoListAdapter?.showFooter(show) } } override fun handleNextItems(result: N) { @@ -414,7 +407,7 @@ abstract class BaseListFragment override fun handleError() { super.handleError() showListFooter(false) - itemsList!!.animateHideRecyclerViewAllowingScrolling() + itemsList.animateHideRecyclerViewAllowingScrolling() } override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences, key: String?) { diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/KioskFragment.kt b/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/KioskFragment.kt index e299ae2..d1fa637 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/KioskFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/kiosk/KioskFragment.kt @@ -86,9 +86,7 @@ open class KioskFragment : BaseListInfoFragment(UserA } } - override fun onCreateView(inflater: LayoutInflater, - container: ViewGroup?, - savedInstanceState: Bundle?): View? { + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { Log.d(TAG, "onCreateView") return inflater.inflate(R.layout.fragment_kiosk, container, false) } diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.kt b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.kt index 4f06b91..724c986 100644 --- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.kt +++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.kt @@ -9,19 +9,19 @@ import android.util.Log import android.util.Pair import android.view.* import android.widget.Toast +import androidx.annotation.OptIn import androidx.appcompat.app.AlertDialog import androidx.appcompat.app.AppCompatActivity import androidx.fragment.app.Fragment +import androidx.media3.common.util.UnstableApi import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView import androidx.viewbinding.ViewBinding import icepick.State import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers import io.reactivex.rxjava3.core.Single -import io.reactivex.rxjava3.core.SingleSource import io.reactivex.rxjava3.disposables.CompositeDisposable import io.reactivex.rxjava3.disposables.Disposable -import io.reactivex.rxjava3.functions.Function import io.reactivex.rxjava3.schedulers.Schedulers import io.reactivex.rxjava3.subjects.PublishSubject import org.reactivestreams.Subscriber @@ -143,8 +143,7 @@ class LocalPlaylistFragment : BaseLocalListFragment, V override val listHeader: ViewBinding get() { - headerBinding = LocalPlaylistHeaderBinding.inflate(requireActivity().layoutInflater, itemsList, - false) + headerBinding = LocalPlaylistHeaderBinding.inflate(requireActivity().layoutInflater, itemsList, false) playlistControlBinding = headerBinding!!.playlistControl headerBinding!!.playlistTitleView.isSelected = true @@ -173,7 +172,7 @@ class LocalPlaylistFragment : BaseLocalListFragment, V itemTouchHelper!!.attachToRecyclerView(itemsList) itemListAdapter!!.setSelectedListener(object : OnClickGesture { - override fun selected(selectedItem: LocalItem) { + @OptIn(UnstableApi::class) override fun selected(selectedItem: LocalItem) { if (selectedItem is PlaylistStreamEntry) { val item = selectedItem.streamEntity @@ -218,10 +217,8 @@ class LocalPlaylistFragment : BaseLocalListFragment, V override fun startLoading(forceLoad: Boolean) { super.startLoading(forceLoad) - if (disposables != null) { - disposables!!.clear() - } - disposables!!.add(debouncedSaver) + disposables?.clear() + disposables?.add(debouncedSaver) isLoadingComplete!!.set(false) isModified!!.set(false) @@ -243,9 +240,7 @@ class LocalPlaylistFragment : BaseLocalListFragment, V saveImmediate() } - override fun onCreateOptionsMenu(menu: Menu, - inflater: MenuInflater - ) { + override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { if (DEBUG) { Log.d(TAG, "onCreateOptionsMenu() called with: " + "menu = [" + menu + "], inflater = [" + inflater + "]") @@ -257,20 +252,13 @@ class LocalPlaylistFragment : BaseLocalListFragment, V override fun onDestroyView() { super.onDestroyView() - if (itemListAdapter != null) { - itemListAdapter!!.unsetSelectedListener() - } + itemListAdapter?.unsetSelectedListener() headerBinding = null playlistControlBinding = null - - if (databaseSubscription != null) { - databaseSubscription!!.cancel() - } - if (disposables != null) { - disposables!!.clear() - } + databaseSubscription?.cancel() + disposables?.clear() databaseSubscription = null itemTouchHelper = null @@ -298,9 +286,8 @@ class LocalPlaylistFragment : BaseLocalListFragment, V showLoading() isLoadingComplete!!.set(false) - if (databaseSubscription != null) { - databaseSubscription!!.cancel() - } + databaseSubscription?.cancel() + databaseSubscription = s databaseSubscription!!.request(1) } @@ -312,14 +299,11 @@ class LocalPlaylistFragment : BaseLocalListFragment, V isLoadingComplete!!.set(true) } - if (databaseSubscription != null) { - databaseSubscription!!.request(1) - } + databaseSubscription?.request(1) } override fun onError(exception: Throwable) { - showError(ErrorInfo(exception, UserAction.REQUESTED_BOOKMARK, - "Loading local playlist")) + showError(ErrorInfo(exception, UserAction.REQUESTED_BOOKMARK, "Loading local playlist")) } override fun onComplete() { @@ -327,29 +311,33 @@ class LocalPlaylistFragment : BaseLocalListFragment, V } override fun onOptionsItemSelected(item: MenuItem): Boolean { - if (item.itemId == R.id.menu_item_share_playlist) { - createShareConfirmationDialog() - } else if (item.itemId == R.id.menu_item_rename_playlist) { - createRenameDialog() - } else if (item.itemId == R.id.menu_item_remove_watched) { - if (!isRewritingPlaylist) { - AlertDialog.Builder(requireContext()) - .setMessage(R.string.remove_watched_popup_warning) - .setTitle(R.string.remove_watched_popup_title) - .setPositiveButton(R.string.ok) { d: DialogInterface?, id: Int -> removeWatchedStreams(false) } - .setNeutralButton( - R.string.remove_watched_popup_yes_and_partially_watched_videos - ) { d: DialogInterface?, id: Int -> removeWatchedStreams(true) } - .setNegativeButton(R.string.cancel - ) { d: DialogInterface, id: Int -> d.cancel() } - .show() + when (item.itemId) { + R.id.menu_item_share_playlist -> { + createShareConfirmationDialog() } - } else if (item.itemId == R.id.menu_item_remove_duplicates) { - if (!isRewritingPlaylist) { - openRemoveDuplicatesDialog() + R.id.menu_item_rename_playlist -> { + createRenameDialog() + } + R.id.menu_item_remove_watched -> { + if (!isRewritingPlaylist) { + AlertDialog.Builder(requireContext()) + .setMessage(R.string.remove_watched_popup_warning) + .setTitle(R.string.remove_watched_popup_title) + .setPositiveButton(R.string.ok) { d: DialogInterface?, id: Int -> removeWatchedStreams(false) } + .setNeutralButton(R.string.remove_watched_popup_yes_and_partially_watched_videos) { + d: DialogInterface?, id: Int -> removeWatchedStreams(true) } + .setNegativeButton(R.string.cancel) { d: DialogInterface, id: Int -> d.cancel() } + .show() + } + } + R.id.menu_item_remove_duplicates -> { + if (!isRewritingPlaylist) { + openRemoveDuplicatesDialog() + } + } + else -> { + return super.onOptionsItemSelected(item) } - } else { - return super.onOptionsItemSelected(item) } return true } @@ -366,33 +354,29 @@ class LocalPlaylistFragment : BaseLocalListFragment, V val context = requireContext() disposables!!.add(playlistManager!!.getPlaylistStreams(playlistId!!) - .flatMapSingle(Function, SingleSource> { playlist: List -> + .flatMapSingle { playlist: List -> Single.just(playlist.stream() - .map(PlaylistStreamEntry::streamEntity) + .map(PlaylistStreamEntry::streamEntity) .map { streamEntity: StreamEntity -> if (shouldSharePlaylistDetails) { - return@map context.getString(R.string.video_details_list_item, - streamEntity.title, streamEntity.url) + return@map context.getString(R.string.video_details_list_item, streamEntity.title, streamEntity.url) } else { return@map streamEntity.url } } .collect(Collectors.joining("\n"))) - }) + } .observeOn(AndroidSchedulers.mainThread()) .subscribe({ urlsText: String? -> - shareText( - context, name!!, if (shouldSharePlaylistDetails - ) context.getString(R.string.share_playlist_content_details, + shareText(context, name!!, if (shouldSharePlaylistDetails) context.getString(R.string.share_playlist_content_details, name, urlsText) else urlsText) }, { throwable: Throwable? -> showUiErrorSnackbar(this, "Sharing playlist", throwable!!) })) } fun removeWatchedStreams(removePartiallyWatched: Boolean) { - if (isRewritingPlaylist) { - return - } + if (isRewritingPlaylist) return + isRewritingPlaylist = true showLoading() @@ -403,8 +387,7 @@ class LocalPlaylistFragment : BaseLocalListFragment, V historyList.stream().map(StreamHistoryEntry::streamId) .collect(Collectors.toList()) } - val streamsMaybe = playlistManager!!.getPlaylistStreams( - playlistId!!) + val streamsMaybe = playlistManager!!.getPlaylistStreams(playlistId!!) .firstElement() .zipWith(historyIdsMaybe) { playlist: List, historyStreamIds: List? -> // Remove Watched, Functionality data @@ -430,16 +413,13 @@ class LocalPlaylistFragment : BaseLocalListFragment, V val playlistItem = playlist[i] val streamStateEntity = streamStates[i] - val indexInHistory = Collections.binarySearch(historyStreamIds, - playlistItem!!.streamId) + val indexInHistory = Collections.binarySearch(historyStreamIds, playlistItem!!.streamId) val duration = playlistItem.toStreamInfoItem().duration - if (indexInHistory < 0 || (streamStateEntity != null - && !streamStateEntity.isFinished(duration))) { + if (indexInHistory < 0 || (streamStateEntity != null && !streamStateEntity.isFinished(duration))) { itemsToKeep.add(playlistItem) } else if (!isThumbnailPermanent && !thumbnailVideoRemoved - && (playlistManager!!.getPlaylistThumbnailStreamId(playlistId!!) - == playlistItem.streamEntity.uid)) { + && (playlistManager!!.getPlaylistThumbnailStreamId(playlistId!!) == playlistItem.streamEntity.uid)) { thumbnailVideoRemoved = true } } @@ -470,17 +450,14 @@ class LocalPlaylistFragment : BaseLocalListFragment, V hideLoading() isRewritingPlaylist = false }, { throwable: Throwable? -> - showError(ErrorInfo( - throwable!!, UserAction.REQUESTED_BOOKMARK, + showError(ErrorInfo(throwable!!, UserAction.REQUESTED_BOOKMARK, "Removing watched videos, partially watched=$removePartiallyWatched")) })) } override fun handleResult(result: List) { super.handleResult(result) - if (itemListAdapter == null) { - return - } + if (itemListAdapter == null) return itemListAdapter!!.clearStreamItemList() @@ -506,21 +483,16 @@ class LocalPlaylistFragment : BaseLocalListFragment, V /////////////////////////////////////////////////////////////////////////// override fun resetFragment() { super.resetFragment() - if (databaseSubscription != null) { - databaseSubscription!!.cancel() - } + databaseSubscription?.cancel() } /*////////////////////////////////////////////////////////////////////////// // Playlist Metadata/Streams Manipulation ////////////////////////////////////////////////////////////////////////// */ private fun createRenameDialog() { - if (playlistId == null || name == null || context == null) { - return - } + if (playlistId == null || name == null || context == null) return - val dialogBinding = - DialogEditTextBinding.inflate(layoutInflater) + val dialogBinding = DialogEditTextBinding.inflate(layoutInflater) dialogBinding.dialogEditText.setHint(R.string.name) dialogBinding.dialogEditText.inputType = InputType.TYPE_CLASS_TEXT dialogBinding.dialogEditText.setSelection(dialogBinding.dialogEditText.text!!.length) @@ -538,62 +510,48 @@ class LocalPlaylistFragment : BaseLocalListFragment, V } private fun changePlaylistName(title: String) { - if (playlistManager == null) { - return - } + if (playlistManager == null) return this.name = title setTitle(title) if (DEBUG) { - Log.d(TAG, "Updating playlist id=[" + playlistId + "] " - + "with new title=[" + title + "] items") + Log.d(TAG, "Updating playlist id=[$playlistId] with new title=[$title] items") } val disposable = playlistManager!!.renamePlaylist(playlistId!!, title) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ longs: Int? -> }, { throwable: Throwable? -> - showError(ErrorInfo( - throwable!!, UserAction.REQUESTED_BOOKMARK, - "Renaming playlist")) + showError(ErrorInfo(throwable!!, UserAction.REQUESTED_BOOKMARK, "Renaming playlist")) }) disposables!!.add(disposable) } private fun changeThumbnailStreamId(thumbnailStreamId: Long, isPermanent: Boolean) { - if (playlistManager == null || (!isPermanent && playlistManager!! - .getIsPlaylistThumbnailPermanent(playlistId!!))) { - return - } + if (playlistManager == null || (!isPermanent && playlistManager!!.getIsPlaylistThumbnailPermanent(playlistId!!))) return val successToast = Toast.makeText(getActivity(), R.string.playlist_thumbnail_change_success, Toast.LENGTH_SHORT) if (DEBUG) { - Log.d(TAG, "Updating playlist id=[" + playlistId + "] " - + "with new thumbnail stream id=[" + thumbnailStreamId + "]") + Log.d(TAG, "Updating playlist id=[$playlistId] with new thumbnail stream id=[$thumbnailStreamId]") } val disposable = playlistManager!! .changePlaylistThumbnail(playlistId!!, thumbnailStreamId, isPermanent) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ ignore: Int? -> successToast.show() }, { throwable: Throwable? -> - showError(ErrorInfo( - throwable!!, UserAction.REQUESTED_BOOKMARK, - "Changing playlist thumbnail")) + showError(ErrorInfo(throwable!!, UserAction.REQUESTED_BOOKMARK, "Changing playlist thumbnail")) }) disposables!!.add(disposable) } private fun updateThumbnailUrl() { - if (playlistManager!!.getIsPlaylistThumbnailPermanent(playlistId!!)) { - return - } + if (playlistManager!!.getIsPlaylistThumbnailPermanent(playlistId!!)) return val thumbnailStreamId = if (!itemListAdapter!!.itemsList.isEmpty()) { - (itemListAdapter!!.itemsList[0] as PlaylistStreamEntry) - .streamEntity.uid + (itemListAdapter!!.itemsList[0] as PlaylistStreamEntry).streamEntity.uid } else { PlaylistEntity.DEFAULT_THUMBNAIL_ID } @@ -611,15 +569,13 @@ class LocalPlaylistFragment : BaseLocalListFragment, V } private fun removeDuplicatesInPlaylist() { - if (isRewritingPlaylist) { - return - } + if (isRewritingPlaylist) return + isRewritingPlaylist = true showLoading() val streamsMaybe = playlistManager!!.getDistinctPlaylistStreams(playlistId!!).firstElement() - disposables!!.add(streamsMaybe.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ itemsToKeep: List? -> @@ -631,16 +587,12 @@ class LocalPlaylistFragment : BaseLocalListFragment, V hideLoading() isRewritingPlaylist = false }, { throwable: Throwable? -> - showError(ErrorInfo( - throwable!!, UserAction.REQUESTED_BOOKMARK, - "Removing duplicated streams")) + showError(ErrorInfo(throwable!!, UserAction.REQUESTED_BOOKMARK, "Removing duplicated streams")) })) } private fun deleteItem(item: PlaylistStreamEntry) { - if (itemListAdapter == null) { - return - } + if (itemListAdapter == null) return itemListAdapter!!.removeItem(item) if (playlistManager!!.getPlaylistThumbnailStreamId(playlistId!!) == item.streamId) { @@ -652,9 +604,7 @@ class LocalPlaylistFragment : BaseLocalListFragment, V } private fun saveChanges() { - if (isModified == null || debouncedSaveSignal == null) { - return - } + if (isModified == null || debouncedSaveSignal == null) return isModified!!.set(true) debouncedSaveSignal!!.onNext(System.currentTimeMillis()) @@ -662,29 +612,22 @@ class LocalPlaylistFragment : BaseLocalListFragment, V private val debouncedSaver: Disposable get() { - if (debouncedSaveSignal == null) { - return Disposable.empty() - } + if (debouncedSaveSignal == null) return Disposable.empty() return debouncedSaveSignal!! .debounce(SAVE_DEBOUNCE_MILLIS, TimeUnit.MILLISECONDS) .observeOn(AndroidSchedulers.mainThread()) .subscribe({ ignored: Long? -> saveImmediate() }, { throwable: Throwable? -> - showError(ErrorInfo( - throwable!!, UserAction.SOMETHING_ELSE, - "Debounced saver")) + showError(ErrorInfo(throwable!!, UserAction.SOMETHING_ELSE, "Debounced saver")) }) } private fun saveImmediate() { - if (playlistManager == null || itemListAdapter == null) { - return - } + if (playlistManager == null || itemListAdapter == null) return // List must be loaded and modified in order to save if (isLoadingComplete == null || isModified == null || !isLoadingComplete!!.get() || !isModified!!.get()) { - Log.w(TAG, "Attempting to save playlist when local playlist " - + "is not loaded or not modified: playlist id=[" + playlistId + "]") + Log.w(TAG, "Attempting to save playlist when local playlist is not loaded or not modified: playlist id=[$playlistId]") return } @@ -697,22 +640,17 @@ class LocalPlaylistFragment : BaseLocalListFragment, V } if (DEBUG) { - Log.d(TAG, "Updating playlist id=[" + playlistId + "] " - + "with [" + streamIds.size + "] items") + Log.d(TAG, "Updating playlist id=[$playlistId] with [${streamIds.size}] items") } val disposable = playlistManager!!.updateJoin(playlistId!!, streamIds) .observeOn(AndroidSchedulers.mainThread()) .subscribe( { - if (isModified != null) { - isModified!!.set(false) - } + if (isModified != null) isModified!!.set(false) }, { throwable: Throwable? -> - showError(ErrorInfo( - throwable!!, - UserAction.REQUESTED_BOOKMARK, "Saving playlist")) + showError(ErrorInfo(throwable!!, UserAction.REQUESTED_BOOKMARK, "Saving playlist")) } ) disposables!!.add(disposable) @@ -731,23 +669,14 @@ class LocalPlaylistFragment : BaseLocalListFragment, V viewSize: Int, viewSizeOutOfBounds: Int, totalSize: Int, - msSinceStartScroll: Long - ): Int { - val standardSpeed = super.interpolateOutOfBoundsScroll(recyclerView, - viewSize, viewSizeOutOfBounds, totalSize, msSinceStartScroll) - val minimumAbsVelocity = max(MINIMUM_INITIAL_DRAG_VELOCITY.toDouble(), - abs(standardSpeed.toDouble())).toInt() + msSinceStartScroll: Long): Int { + val standardSpeed = super.interpolateOutOfBoundsScroll(recyclerView, viewSize, viewSizeOutOfBounds, totalSize, msSinceStartScroll) + val minimumAbsVelocity = max(MINIMUM_INITIAL_DRAG_VELOCITY.toDouble(), abs(standardSpeed.toDouble())).toInt() return minimumAbsVelocity * sign(viewSizeOutOfBounds.toDouble()).toInt() } - override fun onMove(recyclerView: RecyclerView, - source: RecyclerView.ViewHolder, - target: RecyclerView.ViewHolder - ): Boolean { - if (source.itemViewType != target.itemViewType - || itemListAdapter == null) { - return false - } + override fun onMove(recyclerView: RecyclerView, source: RecyclerView.ViewHolder, target: RecyclerView.ViewHolder): Boolean { + if (source.itemViewType != target.itemViewType || itemListAdapter == null) return false val sourceIndex = source.bindingAdapterPosition val targetIndex = target.bindingAdapterPosition @@ -766,8 +695,7 @@ class LocalPlaylistFragment : BaseLocalListFragment, V return false } - override fun onSwiped(viewHolder: RecyclerView.ViewHolder, - swipeDir: Int + override fun onSwiped(viewHolder: RecyclerView.ViewHolder, swipeDir: Int ) { } } @@ -780,7 +708,7 @@ class LocalPlaylistFragment : BaseLocalListFragment, V return getPlayQueue(max(itemListAdapter!!.itemsList.indexOf(infoItem).toDouble(), 0.0).toInt()) } - protected fun showInfoItemDialog(item: PlaylistStreamEntry) { + @OptIn(UnstableApi::class) protected fun showInfoItemDialog(item: PlaylistStreamEntry) { val infoItem = item.toStreamInfoItem() try { @@ -797,21 +725,14 @@ class LocalPlaylistFragment : BaseLocalListFragment, V // set custom actions // all entries modified below have already been added within the builder dialogBuilder - .setAction( - StreamDialogDefaultEntry.START_HERE_ON_BACKGROUND - ) { f: Fragment?, i: StreamInfoItem? -> - playOnBackgroundPlayer( - context, getPlayQueueStartingAt(item), true) + .setAction(StreamDialogDefaultEntry.START_HERE_ON_BACKGROUND) { f: Fragment?, i: StreamInfoItem? -> + playOnBackgroundPlayer(context, getPlayQueueStartingAt(item), true) } - .setAction( - StreamDialogDefaultEntry.SET_AS_PLAYLIST_THUMBNAIL + .setAction(StreamDialogDefaultEntry.SET_AS_PLAYLIST_THUMBNAIL ) { f: Fragment?, i: StreamInfoItem? -> - changeThumbnailStreamId(item.streamEntity.uid, - true) + changeThumbnailStreamId(item.streamEntity.uid, true) } - .setAction( - StreamDialogDefaultEntry.DELETE - ) { f: Fragment?, i: StreamInfoItem? -> deleteItem(item) } + .setAction(StreamDialogDefaultEntry.DELETE) { f: Fragment?, i: StreamInfoItem? -> deleteItem(item) } .create() .show() } catch (e: IllegalArgumentException) { @@ -834,9 +755,7 @@ class LocalPlaylistFragment : BaseLocalListFragment, V get() = getPlayQueue(0) private fun getPlayQueue(index: Int): PlayQueue { - if (itemListAdapter == null) { - return SinglePlayQueue(emptyList(), 0) - } + if (itemListAdapter == null) return SinglePlayQueue(emptyList(), 0) val infoItems: List = itemListAdapter!!.itemsList val streamInfoItems: MutableList = ArrayList(infoItems.size) @@ -858,16 +777,14 @@ class LocalPlaylistFragment : BaseLocalListFragment, V .setTitle(R.string.share_playlist) .setMessage(R.string.share_playlist_with_titles_message) .setCancelable(true) - .setPositiveButton(R.string.share_playlist_with_titles - ) { dialog: DialogInterface?, which: Int -> sharePlaylist( /* shouldSharePlaylistDetails= */true) } - .setNegativeButton(R.string.share_playlist_with_list - ) { dialog: DialogInterface?, which: Int -> sharePlaylist( /* shouldSharePlaylistDetails= */false) } + .setPositiveButton(R.string.share_playlist_with_titles) { + dialog: DialogInterface?, which: Int -> sharePlaylist( /* shouldSharePlaylistDetails= */true) } + .setNegativeButton(R.string.share_playlist_with_list) { + dialog: DialogInterface?, which: Int -> sharePlaylist( /* shouldSharePlaylistDetails= */false) } .show() } - fun setTabsPagerAdapter( - tabsPagerAdapter: SelectedTabsPagerAdapter? - ) { + fun setTabsPagerAdapter(tabsPagerAdapter: SelectedTabsPagerAdapter?) { this.tabsPagerAdapter = tabsPagerAdapter } diff --git a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.kt b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.kt index 174325b..8ffe31b 100644 --- a/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.kt +++ b/app/src/main/java/org/schabi/newpipe/player/ui/MainPlayerUi.kt @@ -21,6 +21,7 @@ import androidx.fragment.app.FragmentManager import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.RecyclerView import androidx.media3.common.VideoSize +import androidx.media3.common.util.UnstableApi import org.schabi.newpipe.MainActivity import org.schabi.newpipe.QueueItemMenuUtil.openPopupMenu import org.schabi.newpipe.R @@ -61,7 +62,7 @@ import kotlin.math.min // Constructor, setup, destroy ////////////////////////////////////////////////////////////////////////// */ //region Constructor, setup, destroy -class MainPlayerUi(player: Player, playerBinding: PlayerBinding) +@UnstableApi class MainPlayerUi(player: Player, playerBinding: PlayerBinding) : VideoPlayerUi(player, playerBinding), OnLayoutChangeListener { override var isFullscreen = false diff --git a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceParser.kt b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceParser.kt index 73cb158..14471cd 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceParser.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/preferencesearch/PreferenceParser.kt @@ -13,14 +13,10 @@ import java.util.* * Parses the corresponding preference-file(s). */ class PreferenceParser(private val context: Context, - private val searchConfiguration: PreferenceSearchConfiguration -) { - private val allPreferences: Map = - PreferenceManager.getDefaultSharedPreferences(context).all - - fun parse( - @XmlRes resId: Int - ): List { + private val searchConfiguration: PreferenceSearchConfiguration) { + private val allPreferences: Map = PreferenceManager.getDefaultSharedPreferences(context).all + + fun parse(@XmlRes resId: Int): List { val results: MutableList = ArrayList() val xpp: XmlPullParser = context.resources.getXml(resId) @@ -31,14 +27,9 @@ class PreferenceParser(private val context: Context, val breadcrumbs: MutableList = ArrayList() while (xpp.eventType != XmlPullParser.END_DOCUMENT) { if (xpp.eventType == XmlPullParser.START_TAG) { - val result = parseSearchResult( - xpp, - concatenateStrings(" > ", breadcrumbs), - resId - ) - - if (!searchConfiguration.parserIgnoreElements.contains(xpp.name) - && result.hasData() + val result = parseSearchResult(xpp, concatenateStrings(" > ", breadcrumbs), resId) + + if (!searchConfiguration.parserIgnoreElements.contains(xpp.name) && result.hasData() && "true" != getAttribute(xpp, NS_SEARCH, "ignore")) { results.add(result) } @@ -47,9 +38,7 @@ class PreferenceParser(private val context: Context, // Example: Video and Audio > Player breadcrumbs.add(if (result.title == null) "" else result.title) } - } else if (xpp.eventType == XmlPullParser.END_TAG - && searchConfiguration.parserContainerElements - .contains(xpp.name)) { + } else if (xpp.eventType == XmlPullParser.END_TAG && searchConfiguration.parserContainerElements.contains(xpp.name)) { breadcrumbs.removeAt(breadcrumbs.size - 1) } @@ -61,46 +50,26 @@ class PreferenceParser(private val context: Context, return results } - private fun getAttribute( - xpp: XmlPullParser, - attribute: String - ): String { + private fun getAttribute(xpp: XmlPullParser, attribute: String): String? { val nsSearchAttr = getAttribute(xpp, NS_SEARCH, attribute) - if (nsSearchAttr != null) { - return nsSearchAttr - } + if (nsSearchAttr != null) return nsSearchAttr + return getAttribute(xpp, NS_ANDROID, attribute) } - private fun getAttribute( - xpp: XmlPullParser, - namespace: String, - attribute: String - ): String { + private fun getAttribute(xpp: XmlPullParser, namespace: String, attribute: String): String? { return xpp.getAttributeValue(namespace, attribute) } - private fun parseSearchResult( - xpp: XmlPullParser, - breadcrumbs: String, - @XmlRes searchIndexItemResId: Int - ): PreferenceSearchItem { + private fun parseSearchResult(xpp: XmlPullParser, breadcrumbs: String, @XmlRes searchIndexItemResId: Int): PreferenceSearchItem { val key = readString(getAttribute(xpp, "key")) val entries = readStringArray(getAttribute(xpp, "entries")) val entryValues = readStringArray(getAttribute(xpp, "entryValues")) return PreferenceSearchItem( key, - tryFillInPreferenceValue( - readString(getAttribute(xpp, "title")), - key, - entries.filterNotNull().toTypedArray(), - entryValues), - tryFillInPreferenceValue( - readString(getAttribute(xpp, "summary")), - key, - entries.filterNotNull().toTypedArray(), - entryValues), + tryFillInPreferenceValue(readString(getAttribute(xpp, "title")), key, entries.filterNotNull().toTypedArray(), entryValues), + tryFillInPreferenceValue(readString(getAttribute(xpp, "summary")), key, entries.filterNotNull().toTypedArray(), entryValues), TextUtils.join(",", entries), breadcrumbs, searchIndexItemResId @@ -108,9 +77,8 @@ class PreferenceParser(private val context: Context, } private fun readStringArray(s: String?): Array { - if (s == null) { - return arrayOfNulls(0) - } + if (s == null) return arrayOfNulls(0) + if (s.startsWith("@")) { try { return context.resources.getStringArray(s.substring(1).toInt()) @@ -122,9 +90,8 @@ class PreferenceParser(private val context: Context, } private fun readString(s: String?): String { - if (s == null) { - return "" - } + if (s == null) return "" + if (s.startsWith("@")) { try { return context.getString(s.substring(1).toInt()) @@ -135,21 +102,13 @@ class PreferenceParser(private val context: Context, return s } - private fun tryFillInPreferenceValue( - s: String?, - key: String?, - entries: Array, - entryValues: Array - ): String { - if (s == null) { - return "" - } - if (key == null) { - return s - } + private fun tryFillInPreferenceValue(s: String?, key: String?, entries: Array, entryValues: Array): String { + if (s == null) return "" + + if (key == null) return s // Resolve value - var prefValue = allPreferences[key] ?: return s + var prefValue = (allPreferences[key] as? String) ?: return s /* * Resolve ListPreference values @@ -157,8 +116,8 @@ class PreferenceParser(private val context: Context, * entryValues = Values/Keys that are saved * entries = Actual human readable names */ - if (entries.size > 0 && entryValues.size == entries.size) { - val entryIndex = Arrays.asList(*entryValues).indexOf(prefValue) + if (entries.isNotEmpty() && entryValues.size == entries.size) { + val entryIndex = listOf(*entryValues).indexOf(prefValue) if (entryIndex != -1) { prefValue = entries[entryIndex] } diff --git a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.kt b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.kt index 655d50a..087556e 100644 --- a/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.kt +++ b/app/src/main/java/org/schabi/newpipe/settings/tabs/Tab.kt @@ -105,7 +105,7 @@ abstract class Tab { // } override val tabId: Int = ID - override fun getTabName(context: Context): String? { + override fun getTabName(context: Context): String { // TODO: find a better name for the blank tab (maybe "blank_tab") or replace it with // context.getString(R.string.app_name); return "NewPipe" // context.getString(R.string.blank_page_summary); @@ -131,7 +131,7 @@ abstract class Tab { // } override val tabId: Int = ID - override fun getTabName(context: Context): String? { + override fun getTabName(context: Context): String { return context.getString(R.string.tab_subscriptions) } @@ -155,7 +155,7 @@ abstract class Tab { // } override val tabId: Int = ID - override fun getTabName(context: Context): String? { + override fun getTabName(context: Context): String { return context.getString(R.string.fragment_feed_title) } @@ -178,7 +178,7 @@ abstract class Tab { // return ID // } override val tabId: Int = ID - override fun getTabName(context: Context): String? { + override fun getTabName(context: Context): String { return context.getString(R.string.tab_bookmarks) } @@ -201,7 +201,7 @@ abstract class Tab { // return ID // } override val tabId: Int = ID - override fun getTabName(context: Context): String? { + override fun getTabName(context: Context): String { return context.getString(R.string.title_activity_history) } @@ -239,7 +239,7 @@ abstract class Tab { // } override val tabId: Int = ID - override fun getTabName(context: Context): String? { + override fun getTabName(context: Context): String { return getTranslatedKioskName(kioskId!!, context) } @@ -258,8 +258,7 @@ abstract class Tab { } override fun writeDataToJson(writerSink: JsonStringWriter) { - writerSink.value(JSON_KIOSK_SERVICE_ID_KEY, kioskServiceId) - .value(JSON_KIOSK_ID_KEY, kioskId) + writerSink.value(JSON_KIOSK_SERVICE_ID_KEY, kioskServiceId).value(JSON_KIOSK_ID_KEY, kioskId) } override fun readDataFromJson(jsonObject: JsonObject) { @@ -268,9 +267,8 @@ abstract class Tab { } override fun equals(obj: Any?): Boolean { - if (obj !is KioskTab) { - return false - } + if (obj !is KioskTab) return false + val other = obj return super.equals(obj) && kioskServiceId == other.kioskServiceId && kioskId == other.kioskId } @@ -296,9 +294,7 @@ abstract class Tab { constructor() : this(-1, NO_URL, NO_NAME) - constructor(channelServiceId: Int, channelUrl: String?, - channelName: String? - ) { + constructor(channelServiceId: Int, channelUrl: String?, channelName: String?) { this.channelServiceId = channelServiceId this.channelUrl = channelUrl this.channelName = channelName @@ -337,9 +333,8 @@ abstract class Tab { } override fun equals(obj: Any?): Boolean { - if (obj !is ChannelTab) { - return false - } + if (obj !is ChannelTab) return false + val other = obj return super.equals(obj) && channelServiceId == other.channelServiceId && channelUrl == other.channelName && channelName == other.channelName } @@ -362,7 +357,7 @@ abstract class Tab { // } override val tabId: Int = ID - override fun getTabName(context: Context): String? { + override fun getTabName(context: Context): String { return getTranslatedKioskName(getDefaultKioskId(context), context) } @@ -463,31 +458,18 @@ abstract class Tab { playlistUrl = jsonObject.getString(JSON_PLAYLIST_URL_KEY, NO_URL) playlistName = jsonObject.getString(JSON_PLAYLIST_NAME_KEY, NO_NAME) playlistId = jsonObject.getInt(JSON_PLAYLIST_ID_KEY, -1).toLong() - playlistType = LocalItemType.valueOf( - jsonObject.getString(JSON_PLAYLIST_TYPE_KEY, - LocalItemType.PLAYLIST_LOCAL_ITEM.toString()) + playlistType = LocalItemType.valueOf(jsonObject.getString(JSON_PLAYLIST_TYPE_KEY, LocalItemType.PLAYLIST_LOCAL_ITEM.toString()) ) } override fun equals(obj: Any?): Boolean { - if (obj !is PlaylistTab) { - return false - } - + if (obj !is PlaylistTab) return false val other = obj - return super.equals(obj) && playlistServiceId == other.playlistServiceId && playlistId == other.playlistId && playlistUrl == other.playlistUrl && playlistName == other.playlistName && playlistType == other.playlistType } override fun hashCode(): Int { - return Objects.hash( - tabId, - playlistServiceId, - playlistId, - playlistUrl, - playlistName, - playlistType - ) + return Objects.hash(tabId, playlistServiceId, playlistId, playlistUrl, playlistName, playlistType) } companion object { @@ -514,10 +496,7 @@ abstract class Tab { fun from(jsonObject: JsonObject): Tab? { val tabId = jsonObject.getInt(JSON_TAB_ID_KEY, -1) - if (tabId == -1) { - return null - } - + if (tabId == -1) return null return from(tabId, jsonObject) } @@ -527,9 +506,7 @@ abstract class Tab { fun typeFrom(tabId: Int): Type? { for (available in Type.entries) { - if (available.tabId == tabId) { - return available - } + if (available.tabId == tabId) return available } return null } diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.kt b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.kt index fbd94f6..c7454b1 100644 --- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.kt +++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.kt @@ -364,15 +364,10 @@ import org.schabi.newpipe.util.external_communication.ShareUtils.tryOpenIntentIn } @JvmStatic - fun openVideoDetailFragment(context: Context, - fragmentManager: FragmentManager, - serviceId: Int, - url: String?, - title: String, - playQueue: PlayQueue?, - switchingPlayers: Boolean) { + fun openVideoDetailFragment(context: Context, fragmentManager: FragmentManager, serviceId: Int, url: String?, title: String, playQueue: PlayQueue?, switchingPlayers: Boolean) { val autoPlay: Boolean val playerType = PlayerHolder.instance?.type + Log.d(TAG, "openVideoDetailFragment: $playerType") autoPlay = when { playerType == null -> { // no player open @@ -393,24 +388,24 @@ import org.schabi.newpipe.util.external_communication.ShareUtils.tryOpenIntentIn } } - val onVideoDetailFragmentReady: RunnableWithVideoDetailFragment = - object: RunnableWithVideoDetailFragment { - override fun run(detailFragment: VideoDetailFragment?) { - if (detailFragment == null) return - expandMainPlayer(detailFragment.requireActivity()) - detailFragment.setAutoPlay(autoPlay) - if (switchingPlayers) { - // Situation when user switches from players to main player. All needed data is - // here, we can start watching (assuming newQueue equals playQueue). - // Starting directly in fullscreen if the previous player type was popup. - detailFragment.openVideoPlayer(playerType == PlayerType.POPUP - || PlayerHelper.isStartMainPlayerFullscreenEnabled(context)) - } else { - detailFragment.selectAndLoadVideo(serviceId, url, title, playQueue) - } - detailFragment.scrollToTop() + val onVideoDetailFragmentReady: RunnableWithVideoDetailFragment = object: RunnableWithVideoDetailFragment { + override fun run(detailFragment: VideoDetailFragment?) { + if (detailFragment == null) return + + expandMainPlayer(detailFragment.requireActivity()) + detailFragment.setAutoPlay(autoPlay) + if (switchingPlayers) { + // Situation when user switches from players to main player. All needed data is + // here, we can start watching (assuming newQueue equals playQueue). + // Starting directly in fullscreen if the previous player type was popup. + detailFragment.openVideoPlayer(playerType == PlayerType.POPUP + || PlayerHelper.isStartMainPlayerFullscreenEnabled(context)) + } else { + detailFragment.selectAndLoadVideo(serviceId, url, title, playQueue) } + detailFragment.scrollToTop() } + } val fragment = fragmentManager.findFragmentById(R.id.fragment_player_holder) if (fragment is VideoDetailFragment && fragment.isVisible()) { diff --git a/changelog.md b/changelog.md index 2edae8a..ac36088 100644 --- a/changelog.md +++ b/changelog.md @@ -27,4 +27,17 @@ Caused by: android.os.TransactionTooLargeException: data parcel size 1257884 byt ## 0.26.3 -* fixed crash bug when doing download \ No newline at end of file +* fixed crash bug when doing download + +## 0.26.4 + +* app built to target to SDK 34 +* set bottomsheet to collapsed when video view is closed, rather than hidden. + +### Issues + +The reason to set it to collapsed is this: on my S21 Android 14 device, bottomSheetBehavior!!.setState(BottomSheetBehavior.STATE_EXPANDED) doesn't expand the bottomsheet, whether it was hidden or collapsed. Originally, the bottomsheet was set to hidden, so when starting a new video from the list, the view is still invisible, making it appear like nothing is happening. + +Now it's set to collapsed, so when a new video is starting, on S21 Android14, the user need to manually expand the bottomsheet. + +On my Android 9 device however, expand on play is automatic. \ No newline at end of file