From 78a9811fe386c05bff52357d3a6dbc2c54cd1767 Mon Sep 17 00:00:00 2001 From: Stypox Date: Fri, 16 Oct 2020 20:01:03 +0200 Subject: [PATCH 1/9] Add a secondary control panel to video detail fragment It is shown when the user expands the description It contains share, open in browser and play in kodi --- .../fragments/detail/VideoDetailFragment.java | 53 +++++++-- .../fragment_video_detail.xml | 102 ++++++++++++++---- .../main/res/layout/fragment_video_detail.xml | 100 +++++++++++++---- app/src/main/res/values/dimens.xml | 5 + 4 files changed, 210 insertions(+), 50 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 0ac49cd7e..7add95a6c 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -11,6 +11,7 @@ import android.content.pm.ActivityInfo; import android.database.ContentObserver; import android.graphics.Color; import android.graphics.drawable.Drawable; +import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.os.Handler; @@ -90,6 +91,7 @@ import org.schabi.newpipe.util.Constants; import org.schabi.newpipe.util.DeviceUtils; import org.schabi.newpipe.util.ExtractorHelper; import org.schabi.newpipe.util.ImageDisplayConstants; +import org.schabi.newpipe.util.KoreUtil; import org.schabi.newpipe.util.ListHelper; import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; @@ -193,6 +195,7 @@ public final class VideoDetailFragment private TabAdapter pageAdapter; private ContentObserver settingsContentObserver; + @Nullable private MainPlayer playerService; private Player player; @@ -452,6 +455,30 @@ public final class VideoDetailFragment this.openDownloadDialog(); } break; + case R.id.detail_controls_share: + if (currentInfo != null) { + ShareUtils.shareText(requireContext(), + currentInfo.getName(), currentInfo.getUrl()); + } + break; + case R.id.detail_controls_open_in_browser: + if (currentInfo != null) { + ShareUtils.openUrlInBrowser(requireContext(), currentInfo.getUrl()); + } + break; + case R.id.detail_controls_play_with_kodi: + if (currentInfo != null) { + try { + NavigationHelper.playWithKore( + requireContext(), Uri.parse(currentInfo.getUrl())); + } catch (final Exception e) { + if (DEBUG) { + Log.i(TAG, "Failed to start kore", e); + } + KoreUtil.showInstallKoreDialog(requireContext()); + } + } + break; case R.id.detail_uploader_root_layout: if (isEmpty(currentInfo.getSubChannelUrl())) { if (!isEmpty(currentInfo.getUploaderUrl())) { @@ -549,6 +576,7 @@ public final class VideoDetailFragment binding.detailDescriptionView.setFocusable(false); binding.detailToggleDescriptionView.setImageResource( ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_expand_more)); + binding.detailSecondaryControlPanel.setVisibility(View.GONE); } else { binding.detailVideoTitleView.setMaxLines(10); binding.detailDescriptionRootLayout.setVisibility(View.VISIBLE); @@ -556,6 +584,7 @@ public final class VideoDetailFragment binding.detailDescriptionView.setMovementMethod(new LargeTextMovementMethod()); binding.detailToggleDescriptionView.setImageResource( ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_expand_less)); + binding.detailSecondaryControlPanel.setVisibility(View.VISIBLE); } } @@ -582,6 +611,9 @@ public final class VideoDetailFragment binding.detailControlsBackground.setBackgroundColor(transparent); binding.detailControlsPopup.setBackgroundColor(transparent); binding.detailControlsDownload.setBackgroundColor(transparent); + binding.detailControlsShare.setBackgroundColor(transparent); + binding.detailControlsOpenInBrowser.setBackgroundColor(transparent); + binding.detailControlsPlayWithKodi.setBackgroundColor(transparent); } } @@ -589,21 +621,22 @@ public final class VideoDetailFragment protected void initListeners() { super.initListeners(); + binding.detailTitleRootLayout.setOnClickListener(this); binding.detailTitleRootLayout.setOnLongClickListener(this); binding.detailUploaderRootLayout.setOnClickListener(this); binding.detailUploaderRootLayout.setOnLongClickListener(this); - binding.detailTitleRootLayout.setOnClickListener(this); binding.detailThumbnailRootLayout.setOnClickListener(this); binding.detailControlsBackground.setOnClickListener(this); + binding.detailControlsBackground.setOnLongClickListener(this); binding.detailControlsPopup.setOnClickListener(this); + binding.detailControlsPopup.setOnLongClickListener(this); binding.detailControlsPlaylistAppend.setOnClickListener(this); binding.detailControlsDownload.setOnClickListener(this); binding.detailControlsDownload.setOnLongClickListener(this); - - binding.detailControlsBackground.setLongClickable(true); - binding.detailControlsPopup.setLongClickable(true); - binding.detailControlsBackground.setOnLongClickListener(this); - binding.detailControlsPopup.setOnLongClickListener(this); + binding.detailControlsShare.setOnClickListener(this); + binding.detailControlsOpenInBrowser.setOnClickListener(this); + binding.detailControlsPlayWithKodi.setOnClickListener(this); + showHideKodiButton(); binding.overlayThumbnail.setOnClickListener(this); binding.overlayThumbnail.setOnLongClickListener(this); @@ -672,6 +705,12 @@ public final class VideoDetailFragment } } + private void showHideKodiButton() { + // show kodi button if it supports the current service and it is enabled in settings + binding.detailControlsPlayWithKodi.setVisibility(KoreUtil.shouldShowPlayWithKodi( + requireContext(), serviceId) ? View.VISIBLE : View.GONE); + } + /*////////////////////////////////////////////////////////////////////////// // OwnStack //////////////////////////////////////////////////////////////////////////*/ @@ -1312,6 +1351,7 @@ public final class VideoDetailFragment binding.detailDescriptionRootLayout.setVisibility(View.GONE); binding.detailToggleDescriptionView.setVisibility(View.GONE); binding.detailTitleRootLayout.setClickable(false); + binding.detailSecondaryControlPanel.setVisibility(View.GONE); if (binding.relatedStreamsLayout != null) { if (showRelatedStreams) { @@ -1431,6 +1471,7 @@ public final class VideoDetailFragment ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_expand_more)); binding.detailToggleDescriptionView.setVisibility(View.VISIBLE); binding.detailDescriptionRootLayout.setVisibility(View.GONE); + binding.detailSecondaryControlPanel.setVisibility(View.GONE); if (info.getUploadDate() != null) { binding.detailUploadDateView.setText(Localization diff --git a/app/src/main/res/layout-large-land/fragment_video_detail.xml b/app/src/main/res/layout-large-land/fragment_video_detail.xml index 73af54d63..5a23ba346 100644 --- a/app/src/main/res/layout-large-land/fragment_video_detail.xml +++ b/app/src/main/res/layout-large-land/fragment_video_detail.xml @@ -426,6 +426,7 @@ + + android:padding="@dimen/detail_control_padding"> - + + + + + + + + + + + + + android:padding="@dimen/detail_control_padding"> - + + + + + + + + + + + 5dp 50dp + + 12sp + 80dp + 55dp + 6dp 16dp 16dp From a314f55a1718f0ada954412e2882b44a41026af8 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sun, 1 Nov 2020 13:55:20 +0100 Subject: [PATCH 2/9] Move description to a tab alongside related streams and comments --- .../java/org/schabi/newpipe/BaseFragment.java | 6 +- .../newpipe/fragments/BaseStateFragment.java | 3 +- .../fragments/detail/DescriptionFragment.java | 93 +++++++++++++++++++ .../fragments/detail/VideoDetailFragment.java | 83 ++++------------- .../fragments/list/BaseListFragment.java | 4 +- .../list/channel/ChannelFragment.java | 4 +- .../list/comments/CommentsFragment.java | 10 +- .../fragments/list/search/SearchFragment.java | 6 +- .../list/videos/RelatedVideosFragment.java | 4 +- .../fragment_video_detail.xml | 50 +--------- .../main/res/layout/fragment_description.xml | 48 ++++++++++ .../main/res/layout/fragment_video_detail.xml | 46 +-------- 12 files changed, 179 insertions(+), 178 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java create mode 100644 app/src/main/res/layout/fragment_description.xml diff --git a/app/src/main/java/org/schabi/newpipe/BaseFragment.java b/app/src/main/java/org/schabi/newpipe/BaseFragment.java index 54513a0af..0d2412778 100644 --- a/app/src/main/java/org/schabi/newpipe/BaseFragment.java +++ b/app/src/main/java/org/schabi/newpipe/BaseFragment.java @@ -35,7 +35,7 @@ public abstract class BaseFragment extends Fragment { //////////////////////////////////////////////////////////////////////////*/ @Override - public void onAttach(final Context context) { + public void onAttach(@NonNull final Context context) { super.onAttach(context); activity = (AppCompatActivity) context; } @@ -61,7 +61,7 @@ public abstract class BaseFragment extends Fragment { @Override - public void onViewCreated(final View rootView, final Bundle savedInstanceState) { + public void onViewCreated(@NonNull final View rootView, final Bundle savedInstanceState) { super.onViewCreated(rootView, savedInstanceState); if (DEBUG) { Log.d(TAG, "onViewCreated() called with: " @@ -73,7 +73,7 @@ public abstract class BaseFragment extends Fragment { } @Override - public void onSaveInstanceState(final Bundle outState) { + public void onSaveInstanceState(@NonNull final Bundle outState) { super.onSaveInstanceState(outState); Icepick.saveInstanceState(this, outState); } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java index 88f9b78b8..f876b767c 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/BaseStateFragment.java @@ -10,6 +10,7 @@ import android.widget.ProgressBar; import android.widget.TextView; import android.widget.Toast; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; @@ -56,7 +57,7 @@ public abstract class BaseStateFragment extends BaseFragment implements ViewC private TextView errorTextView; @Override - public void onViewCreated(final View rootView, final Bundle savedInstanceState) { + public void onViewCreated(@NonNull final View rootView, final Bundle savedInstanceState) { super.onViewCreated(rootView, savedInstanceState); doInitialLoadLogic(); } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java new file mode 100644 index 000000000..b4424928f --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java @@ -0,0 +1,93 @@ +package org.schabi.newpipe.fragments.detail; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.text.HtmlCompat; + +import org.schabi.newpipe.BaseFragment; +import org.schabi.newpipe.databinding.FragmentDescriptionBinding; +import org.schabi.newpipe.extractor.stream.Description; +import org.schabi.newpipe.extractor.stream.StreamInfo; +import org.schabi.newpipe.util.Localization; +import org.schabi.newpipe.util.TextLinkifier; + +import icepick.State; +import io.reactivex.rxjava3.disposables.Disposable; + +import static android.text.TextUtils.isEmpty; + +public class DescriptionFragment extends BaseFragment { + + @State + StreamInfo streamInfo = null; + @Nullable + Disposable descriptionDisposable = null; + + public DescriptionFragment() { + } + + public DescriptionFragment(final StreamInfo streamInfo) { + this.streamInfo = streamInfo; + } + + @Override + public View onCreateView(@NonNull final LayoutInflater inflater, + @Nullable final ViewGroup container, + @Nullable final Bundle savedInstanceState) { + final FragmentDescriptionBinding binding = + FragmentDescriptionBinding.inflate(inflater, container, false); + if (streamInfo != null) { + setupUploadDate(binding.detailUploadDateView); + setupDescription(binding.detailDescriptionView); + } + return binding.getRoot(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + if (descriptionDisposable != null) { + descriptionDisposable.dispose(); + } + } + + private void setupUploadDate(final TextView uploadDateTextView) { + if (streamInfo.getUploadDate() != null) { + uploadDateTextView.setText(Localization + .localizeUploadDate(activity, streamInfo.getUploadDate().offsetDateTime())); + } else { + uploadDateTextView.setVisibility(View.GONE); + } + } + + private void setupDescription(final TextView descriptionTextView) { + final Description description = streamInfo.getDescription(); + if (description == null || isEmpty(description.getContent()) + || description == Description.emptyDescription) { + descriptionTextView.setText(""); + return; + } + + switch (description.getType()) { + case Description.HTML: + descriptionDisposable = TextLinkifier.createLinksFromHtmlBlock(requireContext(), + description.getContent(), descriptionTextView, + HtmlCompat.FROM_HTML_MODE_LEGACY); + break; + case Description.MARKDOWN: + descriptionDisposable = TextLinkifier.createLinksFromMarkdownText(requireContext(), + description.getContent(), descriptionTextView); + break; + case Description.PLAIN_TEXT: default: + descriptionDisposable = TextLinkifier.createLinksFromPlainText(requireContext(), + description.getContent(), descriptionTextView); + break; + } + } +} diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 7add95a6c..8148fb40c 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -38,7 +38,6 @@ import androidx.appcompat.content.res.AppCompatResources; import androidx.appcompat.widget.Toolbar; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.core.content.ContextCompat; -import androidx.core.text.HtmlCompat; import androidx.fragment.app.Fragment; import androidx.preference.PreferenceManager; @@ -61,14 +60,12 @@ import org.schabi.newpipe.extractor.ServiceList; import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.services.youtube.extractors.YoutubeStreamExtractor; import org.schabi.newpipe.extractor.stream.AudioStream; -import org.schabi.newpipe.extractor.stream.Description; import org.schabi.newpipe.extractor.stream.Stream; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.StreamType; import org.schabi.newpipe.extractor.stream.VideoStream; import org.schabi.newpipe.fragments.BackPressable; import org.schabi.newpipe.fragments.BaseStateFragment; -import org.schabi.newpipe.fragments.EmptyFragment; import org.schabi.newpipe.fragments.list.comments.CommentsFragment; import org.schabi.newpipe.fragments.list.videos.RelatedVideosFragment; import org.schabi.newpipe.ktx.AnimationType; @@ -97,9 +94,7 @@ import org.schabi.newpipe.util.Localization; import org.schabi.newpipe.util.NavigationHelper; import org.schabi.newpipe.util.PermissionHelper; import org.schabi.newpipe.util.ShareUtils; -import org.schabi.newpipe.util.TextLinkifier; import org.schabi.newpipe.util.ThemeHelper; -import org.schabi.newpipe.views.LargeTextMovementMethod; import java.util.Iterator; import java.util.LinkedList; @@ -150,7 +145,7 @@ public final class VideoDetailFragment private static final String COMMENTS_TAB_TAG = "COMMENTS"; private static final String RELATED_TAB_TAG = "NEXT VIDEO"; - private static final String EMPTY_TAB_TAG = "EMPTY TAB"; + private static final String DESCRIPTION_TAB_TAG = "DESCRIPTION TAB"; private boolean showRelatedStreams; private boolean showComments; @@ -570,21 +565,16 @@ public final class VideoDetailFragment } private void toggleTitleAndDescription() { - if (binding.detailDescriptionRootLayout.getVisibility() == View.VISIBLE) { - binding.detailVideoTitleView.setMaxLines(1); - binding.detailDescriptionRootLayout.setVisibility(View.GONE); - binding.detailDescriptionView.setFocusable(false); - binding.detailToggleDescriptionView.setImageResource( - ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_expand_more)); - binding.detailSecondaryControlPanel.setVisibility(View.GONE); - } else { + if (binding.detailSecondaryControlPanel.getVisibility() == View.GONE) { binding.detailVideoTitleView.setMaxLines(10); - binding.detailDescriptionRootLayout.setVisibility(View.VISIBLE); - binding.detailDescriptionView.setFocusable(true); - binding.detailDescriptionView.setMovementMethod(new LargeTextMovementMethod()); binding.detailToggleDescriptionView.setImageResource( ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_expand_less)); binding.detailSecondaryControlPanel.setVisibility(View.VISIBLE); + } else { + binding.detailVideoTitleView.setMaxLines(1); + binding.detailToggleDescriptionView.setImageResource( + ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_expand_more)); + binding.detailSecondaryControlPanel.setVisibility(View.GONE); } } @@ -928,10 +918,8 @@ public final class VideoDetailFragment pageAdapter.addFragment(new Fragment(), RELATED_TAB_TAG); } - if (pageAdapter.getCount() == 0) { - pageAdapter.addFragment(new EmptyFragment(), EMPTY_TAB_TAG); - } - + //temp empty fragment. will be updated in handleResult + pageAdapter.addFragment(new Fragment(), DESCRIPTION_TAB_TAG); pageAdapter.notifyDataSetUpdate(); if (pageAdapter.getCount() < 2) { @@ -1153,29 +1141,6 @@ public final class VideoDetailFragment binding.playerPlaceholder.requestLayout(); } - private void prepareDescription(final Description description) { - if (description == null || isEmpty(description.getContent()) - || description == Description.emptyDescription) { - return; - } - - switch (description.getType()) { - case Description.HTML: - disposables.add(TextLinkifier.createLinksFromHtmlBlock(requireContext(), - description.getContent(), binding.detailDescriptionView, - HtmlCompat.FROM_HTML_MODE_LEGACY)); - break; - case Description.MARKDOWN: - disposables.add(TextLinkifier.createLinksFromMarkdownText(requireContext(), - description.getContent(), binding.detailDescriptionView)); - break; - case Description.PLAIN_TEXT: default: - disposables.add(TextLinkifier.createLinksFromPlainText(requireContext(), - description.getContent(), binding.detailDescriptionView)); - break; - } - } - private final ViewTreeObserver.OnPreDrawListener preDrawListener = new ViewTreeObserver.OnPreDrawListener() { @Override @@ -1348,7 +1313,6 @@ public final class VideoDetailFragment binding.detailVideoTitleView.setMaxLines(1); animate(binding.detailVideoTitleView, true, 0); - binding.detailDescriptionRootLayout.setVisibility(View.GONE); binding.detailToggleDescriptionView.setVisibility(View.GONE); binding.detailTitleRootLayout.setClickable(false); binding.detailSecondaryControlPanel.setVisibility(View.GONE); @@ -1379,7 +1343,6 @@ public final class VideoDetailFragment if (binding.relatedStreamsLayout == null) { //phone pageAdapter.updateItem(RELATED_TAB_TAG, RelatedVideosFragment.getInstance(info)); - pageAdapter.notifyDataSetUpdate(); } else { //tablet getChildFragmentManager().beginTransaction() .replace(R.id.relatedStreamsLayout, @@ -1389,6 +1352,10 @@ public final class VideoDetailFragment player != null && player.isFullscreen() ? View.GONE : View.VISIBLE); } } + pageAdapter.updateItem(DESCRIPTION_TAB_TAG, + new DescriptionFragment(info)); + pageAdapter.notifyDataSetUpdate(); + animate(binding.detailThumbnailPlayButton, true, 200); binding.detailVideoTitleView.setText(title); @@ -1397,7 +1364,7 @@ public final class VideoDetailFragment } else if (!isEmpty(info.getUploaderName())) { displayUploaderAsSubChannel(info); } else { - binding.detailUploadDateView.setVisibility(View.GONE); + binding.detailUploaderTextView.setVisibility(View.GONE); binding.detailUploaderThumbnailView.setVisibility(View.GONE); } @@ -1465,23 +1432,12 @@ public final class VideoDetailFragment binding.detailDurationView.setVisibility(View.GONE); } - binding.detailDescriptionView.setVisibility(View.GONE); binding.detailTitleRootLayout.setClickable(true); binding.detailToggleDescriptionView.setImageResource( ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_expand_more)); binding.detailToggleDescriptionView.setVisibility(View.VISIBLE); - binding.detailDescriptionRootLayout.setVisibility(View.GONE); binding.detailSecondaryControlPanel.setVisibility(View.GONE); - if (info.getUploadDate() != null) { - binding.detailUploadDateView.setText(Localization - .localizeUploadDate(activity, info.getUploadDate().offsetDateTime())); - binding.detailUploadDateView.setVisibility(View.VISIBLE); - } else { - binding.detailUploadDateView.setText(null); - binding.detailUploadDateView.setVisibility(View.GONE); - } - sortedVideoStreams = ListHelper.getSortedStreamVideosList( activity, info.getVideoStreams(), @@ -1489,7 +1445,6 @@ public final class VideoDetailFragment false); selectedVideoStreamIndex = ListHelper .getDefaultResolutionIndex(activity, sortedVideoStreams); - prepareDescription(info.getDescription()); updateProgressInfo(info); initThumbnailViews(info); disposables.add(showMetaInfoInTextView(info.getMetaInfo(), binding.detailMetaInfoTextView, @@ -1535,7 +1490,7 @@ public final class VideoDetailFragment binding.detailSubChannelTextView.setText(info.getUploaderName()); binding.detailSubChannelTextView.setVisibility(View.VISIBLE); binding.detailSubChannelTextView.setSelected(true); - binding.detailUploadDateView.setVisibility(View.GONE); + binding.detailUploaderTextView.setVisibility(View.GONE); } private void displayBothUploaderAndSubChannel(final StreamInfo info) { @@ -1546,12 +1501,12 @@ public final class VideoDetailFragment binding.detailSubChannelThumbnailView.setVisibility(View.VISIBLE); if (!isEmpty(info.getUploaderName())) { - binding.detailUploadDateView.setText( + binding.detailUploaderTextView.setText( String.format(getString(R.string.video_detail_by), info.getUploaderName())); - binding.detailUploadDateView.setVisibility(View.VISIBLE); - binding.detailUploadDateView.setSelected(true); + binding.detailUploaderTextView.setVisibility(View.VISIBLE); + binding.detailUploaderTextView.setSelected(true); } else { - binding.detailUploadDateView.setVisibility(View.GONE); + binding.detailUploaderTextView.setVisibility(View.GONE); } } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java index b251d4b93..d42b0a088 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/BaseListFragment.java @@ -70,7 +70,7 @@ public abstract class BaseListFragment extends BaseStateFragment //////////////////////////////////////////////////////////////////////////*/ @Override - public void onAttach(final Context context) { + public void onAttach(@NonNull final Context context) { super.onAttach(context); if (infoListAdapter == null) { @@ -186,7 +186,7 @@ public abstract class BaseListFragment extends BaseStateFragment } @Override - public void onSaveInstanceState(final Bundle bundle) { + public void onSaveInstanceState(@NonNull final Bundle bundle) { super.onSaveInstanceState(bundle); if (useDefaultStateSaving) { savedState = StateSaver diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java index b4dd45e93..e8a5d380b 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelFragment.java @@ -106,7 +106,7 @@ public class ChannelFragment extends BaseListInfoFragment //////////////////////////////////////////////////////////////////////////*/ @Override - public void onAttach(final Context context) { + public void onAttach(@NonNull final Context context) { super.onAttach(context); subscriptionManager = new SubscriptionManager(activity); } @@ -119,7 +119,7 @@ public class ChannelFragment extends BaseListInfoFragment } @Override - public void onViewCreated(final View rootView, final Bundle savedInstanceState) { + public void onViewCreated(@NonNull final View rootView, final Bundle savedInstanceState) { super.onViewCreated(rootView, savedInstanceState); channelBinding = FragmentChannelBinding.bind(rootView); } diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java index c0abc469b..3682fe13b 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/comments/CommentsFragment.java @@ -1,6 +1,5 @@ package org.schabi.newpipe.fragments.list.comments; -import android.content.Context; import android.os.Bundle; import android.view.LayoutInflater; import android.view.Menu; @@ -37,11 +36,6 @@ public class CommentsFragment extends BaseListInfoFragment { // LifeCycle //////////////////////////////////////////////////////////////////////////*/ - @Override - public void onAttach(final Context context) { - super.onAttach(context); - } - @Override public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @@ -52,9 +46,7 @@ public class CommentsFragment extends BaseListInfoFragment { @Override public void onDestroy() { super.onDestroy(); - if (disposables != null) { - disposables.clear(); - } + disposables.clear(); } /*////////////////////////////////////////////////////////////////////////// diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java index b15bb97f2..5273fd396 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/search/SearchFragment.java @@ -196,7 +196,7 @@ public class SearchFragment extends BaseListFragment @@ -588,53 +589,6 @@ android:layout_marginRight="8dp" android:background="?attr/separator_color" /> - - - - - - - - - - - diff --git a/app/src/main/res/layout/fragment_description.xml b/app/src/main/res/layout/fragment_description.xml new file mode 100644 index 000000000..e3845e892 --- /dev/null +++ b/app/src/main/res/layout/fragment_description.xml @@ -0,0 +1,48 @@ + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_video_detail.xml b/app/src/main/res/layout/fragment_video_detail.xml index 612ec389a..7beb41ee3 100644 --- a/app/src/main/res/layout/fragment_video_detail.xml +++ b/app/src/main/res/layout/fragment_video_detail.xml @@ -493,7 +493,8 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal" - android:padding="@dimen/detail_control_padding" + android:paddingHorizontal="@dimen/detail_control_padding" + android:paddingBottom="@dimen/detail_control_padding" android:visibility="gone" tools:visibility="visible"> @@ -571,49 +572,6 @@ android:layout_marginRight="8dp" android:background="?attr/separator_color" /> - - - - - - - - - - - From 4c3ba0fe3d2887a545238d958a5c425e74e219a4 Mon Sep 17 00:00:00 2001 From: Stypox Date: Tue, 8 Dec 2020 21:47:02 +0100 Subject: [PATCH 3/9] Add icons to VideoDetailFragment tab layout for better accessibility --- .../fragments/detail/VideoDetailFragment.java | 68 +++++++++++++------ .../event/CustomBottomSheetBehavior.java | 2 +- app/src/main/res/drawable/dot_default.xml | 12 ---- app/src/main/res/drawable/dot_selected.xml | 12 ---- .../res/drawable/ic_art_track_white_24dp.xml | 5 ++ .../res/drawable/ic_comment_white_24dp.xml | 5 ++ .../drawable/ic_description_white_24dp.xml | 5 ++ app/src/main/res/drawable/tab_selector.xml | 7 -- .../fragment_video_detail.xml | 7 +- .../main/res/layout/fragment_video_detail.xml | 7 +- 10 files changed, 71 insertions(+), 59 deletions(-) delete mode 100644 app/src/main/res/drawable/dot_default.xml delete mode 100644 app/src/main/res/drawable/dot_selected.xml create mode 100644 app/src/main/res/drawable/ic_art_track_white_24dp.xml create mode 100644 app/src/main/res/drawable/ic_comment_white_24dp.xml create mode 100644 app/src/main/res/drawable/ic_description_white_24dp.xml delete mode 100644 app/src/main/res/drawable/tab_selector.xml diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 8148fb40c..e9eb22314 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -29,6 +29,7 @@ import android.view.animation.DecelerateInterpolator; import android.widget.FrameLayout; import android.widget.RelativeLayout; +import androidx.annotation.AttrRes; import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; @@ -45,6 +46,7 @@ import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.PlaybackParameters; import com.google.android.material.appbar.AppBarLayout; import com.google.android.material.bottomsheet.BottomSheetBehavior; +import com.google.android.material.tabs.TabLayout; import com.nostra13.universalimageloader.core.assist.FailReason; import com.nostra13.universalimageloader.core.listener.ImageLoadingListener; import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener; @@ -96,6 +98,7 @@ import org.schabi.newpipe.util.PermissionHelper; import org.schabi.newpipe.util.ShareUtils; import org.schabi.newpipe.util.ThemeHelper; +import java.util.ArrayList; import java.util.Iterator; import java.util.LinkedList; import java.util.List; @@ -147,9 +150,11 @@ public final class VideoDetailFragment private static final String RELATED_TAB_TAG = "NEXT VIDEO"; private static final String DESCRIPTION_TAB_TAG = "DESCRIPTION TAB"; + // tabs private boolean showRelatedStreams; private boolean showComments; private String selectedTabTag; + @AttrRes @NonNull final List tabIcons = new ArrayList<>(); private int updateFlags = 0; @@ -493,7 +498,7 @@ public final class VideoDetailFragment openVideoPlayer(); break; case R.id.detail_title_root_layout: - toggleTitleAndDescription(); + toggleTitleAndSecondaryControls(); break; case R.id.overlay_thumbnail: case R.id.overlay_metadata_layout: @@ -564,7 +569,7 @@ public final class VideoDetailFragment return true; } - private void toggleTitleAndDescription() { + private void toggleTitleAndSecondaryControls() { if (binding.detailSecondaryControlPanel.getVisibility() == View.GONE) { binding.detailVideoTitleView.setMaxLines(10); binding.detailToggleDescriptionView.setImageResource( @@ -907,19 +912,23 @@ public final class VideoDetailFragment selectedTabTag = pageAdapter.getItemTitle(binding.viewPager.getCurrentItem()); } pageAdapter.clearAllItems(); + tabIcons.clear(); if (shouldShowComments()) { pageAdapter.addFragment( CommentsFragment.getInstance(serviceId, url, title), COMMENTS_TAB_TAG); + tabIcons.add(R.drawable.ic_comment_white_24dp); } if (showRelatedStreams && binding.relatedStreamsLayout == null) { //temp empty fragment. will be updated in handleResult pageAdapter.addFragment(new Fragment(), RELATED_TAB_TAG); + tabIcons.add(R.drawable.ic_art_track_white_24dp); } - //temp empty fragment. will be updated in handleResult + // temp empty fragment. will be updated in handleResult pageAdapter.addFragment(new Fragment(), DESCRIPTION_TAB_TAG); + tabIcons.add(R.drawable.ic_description_white_24dp); pageAdapter.notifyDataSetUpdate(); if (pageAdapter.getCount() < 2) { @@ -930,9 +939,45 @@ public final class VideoDetailFragment binding.viewPager.setCurrentItem(position); } binding.tabLayout.setVisibility(View.VISIBLE); + updateTabIcons(); } } + /** + * To be called whenever {@link #pageAdapter} is modified, since that triggers a refresh in + * {@link FragmentVideoDetailBinding#tabLayout} resetting all tab's icons. This reads icons from + * {@link #tabIcons}, which are set in {@link #initTabs()} + */ + private void updateTabIcons() { + for (int i = 0; i < tabIcons.size(); ++i) { + final TabLayout.Tab tab = binding.tabLayout.getTabAt(i); + if (tab != null) { + tab.setIcon(tabIcons.get(i)); + } + } + } + + private void updateTabs(@NonNull final StreamInfo info) { + if (showRelatedStreams) { + if (binding.relatedStreamsLayout == null) { // phone + pageAdapter.updateItem(RELATED_TAB_TAG, + RelatedVideosFragment.getInstance(info)); + } else { // tablet + getChildFragmentManager().beginTransaction() + .replace(R.id.relatedStreamsLayout, + RelatedVideosFragment.getInstance(info)) + .commitAllowingStateLoss(); + binding.relatedStreamsLayout.setVisibility( + player != null && player.isFullscreen() ? View.GONE : View.VISIBLE); + } + } + + pageAdapter.updateItem(DESCRIPTION_TAB_TAG, + new DescriptionFragment(info)); + pageAdapter.notifyDataSetUpdate(); + updateTabIcons(); + } + private boolean shouldShowComments() { try { return showComments && NewPipe.getService(serviceId) @@ -1339,22 +1384,7 @@ public final class VideoDetailFragment currentInfo = info; setInitialData(info.getServiceId(), info.getOriginalUrl(), info.getName(), playQueue); - if (showRelatedStreams) { - if (binding.relatedStreamsLayout == null) { //phone - pageAdapter.updateItem(RELATED_TAB_TAG, - RelatedVideosFragment.getInstance(info)); - } else { //tablet - getChildFragmentManager().beginTransaction() - .replace(R.id.relatedStreamsLayout, - RelatedVideosFragment.getInstance(info)) - .commitAllowingStateLoss(); - binding.relatedStreamsLayout.setVisibility( - player != null && player.isFullscreen() ? View.GONE : View.VISIBLE); - } - } - pageAdapter.updateItem(DESCRIPTION_TAB_TAG, - new DescriptionFragment(info)); - pageAdapter.notifyDataSetUpdate(); + updateTabs(info); animate(binding.detailThumbnailPlayButton, true, 200); binding.detailVideoTitleView.setText(title); diff --git a/app/src/main/java/org/schabi/newpipe/player/event/CustomBottomSheetBehavior.java b/app/src/main/java/org/schabi/newpipe/player/event/CustomBottomSheetBehavior.java index 830d1c55c..61023875c 100644 --- a/app/src/main/java/org/schabi/newpipe/player/event/CustomBottomSheetBehavior.java +++ b/app/src/main/java/org/schabi/newpipe/player/event/CustomBottomSheetBehavior.java @@ -27,7 +27,7 @@ public class CustomBottomSheetBehavior extends BottomSheetBehavior private boolean skippingInterception = false; private final List skipInterceptionOfElements = Arrays.asList( R.id.detail_content_root_layout, R.id.relatedStreamsLayout, - R.id.itemsListPanel, R.id.view_pager, R.id.bottomControls, + R.id.itemsListPanel, R.id.view_pager, R.id.tab_layout, R.id.bottomControls, R.id.playPauseButton, R.id.playPreviousButton, R.id.playNextButton); @Override diff --git a/app/src/main/res/drawable/dot_default.xml b/app/src/main/res/drawable/dot_default.xml deleted file mode 100644 index fac1a2e81..000000000 --- a/app/src/main/res/drawable/dot_default.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - diff --git a/app/src/main/res/drawable/dot_selected.xml b/app/src/main/res/drawable/dot_selected.xml deleted file mode 100644 index 77992e4a6..000000000 --- a/app/src/main/res/drawable/dot_selected.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - diff --git a/app/src/main/res/drawable/ic_art_track_white_24dp.xml b/app/src/main/res/drawable/ic_art_track_white_24dp.xml new file mode 100644 index 000000000..abfdc203a --- /dev/null +++ b/app/src/main/res/drawable/ic_art_track_white_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_comment_white_24dp.xml b/app/src/main/res/drawable/ic_comment_white_24dp.xml new file mode 100644 index 000000000..7361b7fa6 --- /dev/null +++ b/app/src/main/res/drawable/ic_comment_white_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/ic_description_white_24dp.xml b/app/src/main/res/drawable/ic_description_white_24dp.xml new file mode 100644 index 000000000..e7ef3d4b5 --- /dev/null +++ b/app/src/main/res/drawable/ic_description_white_24dp.xml @@ -0,0 +1,5 @@ + + + diff --git a/app/src/main/res/drawable/tab_selector.xml b/app/src/main/res/drawable/tab_selector.xml deleted file mode 100644 index 35581f25d..000000000 --- a/app/src/main/res/drawable/tab_selector.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/app/src/main/res/layout-large-land/fragment_video_detail.xml b/app/src/main/res/layout-large-land/fragment_video_detail.xml index d61e8fbe5..4d5fe2fce 100644 --- a/app/src/main/res/layout-large-land/fragment_video_detail.xml +++ b/app/src/main/res/layout-large-land/fragment_video_detail.xml @@ -603,11 +603,10 @@ + app:tabIconTint="?attr/colorAccent" + app:tabGravity="fill" /> diff --git a/app/src/main/res/layout/fragment_video_detail.xml b/app/src/main/res/layout/fragment_video_detail.xml index 7beb41ee3..0432ed416 100644 --- a/app/src/main/res/layout/fragment_video_detail.xml +++ b/app/src/main/res/layout/fragment_video_detail.xml @@ -586,11 +586,10 @@ + app:tabIconTint="?attr/colorAccent" + app:tabGravity="fill" /> From d043a4f410c2397a25e6bd26cccd7ddbc067fd47 Mon Sep 17 00:00:00 2001 From: Stypox Date: Tue, 29 Dec 2020 16:02:10 +0100 Subject: [PATCH 4/9] Always show tab layout at the bottom of the screen --- .../fragment_video_detail.xml | 22 +++++++++---------- .../main/res/layout/fragment_video_detail.xml | 22 +++++++++---------- 2 files changed, 22 insertions(+), 22 deletions(-) diff --git a/app/src/main/res/layout-large-land/fragment_video_detail.xml b/app/src/main/res/layout-large-land/fragment_video_detail.xml index 4d5fe2fce..63e67f1bd 100644 --- a/app/src/main/res/layout-large-land/fragment_video_detail.xml +++ b/app/src/main/res/layout-large-land/fragment_video_detail.xml @@ -591,24 +591,24 @@ - + app:layout_behavior="@string/appbar_scrolling_view_behavior" + android:paddingBottom="48dp"/> - - - + diff --git a/app/src/main/res/layout/fragment_video_detail.xml b/app/src/main/res/layout/fragment_video_detail.xml index 0432ed416..0a18874d7 100644 --- a/app/src/main/res/layout/fragment_video_detail.xml +++ b/app/src/main/res/layout/fragment_video_detail.xml @@ -574,24 +574,24 @@ - + app:layout_behavior="@string/appbar_scrolling_view_behavior" + android:paddingBottom="48dp"/> - - - + From 750c4ffbd3ccfa9e6cf17ad6fb2f3a0e58e4a2bb Mon Sep 17 00:00:00 2001 From: Stypox Date: Tue, 29 Dec 2020 22:20:33 +0100 Subject: [PATCH 5/9] Add preference to hide description tab in video details --- .../fragments/detail/VideoDetailFragment.java | 69 +++++++++---------- app/src/main/res/values/settings_keys.xml | 3 +- app/src/main/res/values/strings.xml | 6 +- app/src/main/res/xml/content_settings.xml | 13 +++- 4 files changed, 49 insertions(+), 42 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index e9eb22314..375fb0918 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -130,8 +130,6 @@ public final class VideoDetailFragment OnKeyDownListener { public static final String KEY_SWITCHING_PLAYERS = "switching_players"; - private static final int RELATED_STREAMS_UPDATE_FLAG = 0x1; - private static final int COMMENTS_UPDATE_FLAG = 0x2; private static final float MAX_OVERLAY_ALPHA = 0.9f; private static final float MAX_PLAYER_HEIGHT = 0.7f; @@ -151,12 +149,12 @@ public final class VideoDetailFragment private static final String DESCRIPTION_TAB_TAG = "DESCRIPTION TAB"; // tabs - private boolean showRelatedStreams; private boolean showComments; + private boolean showRelatedStreams; + private boolean showDescription; private String selectedTabTag; @AttrRes @NonNull final List tabIcons = new ArrayList<>(); - - private int updateFlags = 0; + private boolean tabSettingsChanged = false; @State protected int serviceId = Constants.NO_SERVICE_ID; @@ -275,17 +273,13 @@ public final class VideoDetailFragment public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); - showRelatedStreams = PreferenceManager.getDefaultSharedPreferences(activity) - .getBoolean(getString(R.string.show_next_video_key), true); - - showComments = PreferenceManager.getDefaultSharedPreferences(activity) - .getBoolean(getString(R.string.show_comments_key), true); - - selectedTabTag = PreferenceManager.getDefaultSharedPreferences(activity) - .getString(getString(R.string.stream_info_selected_tab_key), COMMENTS_TAB_TAG); - - PreferenceManager.getDefaultSharedPreferences(activity) - .registerOnSharedPreferenceChangeListener(this); + final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(activity); + showComments = prefs.getBoolean(getString(R.string.show_comments_key), true); + showRelatedStreams = prefs.getBoolean(getString(R.string.show_next_video_key), true); + showDescription = prefs.getBoolean(getString(R.string.show_description_key), true); + selectedTabTag = prefs.getString( + getString(R.string.stream_info_selected_tab_key), COMMENTS_TAB_TAG); + prefs.registerOnSharedPreferenceChangeListener(this); setupBroadcastReceiver(); @@ -330,17 +324,12 @@ public final class VideoDetailFragment setupBrightness(); - if (updateFlags != 0) { - if (!isLoading.get() && currentInfo != null) { - if ((updateFlags & RELATED_STREAMS_UPDATE_FLAG) != 0) { - startLoading(false); - } - if ((updateFlags & COMMENTS_UPDATE_FLAG) != 0) { - startLoading(false); - } + if (tabSettingsChanged) { + tabSettingsChanged = false; + initTabs(); + if (currentInfo != null) { + updateTabs(currentInfo); } - - updateFlags = 0; } // Check if it was loading when the fragment was stopped/paused @@ -415,12 +404,15 @@ public final class VideoDetailFragment @Override public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, final String key) { - if (key.equals(getString(R.string.show_next_video_key))) { - showRelatedStreams = sharedPreferences.getBoolean(key, true); - updateFlags |= RELATED_STREAMS_UPDATE_FLAG; - } else if (key.equals(getString(R.string.show_comments_key))) { + if (key.equals(getString(R.string.show_comments_key))) { showComments = sharedPreferences.getBoolean(key, true); - updateFlags |= COMMENTS_UPDATE_FLAG; + tabSettingsChanged = true; + } else if (key.equals(getString(R.string.show_next_video_key))) { + showRelatedStreams = sharedPreferences.getBoolean(key, true); + tabSettingsChanged = true; + } else if (key.equals(getString(R.string.show_description_key))) { + showComments = sharedPreferences.getBoolean(key, true); + tabSettingsChanged = true; } } @@ -926,9 +918,11 @@ public final class VideoDetailFragment tabIcons.add(R.drawable.ic_art_track_white_24dp); } - // temp empty fragment. will be updated in handleResult - pageAdapter.addFragment(new Fragment(), DESCRIPTION_TAB_TAG); - tabIcons.add(R.drawable.ic_description_white_24dp); + if (showDescription) { + // temp empty fragment. will be updated in handleResult + pageAdapter.addFragment(new Fragment(), DESCRIPTION_TAB_TAG); + tabIcons.add(R.drawable.ic_description_white_24dp); + } pageAdapter.notifyDataSetUpdate(); if (pageAdapter.getCount() < 2) { @@ -972,8 +966,11 @@ public final class VideoDetailFragment } } - pageAdapter.updateItem(DESCRIPTION_TAB_TAG, - new DescriptionFragment(info)); + if (showDescription) { + pageAdapter.updateItem(DESCRIPTION_TAB_TAG, + new DescriptionFragment(info)); + } + pageAdapter.notifyDataSetUpdate(); updateTabIcons(); } diff --git a/app/src/main/res/values/settings_keys.xml b/app/src/main/res/values/settings_keys.xml index 1e74f25a6..1c4ad11a3 100644 --- a/app/src/main/res/values/settings_keys.xml +++ b/app/src/main/res/values/settings_keys.xml @@ -197,8 +197,9 @@ show_search_suggestions show_play_with_kodi - show_next_video show_comments + show_next_video + show_description show_meta_info stream_info_selected_tab show_hold_to_append diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 82a5bda6c..207680fef 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -91,9 +91,12 @@ Switching from one player to another may replace your queue The active player queue will be replaced Load thumbnails + Turn off to prevent loading thumbnails, saving data and memory usage. Changes clear both in-memory and on-disk image cache. Show comments Turn off to hide comments - Turn off to prevent loading thumbnails, saving data and memory usage. Changes clear both in-memory and on-disk image cache. + Show \'Next\' and \'Similar\' videos + Show description + Turn off to hide video description and additional information Show meta info Turn off to hide meta info boxes with additional information about the stream creator, stream content or a search request. Image cache wiped @@ -124,7 +127,6 @@ Continue playing after interruptions (e.g. phonecalls) Download Autoplay - Show \'Next\' and \'Similar\' videos Show \"Hold to append\" tip Show tip when pressing the background or the popup button in video \"Details:\" Unsupported URL diff --git a/app/src/main/res/xml/content_settings.xml b/app/src/main/res/xml/content_settings.xml index 914fb2e59..70fb3b977 100644 --- a/app/src/main/res/xml/content_settings.xml +++ b/app/src/main/res/xml/content_settings.xml @@ -72,6 +72,13 @@ android:title="@string/download_thumbnail_title" app:iconSpaceReserved="false" /> + + Date: Thu, 14 Jan 2021 11:06:37 +0100 Subject: [PATCH 6/9] Add content description to detail fragment tabs --- .../fragments/detail/VideoDetailFragment.java | 18 +++++++++++++----- app/src/main/res/values/strings.xml | 3 +++ 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index 375fb0918..cb34fc189 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -33,6 +33,7 @@ import androidx.annotation.AttrRes; import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.StringRes; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.content.res.AppCompatResources; @@ -154,6 +155,7 @@ public final class VideoDetailFragment private boolean showDescription; private String selectedTabTag; @AttrRes @NonNull final List tabIcons = new ArrayList<>(); + @StringRes @NonNull final List tabContentDescriptions = new ArrayList<>(); private boolean tabSettingsChanged = false; @State @@ -905,23 +907,27 @@ public final class VideoDetailFragment } pageAdapter.clearAllItems(); tabIcons.clear(); + tabContentDescriptions.clear(); if (shouldShowComments()) { pageAdapter.addFragment( CommentsFragment.getInstance(serviceId, url, title), COMMENTS_TAB_TAG); tabIcons.add(R.drawable.ic_comment_white_24dp); + tabContentDescriptions.add(R.string.comments_tab_description); } if (showRelatedStreams && binding.relatedStreamsLayout == null) { //temp empty fragment. will be updated in handleResult pageAdapter.addFragment(new Fragment(), RELATED_TAB_TAG); tabIcons.add(R.drawable.ic_art_track_white_24dp); + tabContentDescriptions.add(R.string.related_streams_tab_description); } if (showDescription) { // temp empty fragment. will be updated in handleResult pageAdapter.addFragment(new Fragment(), DESCRIPTION_TAB_TAG); tabIcons.add(R.drawable.ic_description_white_24dp); + tabContentDescriptions.add(R.string.description_tab_description); } pageAdapter.notifyDataSetUpdate(); @@ -933,20 +939,22 @@ public final class VideoDetailFragment binding.viewPager.setCurrentItem(position); } binding.tabLayout.setVisibility(View.VISIBLE); - updateTabIcons(); + updateTabIconsAndContentDescriptions(); } } /** * To be called whenever {@link #pageAdapter} is modified, since that triggers a refresh in - * {@link FragmentVideoDetailBinding#tabLayout} resetting all tab's icons. This reads icons from - * {@link #tabIcons}, which are set in {@link #initTabs()} + * {@link FragmentVideoDetailBinding#tabLayout} resetting all tab's icons and content + * descriptions. This reads icons from {@link #tabIcons} and content descriptions from + * {@link #tabContentDescriptions}, which are all set in {@link #initTabs()}. */ - private void updateTabIcons() { + private void updateTabIconsAndContentDescriptions() { for (int i = 0; i < tabIcons.size(); ++i) { final TabLayout.Tab tab = binding.tabLayout.getTabAt(i); if (tab != null) { tab.setIcon(tabIcons.get(i)); + tab.setContentDescription(tabContentDescriptions.get(i)); } } } @@ -972,7 +980,7 @@ public final class VideoDetailFragment } pageAdapter.notifyDataSetUpdate(); - updateTabIcons(); + updateTabIconsAndContentDescriptions(); } private boolean shouldShowComments() { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 207680fef..702dcafbf 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -281,6 +281,9 @@ Uploader\'s avatar thumbnail Likes Dislikes + Comments + Related streams + Description Use Tor (Experimental) Force download traffic through Tor for increased privacy (streaming videos not yet supported). Report error From 71d92c8d1b1a440b7c0b5dce5f198480fe5af15d Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 14 Jan 2021 13:18:34 +0100 Subject: [PATCH 7/9] Hide tab layout in detail fragment when there is no space --- .../fragments/detail/VideoDetailFragment.java | 55 +++++++++++++++++-- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index cb34fc189..befd53678 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -10,6 +10,8 @@ import android.content.SharedPreferences; import android.content.pm.ActivityInfo; import android.database.ContentObserver; import android.graphics.Color; +import android.graphics.Point; +import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Build; @@ -19,6 +21,7 @@ import android.os.Looper; import android.provider.Settings; import android.util.DisplayMetrics; import android.util.Log; +import android.util.TypedValue; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; @@ -157,6 +160,7 @@ public final class VideoDetailFragment @AttrRes @NonNull final List tabIcons = new ArrayList<>(); @StringRes @NonNull final List tabContentDescriptions = new ArrayList<>(); private boolean tabSettingsChanged = false; + private int lastAppBarVerticalOffset = Integer.MAX_VALUE; // prevents useless updates @State protected int serviceId = Constants.NO_SERVICE_ID; @@ -575,6 +579,7 @@ public final class VideoDetailFragment ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_expand_more)); binding.detailSecondaryControlPanel.setVisibility(View.GONE); } + updateTabLayoutVisibility(); } /*////////////////////////////////////////////////////////////////////////// @@ -638,6 +643,14 @@ public final class VideoDetailFragment binding.detailControlsBackground.setOnTouchListener(getOnControlsTouchListener()); binding.detailControlsPopup.setOnTouchListener(getOnControlsTouchListener()); + binding.appBarLayout.addOnOffsetChangedListener((layout, verticalOffset) -> { + // prevent useless updates to tab layout visibility if nothing changed + if (verticalOffset != lastAppBarVerticalOffset) { + lastAppBarVerticalOffset = verticalOffset; + updateTabLayoutVisibility(); + } + }); + setupBottomPlayer(); if (!PlayerHolder.bound) { setHeightThumbnail(); @@ -901,6 +914,10 @@ public final class VideoDetailFragment }); } + /*////////////////////////////////////////////////////////////////////////// + // Tabs + //////////////////////////////////////////////////////////////////////////*/ + private void initTabs() { if (pageAdapter.getCount() != 0) { selectedTabTag = pageAdapter.getItemTitle(binding.viewPager.getCurrentItem()); @@ -931,16 +948,14 @@ public final class VideoDetailFragment } pageAdapter.notifyDataSetUpdate(); - if (pageAdapter.getCount() < 2) { - binding.tabLayout.setVisibility(View.GONE); - } else { + if (pageAdapter.getCount() >= 2) { final int position = pageAdapter.getItemPositionByTitle(selectedTabTag); if (position != -1) { binding.viewPager.setCurrentItem(position); } - binding.tabLayout.setVisibility(View.VISIBLE); updateTabIconsAndContentDescriptions(); } + updateTabLayoutVisibility(); } /** @@ -994,8 +1009,40 @@ public final class VideoDetailFragment } } + public void updateTabLayoutVisibility() { + if (pageAdapter.getCount() < 2) { + binding.tabLayout.setVisibility(View.GONE); + } else { + binding.tabLayout.post(() -> { + if (getContext() != null) { + final Rect pagerHitRect = new Rect(); + binding.viewPager.getHitRect(pagerHitRect); + + final Point displaySize = new Point(); + Objects.requireNonNull(ContextCompat.getSystemService(getContext(), + WindowManager.class)).getDefaultDisplay().getSize(displaySize); + + final int viewPagerVisibleHeight = displaySize.y - pagerHitRect.top; + // see TabLayout.DEFAULT_HEIGHT + final float tabLayoutHeight = TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_DIP, 48, getResources().getDisplayMetrics()); + + if (viewPagerVisibleHeight > tabLayoutHeight * 2) { + // no translation at all when viewPagerVisibleHeight > tabLayout.height * 3 + binding.tabLayout.setTranslationY( + Math.max(0, tabLayoutHeight * 3 - viewPagerVisibleHeight)); + binding.tabLayout.setVisibility(View.VISIBLE); + } else { + binding.tabLayout.setVisibility(View.GONE); + } + } + }); + } + } + public void scrollToTop() { binding.appBarLayout.setExpanded(true, true); + updateTabLayoutVisibility(); } /*////////////////////////////////////////////////////////////////////////// From 243f539439d21320d9d96ed6ecbfc1eab20342c0 Mon Sep 17 00:00:00 2001 From: Stypox Date: Thu, 14 Jan 2021 22:34:55 +0100 Subject: [PATCH 8/9] Use KoreUtil function --- .../fragments/detail/VideoDetailFragment.java | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index befd53678..f8a6e50d5 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -620,6 +620,7 @@ public final class VideoDetailFragment binding.detailUploaderRootLayout.setOnClickListener(this); binding.detailUploaderRootLayout.setOnLongClickListener(this); binding.detailThumbnailRootLayout.setOnClickListener(this); + binding.detailControlsBackground.setOnClickListener(this); binding.detailControlsBackground.setOnLongClickListener(this); binding.detailControlsPopup.setOnClickListener(this); @@ -630,7 +631,8 @@ public final class VideoDetailFragment binding.detailControlsShare.setOnClickListener(this); binding.detailControlsOpenInBrowser.setOnClickListener(this); binding.detailControlsPlayWithKodi.setOnClickListener(this); - showHideKodiButton(); + binding.detailControlsPlayWithKodi.setVisibility(KoreUtil.shouldShowPlayWithKodi( + requireContext(), serviceId) ? View.VISIBLE : View.GONE); binding.overlayThumbnail.setOnClickListener(this); binding.overlayThumbnail.setOnLongClickListener(this); @@ -707,12 +709,6 @@ public final class VideoDetailFragment } } - private void showHideKodiButton() { - // show kodi button if it supports the current service and it is enabled in settings - binding.detailControlsPlayWithKodi.setVisibility(KoreUtil.shouldShowPlayWithKodi( - requireContext(), serviceId) ? View.VISIBLE : View.GONE); - } - /*////////////////////////////////////////////////////////////////////////// // OwnStack //////////////////////////////////////////////////////////////////////////*/ @@ -979,7 +975,7 @@ public final class VideoDetailFragment if (binding.relatedStreamsLayout == null) { // phone pageAdapter.updateItem(RELATED_TAB_TAG, RelatedVideosFragment.getInstance(info)); - } else { // tablet + } else { // tablet + TV getChildFragmentManager().beginTransaction() .replace(R.id.relatedStreamsLayout, RelatedVideosFragment.getInstance(info)) From 3868243c2a79ef88820701b2568c78ac17cf3828 Mon Sep 17 00:00:00 2001 From: Stypox Date: Sun, 17 Jan 2021 15:59:29 +0100 Subject: [PATCH 9/9] Animate secondary controls toggle --- .../fragments/detail/VideoDetailFragment.java | 16 ++++++++-------- .../layout-large-land/fragment_video_detail.xml | 2 +- .../main/res/layout/fragment_video_detail.xml | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java index f8a6e50d5..cb4cfb8b6 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/VideoDetailFragment.java @@ -119,6 +119,7 @@ import static android.text.TextUtils.isEmpty; import static org.schabi.newpipe.extractor.StreamingService.ServiceInfo.MediaCapability.COMMENTS; import static org.schabi.newpipe.extractor.stream.StreamExtractor.NO_AGE_LIMIT; import static org.schabi.newpipe.ktx.ViewUtils.animate; +import static org.schabi.newpipe.ktx.ViewUtils.animateRotation; import static org.schabi.newpipe.player.helper.PlayerHelper.globalScreenOrientationLocked; import static org.schabi.newpipe.player.helper.PlayerHelper.isClearingQueueConfirmationRequired; import static org.schabi.newpipe.player.playqueue.PlayQueueItem.RECOVERY_UNSET; @@ -570,13 +571,13 @@ public final class VideoDetailFragment private void toggleTitleAndSecondaryControls() { if (binding.detailSecondaryControlPanel.getVisibility() == View.GONE) { binding.detailVideoTitleView.setMaxLines(10); - binding.detailToggleDescriptionView.setImageResource( - ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_expand_less)); + animateRotation(binding.detailToggleSecondaryControlsView, + Player.DEFAULT_CONTROLS_DURATION, 180); binding.detailSecondaryControlPanel.setVisibility(View.VISIBLE); } else { binding.detailVideoTitleView.setMaxLines(1); - binding.detailToggleDescriptionView.setImageResource( - ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_expand_more)); + animateRotation(binding.detailToggleSecondaryControlsView, + Player.DEFAULT_CONTROLS_DURATION, 0); binding.detailSecondaryControlPanel.setVisibility(View.GONE); } updateTabLayoutVisibility(); @@ -1406,7 +1407,7 @@ public final class VideoDetailFragment binding.detailVideoTitleView.setMaxLines(1); animate(binding.detailVideoTitleView, true, 0); - binding.detailToggleDescriptionView.setVisibility(View.GONE); + binding.detailToggleSecondaryControlsView.setVisibility(View.GONE); binding.detailTitleRootLayout.setClickable(false); binding.detailSecondaryControlPanel.setVisibility(View.GONE); @@ -1511,9 +1512,8 @@ public final class VideoDetailFragment } binding.detailTitleRootLayout.setClickable(true); - binding.detailToggleDescriptionView.setImageResource( - ThemeHelper.resolveResourceIdFromAttr(requireContext(), R.attr.ic_expand_more)); - binding.detailToggleDescriptionView.setVisibility(View.VISIBLE); + binding.detailToggleSecondaryControlsView.setRotation(0); + binding.detailToggleSecondaryControlsView.setVisibility(View.VISIBLE); binding.detailSecondaryControlPanel.setVisibility(View.GONE); sortedVideoStreams = ListHelper.getSortedStreamVideosList( diff --git a/app/src/main/res/layout-large-land/fragment_video_detail.xml b/app/src/main/res/layout-large-land/fragment_video_detail.xml index 63e67f1bd..14459b494 100644 --- a/app/src/main/res/layout-large-land/fragment_video_detail.xml +++ b/app/src/main/res/layout-large-land/fragment_video_detail.xml @@ -193,7 +193,7 @@ tools:text="Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed a ultricies ex. Integer sit amet sodales risus. Duis non mi et urna pretium bibendum. Nunc eleifend est quis ipsum porttitor egestas. Sed facilisis, nisl quis eleifend pellentesque, orci metus egestas dolor, at accumsan eros metus quis libero." />