From 39b4ed082c5ade58aaf098d5a9d3b20b839f047a Mon Sep 17 00:00:00 2001 From: ThetaDev Date: Wed, 5 Apr 2023 16:17:31 +0200 Subject: [PATCH] refactor: common code from ChannelInfo/Description -> BaseInfoFragment --- .../fragments/detail/BaseInfoFragment.java | 206 ++++++++++++++++++ .../fragments/detail/DescriptionFragment.java | 186 ++++------------ .../list/channel/ChannelInfoFragment.java | 143 ++++-------- 3 files changed, 286 insertions(+), 249 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/fragments/detail/BaseInfoFragment.java diff --git a/app/src/main/java/org/schabi/newpipe/fragments/detail/BaseInfoFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/detail/BaseInfoFragment.java new file mode 100644 index 000000000..d8aea1a03 --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/BaseInfoFragment.java @@ -0,0 +1,206 @@ +package org.schabi.newpipe.fragments.detail; + +import static android.text.TextUtils.isEmpty; +import static org.schabi.newpipe.extractor.utils.Utils.isBlank; +import static org.schabi.newpipe.util.text.TextLinkifier.SET_LINK_MOVEMENT_METHOD; + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.LinearLayout; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.annotation.StringRes; +import androidx.appcompat.widget.TooltipCompat; +import androidx.core.text.HtmlCompat; + +import com.google.android.material.chip.Chip; + +import org.schabi.newpipe.BaseFragment; +import org.schabi.newpipe.R; +import org.schabi.newpipe.databinding.FragmentDescriptionBinding; +import org.schabi.newpipe.databinding.ItemMetadataBinding; +import org.schabi.newpipe.databinding.ItemMetadataTagsBinding; +import org.schabi.newpipe.extractor.StreamingService; +import org.schabi.newpipe.extractor.stream.Description; +import org.schabi.newpipe.util.NavigationHelper; +import org.schabi.newpipe.util.external_communication.ShareUtils; +import org.schabi.newpipe.util.text.TextLinkifier; + +import java.util.List; + +import io.reactivex.rxjava3.disposables.CompositeDisposable; + +public abstract class BaseInfoFragment extends BaseFragment { + final CompositeDisposable descriptionDisposables = new CompositeDisposable(); + FragmentDescriptionBinding binding; + + @Override + public View onCreateView(@NonNull final LayoutInflater inflater, + @Nullable final ViewGroup container, + @Nullable final Bundle savedInstanceState) { + binding = FragmentDescriptionBinding.inflate(inflater, container, false); + setupDescription(); + setupMetadata(inflater, binding.detailMetadataLayout); + addTagsMetadataItem(inflater, binding.detailMetadataLayout); + return binding.getRoot(); + } + + @Override + public void onDestroy() { + descriptionDisposables.clear(); + super.onDestroy(); + } + + /** + * Get the description to display. + * @return description object + */ + @Nullable + protected abstract Description getDescription(); + + /** + * Get the streaming service. Used for generating description links. + * @return streaming service + */ + @Nullable + protected abstract StreamingService getService(); + + /** + * Get the streaming service ID. Used for tag links. + * @return service ID + */ + protected abstract int getServiceId(); + + /** + * Get the URL of the described video. Used for generating description links. + * @return stream URL + */ + @Nullable + protected abstract String getStreamUrl(); + + /** + * Get the list of tags to display below the description. + * @return tag list + */ + @Nullable + public abstract List getTags(); + + /** + * Add additional metadata to display. + * @param inflater LayoutInflater + * @param layout detailMetadataLayout + */ + protected abstract void setupMetadata(LayoutInflater inflater, LinearLayout layout); + + private void setupDescription() { + final Description description = getDescription(); + if (description == null || isEmpty(description.getContent()) + || description == Description.EMPTY_DESCRIPTION) { + binding.detailDescriptionView.setVisibility(View.GONE); + binding.detailSelectDescriptionButton.setVisibility(View.GONE); + return; + } + + // start with disabled state. This also loads description content (!) + disableDescriptionSelection(); + + binding.detailSelectDescriptionButton.setOnClickListener(v -> { + if (binding.detailDescriptionNoteView.getVisibility() == View.VISIBLE) { + disableDescriptionSelection(); + } else { + // enable selection only when button is clicked to prevent flickering + enableDescriptionSelection(); + } + }); + } + + private void enableDescriptionSelection() { + binding.detailDescriptionNoteView.setVisibility(View.VISIBLE); + binding.detailDescriptionView.setTextIsSelectable(true); + + final String buttonLabel = getString(R.string.description_select_disable); + binding.detailSelectDescriptionButton.setContentDescription(buttonLabel); + TooltipCompat.setTooltipText(binding.detailSelectDescriptionButton, buttonLabel); + binding.detailSelectDescriptionButton.setImageResource(R.drawable.ic_close); + } + + private void disableDescriptionSelection() { + // show description content again, otherwise some links are not clickable + TextLinkifier.fromDescription(binding.detailDescriptionView, + getDescription(), HtmlCompat.FROM_HTML_MODE_LEGACY, + getService(), getStreamUrl(), + descriptionDisposables, SET_LINK_MOVEMENT_METHOD); + + binding.detailDescriptionNoteView.setVisibility(View.GONE); + binding.detailDescriptionView.setTextIsSelectable(false); + + final String buttonLabel = getString(R.string.description_select_enable); + binding.detailSelectDescriptionButton.setContentDescription(buttonLabel); + TooltipCompat.setTooltipText(binding.detailSelectDescriptionButton, buttonLabel); + binding.detailSelectDescriptionButton.setImageResource(R.drawable.ic_select_all); + } + + protected void addMetadataItem(final LayoutInflater inflater, + final LinearLayout layout, + final boolean linkifyContent, + @StringRes final int type, + @Nullable final String content) { + if (isBlank(content)) { + return; + } + + final ItemMetadataBinding itemBinding = + ItemMetadataBinding.inflate(inflater, layout, false); + + itemBinding.metadataTypeView.setText(type); + itemBinding.metadataTypeView.setOnLongClickListener(v -> { + ShareUtils.copyToClipboard(requireContext(), content); + return true; + }); + + if (linkifyContent) { + TextLinkifier.fromPlainText(itemBinding.metadataContentView, content, null, null, + descriptionDisposables, SET_LINK_MOVEMENT_METHOD); + } else { + itemBinding.metadataContentView.setText(content); + } + + itemBinding.metadataContentView.setClickable(true); + + layout.addView(itemBinding.getRoot()); + } + + private void addTagsMetadataItem(final LayoutInflater inflater, final LinearLayout layout) { + final List tags = getTags(); + + if (tags != null && !tags.isEmpty()) { + final var itemBinding = ItemMetadataTagsBinding.inflate(inflater, layout, false); + + tags.stream().sorted(String.CASE_INSENSITIVE_ORDER).forEach(tag -> { + final Chip chip = (Chip) inflater.inflate(R.layout.chip, + itemBinding.metadataTagsChips, false); + chip.setText(tag); + chip.setOnClickListener(this::onTagClick); + chip.setOnLongClickListener(this::onTagLongClick); + itemBinding.metadataTagsChips.addView(chip); + }); + + layout.addView(itemBinding.getRoot()); + } + } + + private void onTagClick(final View chip) { + if (getParentFragment() != null) { + NavigationHelper.openSearchFragment(getParentFragment().getParentFragmentManager(), + getServiceId(), ((Chip) chip).getText().toString()); + } + } + + private boolean onTagLongClick(final View chip) { + ShareUtils.copyToClipboard(requireContext(), ((Chip) chip).getText().toString()); + return true; + } +} 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 index d364c0c0f..cf99365dc 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/detail/DescriptionFragment.java @@ -1,46 +1,29 @@ package org.schabi.newpipe.fragments.detail; -import static android.text.TextUtils.isEmpty; import static org.schabi.newpipe.extractor.stream.StreamExtractor.NO_AGE_LIMIT; -import static org.schabi.newpipe.extractor.utils.Utils.isBlank; import static org.schabi.newpipe.util.Localization.getAppLocale; -import static org.schabi.newpipe.util.text.TextLinkifier.SET_LINK_MOVEMENT_METHOD; -import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; -import android.view.ViewGroup; import android.widget.LinearLayout; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.annotation.StringRes; -import androidx.appcompat.widget.TooltipCompat; -import androidx.core.text.HtmlCompat; -import com.google.android.material.chip.Chip; - -import org.schabi.newpipe.BaseFragment; import org.schabi.newpipe.R; -import org.schabi.newpipe.databinding.FragmentDescriptionBinding; -import org.schabi.newpipe.databinding.ItemMetadataBinding; -import org.schabi.newpipe.databinding.ItemMetadataTagsBinding; +import org.schabi.newpipe.extractor.StreamingService; 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.NavigationHelper; -import org.schabi.newpipe.util.external_communication.ShareUtils; -import org.schabi.newpipe.util.text.TextLinkifier; + +import java.util.List; import icepick.State; -import io.reactivex.rxjava3.disposables.CompositeDisposable; -public class DescriptionFragment extends BaseFragment { +public class DescriptionFragment extends BaseInfoFragment { @State StreamInfo streamInfo = null; - final CompositeDisposable descriptionDisposables = new CompositeDisposable(); - FragmentDescriptionBinding binding; public DescriptionFragment() { } @@ -49,86 +32,56 @@ public class DescriptionFragment extends BaseFragment { this.streamInfo = streamInfo; } + @Nullable @Override - public View onCreateView(@NonNull final LayoutInflater inflater, - @Nullable final ViewGroup container, - @Nullable final Bundle savedInstanceState) { - binding = FragmentDescriptionBinding.inflate(inflater, container, false); - if (streamInfo != null) { - setupUploadDate(); - setupDescription(); - setupMetadata(inflater, binding.detailMetadataLayout); + protected Description getDescription() { + if (streamInfo == null) { + return null; } - return binding.getRoot(); + return streamInfo.getDescription(); + } + + @Nullable + @Override + protected StreamingService getService() { + if (streamInfo == null) { + return null; + } + return streamInfo.getService(); } @Override - public void onDestroy() { - descriptionDisposables.clear(); - super.onDestroy(); + protected int getServiceId() { + return streamInfo.getServiceId(); } + @Nullable + @Override + protected String getStreamUrl() { + if (streamInfo == null) { + return null; + } + return streamInfo.getUrl(); + } - private void setupUploadDate() { + @Nullable + @Override + public List getTags() { + if (streamInfo == null) { + return null; + } + return streamInfo.getTags(); + } + + protected void setupMetadata(final LayoutInflater inflater, + final LinearLayout layout) { if (streamInfo.getUploadDate() != null) { binding.detailUploadDateView.setText(Localization .localizeUploadDate(activity, streamInfo.getUploadDate().offsetDateTime())); } else { binding.detailUploadDateView.setVisibility(View.GONE); } - } - - private void setupDescription() { - final Description description = streamInfo.getDescription(); - if (description == null || isEmpty(description.getContent()) - || description == Description.EMPTY_DESCRIPTION) { - binding.detailDescriptionView.setVisibility(View.GONE); - binding.detailSelectDescriptionButton.setVisibility(View.GONE); - return; - } - - // start with disabled state. This also loads description content (!) - disableDescriptionSelection(); - - binding.detailSelectDescriptionButton.setOnClickListener(v -> { - if (binding.detailDescriptionNoteView.getVisibility() == View.VISIBLE) { - disableDescriptionSelection(); - } else { - // enable selection only when button is clicked to prevent flickering - enableDescriptionSelection(); - } - }); - } - - private void enableDescriptionSelection() { - binding.detailDescriptionNoteView.setVisibility(View.VISIBLE); - binding.detailDescriptionView.setTextIsSelectable(true); - - final String buttonLabel = getString(R.string.description_select_disable); - binding.detailSelectDescriptionButton.setContentDescription(buttonLabel); - TooltipCompat.setTooltipText(binding.detailSelectDescriptionButton, buttonLabel); - binding.detailSelectDescriptionButton.setImageResource(R.drawable.ic_close); - } - - private void disableDescriptionSelection() { - // show description content again, otherwise some links are not clickable - TextLinkifier.fromDescription(binding.detailDescriptionView, - streamInfo.getDescription(), HtmlCompat.FROM_HTML_MODE_LEGACY, - streamInfo.getService(), streamInfo.getUrl(), - descriptionDisposables, SET_LINK_MOVEMENT_METHOD); - - binding.detailDescriptionNoteView.setVisibility(View.GONE); - binding.detailDescriptionView.setTextIsSelectable(false); - - final String buttonLabel = getString(R.string.description_select_enable); - binding.detailSelectDescriptionButton.setContentDescription(buttonLabel); - TooltipCompat.setTooltipText(binding.detailSelectDescriptionButton, buttonLabel); - binding.detailSelectDescriptionButton.setImageResource(R.drawable.ic_select_all); - } - - private void setupMetadata(final LayoutInflater inflater, - final LinearLayout layout) { addMetadataItem(inflater, layout, false, R.string.metadata_category, streamInfo.getCategory()); @@ -153,67 +106,6 @@ public class DescriptionFragment extends BaseFragment { streamInfo.getHost()); addMetadataItem(inflater, layout, true, R.string.metadata_thumbnail_url, streamInfo.getThumbnailUrl()); - - addTagsMetadataItem(inflater, layout); - } - - private void addMetadataItem(final LayoutInflater inflater, - final LinearLayout layout, - final boolean linkifyContent, - @StringRes final int type, - @Nullable final String content) { - if (isBlank(content)) { - return; - } - - final ItemMetadataBinding itemBinding = - ItemMetadataBinding.inflate(inflater, layout, false); - - itemBinding.metadataTypeView.setText(type); - itemBinding.metadataTypeView.setOnLongClickListener(v -> { - ShareUtils.copyToClipboard(requireContext(), content); - return true; - }); - - if (linkifyContent) { - TextLinkifier.fromPlainText(itemBinding.metadataContentView, content, null, null, - descriptionDisposables, SET_LINK_MOVEMENT_METHOD); - } else { - itemBinding.metadataContentView.setText(content); - } - - itemBinding.metadataContentView.setClickable(true); - - layout.addView(itemBinding.getRoot()); - } - - private void addTagsMetadataItem(final LayoutInflater inflater, final LinearLayout layout) { - if (streamInfo.getTags() != null && !streamInfo.getTags().isEmpty()) { - final var itemBinding = ItemMetadataTagsBinding.inflate(inflater, layout, false); - - streamInfo.getTags().stream().sorted(String.CASE_INSENSITIVE_ORDER).forEach(tag -> { - final Chip chip = (Chip) inflater.inflate(R.layout.chip, - itemBinding.metadataTagsChips, false); - chip.setText(tag); - chip.setOnClickListener(this::onTagClick); - chip.setOnLongClickListener(this::onTagLongClick); - itemBinding.metadataTagsChips.addView(chip); - }); - - layout.addView(itemBinding.getRoot()); - } - } - - private void onTagClick(final View chip) { - if (getParentFragment() != null) { - NavigationHelper.openSearchFragment(getParentFragment().getParentFragmentManager(), - streamInfo.getServiceId(), ((Chip) chip).getText().toString()); - } - } - - private boolean onTagLongClick(final View chip) { - ShareUtils.copyToClipboard(requireContext(), ((Chip) chip).getText().toString()); - return true; } private void addPrivacyMetadataItem(final LayoutInflater inflater, final LinearLayout layout) { diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelInfoFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelInfoFragment.java index ba1faab8f..70b182a75 100644 --- a/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelInfoFragment.java +++ b/app/src/main/java/org/schabi/newpipe/fragments/list/channel/ChannelInfoFragment.java @@ -1,44 +1,28 @@ package org.schabi.newpipe.fragments.list.channel; import static org.schabi.newpipe.extractor.stream.StreamExtractor.UNKNOWN_SUBSCRIBER_COUNT; -import static org.schabi.newpipe.extractor.utils.Utils.isBlank; import android.content.Context; -import android.os.Bundle; import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; import android.widget.LinearLayout; -import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.annotation.StringRes; -import com.google.android.material.chip.Chip; - -import org.schabi.newpipe.BaseFragment; import org.schabi.newpipe.R; -import org.schabi.newpipe.databinding.FragmentChannelInfoBinding; -import org.schabi.newpipe.databinding.ItemMetadataBinding; -import org.schabi.newpipe.databinding.ItemMetadataTagsBinding; +import org.schabi.newpipe.extractor.StreamingService; import org.schabi.newpipe.extractor.channel.ChannelInfo; +import org.schabi.newpipe.extractor.stream.Description; +import org.schabi.newpipe.fragments.detail.BaseInfoFragment; import org.schabi.newpipe.util.Localization; -import org.schabi.newpipe.util.NavigationHelper; -import org.schabi.newpipe.util.external_communication.ShareUtils; -import org.schabi.newpipe.util.external_communication.TextLinkifier; import java.util.List; import icepick.State; -import io.reactivex.rxjava3.disposables.CompositeDisposable; -public class ChannelInfoFragment extends BaseFragment { +public class ChannelInfoFragment extends BaseInfoFragment { @State protected ChannelInfo channelInfo; - private final CompositeDisposable disposables = new CompositeDisposable(); - private FragmentChannelInfoBinding binding; - public static ChannelInfoFragment getInstance(final ChannelInfo channelInfo) { final ChannelInfoFragment fragment = new ChannelInfoFragment(); fragment.channelInfo = channelInfo; @@ -49,96 +33,51 @@ public class ChannelInfoFragment extends BaseFragment { super(); } + @Nullable @Override - public View onCreateView(@NonNull final LayoutInflater inflater, - @Nullable final ViewGroup container, - final Bundle savedInstanceState) { - binding = FragmentChannelInfoBinding.inflate(inflater, container, false); - loadDescription(); - setupMetadata(inflater, binding.detailMetadataLayout); - return binding.getRoot(); - } - - @Override - public void onDestroy() { - super.onDestroy(); - disposables.clear(); - } - - private void loadDescription() { - final String description = channelInfo.getDescription(); - - if (description == null || description.isEmpty()) { - binding.descriptionTitle.setVisibility(View.GONE); - binding.descriptionView.setVisibility(View.GONE); - } else { - TextLinkifier.createLinksFromPlainText( - binding.descriptionView, description, null, disposables); + protected Description getDescription() { + if (channelInfo == null) { + return null; } + return new Description(channelInfo.getDescription(), Description.PLAIN_TEXT); } - private void setupMetadata(final LayoutInflater inflater, - final LinearLayout layout) { + @Nullable + @Override + protected StreamingService getService() { + if (channelInfo == null) { + return null; + } + return channelInfo.getService(); + } + + @Override + protected int getServiceId() { + return channelInfo.getServiceId(); + } + + @Nullable + @Override + protected String getStreamUrl() { + return null; + } + + @Nullable + @Override + public List getTags() { + if (channelInfo == null) { + return null; + } + return channelInfo.getTags(); + } + + protected void setupMetadata(final LayoutInflater inflater, + final LinearLayout layout) { final Context context = getContext(); if (channelInfo.getSubscriberCount() != UNKNOWN_SUBSCRIBER_COUNT) { - addMetadataItem(inflater, layout, R.string.metadata_subscribers, + addMetadataItem(inflater, layout, false, R.string.metadata_subscribers, Localization.localizeNumber(context, channelInfo.getSubscriberCount())); } - - addTagsMetadataItem(inflater, layout); - } - - private void addMetadataItem(final LayoutInflater inflater, - final LinearLayout layout, - @StringRes final int type, - @Nullable final String content) { - if (isBlank(content)) { - return; - } - - final ItemMetadataBinding itemBinding = - ItemMetadataBinding.inflate(inflater, layout, false); - - itemBinding.metadataTypeView.setText(type); - itemBinding.metadataTypeView.setOnLongClickListener(v -> { - ShareUtils.copyToClipboard(requireContext(), content); - return true; - }); - - itemBinding.metadataContentView.setText(content); - - layout.addView(itemBinding.getRoot()); - } - - private void addTagsMetadataItem(final LayoutInflater inflater, final LinearLayout layout) { - final List tags = channelInfo.getTags(); - - if (!tags.isEmpty()) { - final var itemBinding = ItemMetadataTagsBinding.inflate(inflater, layout, false); - - tags.stream().sorted(String.CASE_INSENSITIVE_ORDER).forEach(tag -> { - final Chip chip = (Chip) inflater.inflate(R.layout.chip, - itemBinding.metadataTagsChips, false); - chip.setText(tag); - chip.setOnClickListener(this::onTagClick); - chip.setOnLongClickListener(this::onTagLongClick); - itemBinding.metadataTagsChips.addView(chip); - }); - - layout.addView(itemBinding.getRoot()); - } - } - - private void onTagClick(final View chip) { - if (getParentFragment() != null) { - NavigationHelper.openSearchFragment(getParentFragment().getParentFragmentManager(), - channelInfo.getServiceId(), ((Chip) chip).getText().toString()); - } - } - - private boolean onTagLongClick(final View chip) { - ShareUtils.copyToClipboard(requireContext(), ((Chip) chip).getText().toString()); - return true; } }