Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show content metadata below the description #5946

Merged
merged 6 commits into from
Jun 3, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,46 @@
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
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.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.ShareUtils;
import org.schabi.newpipe.util.TextLinkifier;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import icepick.State;
import io.reactivex.rxjava3.disposables.Disposable;

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;

public class DescriptionFragment extends BaseFragment {

@State
StreamInfo streamInfo = null;
@Nullable
Disposable descriptionDisposable = null;
FragmentDescriptionBinding binding;

public DescriptionFragment() {
}
Expand All @@ -40,11 +56,11 @@ public DescriptionFragment(final StreamInfo streamInfo) {
public View onCreateView(@NonNull final LayoutInflater inflater,
@Nullable final ViewGroup container,
@Nullable final Bundle savedInstanceState) {
final FragmentDescriptionBinding binding =
FragmentDescriptionBinding.inflate(inflater, container, false);
binding = FragmentDescriptionBinding.inflate(inflater, container, false);
if (streamInfo != null) {
setupUploadDate(binding.detailUploadDateView);
setupDescription(binding.detailDescriptionView);
setupUploadDate();
setupDescription();
setupMetadata(inflater, binding.detailMetadataLayout);
}
return binding.getRoot();
}
Expand All @@ -57,37 +73,197 @@ public void onDestroy() {
}
}

private void setupUploadDate(final TextView uploadDateTextView) {

private void setupUploadDate() {
if (streamInfo.getUploadDate() != null) {
uploadDateTextView.setText(Localization
binding.detailUploadDateView.setText(Localization
.localizeUploadDate(activity, streamInfo.getUploadDate().offsetDateTime()));
} else {
uploadDateTextView.setVisibility(View.GONE);
binding.detailUploadDateView.setVisibility(View.GONE);
}
}

private void setupDescription(final TextView descriptionTextView) {

private void setupDescription() {
final Description description = streamInfo.getDescription();
if (description == null || isEmpty(description.getContent())
|| description == Description.emptyDescription) {
descriptionTextView.setText("");
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
loadDescriptionContent();

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 loadDescriptionContent() {
final Description description = streamInfo.getDescription();
switch (description.getType()) {
case Description.HTML:
descriptionDisposable = TextLinkifier.createLinksFromHtmlBlock(requireContext(),
description.getContent(), descriptionTextView,
description.getContent(), binding.detailDescriptionView,
HtmlCompat.FROM_HTML_MODE_LEGACY);
break;
case Description.MARKDOWN:
descriptionDisposable = TextLinkifier.createLinksFromMarkdownText(requireContext(),
description.getContent(), descriptionTextView);
description.getContent(), binding.detailDescriptionView);
break;
case Description.PLAIN_TEXT: default:
descriptionDisposable = TextLinkifier.createLinksFromPlainText(requireContext(),
description.getContent(), descriptionTextView);
description.getContent(), binding.detailDescriptionView);
break;
}
}


private void setupMetadata(final LayoutInflater inflater,
final LinearLayout layout) {
addMetadataItem(inflater, layout, false,
R.string.metadata_category, streamInfo.getCategory());

addTagsMetadataItem(inflater, layout);

addMetadataItem(inflater, layout, false,
R.string.metadata_licence, streamInfo.getLicence());

addPrivacyMetadataItem(inflater, layout);

if (streamInfo.getAgeLimit() != NO_AGE_LIMIT) {
addMetadataItem(inflater, layout, false,
R.string.metadata_age_limit, String.valueOf(streamInfo.getAgeLimit()));
}

if (streamInfo.getLanguageInfo() != null) {
addMetadataItem(inflater, layout, false,
R.string.metadata_language, streamInfo.getLanguageInfo().getDisplayLanguage());
}

addMetadataItem(inflater, layout, true,
R.string.metadata_support, streamInfo.getSupportInfo());
addMetadataItem(inflater, layout, true,
R.string.metadata_host, streamInfo.getHost());
addMetadataItem(inflater, layout, true,
R.string.metadata_thumbnail_url, streamInfo.getThumbnailUrl());
}

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.createLinksFromPlainText(requireContext(),
content, itemBinding.metadataContentView);
} else {
itemBinding.metadataContentView.setText(content);
}

layout.addView(itemBinding.getRoot());
}

private void addTagsMetadataItem(final LayoutInflater inflater, final LinearLayout layout) {
if (streamInfo.getTags() != null && !streamInfo.getTags().isEmpty()) {
final ItemMetadataTagsBinding itemBinding
= ItemMetadataTagsBinding.inflate(inflater, layout, false);

final List<String> tags = new ArrayList<>(streamInfo.getTags());
Collections.sort(tags);
for (final String tag : tags) {
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) {
if (streamInfo.getPrivacy() != null) {
@StringRes final int contentRes;
switch (streamInfo.getPrivacy()) {
case PUBLIC:
contentRes = R.string.metadata_privacy_public;
break;
case UNLISTED:
contentRes = R.string.metadata_privacy_unlisted;
break;
case PRIVATE:
contentRes = R.string.metadata_privacy_private;
break;
case INTERNAL:
contentRes = R.string.metadata_privacy_internal;
break;
case OTHER: default:
contentRes = 0;
break;
}

if (contentRes != 0) {
addMetadataItem(inflater, layout, false,
R.string.metadata_privacy, getString(contentRes));
}
}
}
}
10 changes: 10 additions & 0 deletions app/src/main/res/drawable-night/ic_select_all.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#ffffff">
<path
android:fillColor="#ffffff"
android:pathData="M3,5h2L5,3c-1.1,0 -2,0.9 -2,2zM3,13h2v-2L3,11v2zM7,21h2v-2L7,19v2zM3,9h2L5,7L3,7v2zM13,3h-2v2h2L13,3zM19,3v2h2c0,-1.1 -0.9,-2 -2,-2zM5,21v-2L3,19c0,1.1 0.9,2 2,2zM3,17h2v-2L3,15v2zM9,3L7,3v2h2L9,3zM11,21h2v-2h-2v2zM19,13h2v-2h-2v2zM19,21c1.1,0 2,-0.9 2,-2h-2v2zM19,9h2L21,7h-2v2zM19,17h2v-2h-2v2zM15,21h2v-2h-2v2zM15,5h2L17,3h-2v2zM7,17h10L17,7L7,7v10zM9,9h6v6L9,15L9,9z"/>
</vector>
10 changes: 10 additions & 0 deletions app/src/main/res/drawable/ic_select_all.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="#000000">
<path
android:fillColor="#000000"
android:pathData="M3,5h2L5,3c-1.1,0 -2,0.9 -2,2zM3,13h2v-2L3,11v2zM7,21h2v-2L7,19v2zM3,9h2L5,7L3,7v2zM13,3h-2v2h2L13,3zM19,3v2h2c0,-1.1 -0.9,-2 -2,-2zM5,21v-2L3,19c0,1.1 0.9,2 2,2zM3,17h2v-2L3,15v2zM9,3L7,3v2h2L9,3zM11,21h2v-2h-2v2zM19,13h2v-2h-2v2zM19,21c1.1,0 2,-0.9 2,-2h-2v2zM19,9h2L21,7h-2v2zM19,17h2v-2h-2v2zM15,21h2v-2h-2v2zM15,5h2L17,3h-2v2zM7,17h10L17,7L7,7v10zM9,9h6v6L9,15L9,9z"/>
</vector>
9 changes: 9 additions & 0 deletions app/src/main/res/layout/chip.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This is used to inflate a chip with a Material theme, otherwise it would crash -->
<!-- Theme.MaterialComponents.DayNight is used to guarantee auto day/night switching -->
<com.google.android.material.chip.Chip xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:theme="@style/Theme.MaterialComponents.DayNight.Bridge"
tools:text="I'm a correctly themed chip!" />
Loading