44import android .view .LayoutInflater ;
55import android .view .View ;
66import android .view .ViewGroup ;
7- import android .widget .TextView ;
7+ import android .widget .LinearLayout ;
88
99import androidx .annotation .NonNull ;
1010import androidx .annotation .Nullable ;
11+ import androidx .annotation .StringRes ;
12+ import androidx .appcompat .widget .TooltipCompat ;
1113import androidx .core .text .HtmlCompat ;
1214
15+ import com .google .android .material .chip .Chip ;
16+
1317import org .schabi .newpipe .BaseFragment ;
18+ import org .schabi .newpipe .R ;
1419import org .schabi .newpipe .databinding .FragmentDescriptionBinding ;
20+ import org .schabi .newpipe .databinding .ItemMetadataBinding ;
21+ import org .schabi .newpipe .databinding .ItemMetadataTagsBinding ;
1522import org .schabi .newpipe .extractor .stream .Description ;
1623import org .schabi .newpipe .extractor .stream .StreamInfo ;
1724import org .schabi .newpipe .util .Localization ;
25+ import org .schabi .newpipe .util .NavigationHelper ;
26+ import org .schabi .newpipe .util .ShareUtils ;
1827import org .schabi .newpipe .util .TextLinkifier ;
1928
29+ import java .util .ArrayList ;
30+ import java .util .Collections ;
31+ import java .util .List ;
32+
2033import icepick .State ;
2134import io .reactivex .rxjava3 .disposables .Disposable ;
2235
2336import static android .text .TextUtils .isEmpty ;
37+ import static org .schabi .newpipe .extractor .stream .StreamExtractor .NO_AGE_LIMIT ;
38+ import static org .schabi .newpipe .extractor .utils .Utils .isBlank ;
2439
2540public class DescriptionFragment extends BaseFragment {
2641
2742 @ State
2843 StreamInfo streamInfo = null ;
2944 @ Nullable
3045 Disposable descriptionDisposable = null ;
46+ FragmentDescriptionBinding binding ;
3147
3248 public DescriptionFragment () {
3349 }
@@ -40,11 +56,11 @@ public DescriptionFragment(final StreamInfo streamInfo) {
4056 public View onCreateView (@ NonNull final LayoutInflater inflater ,
4157 @ Nullable final ViewGroup container ,
4258 @ Nullable final Bundle savedInstanceState ) {
43- final FragmentDescriptionBinding binding =
44- FragmentDescriptionBinding .inflate (inflater , container , false );
59+ binding = FragmentDescriptionBinding .inflate (inflater , container , false );
4560 if (streamInfo != null ) {
46- setupUploadDate (binding .detailUploadDateView );
47- setupDescription (binding .detailDescriptionView );
61+ setupUploadDate ();
62+ setupDescription ();
63+ setupMetadata (inflater , binding .detailMetadataLayout );
4864 }
4965 return binding .getRoot ();
5066 }
@@ -57,37 +73,197 @@ public void onDestroy() {
5773 }
5874 }
5975
60- private void setupUploadDate (final TextView uploadDateTextView ) {
76+
77+ private void setupUploadDate () {
6178 if (streamInfo .getUploadDate () != null ) {
62- uploadDateTextView .setText (Localization
79+ binding . detailUploadDateView .setText (Localization
6380 .localizeUploadDate (activity , streamInfo .getUploadDate ().offsetDateTime ()));
6481 } else {
65- uploadDateTextView .setVisibility (View .GONE );
82+ binding . detailUploadDateView .setVisibility (View .GONE );
6683 }
6784 }
6885
69- private void setupDescription (final TextView descriptionTextView ) {
86+
87+ private void setupDescription () {
7088 final Description description = streamInfo .getDescription ();
7189 if (description == null || isEmpty (description .getContent ())
7290 || description == Description .emptyDescription ) {
73- descriptionTextView .setText ("" );
91+ binding .detailDescriptionView .setVisibility (View .GONE );
92+ binding .detailSelectDescriptionButton .setVisibility (View .GONE );
7493 return ;
7594 }
7695
96+ // start with disabled state. This also loads description content (!)
97+ disableDescriptionSelection ();
98+
99+ binding .detailSelectDescriptionButton .setOnClickListener (v -> {
100+ if (binding .detailDescriptionNoteView .getVisibility () == View .VISIBLE ) {
101+ disableDescriptionSelection ();
102+ } else {
103+ // enable selection only when button is clicked to prevent flickering
104+ enableDescriptionSelection ();
105+ }
106+ });
107+ }
108+
109+ private void enableDescriptionSelection () {
110+ binding .detailDescriptionNoteView .setVisibility (View .VISIBLE );
111+ binding .detailDescriptionView .setTextIsSelectable (true );
112+
113+ final String buttonLabel = getString (R .string .description_select_disable );
114+ binding .detailSelectDescriptionButton .setContentDescription (buttonLabel );
115+ TooltipCompat .setTooltipText (binding .detailSelectDescriptionButton , buttonLabel );
116+ binding .detailSelectDescriptionButton .setImageResource (R .drawable .ic_close );
117+ }
118+
119+ private void disableDescriptionSelection () {
120+ // show description content again, otherwise some links are not clickable
121+ loadDescriptionContent ();
122+
123+ binding .detailDescriptionNoteView .setVisibility (View .GONE );
124+ binding .detailDescriptionView .setTextIsSelectable (false );
125+
126+ final String buttonLabel = getString (R .string .description_select_enable );
127+ binding .detailSelectDescriptionButton .setContentDescription (buttonLabel );
128+ TooltipCompat .setTooltipText (binding .detailSelectDescriptionButton , buttonLabel );
129+ binding .detailSelectDescriptionButton .setImageResource (R .drawable .ic_select_all );
130+ }
131+
132+ private void loadDescriptionContent () {
133+ final Description description = streamInfo .getDescription ();
77134 switch (description .getType ()) {
78135 case Description .HTML :
79136 descriptionDisposable = TextLinkifier .createLinksFromHtmlBlock (requireContext (),
80- description .getContent (), descriptionTextView ,
137+ description .getContent (), binding . detailDescriptionView ,
81138 HtmlCompat .FROM_HTML_MODE_LEGACY );
82139 break ;
83140 case Description .MARKDOWN :
84141 descriptionDisposable = TextLinkifier .createLinksFromMarkdownText (requireContext (),
85- description .getContent (), descriptionTextView );
142+ description .getContent (), binding . detailDescriptionView );
86143 break ;
87144 case Description .PLAIN_TEXT : default :
88145 descriptionDisposable = TextLinkifier .createLinksFromPlainText (requireContext (),
89- description .getContent (), descriptionTextView );
146+ description .getContent (), binding . detailDescriptionView );
90147 break ;
91148 }
92149 }
150+
151+
152+ private void setupMetadata (final LayoutInflater inflater ,
153+ final LinearLayout layout ) {
154+ addMetadataItem (inflater , layout , false ,
155+ R .string .metadata_category , streamInfo .getCategory ());
156+
157+ addTagsMetadataItem (inflater , layout );
158+
159+ addMetadataItem (inflater , layout , false ,
160+ R .string .metadata_licence , streamInfo .getLicence ());
161+
162+ addPrivacyMetadataItem (inflater , layout );
163+
164+ if (streamInfo .getAgeLimit () != NO_AGE_LIMIT ) {
165+ addMetadataItem (inflater , layout , false ,
166+ R .string .metadata_age_limit , String .valueOf (streamInfo .getAgeLimit ()));
167+ }
168+
169+ if (streamInfo .getLanguageInfo () != null ) {
170+ addMetadataItem (inflater , layout , false ,
171+ R .string .metadata_language , streamInfo .getLanguageInfo ().getDisplayLanguage ());
172+ }
173+
174+ addMetadataItem (inflater , layout , true ,
175+ R .string .metadata_support , streamInfo .getSupportInfo ());
176+ addMetadataItem (inflater , layout , true ,
177+ R .string .metadata_host , streamInfo .getHost ());
178+ addMetadataItem (inflater , layout , true ,
179+ R .string .metadata_thumbnail_url , streamInfo .getThumbnailUrl ());
180+ }
181+
182+ private void addMetadataItem (final LayoutInflater inflater ,
183+ final LinearLayout layout ,
184+ final boolean linkifyContent ,
185+ @ StringRes final int type ,
186+ @ Nullable final String content ) {
187+ if (isBlank (content )) {
188+ return ;
189+ }
190+
191+ final ItemMetadataBinding itemBinding
192+ = ItemMetadataBinding .inflate (inflater , layout , false );
193+
194+ itemBinding .metadataTypeView .setText (type );
195+ itemBinding .metadataTypeView .setOnLongClickListener (v -> {
196+ ShareUtils .copyToClipboard (requireContext (), content );
197+ return true ;
198+ });
199+
200+ if (linkifyContent ) {
201+ TextLinkifier .createLinksFromPlainText (requireContext (),
202+ content , itemBinding .metadataContentView );
203+ } else {
204+ itemBinding .metadataContentView .setText (content );
205+ }
206+
207+ layout .addView (itemBinding .getRoot ());
208+ }
209+
210+ private void addTagsMetadataItem (final LayoutInflater inflater , final LinearLayout layout ) {
211+ if (streamInfo .getTags () != null && !streamInfo .getTags ().isEmpty ()) {
212+ final ItemMetadataTagsBinding itemBinding
213+ = ItemMetadataTagsBinding .inflate (inflater , layout , false );
214+
215+ final List <String > tags = new ArrayList <>(streamInfo .getTags ());
216+ Collections .sort (tags );
217+ for (final String tag : tags ) {
218+ final Chip chip = (Chip ) inflater .inflate (R .layout .chip ,
219+ itemBinding .metadataTagsChips , false );
220+ chip .setText (tag );
221+ chip .setOnClickListener (this ::onTagClick );
222+ chip .setOnLongClickListener (this ::onTagLongClick );
223+ itemBinding .metadataTagsChips .addView (chip );
224+ }
225+
226+ layout .addView (itemBinding .getRoot ());
227+ }
228+ }
229+
230+ private void onTagClick (final View chip ) {
231+ if (getParentFragment () != null ) {
232+ NavigationHelper .openSearchFragment (getParentFragment ().getParentFragmentManager (),
233+ streamInfo .getServiceId (), ((Chip ) chip ).getText ().toString ());
234+ }
235+ }
236+
237+ private boolean onTagLongClick (final View chip ) {
238+ ShareUtils .copyToClipboard (requireContext (), ((Chip ) chip ).getText ().toString ());
239+ return true ;
240+ }
241+
242+ private void addPrivacyMetadataItem (final LayoutInflater inflater , final LinearLayout layout ) {
243+ if (streamInfo .getPrivacy () != null ) {
244+ @ StringRes final int contentRes ;
245+ switch (streamInfo .getPrivacy ()) {
246+ case PUBLIC :
247+ contentRes = R .string .metadata_privacy_public ;
248+ break ;
249+ case UNLISTED :
250+ contentRes = R .string .metadata_privacy_unlisted ;
251+ break ;
252+ case PRIVATE :
253+ contentRes = R .string .metadata_privacy_private ;
254+ break ;
255+ case INTERNAL :
256+ contentRes = R .string .metadata_privacy_internal ;
257+ break ;
258+ case OTHER : default :
259+ contentRes = 0 ;
260+ break ;
261+ }
262+
263+ if (contentRes != 0 ) {
264+ addMetadataItem (inflater , layout , false ,
265+ R .string .metadata_privacy , getString (contentRes ));
266+ }
267+ }
268+ }
93269}
0 commit comments