From 40317776063b55c52a6e86f36b1ad417ca623fec Mon Sep 17 00:00:00 2001 From: TiA4f8R <74829229+TiA4f8R@users.noreply.github.com> Date: Mon, 1 Feb 2021 16:40:54 +0100 Subject: [PATCH 01/20] Open recognized timestamps in the description of contents in the popup player This commit adds support of opening recognized timestamps in the popup player instead of starting an intent which opens the YouTube website with the video timestamp. --- .../util/CommentTextOnTouchListener.java | 70 +---------- .../schabi/newpipe/util/TextLinkifier.java | 4 +- .../org/schabi/newpipe/util/URLHandler.java | 113 ++++++++++++++++++ 3 files changed, 118 insertions(+), 69 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/util/URLHandler.java diff --git a/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java b/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java index d26116139f0..f48d39da0b0 100644 --- a/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java +++ b/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java @@ -1,6 +1,5 @@ package org.schabi.newpipe.util; -import android.content.Context; import android.text.Layout; import android.text.Selection; import android.text.Spannable; @@ -11,27 +10,9 @@ import android.view.View; import android.widget.TextView; -import org.schabi.newpipe.extractor.NewPipe; -import org.schabi.newpipe.extractor.StreamingService; -import org.schabi.newpipe.extractor.exceptions.ExtractionException; -import org.schabi.newpipe.extractor.exceptions.ParsingException; -import org.schabi.newpipe.extractor.linkhandler.LinkHandlerFactory; -import org.schabi.newpipe.extractor.stream.StreamInfo; -import org.schabi.newpipe.player.playqueue.PlayQueue; -import org.schabi.newpipe.player.playqueue.SinglePlayQueue; - -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; -import io.reactivex.rxjava3.core.Single; -import io.reactivex.rxjava3.schedulers.Schedulers; - public class CommentTextOnTouchListener implements View.OnTouchListener { public static final CommentTextOnTouchListener INSTANCE = new CommentTextOnTouchListener(); - private static final Pattern TIMESTAMP_PATTERN = Pattern.compile("(.*)#timestamp=(\\d+)"); - @Override public boolean onTouch(final View v, final MotionEvent event) { if (!(v instanceof TextView)) { @@ -66,7 +47,8 @@ public boolean onTouch(final View v, final MotionEvent event) { if (action == MotionEvent.ACTION_UP) { boolean handled = false; if (link[0] instanceof URLSpan) { - handled = handleUrl(v.getContext(), (URLSpan) link[0]); + handled = URLHandler.handleUrl(v.getContext(), + ((URLSpan) link[0]).getURL(), 1); } if (!handled) { ShareUtils.openUrlInBrowser(v.getContext(), @@ -83,52 +65,4 @@ public boolean onTouch(final View v, final MotionEvent event) { } return false; } - - private boolean handleUrl(final Context context, final URLSpan urlSpan) { - String url = urlSpan.getURL(); - int seconds = -1; - final Matcher matcher = TIMESTAMP_PATTERN.matcher(url); - if (matcher.matches()) { - url = matcher.group(1); - seconds = Integer.parseInt(matcher.group(2)); - } - final StreamingService service; - final StreamingService.LinkType linkType; - try { - service = NewPipe.getServiceByUrl(url); - linkType = service.getLinkTypeByUrl(url); - } catch (final ExtractionException e) { - return false; - } - if (linkType == StreamingService.LinkType.NONE) { - return false; - } - if (linkType == StreamingService.LinkType.STREAM && seconds != -1) { - return playOnPopup(context, url, service, seconds); - } else { - NavigationHelper.openRouterActivity(context, url); - return true; - } - } - - private boolean playOnPopup(final Context context, final String url, - final StreamingService service, final int seconds) { - final LinkHandlerFactory factory = service.getStreamLHFactory(); - final String cleanUrl; - try { - cleanUrl = factory.getUrl(factory.getId(url)); - } catch (final ParsingException e) { - return false; - } - final Single single - = ExtractorHelper.getStreamInfo(service.getServiceId(), cleanUrl, false); - single.subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(info -> { - final PlayQueue playQueue - = new SinglePlayQueue((StreamInfo) info, seconds * 1000); - NavigationHelper.playOnPopupPlayer(context, playQueue, false); - }); - return true; - } } diff --git a/app/src/main/java/org/schabi/newpipe/util/TextLinkifier.java b/app/src/main/java/org/schabi/newpipe/util/TextLinkifier.java index 08767733339..4fc3608bfb6 100644 --- a/app/src/main/java/org/schabi/newpipe/util/TextLinkifier.java +++ b/app/src/main/java/org/schabi/newpipe/util/TextLinkifier.java @@ -115,7 +115,9 @@ private static Disposable changeIntentsOfDescriptionLinks(final Context context, for (final URLSpan span : urls) { final ClickableSpan clickableSpan = new ClickableSpan() { public void onClick(@NonNull final View view) { - ShareUtils.openUrlInBrowser(context, span.getURL(), false); + if (!URLHandler.handleUrl(context, span.getURL(), 0)) { + ShareUtils.openUrlInBrowser(context, span.getURL(), false); + } } }; diff --git a/app/src/main/java/org/schabi/newpipe/util/URLHandler.java b/app/src/main/java/org/schabi/newpipe/util/URLHandler.java new file mode 100644 index 00000000000..761a91e50ad --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/util/URLHandler.java @@ -0,0 +1,113 @@ +package org.schabi.newpipe.util; + +import android.content.Context; + +import org.schabi.newpipe.extractor.NewPipe; +import org.schabi.newpipe.extractor.StreamingService; +import org.schabi.newpipe.extractor.exceptions.ExtractionException; +import org.schabi.newpipe.extractor.exceptions.ParsingException; +import org.schabi.newpipe.extractor.linkhandler.LinkHandlerFactory; +import org.schabi.newpipe.extractor.stream.StreamInfo; +import org.schabi.newpipe.player.playqueue.PlayQueue; +import org.schabi.newpipe.player.playqueue.SinglePlayQueue; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; +import io.reactivex.rxjava3.core.Single; +import io.reactivex.rxjava3.schedulers.Schedulers; + +public final class URLHandler { + + private URLHandler() { + } + + /** + * Check if an URL can be handled in NewPipe. + *
+ * This method will check if the provided url can be handled in NewPipe or not. If this is a + * service URL with a timestamp, the popup player will be opened. + *
+ * The timestamp param accepts two integers, corresponding to two timestamps types:
+ * 0 for {@code &t=} (used for timestamps in descriptions),
+ * 1 for {@code #timestamp=} (used for timestamps in comments).
+ * Any other value of this integer will return false.
+ *
+ * @param context the context to be used
+ * @param url the URL to check if it can be handled
+ * @param timestampType the type of timestamp
+ * @return true if the URL can be handled by NewPipe, false if it cannot
+ */
+ public static boolean handleUrl(final Context context, final String url, final int timestampType) {
+ String matchedUrl = "";
+ int seconds = -1;
+ final Pattern TIMESTAMP_PATTERN;
+
+ if (timestampType == 0) {
+ TIMESTAMP_PATTERN = Pattern.compile("(.*)&t=(\\d+)");
+ } else if (timestampType == 1) {
+ TIMESTAMP_PATTERN = Pattern.compile("(.*)#timestamp=(\\d+)");
+ } else {
+ return false;
+ }
+
+ final Matcher matcher = TIMESTAMP_PATTERN.matcher(url);
+ if (matcher.matches()) {
+ matchedUrl = matcher.group(1);
+ seconds = Integer.parseInt(matcher.group(2));
+ }
+
+ final StreamingService service;
+ final StreamingService.LinkType linkType;
+
+ try {
+ service = NewPipe.getServiceByUrl(matchedUrl);
+ linkType = service.getLinkTypeByUrl(matchedUrl);
+ } catch (final ExtractionException e) {
+ return false;
+ }
+
+ if (linkType == StreamingService.LinkType.NONE) {
+ return false;
+ }
+ if (linkType == StreamingService.LinkType.STREAM && seconds != -1) {
+ return playOnPopup(context, matchedUrl, service, seconds);
+ } else {
+ NavigationHelper.openRouterActivity(context, matchedUrl);
+ return true;
+ }
+ }
+
+ /**
+ * Play a content in the floating player.
+ *
+ * @param context the context to be used
+ * @param url the URL of the content
+ * @param service the service of the content
+ * @param seconds the position in seconds at which the floating player will start
+ * @return true if the playback of the content has successfully started or false if not
+ */
+ private static boolean playOnPopup(final Context context, final String url,
+ final StreamingService service, final int seconds) {
+ final LinkHandlerFactory factory = service.getStreamLHFactory();
+ final String cleanUrl;
+
+ try {
+ cleanUrl = factory.getUrl(factory.getId(url));
+ } catch (final ParsingException e) {
+ return false;
+ }
+
+ final Single single
+ = ExtractorHelper.getStreamInfo(service.getServiceId(), cleanUrl, false);
+ single.subscribeOn(Schedulers.io())
+ .observeOn(AndroidSchedulers.mainThread())
+ .subscribe(info -> {
+ final PlayQueue playQueue
+ = new SinglePlayQueue((StreamInfo) info, seconds * 1000);
+ NavigationHelper.playOnPopupPlayer(context, playQueue, false);
+ });
+ return true;
+ }
+}
From ae9349e36c05f0f0a3c368dce453344a25e68578 Mon Sep 17 00:00:00 2001
From: TiA4f8R <74829229+TiA4f8R@users.noreply.github.com>
Date: Sat, 13 Mar 2021 12:20:45 +0100
Subject: [PATCH 02/20] Initial work: add support for opening timestamps in
plain text descriptions This commit adds support for opening plain text
timestamps by parsing the description text using a regular expression, add a
click listener for each timestamp which opens the popup player at the
indicated time in the timestamp. In order to do this, playOnPopup method of
the URLHandler class. Also, handleUrl method of this class has been renamed
to canHandleUrl.
---
.../fragments/detail/DescriptionFragment.java | 14 +-
.../util/CommentTextOnTouchListener.java | 2 +-
.../schabi/newpipe/util/ExtractorHelper.java | 3 +-
.../schabi/newpipe/util/TextLinkifier.java | 149 ++++++++++++++----
.../org/schabi/newpipe/util/URLHandler.java | 22 ++-
5 files changed, 150 insertions(+), 40 deletions(-)
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 5f1cbc36583..49ac3ef01a8 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
@@ -19,6 +19,7 @@
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;
@@ -131,19 +132,24 @@ private void disableDescriptionSelection() {
private void loadDescriptionContent() {
final Description description = streamInfo.getDescription();
+ final String contentUrl = streamInfo.getUrl();
+ final StreamingService service = streamInfo.getService();
+
switch (description.getType()) {
case Description.HTML:
descriptionDisposable = TextLinkifier.createLinksFromHtmlBlock(requireContext(),
description.getContent(), binding.detailDescriptionView,
- HtmlCompat.FROM_HTML_MODE_LEGACY);
+ service, contentUrl, HtmlCompat.FROM_HTML_MODE_LEGACY);
break;
case Description.MARKDOWN:
descriptionDisposable = TextLinkifier.createLinksFromMarkdownText(requireContext(),
- description.getContent(), binding.detailDescriptionView);
+ description.getContent(), binding.detailDescriptionView,
+ service, contentUrl);
break;
case Description.PLAIN_TEXT: default:
descriptionDisposable = TextLinkifier.createLinksFromPlainText(requireContext(),
- description.getContent(), binding.detailDescriptionView);
+ description.getContent(), binding.detailDescriptionView,
+ service, contentUrl);
break;
}
}
@@ -199,7 +205,7 @@ private void addMetadataItem(final LayoutInflater inflater,
if (linkifyContent) {
TextLinkifier.createLinksFromPlainText(requireContext(),
- content, itemBinding.metadataContentView);
+ content, itemBinding.metadataContentView, null, null);
} else {
itemBinding.metadataContentView.setText(content);
}
diff --git a/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java b/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java
index f48d39da0b0..51d8539f48b 100644
--- a/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java
+++ b/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java
@@ -47,7 +47,7 @@ public boolean onTouch(final View v, final MotionEvent event) {
if (action == MotionEvent.ACTION_UP) {
boolean handled = false;
if (link[0] instanceof URLSpan) {
- handled = URLHandler.handleUrl(v.getContext(),
+ handled = URLHandler.canHandleUrl(v.getContext(),
((URLSpan) link[0]).getURL(), 1);
}
if (!handled) {
diff --git a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
index af7cafc1518..0b8dbb751ba 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
@@ -311,7 +311,8 @@ public static Disposable showMetaInfoInTextView(@Nullable final List
* This will call
- * {@link TextLinkifier#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView)}
+ * {@link TextLinkifier#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView,
+ * StreamingService, String)}
* after having linked the URLs with {@link HtmlCompat#fromHtml(String, int)}.
*
- * @param context the context to use
- * @param htmlBlock the htmlBlock to be linked
- * @param textView the TextView to set the htmlBlock linked
- * @param htmlCompatFlag the int flag to be set when {@link HtmlCompat#fromHtml(String, int)}
- * will be called
+ * @param context the context to use
+ * @param htmlBlock the htmlBlock to be linked
+ * @param textView the TextView to set the htmlBlock linked
+ * @param streamingService the {@link StreamingService} of the content
+ * @param contentUrl the URL of the content
+ * @param htmlCompatFlag the int flag to be set when {@link HtmlCompat#fromHtml(String, int)}
+ * will be called
* @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed
*/
public static Disposable createLinksFromHtmlBlock(final Context context,
final String htmlBlock,
final TextView textView,
+ final StreamingService streamingService,
+ final String contentUrl,
final int htmlCompatFlag) {
return changeIntentsOfDescriptionLinks(context,
- HtmlCompat.fromHtml(htmlBlock, htmlCompatFlag), textView);
+ HtmlCompat.fromHtml(htmlBlock, htmlCompatFlag), textView, streamingService,
+ contentUrl);
}
/**
* Create web links for contents with a plain text description.
*
* This will call
- * {@link TextLinkifier#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView)}
+ * {@link TextLinkifier#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView,
+ * StreamingService, String)}
* after having linked the URLs with {@link TextView#setAutoLinkMask(int)} and
* {@link TextView#setText(CharSequence, TextView.BufferType)}.
*
- * @param context the context to use
- * @param plainTextBlock the block of plain text to be linked
- * @param textView the TextView to set the plain text block linked
+ * @param context the context to use
+ * @param plainTextBlock the block of plain text to be linked
+ * @param textView the TextView to set the plain text block linked
+ * @param streamingService the {@link StreamingService} of the content
+ * @param contentUrl the URL of the content
* @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed
*/
public static Disposable createLinksFromPlainText(final Context context,
final String plainTextBlock,
- final TextView textView) {
+ final TextView textView,
+ final StreamingService streamingService,
+ final String contentUrl) {
textView.setAutoLinkMask(Linkify.WEB_URLS);
textView.setText(plainTextBlock, TextView.BufferType.SPANNABLE);
- return changeIntentsOfDescriptionLinks(context, textView.getText(), textView);
+ return changeIntentsOfDescriptionLinks(context, textView.getText(), textView,
+ streamingService, contentUrl);
}
/**
* Create web links for contents with a markdown description.
*
* This will call
- * {@link TextLinkifier#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView)}
+ * {@link TextLinkifier#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView,
+ * StreamingService, String)}
* after creating an {@link Markwon} object and using
* {@link Markwon#setMarkdown(TextView, String)}.
*
- * @param context the context to use
- * @param markdownBlock the block of markdown text to be linked
- * @param textView the TextView to set the plain text block linked
+ * @param context the context to use
+ * @param markdownBlock the block of markdown text to be linked
+ * @param textView the TextView to set the plain text block linked
+ * @param streamingService the {@link StreamingService} of the content
+ * @param contentUrl the URL of the content
* @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed
*/
public static Disposable createLinksFromMarkdownText(final Context context,
final String markdownBlock,
- final TextView textView) {
+ final TextView textView,
+ final StreamingService streamingService,
+ final String contentUrl) {
final Markwon markwon = Markwon.builder(context).usePlugin(LinkifyPlugin.create()).build();
markwon.setMarkdown(textView, markdownBlock);
- return changeIntentsOfDescriptionLinks(context, textView.getText(), textView);
+ return changeIntentsOfDescriptionLinks(context, textView.getText(), textView,
+ streamingService, contentUrl);
}
+ private static final Pattern TIMESTAMPS_PATTERN_IN_PLAIN_TEXT =
+ Pattern.compile("(?:([0-5]?[0-9]):)?([0-5]?[0-9]):([0-5][0-9])");
+
/**
- * Change links generated by libraries in the description of a content to a custom link action.
+ * Add click listeners which opens the popup player on timestamps in a plain text.
*
- * Instead of using an {@link android.content.Intent#ACTION_VIEW} intent in the description of a
- * content, this method will parse the {@link CharSequence} and replace all current web links
+ * This method finds all timestamps in the {@link SpannableStringBuilder} of the description
+ * using a regular expression, adds for each a {@link ClickableSpan} which opens the popup
+ * player at the time indicated in the timestamps.
+ *
+ * @param context the context to use
+ * @param spannableDescription the SpannableStringBuilder with the text of the
+ * content description
+ * @param contentUrl the URL of the content
+ * @param streamingService the {@link StreamingService} of the content
+ */
+ private static void addClickListenersOnTimestamps(final Context context,
+ final SpannableStringBuilder
+ spannableDescription,
+ final String contentUrl,
+ final StreamingService streamingService) {
+ final String descriptionText = spannableDescription.toString();
+ final Matcher timestampMatches = TIMESTAMPS_PATTERN_IN_PLAIN_TEXT.matcher(descriptionText);
+
+ while (timestampMatches.find()) {
+ final int timestampStart = timestampMatches.start(0);
+ final int timestampEnd = timestampMatches.end(0);
+ final String parsedTimestamp = descriptionText.substring(timestampStart, timestampEnd);
+ final String[] timestampParts = parsedTimestamp.split(":");
+ final int seconds;
+ if (timestampParts.length == 3) { // timestamp format: XX:XX:XX
+ seconds = Integer.parseInt(timestampParts[0]) * 3600 + Integer.parseInt(
+ timestampParts[1]) * 60 + Integer.parseInt(timestampParts[2]);
+ spannableDescription.setSpan(new ClickableSpan() {
+ @Override
+ public void onClick(@NonNull final View view) {
+ playOnPopup(context, contentUrl, streamingService, seconds);
+ }
+ }, timestampStart, timestampEnd, 0);
+ } else if (timestampParts.length == 2) { // timestamp format: XX:XX
+ seconds = Integer.parseInt(timestampParts[0]) * 60 + Integer.parseInt(
+ timestampParts[1]);
+ spannableDescription.setSpan(new ClickableSpan() {
+ @Override
+ public void onClick(@NonNull final View view) {
+ playOnPopup(context, contentUrl, streamingService, seconds);
+ }
+ }, timestampStart, timestampEnd, 0);
+ }
+ }
+ }
+
+ /**
+ * Change links generated by libraries in the description of a content to a custom link action
+ * and add click listeners on timestamps in this description.
+ *
+ * Instead of using an {@link android.content.Intent#ACTION_VIEW} intent in the description of
+ * a content, this method will parse the {@link CharSequence} and replace all current web links
* with {@link ShareUtils#openUrlInBrowser(Context, String, boolean)}.
+ * This method will also add click listeners on timestamps in this description, which will play
+ * the content in the popup player at the time indicated in the timestamp, by using
+ * {@link TextLinkifier#addClickListenersOnTimestamps(Context, SpannableStringBuilder, String,
+ * StreamingService)} method.
*
* This method is required in order to intercept links and e.g. show a confirmation dialog
* before opening a web link.
*
- * @param context the context to use
- * @param chars the CharSequence to be parsed
- * @param textView the TextView in which the converted CharSequence will be applied
+ * @param context the context to use
+ * @param chars the CharSequence to be parsed
+ * @param textView the TextView in which the converted CharSequence will be applied
+ * @param streamingService the {@link StreamingService} of the content
+ * @param contentUrl the URL of the content
* @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed
*/
private static Disposable changeIntentsOfDescriptionLinks(final Context context,
final CharSequence chars,
- final TextView textView) {
+ final TextView textView,
+ final StreamingService
+ streamingService,
+ final String contentUrl) {
return Single.fromCallable(() -> {
+ // add custom click actions on web links
final SpannableStringBuilder textBlockLinked = new SpannableStringBuilder(chars);
final URLSpan[] urls = textBlockLinked.getSpans(0, chars.length(), URLSpan.class);
for (final URLSpan span : urls) {
final ClickableSpan clickableSpan = new ClickableSpan() {
public void onClick(@NonNull final View view) {
- if (!URLHandler.handleUrl(context, span.getURL(), 0)) {
+ if (!URLHandler.canHandleUrl(context, span.getURL(), 0)) {
ShareUtils.openUrlInBrowser(context, span.getURL(), false);
}
}
@@ -126,6 +214,13 @@ public void onClick(@NonNull final View view) {
textBlockLinked.removeSpan(span);
}
+ // add click actions on plain text timestamps only for description of contents,
+ // unneeded for metainfo TextViews
+ if (contentUrl != null || streamingService != null) {
+ addClickListenersOnTimestamps(context, textBlockLinked, contentUrl,
+ streamingService);
+ }
+
return textBlockLinked;
}).subscribeOn(Schedulers.computation())
.observeOn(AndroidSchedulers.mainThread())
diff --git a/app/src/main/java/org/schabi/newpipe/util/URLHandler.java b/app/src/main/java/org/schabi/newpipe/util/URLHandler.java
index 761a91e50ad..17555f0f92f 100644
--- a/app/src/main/java/org/schabi/newpipe/util/URLHandler.java
+++ b/app/src/main/java/org/schabi/newpipe/util/URLHandler.java
@@ -39,25 +39,31 @@ private URLHandler() {
* @param timestampType the type of timestamp
* @return true if the URL can be handled by NewPipe, false if it cannot
*/
- public static boolean handleUrl(final Context context, final String url, final int timestampType) {
+ public static boolean canHandleUrl(final Context context,
+ final String url,
+ final int timestampType) {
String matchedUrl = "";
int seconds = -1;
- final Pattern TIMESTAMP_PATTERN;
+ final Pattern timestampPattern;
if (timestampType == 0) {
- TIMESTAMP_PATTERN = Pattern.compile("(.*)&t=(\\d+)");
+ timestampPattern = Pattern.compile("(.*)&t=(\\d+)");
} else if (timestampType == 1) {
- TIMESTAMP_PATTERN = Pattern.compile("(.*)#timestamp=(\\d+)");
+ timestampPattern = Pattern.compile("(.*)#timestamp=(\\d+)");
} else {
return false;
}
- final Matcher matcher = TIMESTAMP_PATTERN.matcher(url);
+ final Matcher matcher = timestampPattern.matcher(url);
if (matcher.matches()) {
matchedUrl = matcher.group(1);
seconds = Integer.parseInt(matcher.group(2));
}
+ if (matchedUrl == null || matchedUrl.isEmpty()) {
+ return false;
+ }
+
final StreamingService service;
final StreamingService.LinkType linkType;
@@ -88,8 +94,10 @@ public static boolean handleUrl(final Context context, final String url, final i
* @param seconds the position in seconds at which the floating player will start
* @return true if the playback of the content has successfully started or false if not
*/
- private static boolean playOnPopup(final Context context, final String url,
- final StreamingService service, final int seconds) {
+ public static boolean playOnPopup(final Context context,
+ final String url,
+ final StreamingService service,
+ final int seconds) {
final LinkHandlerFactory factory = service.getStreamLHFactory();
final String cleanUrl;
From 9e9d1a04e46fba9bf5c597dac5d39a924d1be308 Mon Sep 17 00:00:00 2001
From: TiA4f8R <74829229+TiA4f8R@users.noreply.github.com>
Date: Sat, 13 Mar 2021 12:37:54 +0100
Subject: [PATCH 03/20] Fix toast shown when falling back to Google Play Store
URL and the action of Open with Kodi button in the player Add a boolean
param, showToast, in ShareUtils.openIntentInApp and only show toast "No app
on your device can open this" if this boolean is true. Fix the action of play
with Kodi button by applying the fix provided in #5599 (adding the flag
Intent.FLAG_ACTIVITY_NEW_TASK to the intent in NavigationHelper.playWithKore
method). Do also some cleanup in viewWithFileProvider and shareFile methods
of MissionAdapter class.
---
.../schabi/newpipe/error/ErrorActivity.java | 5 +-
.../schabi/newpipe/util/NavigationHelper.java | 2 +-
.../org/schabi/newpipe/util/ShareUtils.java | 65 ++++++++++++-------
.../giga/ui/adapter/MissionAdapter.java | 18 ++---
4 files changed, 47 insertions(+), 43 deletions(-)
diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java
index 106a86cfad3..5fc9a72ca70 100644
--- a/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java
@@ -220,13 +220,10 @@ private void openPrivacyPolicyDialog(final Context context, final String action)
+ getString(R.string.app_name) + " "
+ BuildConfig.VERSION_NAME)
.putExtra(Intent.EXTRA_TEXT, buildJson());
- if (i.resolveActivity(getPackageManager()) != null) {
- ShareUtils.openIntentInApp(context, i);
- }
+ ShareUtils.openIntentInApp(context, i, true);
} else if (action.equals("GITHUB")) { // open the NewPipe issue page on GitHub
ShareUtils.openUrlInBrowser(this, ERROR_GITHUB_ISSUE_URL, false);
}
-
})
.setNegativeButton(R.string.decline, (dialog, which) -> {
// do nothing
diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
index 106399735ab..2f1851efe3b 100644
--- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
@@ -252,7 +252,7 @@ public static void playOnExternalPlayer(final Context context, final String name
public static void resolveActivityOrAskToInstall(final Context context, final Intent intent) {
if (intent.resolveActivity(context.getPackageManager()) != null) {
- ShareUtils.openIntentInApp(context, intent);
+ ShareUtils.openIntentInApp(context, intent, false);
} else {
if (context instanceof Activity) {
new AlertDialog.Builder(context)
diff --git a/app/src/main/java/org/schabi/newpipe/util/ShareUtils.java b/app/src/main/java/org/schabi/newpipe/util/ShareUtils.java
index 45ec1d01557..18b2aa5d0ef 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ShareUtils.java
+++ b/app/src/main/java/org/schabi/newpipe/util/ShareUtils.java
@@ -25,9 +25,9 @@ private ShareUtils() {
* second param (a system chooser will be opened if there are multiple markets and no default)
* and falls back to Google Play Store web URL if no app to handle the market scheme was found.
*
- * It uses {@link ShareUtils#openIntentInApp(Context, Intent)} to open market scheme and
- * {@link ShareUtils#openUrlInBrowser(Context, String, boolean)} to open Google Play Store web
- * URL with false for the boolean param.
+ * It uses {@link ShareUtils#openIntentInApp(Context, Intent, boolean)} to open market scheme
+ * and {@link ShareUtils#openUrlInBrowser(Context, String, boolean)} to open Google Play Store
+ * web URL with false for the boolean param.
*
* @param context the context to use
* @param packageId the package id of the app to be installed
@@ -36,7 +36,7 @@ public static void installApp(final Context context, final String packageId) {
// Try market:// scheme
final boolean marketSchemeResult = openIntentInApp(context, new Intent(Intent.ACTION_VIEW,
Uri.parse("market://details?id=" + packageId))
- .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
+ .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK), false);
if (!marketSchemeResult) {
// Fall back to Google Play Store Web URL (F-Droid can handle it)
openUrlInBrowser(context,
@@ -48,7 +48,7 @@ public static void installApp(final Context context, final String packageId) {
* Open the url with the system default browser.
*
* If no browser is set as default, fallbacks to
- * {@link ShareUtils#openAppChooser(Context, Intent, String)}
+ * {@link ShareUtils#openAppChooser(Context, Intent, boolean)}
*
* @param context the context to use
* @param url the url to browse
@@ -71,7 +71,7 @@ public static boolean openUrlInBrowser(final Context context, final String url,
if (defaultPackageName.equals("android")) {
// No browser set as default (doesn't work on some devices)
- openAppChooser(context, intent, context.getString(R.string.open_with));
+ openAppChooser(context, intent, true);
} else {
if (defaultPackageName.isEmpty()) {
// No app installed to open a web url
@@ -84,7 +84,7 @@ public static boolean openUrlInBrowser(final Context context, final String url,
} catch (final ActivityNotFoundException e) {
// Not a browser but an app chooser because of OEMs changes
intent.setPackage(null);
- openAppChooser(context, intent, context.getString(R.string.open_with));
+ openAppChooser(context, intent, true);
}
}
}
@@ -96,7 +96,7 @@ public static boolean openUrlInBrowser(final Context context, final String url,
* Open the url with the system default browser.
*
* If no browser is set as default, fallbacks to
- * {@link ShareUtils#openAppChooser(Context, Intent, String)}
+ * {@link ShareUtils#openAppChooser(Context, Intent, boolean)}
*
* This calls {@link ShareUtils#openUrlInBrowser(Context, String, boolean)} with true
* for the boolean parameter
@@ -116,22 +116,29 @@ public static boolean openUrlInBrowser(final Context context, final String url)
* {@link ShareUtils#openUrlInBrowser(Context, String, boolean)} should be used.
*
* If no app is set as default, fallbacks to
- * {@link ShareUtils#openAppChooser(Context, Intent, String)}
+ * {@link ShareUtils#openAppChooser(Context, Intent, boolean)}.
+ *
*
- * @param context the context to use
- * @param intent the intent to open
+ * @param context the context to use
+ * @param intent the intent to open
+ * @param showToast the boolean to set if a toast is displayed to user when no app is installed
+ * to open the intent (true) or not (false)
* @return true if the intent can be opened or false if it cannot be
*/
- public static boolean openIntentInApp(final Context context, final Intent intent) {
+ public static boolean openIntentInApp(final Context context, final Intent intent,
+ final boolean showToast) {
final String defaultPackageName = getDefaultAppPackageName(context, intent);
if (defaultPackageName.equals("android")) {
// No app set as default (doesn't work on some devices)
- openAppChooser(context, intent, context.getString(R.string.open_with));
+ openAppChooser(context, intent, true);
} else {
if (defaultPackageName.isEmpty()) {
// No app installed to open the intent
- Toast.makeText(context, R.string.no_app_to_open_intent, Toast.LENGTH_LONG).show();
+ if (showToast) {
+ Toast.makeText(context, R.string.no_app_to_open_intent, Toast.LENGTH_LONG)
+ .show();
+ }
return false;
} else {
try {
@@ -140,7 +147,7 @@ public static boolean openIntentInApp(final Context context, final Intent intent
} catch (final ActivityNotFoundException e) {
// Not an app to open the intent but an app chooser because of OEMs changes
intent.setPackage(null);
- openAppChooser(context, intent, context.getString(R.string.open_with));
+ openAppChooser(context, intent, true);
}
}
}
@@ -152,18 +159,25 @@ public static boolean openIntentInApp(final Context context, final Intent intent
* Open the system chooser to launch an intent.
*
* This method opens an {@link android.content.Intent#ACTION_CHOOSER} of the intent putted
- * as the viewIntent param. A string for the chooser's title must be passed as the last param.
+ * as the intent param. If the setTitleChooser boolean is true, the string "Open with" will be
+ * set as the title of the system chooser.
+ * For Android P and higher, title for {@link android.content.Intent#ACTION_SEND} system
+ * choosers must be set on this intent, not on the
+ * {@link android.content.Intent#ACTION_CHOOSER} intent.
*
- * @param context the context to use
- * @param intent the intent to open
- * @param chooserStringTitle the string of chooser's title
+ * @param context the context to use
+ * @param intent the intent to open
+ * @param setTitleChooser set the title "Open with" to the chooser if true, else not
*/
- private static void openAppChooser(final Context context, final Intent intent,
- final String chooserStringTitle) {
+ private static void openAppChooser(final Context context,
+ final Intent intent,
+ final boolean setTitleChooser) {
final Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
chooserIntent.putExtra(Intent.EXTRA_INTENT, intent);
- chooserIntent.putExtra(Intent.EXTRA_TITLE, chooserStringTitle);
chooserIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ if (setTitleChooser) {
+ chooserIntent.putExtra(Intent.EXTRA_TITLE, context.getString(R.string.open_with));
+ }
context.startActivity(chooserIntent);
}
@@ -201,10 +215,13 @@ private static String getDefaultAppPackageName(final Context context, final Inte
public static void shareText(final Context context, final String subject, final String url) {
final Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("text/plain");
- shareIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
+ if (!subject.isEmpty()) {
+ shareIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
+ }
shareIntent.putExtra(Intent.EXTRA_TEXT, url);
+ shareIntent.putExtra(Intent.EXTRA_TITLE, context.getString(R.string.share_dialog_title));
- openAppChooser(context, shareIntent, context.getString(R.string.share_dialog_title));
+ openAppChooser(context, shareIntent, false);
}
/**
diff --git a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java
index 45ee290f6f9..b31933dfdd2 100644
--- a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java
+++ b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java
@@ -348,10 +348,8 @@ private void viewWithFileProvider(Mission mission) {
if (BuildConfig.DEBUG)
Log.v(TAG, "Mime: " + mimeType + " package: " + BuildConfig.APPLICATION_ID + ".provider");
- final Uri uri = resolveShareableUri(mission);
-
Intent intent = new Intent(Intent.ACTION_VIEW);
- intent.setDataAndType(uri, mimeType);
+ intent.setDataAndType(resolveShareableUri(mission), mimeType);
intent.addFlags(FLAG_GRANT_READ_URI_PERMISSION);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
@@ -361,10 +359,8 @@ private void viewWithFileProvider(Mission mission) {
intent.addFlags(FLAG_ACTIVITY_NEW_TASK);
}
- //mContext.grantUriPermission(packageName, uri, Intent.FLAG_GRANT_READ_URI_PERMISSION);
-
if (intent.resolveActivity(mContext.getPackageManager()) != null) {
- ShareUtils.openIntentInApp(mContext, intent);
+ ShareUtils.openIntentInApp(mContext, intent, false);
} else {
Toast.makeText(mContext, R.string.toast_no_player, Toast.LENGTH_LONG).show();
}
@@ -377,19 +373,13 @@ private void shareFile(Mission mission) {
shareIntent.setType(resolveMimeType(mission));
shareIntent.putExtra(Intent.EXTRA_STREAM, resolveShareableUri(mission));
shareIntent.addFlags(FLAG_GRANT_READ_URI_PERMISSION);
+
final Intent intent = new Intent(Intent.ACTION_CHOOSER);
intent.putExtra(Intent.EXTRA_INTENT, shareIntent);
intent.putExtra(Intent.EXTRA_TITLE, mContext.getString(R.string.share_dialog_title));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
- try {
- intent.setPackage("android");
- mContext.startActivity(intent);
- } catch (final ActivityNotFoundException e) {
- // falling back to OEM chooser if Android's system chooser was removed by the OEM
- intent.setPackage(null);
- mContext.startActivity(intent);
- }
+ mContext.startActivity(intent);
}
/**
From 6abdd2a6d8f7f9078c0811ebacc34357a586150b Mon Sep 17 00:00:00 2001
From: TiA4f8R <74829229+TiA4f8R@users.noreply.github.com>
Date: Sat, 13 Mar 2021 12:43:51 +0100
Subject: [PATCH 04/20] Try to change message of the system chooser for the
update notification This commit tries to change the title of the system
chooser shown, which is from Android System ("Open links with"), when no
defaut browser is present, for the update notification.
---
.../main/java/org/schabi/newpipe/CheckForNewAppVersion.java | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java b/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java
index f84d986aab0..7bddb1e95a7 100644
--- a/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java
+++ b/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java
@@ -130,12 +130,11 @@ private static void compareAppVersionAndShowNotification(@NonNull final Applicat
if (BuildConfig.VERSION_CODE < versionCode) {
// A pending intent to open the apk location url in the browser.
final Intent viewIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(apkLocationUrl));
+ viewIntent.putExtra(Intent.EXTRA_TITLE, R.string.open_with);
final Intent intent = new Intent(Intent.ACTION_CHOOSER);
intent.putExtra(Intent.EXTRA_INTENT, viewIntent);
- intent.putExtra(Intent.EXTRA_TITLE, R.string.open_with);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-
final PendingIntent pendingIntent
= PendingIntent.getActivity(application, 0, intent, 0);
From c9729403380aa675af3333919b5457592f1c7ec2 Mon Sep 17 00:00:00 2001
From: TiA4f8R <74829229+TiA4f8R@users.noreply.github.com>
Date: Thu, 18 Mar 2021 18:42:25 +0100
Subject: [PATCH 05/20] Apply suggested changes and fix some warnings
---
.../util/CommentTextOnTouchListener.java | 2 +-
.../schabi/newpipe/util/TextLinkifier.java | 32 +++++++++----------
.../org/schabi/newpipe/util/URLHandler.java | 19 ++++++-----
3 files changed, 28 insertions(+), 25 deletions(-)
diff --git a/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java b/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java
index 51d8539f48b..f48d39da0b0 100644
--- a/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java
+++ b/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java
@@ -47,7 +47,7 @@ public boolean onTouch(final View v, final MotionEvent event) {
if (action == MotionEvent.ACTION_UP) {
boolean handled = false;
if (link[0] instanceof URLSpan) {
- handled = URLHandler.canHandleUrl(v.getContext(),
+ handled = URLHandler.handleUrl(v.getContext(),
((URLSpan) link[0]).getURL(), 1);
}
if (!handled) {
diff --git a/app/src/main/java/org/schabi/newpipe/util/TextLinkifier.java b/app/src/main/java/org/schabi/newpipe/util/TextLinkifier.java
index 37bc80f725d..56c69f47cd0 100644
--- a/app/src/main/java/org/schabi/newpipe/util/TextLinkifier.java
+++ b/app/src/main/java/org/schabi/newpipe/util/TextLinkifier.java
@@ -29,6 +29,8 @@
public final class TextLinkifier {
public static final String TAG = TextLinkifier.class.getSimpleName();
+ private static final Pattern TIMESTAMPS_PATTERN_IN_PLAIN_TEXT =
+ Pattern.compile("(?:([0-5]?[0-9]):)?([0-5]?[0-9]):([0-5][0-9])");
private TextLinkifier() {
}
@@ -115,9 +117,6 @@ public static Disposable createLinksFromMarkdownText(final Context context,
streamingService, contentUrl);
}
- private static final Pattern TIMESTAMPS_PATTERN_IN_PLAIN_TEXT =
- Pattern.compile("(?:([0-5]?[0-9]):)?([0-5]?[0-9]):([0-5][0-9])");
-
/**
* Add click listeners which opens the popup player on timestamps in a plain text.
*
@@ -125,11 +124,11 @@ public static Disposable createLinksFromMarkdownText(final Context context,
* using a regular expression, adds for each a {@link ClickableSpan} which opens the popup
* player at the time indicated in the timestamps.
*
- * @param context the context to use
- * @param spannableDescription the SpannableStringBuilder with the text of the
- * content description
- * @param contentUrl the URL of the content
- * @param streamingService the {@link StreamingService} of the content
+ * @param context the context to use
+ * @param spannableDescription the SpannableStringBuilder with the text of the
+ * content description
+ * @param contentUrl the URL of the content
+ * @param streamingService the {@link StreamingService} of the content
*/
private static void addClickListenersOnTimestamps(final Context context,
final SpannableStringBuilder
@@ -144,23 +143,24 @@ private static void addClickListenersOnTimestamps(final Context context,
final int timestampEnd = timestampMatches.end(0);
final String parsedTimestamp = descriptionText.substring(timestampStart, timestampEnd);
final String[] timestampParts = parsedTimestamp.split(":");
- final int seconds;
+ final int time;
if (timestampParts.length == 3) { // timestamp format: XX:XX:XX
- seconds = Integer.parseInt(timestampParts[0]) * 3600 + Integer.parseInt(
- timestampParts[1]) * 60 + Integer.parseInt(timestampParts[2]);
+ time = Integer.parseInt(timestampParts[0]) * 3600 // hours
+ + Integer.parseInt(timestampParts[1]) * 60 // minutes
+ + Integer.parseInt(timestampParts[2]); // seconds
spannableDescription.setSpan(new ClickableSpan() {
@Override
public void onClick(@NonNull final View view) {
- playOnPopup(context, contentUrl, streamingService, seconds);
+ playOnPopup(context, contentUrl, streamingService, time);
}
}, timestampStart, timestampEnd, 0);
} else if (timestampParts.length == 2) { // timestamp format: XX:XX
- seconds = Integer.parseInt(timestampParts[0]) * 60 + Integer.parseInt(
- timestampParts[1]);
+ time = Integer.parseInt(timestampParts[0]) * 60 // minutes
+ + Integer.parseInt(timestampParts[1]); // seconds
spannableDescription.setSpan(new ClickableSpan() {
@Override
public void onClick(@NonNull final View view) {
- playOnPopup(context, contentUrl, streamingService, seconds);
+ playOnPopup(context, contentUrl, streamingService, time);
}
}, timestampStart, timestampEnd, 0);
}
@@ -203,7 +203,7 @@ private static Disposable changeIntentsOfDescriptionLinks(final Context context,
for (final URLSpan span : urls) {
final ClickableSpan clickableSpan = new ClickableSpan() {
public void onClick(@NonNull final View view) {
- if (!URLHandler.canHandleUrl(context, span.getURL(), 0)) {
+ if (!URLHandler.handleUrl(context, span.getURL(), 0)) {
ShareUtils.openUrlInBrowser(context, span.getURL(), false);
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/util/URLHandler.java b/app/src/main/java/org/schabi/newpipe/util/URLHandler.java
index 17555f0f92f..6c5c574e846 100644
--- a/app/src/main/java/org/schabi/newpipe/util/URLHandler.java
+++ b/app/src/main/java/org/schabi/newpipe/util/URLHandler.java
@@ -19,12 +19,15 @@
import io.reactivex.rxjava3.schedulers.Schedulers;
public final class URLHandler {
+ private static final Pattern AMPERSAND_TIMESTAMP_PATTERN = Pattern.compile("(.*)&t=(\\d+)");
+ private static final Pattern HASHTAG_TIMESTAMP_PATTERN =
+ Pattern.compile("(.*)#timestamp=(\\d+)");
private URLHandler() {
}
/**
- * Check if an URL can be handled in NewPipe.
+ * Handle an URL in NewPipe.
*
* This method will check if the provided url can be handled in NewPipe or not. If this is a
* service URL with a timestamp, the popup player will be opened.
@@ -39,17 +42,17 @@ private URLHandler() {
* @param timestampType the type of timestamp
* @return true if the URL can be handled by NewPipe, false if it cannot
*/
- public static boolean canHandleUrl(final Context context,
- final String url,
- final int timestampType) {
+ public static boolean handleUrl(final Context context,
+ final String url,
+ final int timestampType) {
String matchedUrl = "";
int seconds = -1;
final Pattern timestampPattern;
if (timestampType == 0) {
- timestampPattern = Pattern.compile("(.*)&t=(\\d+)");
+ timestampPattern = AMPERSAND_TIMESTAMP_PATTERN;
} else if (timestampType == 1) {
- timestampPattern = Pattern.compile("(.*)#timestamp=(\\d+)");
+ timestampPattern = HASHTAG_TIMESTAMP_PATTERN;
} else {
return false;
}
@@ -107,13 +110,13 @@ public static boolean playOnPopup(final Context context,
return false;
}
- final Single single
+ final Single
- * It uses {@link ShareUtils#openIntentInApp(Context, Intent, boolean)} to open market scheme
- * and {@link ShareUtils#openUrlInBrowser(Context, String, boolean)} to open Google Play Store
+ * It uses {@link #openIntentInApp(Context, Intent, boolean)} to open market scheme
+ * and {@link #openUrlInBrowser(Context, String, boolean)} to open Google Play Store
* web URL with false for the boolean param.
*
* @param context the context to use
* @param packageId the package id of the app to be installed
*/
public static void installApp(final Context context, final String packageId) {
- // Try market:// scheme
+ // Try market scheme
final boolean marketSchemeResult = openIntentInApp(context, new Intent(Intent.ACTION_VIEW,
Uri.parse("market://details?id=" + packageId))
.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK), false);
@@ -48,7 +49,7 @@ public static void installApp(final Context context, final String packageId) {
* Open the url with the system default browser.
*
* If no browser is set as default, fallbacks to
- * {@link ShareUtils#openAppChooser(Context, Intent, boolean)}
+ * {@link #openAppChooser(Context, Intent, boolean)}
*
* @param context the context to use
* @param url the url to browse
@@ -56,7 +57,8 @@ public static void installApp(final Context context, final String packageId) {
* for HTTP protocol or for the created intent
* @return true if the URL can be opened or false if it cannot
*/
- public static boolean openUrlInBrowser(final Context context, final String url,
+ public static boolean openUrlInBrowser(final Context context,
+ final String url,
final boolean httpDefaultBrowserTest) {
final String defaultPackageName;
final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url))
@@ -96,9 +98,9 @@ public static boolean openUrlInBrowser(final Context context, final String url,
* Open the url with the system default browser.
*
* If no browser is set as default, fallbacks to
- * {@link ShareUtils#openAppChooser(Context, Intent, boolean)}
+ * {@link #openAppChooser(Context, Intent, boolean)}
*
- * This calls {@link ShareUtils#openUrlInBrowser(Context, String, boolean)} with true
+ * This calls {@link #openUrlInBrowser(Context, String, boolean)} with true
* for the boolean parameter
*
* @param context the context to use
@@ -113,19 +115,20 @@ public static boolean openUrlInBrowser(final Context context, final String url)
* Open an intent with the system default app.
*
* The intent can be of every type, excepted a web intent for which
- * {@link ShareUtils#openUrlInBrowser(Context, String, boolean)} should be used.
+ * {@link #openUrlInBrowser(Context, String, boolean)} should be used.
*
* If no app is set as default, fallbacks to
- * {@link ShareUtils#openAppChooser(Context, Intent, boolean)}.
+ * {@link #openAppChooser(Context, Intent, boolean)}.
*
*
* @param context the context to use
* @param intent the intent to open
- * @param showToast the boolean to set if a toast is displayed to user when no app is installed
+ * @param showToast a boolean to set if a toast is displayed to user when no app is installed
* to open the intent (true) or not (false)
* @return true if the intent can be opened or false if it cannot be
*/
- public static boolean openIntentInApp(final Context context, final Intent intent,
+ public static boolean openIntentInApp(final Context context,
+ final Intent intent,
final boolean showToast) {
final String defaultPackageName = getDefaultAppPackageName(context, intent);
@@ -178,6 +181,36 @@ private static void openAppChooser(final Context context,
if (setTitleChooser) {
chooserIntent.putExtra(Intent.EXTRA_TITLE, context.getString(R.string.open_with));
}
+
+ // Migrate any clip data and flags from the original intent.
+ final int permFlags;
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+ permFlags = intent.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+ | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
+ | Intent.FLAG_GRANT_PREFIX_URI_PERMISSION);
+ } else {
+ permFlags = intent.getFlags() & (Intent.FLAG_GRANT_READ_URI_PERMISSION
+ | Intent.FLAG_GRANT_WRITE_URI_PERMISSION
+ | Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION);
+ }
+ if (permFlags != 0) {
+ ClipData targetClipData = intent.getClipData();
+ if (targetClipData == null && intent.getData() != null) {
+ final ClipData.Item item = new ClipData.Item(intent.getData());
+ final String[] mimeTypes;
+ if (intent.getType() != null) {
+ mimeTypes = new String[] {intent.getType()};
+ } else {
+ mimeTypes = new String[] {};
+ }
+ targetClipData = new ClipData(null, mimeTypes, item);
+ }
+ if (targetClipData != null) {
+ chooserIntent.setClipData(targetClipData);
+ chooserIntent.addFlags(permFlags);
+ }
+ }
context.startActivity(chooserIntent);
}
@@ -208,24 +241,45 @@ private static String getDefaultAppPackageName(final Context context, final Inte
/**
* Open the android share menu to share the current url.
*
- * @param context the context to use
- * @param subject the url subject, typically the title
- * @param url the url to share
+ * @param context the context to use
+ * @param subject the url subject, typically the title
+ * @param url the url to share
+ * @param imagePreviewUrl the image of the subject
*/
- public static void shareText(final Context context, final String subject, final String url) {
- shareText(context, subject, url, true);
+ public static void shareText(final Context context,
+ final String subject,
+ final String url,
+ final String imagePreviewUrl) {
+ shareText(context, subject, url, imagePreviewUrl, true);
}
-
+ /**
+ * Open the android share sheet to share the current url.
+ *
+ * For Android 10+ users, a content preview is shown, which includes the title of the shared
+ * content.
+ * Support sharing the image of the content needs to done, if possible.
+ *
+ * @param context the context to use
+ * @param subject the url subject, typically the title
+ * @param url the url to share
+ * @param imagePreviewUrl the image of the subject
+ * @param showPreviewText show the subject as an extra title of the Android share sheet if true
+ */
public static void shareText(final Context context,
final String subject,
final String url,
+ final String imagePreviewUrl,
final boolean showPreviewText) {
final Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("text/plain");
- if (!subject.isEmpty() && showPreviewText) {
- shareIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
+ if (!imagePreviewUrl.isEmpty() && !subject.isEmpty() && showPreviewText) {
shareIntent.putExtra(Intent.EXTRA_TITLE, subject);
+ /* TODO: add the image of the content to Android share sheet with setClipData after
+ generating a content URI of this image, then use ClipData.newUri(the content
+ resolver, null, the content URI) and set the ClipData to the share intent with
+ shareIntent.setClipData(generated ClipData).*/
+ //shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
}
shareIntent.putExtra(Intent.EXTRA_TEXT, url);
diff --git a/app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java b/app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java
index 610f9f85237..a8ee1ebaaee 100644
--- a/app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java
+++ b/app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java
@@ -88,7 +88,8 @@ public enum StreamDialogEntry {
}),
share(R.string.share, (fragment, item) ->
- ShareUtils.shareText(fragment.getContext(), item.getName(), item.getUrl())),
+ ShareUtils.shareText(fragment.getContext(), item.getName(), item.getUrl(),
+ item.getThumbnailUrl())),
open_in_browser(R.string.open_in_browser, (fragment, item) ->
ShareUtils.openUrlInBrowser(fragment.getContext(), item.getUrl()));
diff --git a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java
index b31933dfdd2..44c20b5415c 100644
--- a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java
+++ b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java
@@ -376,8 +376,11 @@ private void shareFile(Mission mission) {
final Intent intent = new Intent(Intent.ACTION_CHOOSER);
intent.putExtra(Intent.EXTRA_INTENT, shareIntent);
- intent.putExtra(Intent.EXTRA_TITLE, mContext.getString(R.string.share_dialog_title));
+ if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.O_MR1) {
+ intent.putExtra(Intent.EXTRA_TITLE, mContext.getString(R.string.share_dialog_title));
+ }
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ intent.addFlags(FLAG_GRANT_READ_URI_PERMISSION);
mContext.startActivity(intent);
}
From d6decc05d735ce96323687646c2c2673819e7b36 Mon Sep 17 00:00:00 2001
From: TiA4f8R <74829229+TiA4f8R@users.noreply.github.com>
Date: Fri, 26 Mar 2021 13:28:11 +0100
Subject: [PATCH 08/20] Move some classes to a new subpackage and adress
requested changes Rename URLHandler and KoreUtil classes to
InternalUrlsHandler and KoreUtils. Move InternalUrlsHandler, KoreUtils,
TextLinkfier, ShareUtils classes to external_communication subpackage. Remove
unused param showPreviewText in shareText method of ShareUtils class. Add
initial work to be able to display an image preview of the content shared
(not for downloads). Use a better regular expression to parse timestamps in
plain text descriptions.
---
.../org/schabi/newpipe/RouterActivity.java | 2 +-
.../schabi/newpipe/error/ErrorActivity.java | 2 +-
.../fragments/detail/DescriptionFragment.java | 4 +-
.../fragments/detail/VideoDetailFragment.java | 8 +--
.../fragments/list/BaseListFragment.java | 4 +-
.../list/channel/ChannelFragment.java | 2 +-
.../list/playlist/PlaylistFragment.java | 6 +--
.../holder/CommentsMiniInfoItemHolder.java | 2 +-
.../history/StatisticsPlaylistFragment.java | 4 +-
.../local/playlist/LocalPlaylistFragment.java | 4 +-
.../subscription/SubscriptionFragment.kt | 2 +-
.../newpipe/player/PlayQueueActivity.java | 2 +-
.../org/schabi/newpipe/player/Player.java | 8 +--
.../util/CommentTextOnTouchListener.java | 5 +-
.../schabi/newpipe/util/ExtractorHelper.java | 1 +
.../schabi/newpipe/util/NavigationHelper.java | 3 +-
.../newpipe/util/StreamDialogEntry.java | 4 +-
.../InternalUrlsHandler.java} | 8 +--
.../KoreUtils.java} | 7 +--
.../ShareUtils.java | 50 +++++++------------
.../TextLinkifier.java | 13 ++---
.../giga/ui/adapter/MissionAdapter.java | 3 +-
22 files changed, 70 insertions(+), 74 deletions(-)
rename app/src/main/java/org/schabi/newpipe/util/{URLHandler.java => external_communication/InternalUrlsHandler.java} (95%)
rename app/src/main/java/org/schabi/newpipe/util/{KoreUtil.java => external_communication/KoreUtils.java} (88%)
rename app/src/main/java/org/schabi/newpipe/util/{ => external_communication}/ShareUtils.java (87%)
rename app/src/main/java/org/schabi/newpipe/util/{ => external_communication}/TextLinkifier.java (96%)
diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java
index af6ca077c1d..222b6a3d991 100644
--- a/app/src/main/java/org/schabi/newpipe/RouterActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/RouterActivity.java
@@ -69,7 +69,7 @@
import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PermissionHelper;
-import org.schabi.newpipe.util.ShareUtils;
+import org.schabi.newpipe.util.external_communication.ShareUtils;
import org.schabi.newpipe.util.ThemeHelper;
import org.schabi.newpipe.util.urlfinder.UrlFinder;
import org.schabi.newpipe.views.FocusOverlayView;
diff --git a/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java
index 5fc9a72ca70..0f9d6569c4d 100644
--- a/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/error/ErrorActivity.java
@@ -27,7 +27,7 @@
import org.schabi.newpipe.R;
import org.schabi.newpipe.databinding.ActivityErrorBinding;
import org.schabi.newpipe.util.Localization;
-import org.schabi.newpipe.util.ShareUtils;
+import org.schabi.newpipe.util.external_communication.ShareUtils;
import org.schabi.newpipe.util.ThemeHelper;
import java.time.LocalDateTime;
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 49ac3ef01a8..fb2f07eecc8 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
@@ -24,8 +24,8 @@
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 org.schabi.newpipe.util.external_communication.ShareUtils;
+import org.schabi.newpipe.util.external_communication.TextLinkifier;
import java.util.ArrayList;
import java.util.Collections;
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 47257dd1f7e..c86d9596b18 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
@@ -91,12 +91,12 @@
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.external_communication.KoreUtils;
import org.schabi.newpipe.util.ListHelper;
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.external_communication.ShareUtils;
import org.schabi.newpipe.util.ThemeHelper;
import java.util.ArrayList;
@@ -472,7 +472,7 @@ public void onClick(final View v) {
if (DEBUG) {
Log.i(TAG, "Failed to start kore", e);
}
- KoreUtil.showInstallKoreDialog(requireContext());
+ KoreUtils.showInstallKoreDialog(requireContext());
}
}
break;
@@ -631,7 +631,7 @@ protected void initListeners() {
binding.detailControlsShare.setOnClickListener(this);
binding.detailControlsOpenInBrowser.setOnClickListener(this);
binding.detailControlsPlayWithKodi.setOnClickListener(this);
- binding.detailControlsPlayWithKodi.setVisibility(KoreUtil.shouldShowPlayWithKodi(
+ binding.detailControlsPlayWithKodi.setVisibility(KoreUtils.shouldShowPlayWithKodi(
requireContext(), serviceId) ? View.VISIBLE : View.GONE);
binding.overlayThumbnail.setOnClickListener(this);
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 72855868559..c28a41b7d47 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
@@ -33,7 +33,7 @@
import org.schabi.newpipe.info_list.InfoItemDialog;
import org.schabi.newpipe.info_list.InfoListAdapter;
import org.schabi.newpipe.player.helper.PlayerHolder;
-import org.schabi.newpipe.util.KoreUtil;
+import org.schabi.newpipe.util.external_communication.KoreUtils;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.OnClickGesture;
import org.schabi.newpipe.util.StateSaver;
@@ -371,7 +371,7 @@ protected void showStreamDialog(final StreamInfoItem item) {
));
}
entries.add(StreamDialogEntry.open_in_browser);
- if (KoreUtil.shouldShowPlayWithKodi(context, item.getServiceId())) {
+ if (KoreUtils.shouldShowPlayWithKodi(context, item.getServiceId())) {
entries.add(StreamDialogEntry.play_with_kodi);
}
if (!isNullOrEmpty(item.getUploaderUrl())) {
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 a94d6003239..fe07a8dc947 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
@@ -43,7 +43,7 @@
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
-import org.schabi.newpipe.util.ShareUtils;
+import org.schabi.newpipe.util.external_communication.ShareUtils;
import org.schabi.newpipe.util.ThemeHelper;
import java.util.ArrayList;
diff --git a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java
index f7d3b34f8b3..938cf9d6619 100644
--- a/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/fragments/list/playlist/PlaylistFragment.java
@@ -42,10 +42,10 @@
import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue;
import org.schabi.newpipe.util.ExtractorHelper;
import org.schabi.newpipe.util.ImageDisplayConstants;
-import org.schabi.newpipe.util.KoreUtil;
+import org.schabi.newpipe.util.external_communication.KoreUtils;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
-import org.schabi.newpipe.util.ShareUtils;
+import org.schabi.newpipe.util.external_communication.ShareUtils;
import org.schabi.newpipe.util.StreamDialogEntry;
import java.util.ArrayList;
@@ -162,7 +162,7 @@ protected void showStreamDialog(final StreamInfoItem item) {
));
}
entries.add(StreamDialogEntry.open_in_browser);
- if (KoreUtil.shouldShowPlayWithKodi(context, item.getServiceId())) {
+ if (KoreUtils.shouldShowPlayWithKodi(context, item.getServiceId())) {
entries.add(StreamDialogEntry.play_with_kodi);
}
diff --git a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java
index ae7ddfd6350..629240dc6bf 100644
--- a/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java
+++ b/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder.java
@@ -24,7 +24,7 @@
import org.schabi.newpipe.util.ImageDisplayConstants;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
-import org.schabi.newpipe.util.ShareUtils;
+import org.schabi.newpipe.util.external_communication.ShareUtils;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
diff --git a/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java
index 10aa8aa68df..2fe757dea02 100644
--- a/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/local/history/StatisticsPlaylistFragment.java
@@ -36,7 +36,7 @@
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
import org.schabi.newpipe.settings.HistorySettingsFragment;
-import org.schabi.newpipe.util.KoreUtil;
+import org.schabi.newpipe.util.external_communication.KoreUtils;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.OnClickGesture;
import org.schabi.newpipe.util.StreamDialogEntry;
@@ -359,7 +359,7 @@ private void showStreamDialog(final StreamStatisticsEntry item) {
));
}
entries.add(StreamDialogEntry.open_in_browser);
- if (KoreUtil.shouldShowPlayWithKodi(context, infoItem.getServiceId())) {
+ if (KoreUtils.shouldShowPlayWithKodi(context, infoItem.getServiceId())) {
entries.add(StreamDialogEntry.play_with_kodi);
}
diff --git a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java
index f79282641cb..768b0b86215 100644
--- a/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java
+++ b/app/src/main/java/org/schabi/newpipe/local/playlist/LocalPlaylistFragment.java
@@ -44,7 +44,7 @@
import org.schabi.newpipe.player.helper.PlayerHolder;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
-import org.schabi.newpipe.util.KoreUtil;
+import org.schabi.newpipe.util.external_communication.KoreUtils;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.OnClickGesture;
@@ -770,7 +770,7 @@ protected void showStreamItemDialog(final PlaylistStreamEntry item) {
));
}
entries.add(StreamDialogEntry.open_in_browser);
- if (KoreUtil.shouldShowPlayWithKodi(context, infoItem.getServiceId())) {
+ if (KoreUtils.shouldShowPlayWithKodi(context, infoItem.getServiceId())) {
entries.add(StreamDialogEntry.play_with_kodi);
}
diff --git a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt
index 060e5066b58..488ab68afb7 100644
--- a/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt
+++ b/app/src/main/java/org/schabi/newpipe/local/subscription/SubscriptionFragment.kt
@@ -56,9 +56,9 @@ import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.KEY_VALUE
import org.schabi.newpipe.local.subscription.services.SubscriptionsImportService.PREVIOUS_EXPORT_MODE
import org.schabi.newpipe.streams.io.StoredFileHelper
+import org.schabi.newpipe.util.external_communication.ShareUtils
import org.schabi.newpipe.util.NavigationHelper
import org.schabi.newpipe.util.OnClickGesture
-import org.schabi.newpipe.util.ShareUtils
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
diff --git a/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java b/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java
index 403848dd6e8..13b66af8004 100644
--- a/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java
+++ b/app/src/main/java/org/schabi/newpipe/player/PlayQueueActivity.java
@@ -47,7 +47,7 @@
import static org.schabi.newpipe.player.helper.PlayerHelper.formatSpeed;
import static org.schabi.newpipe.util.Localization.assureCorrectAppLanguage;
-import static org.schabi.newpipe.util.ShareUtils.shareText;
+import static org.schabi.newpipe.util.external_communication.ShareUtils.shareText;
public final class PlayQueueActivity extends AppCompatActivity
implements PlayerEventListener, SeekBar.OnSeekBarChangeListener,
diff --git a/app/src/main/java/org/schabi/newpipe/player/Player.java b/app/src/main/java/org/schabi/newpipe/player/Player.java
index 7600346ae46..af04695c070 100644
--- a/app/src/main/java/org/schabi/newpipe/player/Player.java
+++ b/app/src/main/java/org/schabi/newpipe/player/Player.java
@@ -123,11 +123,11 @@
import org.schabi.newpipe.player.resolver.VideoPlaybackResolver;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.ImageDisplayConstants;
-import org.schabi.newpipe.util.KoreUtil;
+import org.schabi.newpipe.util.external_communication.KoreUtils;
import org.schabi.newpipe.util.ListHelper;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.SerializedCache;
-import org.schabi.newpipe.util.ShareUtils;
+import org.schabi.newpipe.util.external_communication.ShareUtils;
import org.schabi.newpipe.views.ExpandableSurfaceView;
import java.io.IOException;
@@ -1033,7 +1033,7 @@ private void showHideKodiButton() {
// show kodi button if it supports the current service and it is enabled in settings
binding.playWithKodi.setVisibility(videoPlayerSelected()
&& playQueue != null && playQueue.getItem() != null
- && KoreUtil.shouldShowPlayWithKodi(context, playQueue.getItem().getServiceId())
+ && KoreUtils.shouldShowPlayWithKodi(context, playQueue.getItem().getServiceId())
? View.VISIBLE : View.GONE);
}
//endregion
@@ -3725,7 +3725,7 @@ private void onPlayWithKodiClicked() {
if (DEBUG) {
Log.i(TAG, "Failed to start kore", e);
}
- KoreUtil.showInstallKoreDialog(getParentActivity());
+ KoreUtils.showInstallKoreDialog(getParentActivity());
}
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java b/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java
index f48d39da0b0..8fe59b03655 100644
--- a/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java
+++ b/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java
@@ -10,6 +10,9 @@
import android.view.View;
import android.widget.TextView;
+import org.schabi.newpipe.util.external_communication.ShareUtils;
+import org.schabi.newpipe.util.external_communication.InternalUrlsHandler;
+
public class CommentTextOnTouchListener implements View.OnTouchListener {
public static final CommentTextOnTouchListener INSTANCE = new CommentTextOnTouchListener();
@@ -47,7 +50,7 @@ public boolean onTouch(final View v, final MotionEvent event) {
if (action == MotionEvent.ACTION_UP) {
boolean handled = false;
if (link[0] instanceof URLSpan) {
- handled = URLHandler.handleUrl(v.getContext(),
+ handled = InternalUrlsHandler.handleUrl(v.getContext(),
((URLSpan) link[0]).getURL(), 1);
}
if (!handled) {
diff --git a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
index 0b8dbb751ba..2d841b0c836 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/ExtractorHelper.java
@@ -30,6 +30,7 @@
import org.schabi.newpipe.MainActivity;
import org.schabi.newpipe.R;
+import org.schabi.newpipe.util.external_communication.TextLinkifier;
import org.schabi.newpipe.extractor.Info;
import org.schabi.newpipe.extractor.InfoItem;
import org.schabi.newpipe.extractor.ListExtractor.InfoItemsPage;
diff --git a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
index 2f1851efe3b..d4d158eedd3 100644
--- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
+++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java
@@ -53,10 +53,11 @@
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.PlayQueueItem;
import org.schabi.newpipe.settings.SettingsActivity;
+import org.schabi.newpipe.util.external_communication.ShareUtils;
import java.util.ArrayList;
-import static org.schabi.newpipe.util.ShareUtils.installApp;
+import static org.schabi.newpipe.util.external_communication.ShareUtils.installApp;
public final class NavigationHelper {
public static final String MAIN_FRAGMENT_TAG = "main_fragment_tag";
diff --git a/app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java b/app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java
index a8ee1ebaaee..ad646066497 100644
--- a/app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java
+++ b/app/src/main/java/org/schabi/newpipe/util/StreamDialogEntry.java
@@ -12,6 +12,8 @@
import org.schabi.newpipe.player.MainPlayer;
import org.schabi.newpipe.player.helper.PlayerHolder;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
+import org.schabi.newpipe.util.external_communication.KoreUtils;
+import org.schabi.newpipe.util.external_communication.ShareUtils;
import java.util.Collections;
import java.util.List;
@@ -83,7 +85,7 @@ public enum StreamDialogEntry {
try {
NavigationHelper.playWithKore(fragment.requireContext(), videoUrl);
} catch (final Exception e) {
- KoreUtil.showInstallKoreDialog(fragment.getActivity());
+ KoreUtils.showInstallKoreDialog(fragment.getActivity());
}
}),
diff --git a/app/src/main/java/org/schabi/newpipe/util/URLHandler.java b/app/src/main/java/org/schabi/newpipe/util/external_communication/InternalUrlsHandler.java
similarity index 95%
rename from app/src/main/java/org/schabi/newpipe/util/URLHandler.java
rename to app/src/main/java/org/schabi/newpipe/util/external_communication/InternalUrlsHandler.java
index 6c5c574e846..7b81538a3cd 100644
--- a/app/src/main/java/org/schabi/newpipe/util/URLHandler.java
+++ b/app/src/main/java/org/schabi/newpipe/util/external_communication/InternalUrlsHandler.java
@@ -1,4 +1,4 @@
-package org.schabi.newpipe.util;
+package org.schabi.newpipe.util.external_communication;
import android.content.Context;
@@ -10,6 +10,8 @@
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.player.playqueue.PlayQueue;
import org.schabi.newpipe.player.playqueue.SinglePlayQueue;
+import org.schabi.newpipe.util.ExtractorHelper;
+import org.schabi.newpipe.util.NavigationHelper;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -18,12 +20,12 @@
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.schedulers.Schedulers;
-public final class URLHandler {
+public final class InternalUrlsHandler {
private static final Pattern AMPERSAND_TIMESTAMP_PATTERN = Pattern.compile("(.*)&t=(\\d+)");
private static final Pattern HASHTAG_TIMESTAMP_PATTERN =
Pattern.compile("(.*)#timestamp=(\\d+)");
- private URLHandler() {
+ private InternalUrlsHandler() {
}
/**
diff --git a/app/src/main/java/org/schabi/newpipe/util/KoreUtil.java b/app/src/main/java/org/schabi/newpipe/util/external_communication/KoreUtils.java
similarity index 88%
rename from app/src/main/java/org/schabi/newpipe/util/KoreUtil.java
rename to app/src/main/java/org/schabi/newpipe/util/external_communication/KoreUtils.java
index de6f3fa9a8d..5844a0c6c41 100644
--- a/app/src/main/java/org/schabi/newpipe/util/KoreUtil.java
+++ b/app/src/main/java/org/schabi/newpipe/util/external_communication/KoreUtils.java
@@ -1,4 +1,4 @@
-package org.schabi.newpipe.util;
+package org.schabi.newpipe.util.external_communication;
import android.content.Context;
@@ -7,9 +7,10 @@
import org.schabi.newpipe.R;
import org.schabi.newpipe.extractor.ServiceList;
+import org.schabi.newpipe.util.NavigationHelper;
-public final class KoreUtil {
- private KoreUtil() { }
+public final class KoreUtils {
+ private KoreUtils() { }
public static boolean isServiceSupportedByKore(final int serviceId) {
return (serviceId == ServiceList.YouTube.getServiceId()
diff --git a/app/src/main/java/org/schabi/newpipe/util/ShareUtils.java b/app/src/main/java/org/schabi/newpipe/util/external_communication/ShareUtils.java
similarity index 87%
rename from app/src/main/java/org/schabi/newpipe/util/ShareUtils.java
rename to app/src/main/java/org/schabi/newpipe/util/external_communication/ShareUtils.java
index 682830741ae..af6e49d72d3 100644
--- a/app/src/main/java/org/schabi/newpipe/util/ShareUtils.java
+++ b/app/src/main/java/org/schabi/newpipe/util/external_communication/ShareUtils.java
@@ -1,4 +1,4 @@
-package org.schabi.newpipe.util;
+package org.schabi.newpipe.util.external_communication;
import android.content.ActivityNotFoundException;
import android.content.ClipData;
@@ -239,49 +239,35 @@ private static String getDefaultAppPackageName(final Context context, final Inte
}
/**
- * Open the android share menu to share the current url.
- *
- * @param context the context to use
- * @param subject the url subject, typically the title
- * @param url the url to share
- * @param imagePreviewUrl the image of the subject
- */
- public static void shareText(final Context context,
- final String subject,
- final String url,
- final String imagePreviewUrl) {
- shareText(context, subject, url, imagePreviewUrl, true);
- }
-
- /**
- * Open the android share sheet to share the current url.
+ * Open the android share sheet to share a content.
*
* For Android 10+ users, a content preview is shown, which includes the title of the shared
* content.
* Support sharing the image of the content needs to done, if possible.
*
* @param context the context to use
- * @param subject the url subject, typically the title
- * @param url the url to share
+ * @param title the title of the content
+ * @param content the content to share
* @param imagePreviewUrl the image of the subject
- * @param showPreviewText show the subject as an extra title of the Android share sheet if true
*/
public static void shareText(final Context context,
- final String subject,
- final String url,
- final String imagePreviewUrl,
- final boolean showPreviewText) {
+ final String title,
+ final String content,
+ final String imagePreviewUrl) {
final Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("text/plain");
- if (!imagePreviewUrl.isEmpty() && !subject.isEmpty() && showPreviewText) {
- shareIntent.putExtra(Intent.EXTRA_TITLE, subject);
- /* TODO: add the image of the content to Android share sheet with setClipData after
- generating a content URI of this image, then use ClipData.newUri(the content
- resolver, null, the content URI) and set the ClipData to the share intent with
- shareIntent.setClipData(generated ClipData).*/
- //shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ if (!title.isEmpty()) {
+ shareIntent.putExtra(Intent.EXTRA_TITLE, title);
}
- shareIntent.putExtra(Intent.EXTRA_TEXT, url);
+
+ /* TODO: add the image of the content to Android share sheet with setClipData after
+ generating a content URI of this image, then use ClipData.newUri(the content resolver,
+ null, the content URI) and set the ClipData to the share intent with
+ shareIntent.setClipData(generated ClipData).
+ if (!imagePreviewUrl.isEmpty()) {
+ //shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
+ }*/
+ shareIntent.putExtra(Intent.EXTRA_TEXT, content);
openAppChooser(context, shareIntent, false);
}
diff --git a/app/src/main/java/org/schabi/newpipe/util/TextLinkifier.java b/app/src/main/java/org/schabi/newpipe/util/external_communication/TextLinkifier.java
similarity index 96%
rename from app/src/main/java/org/schabi/newpipe/util/TextLinkifier.java
rename to app/src/main/java/org/schabi/newpipe/util/external_communication/TextLinkifier.java
index 56c69f47cd0..2b205a5864e 100644
--- a/app/src/main/java/org/schabi/newpipe/util/TextLinkifier.java
+++ b/app/src/main/java/org/schabi/newpipe/util/external_communication/TextLinkifier.java
@@ -1,4 +1,4 @@
-package org.schabi.newpipe.util;
+package org.schabi.newpipe.util.external_communication;
import android.content.Context;
import android.text.SpannableStringBuilder;
@@ -25,12 +25,13 @@
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.schedulers.Schedulers;
-import static org.schabi.newpipe.util.URLHandler.playOnPopup;
+import static org.schabi.newpipe.util.external_communication.InternalUrlsHandler.playOnPopup;
public final class TextLinkifier {
public static final String TAG = TextLinkifier.class.getSimpleName();
private static final Pattern TIMESTAMPS_PATTERN_IN_PLAIN_TEXT =
- Pattern.compile("(?:([0-5]?[0-9]):)?([0-5]?[0-9]):([0-5][0-9])");
+ Pattern.compile("(?:^|(?![:])\\W)(?:([0-5]?[0-9]):)?([0-5]?[0-9]):"
+ + "([0-5][0-9])(?=$|(?![:])\\W)");
private TextLinkifier() {
}
@@ -139,8 +140,8 @@ private static void addClickListenersOnTimestamps(final Context context,
final Matcher timestampMatches = TIMESTAMPS_PATTERN_IN_PLAIN_TEXT.matcher(descriptionText);
while (timestampMatches.find()) {
- final int timestampStart = timestampMatches.start(0);
- final int timestampEnd = timestampMatches.end(0);
+ final int timestampStart = timestampMatches.start(2);
+ final int timestampEnd = timestampMatches.end(3);
final String parsedTimestamp = descriptionText.substring(timestampStart, timestampEnd);
final String[] timestampParts = parsedTimestamp.split(":");
final int time;
@@ -203,7 +204,7 @@ private static Disposable changeIntentsOfDescriptionLinks(final Context context,
for (final URLSpan span : urls) {
final ClickableSpan clickableSpan = new ClickableSpan() {
public void onClick(@NonNull final View view) {
- if (!URLHandler.handleUrl(context, span.getURL(), 0)) {
+ if (!InternalUrlsHandler.handleUrl(context, span.getURL(), 0)) {
ShareUtils.openUrlInBrowser(context, span.getURL(), false);
}
}
diff --git a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java
index 44c20b5415c..d77afde9707 100644
--- a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java
+++ b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java
@@ -2,7 +2,6 @@
import android.annotation.SuppressLint;
import android.app.NotificationManager;
-import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.Intent;
import android.graphics.Color;
@@ -45,7 +44,7 @@
import org.schabi.newpipe.error.ErrorInfo;
import org.schabi.newpipe.error.UserAction;
import org.schabi.newpipe.util.NavigationHelper;
-import org.schabi.newpipe.util.ShareUtils;
+import org.schabi.newpipe.util.external_communication.ShareUtils;
import java.io.File;
import java.net.URI;
From e5df2f65b8e26c3671fc61297437c4c2c66858b5 Mon Sep 17 00:00:00 2001
From: TiA4f8R <74829229+TiA4f8R@users.noreply.github.com>
Date: Fri, 26 Mar 2021 13:28:11 +0100
Subject: [PATCH 09/20] Move some classes to a new subpackage and adress
requested changes Rename URLHandler and KoreUtil classes to
InternalUrlsHandler and KoreUtils. Move InternalUrlsHandler, KoreUtils,
TextLinkfier, ShareUtils classes to external_communication subpackage. Remove
unused param showPreviewText in shareText method of ShareUtils class. Add
initial work to be able to display an image preview of the content shared
(not for downloads). Use a better regular expression to parse timestamps in
plain text descriptions.
---
app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt b/app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt
index 2f015a049ec..d40bb209eaf 100644
--- a/app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt
+++ b/app/src/main/java/org/schabi/newpipe/about/AboutActivity.kt
@@ -16,8 +16,8 @@ import org.schabi.newpipe.BuildConfig
import org.schabi.newpipe.R
import org.schabi.newpipe.databinding.ActivityAboutBinding
import org.schabi.newpipe.databinding.FragmentAboutBinding
+import org.schabi.newpipe.util.external_communication.ShareUtils
import org.schabi.newpipe.util.Localization
-import org.schabi.newpipe.util.ShareUtils
import org.schabi.newpipe.util.ThemeHelper
class AboutActivity : AppCompatActivity() {
From 267686fd3772c69cf06f417cbd81165ae5741040 Mon Sep 17 00:00:00 2001
From: TiA4f8R <74829229+TiA4f8R@users.noreply.github.com>
Date: Sat, 27 Mar 2021 18:45:05 +0100
Subject: [PATCH 10/20] Initial work: add support for opening hashtags in plain
text descriptions This commit adds supports for opening hashtags in plain
text descriptions, using the same logic as timestamps. Every hashtag opens a
search on the current service with the text in the hashtag. Also use a better
regular expression for parsing timestamps.
---
.../external_communication/TextLinkifier.java | 54 ++++++++++++++++---
1 file changed, 46 insertions(+), 8 deletions(-)
diff --git a/app/src/main/java/org/schabi/newpipe/util/external_communication/TextLinkifier.java b/app/src/main/java/org/schabi/newpipe/util/external_communication/TextLinkifier.java
index 2b205a5864e..1859d42d855 100644
--- a/app/src/main/java/org/schabi/newpipe/util/external_communication/TextLinkifier.java
+++ b/app/src/main/java/org/schabi/newpipe/util/external_communication/TextLinkifier.java
@@ -14,6 +14,7 @@
import androidx.core.text.HtmlCompat;
import org.schabi.newpipe.extractor.StreamingService;
+import org.schabi.newpipe.util.NavigationHelper;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -29,9 +30,9 @@
public final class TextLinkifier {
public static final String TAG = TextLinkifier.class.getSimpleName();
- private static final Pattern TIMESTAMPS_PATTERN_IN_PLAIN_TEXT =
- Pattern.compile("(?:^|(?![:])\\W)(?:([0-5]?[0-9]):)?([0-5]?[0-9]):"
- + "([0-5][0-9])(?=$|(?![:])\\W)");
+ private static final Pattern HASHTAGS_PATTERN = Pattern.compile("(#[A-Za-z0-9_]+)");
+ private static final Pattern TIMESTAMPS_PATTERN = Pattern.compile(
+ "(?:^|(?!:)\\W)(?:([0-5]?[0-9]):)?([0-5]?[0-9]):([0-5][0-9])(?=$|(?!:)\\W)");
private TextLinkifier() {
}
@@ -118,6 +119,41 @@ public static Disposable createLinksFromMarkdownText(final Context context,
streamingService, contentUrl);
}
+ /**
+ * Add click listeners which opens a search on hashtags in a plain text.
+ *
+ * This method finds all timestamps in the {@link SpannableStringBuilder} of the description
+ * using a regular expression, adds for each a {@link ClickableSpan} which opens
+ * {@link NavigationHelper#openSearch(Context, int, String)} and makes a search on the hashtag,
+ * in the service of the content.
+ *
+ * @param context the context to use
+ * @param spannableDescription the SpannableStringBuilder with the text of the
+ * content description
+ * @param streamingService the {@link StreamingService} of the content
+ */
+ private static void addClickListenersOnHashtags(final Context context,
+ final SpannableStringBuilder
+ spannableDescription,
+ final StreamingService streamingService) {
+ final String descriptionText = spannableDescription.toString();
+ final Matcher hashtagsMatches = HASHTAGS_PATTERN.matcher(descriptionText);
+
+ while (hashtagsMatches.find()) {
+ final int hashtagStart = hashtagsMatches.start(1);
+ final int hashtagEnd = hashtagsMatches.end(1);
+ final String parsedHashtag = descriptionText.substring(hashtagStart, hashtagEnd);
+
+ spannableDescription.setSpan(new ClickableSpan() {
+ @Override
+ public void onClick(@NonNull final View view) {
+ NavigationHelper.openSearch(context, streamingService.getServiceId(),
+ parsedHashtag);
+ }
+ }, hashtagStart, hashtagEnd, 0);
+ }
+ }
+
/**
* Add click listeners which opens the popup player on timestamps in a plain text.
*
@@ -137,11 +173,11 @@ private static void addClickListenersOnTimestamps(final Context context,
final String contentUrl,
final StreamingService streamingService) {
final String descriptionText = spannableDescription.toString();
- final Matcher timestampMatches = TIMESTAMPS_PATTERN_IN_PLAIN_TEXT.matcher(descriptionText);
+ final Matcher timestampsMatches = TIMESTAMPS_PATTERN.matcher(descriptionText);
- while (timestampMatches.find()) {
- final int timestampStart = timestampMatches.start(2);
- final int timestampEnd = timestampMatches.end(3);
+ while (timestampsMatches.find()) {
+ final int timestampStart = timestampsMatches.start(2);
+ final int timestampEnd = timestampsMatches.end(3);
final String parsedTimestamp = descriptionText.substring(timestampStart, timestampEnd);
final String[] timestampParts = parsedTimestamp.split(":");
final int time;
@@ -178,7 +214,8 @@ public void onClick(@NonNull final View view) {
* This method will also add click listeners on timestamps in this description, which will play
* the content in the popup player at the time indicated in the timestamp, by using
* {@link TextLinkifier#addClickListenersOnTimestamps(Context, SpannableStringBuilder, String,
- * StreamingService)} method.
+ * StreamingService)} method and click listeners on hashtags, which will open a search
+ * on the current service with the hashtag.
*
* This method is required in order to intercept links and e.g. show a confirmation dialog
* before opening a web link.
@@ -220,6 +257,7 @@ public void onClick(@NonNull final View view) {
if (contentUrl != null || streamingService != null) {
addClickListenersOnTimestamps(context, textBlockLinked, contentUrl,
streamingService);
+ addClickListenersOnHashtags(context, textBlockLinked, streamingService);
}
return textBlockLinked;
From 2702700d10222c272e05832f2dc8da7b6e109c73 Mon Sep 17 00:00:00 2001
From: TiA4f8R <74829229+TiA4f8R@users.noreply.github.com>
Date: Fri, 2 Apr 2021 18:48:58 +0200
Subject: [PATCH 11/20] Don't use a chooser for other intents than opening a
content in a browser or sharing a content to other apps Use an ACTION_CHOOSER
intent has a negative impact for user experience, because user cannot set as
default an activity for an intent
---
.../schabi/newpipe/CheckForNewAppVersion.java | 6 +---
.../schabi/newpipe/about/LicenseFragment.kt | 1 -
.../external_communication/ShareUtils.java | 35 ++++++-------------
3 files changed, 12 insertions(+), 30 deletions(-)
diff --git a/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java b/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java
index 7bddb1e95a7..37ca0e400de 100644
--- a/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java
+++ b/app/src/main/java/org/schabi/newpipe/CheckForNewAppVersion.java
@@ -129,11 +129,7 @@ private static void compareAppVersionAndShowNotification(@NonNull final Applicat
if (BuildConfig.VERSION_CODE < versionCode) {
// A pending intent to open the apk location url in the browser.
- final Intent viewIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(apkLocationUrl));
- viewIntent.putExtra(Intent.EXTRA_TITLE, R.string.open_with);
-
- final Intent intent = new Intent(Intent.ACTION_CHOOSER);
- intent.putExtra(Intent.EXTRA_INTENT, viewIntent);
+ final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(apkLocationUrl));
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
final PendingIntent pendingIntent
= PendingIntent.getActivity(application, 0, intent, 0);
diff --git a/app/src/main/java/org/schabi/newpipe/about/LicenseFragment.kt b/app/src/main/java/org/schabi/newpipe/about/LicenseFragment.kt
index 24995596882..ba0c04eb099 100644
--- a/app/src/main/java/org/schabi/newpipe/about/LicenseFragment.kt
+++ b/app/src/main/java/org/schabi/newpipe/about/LicenseFragment.kt
@@ -19,7 +19,6 @@ import java.util.Objects
*/
class LicenseFragment : Fragment() {
private lateinit var softwareComponents: Array
- * If no app is set as default, fallbacks to
- * {@link #openAppChooser(Context, Intent, boolean)}.
- *
+ * If no app can open the intent, a toast with the message {@code No app on your device can
+ * open this} is shown.
*
* @param context the context to use
* @param intent the intent to open
@@ -132,27 +131,15 @@ public static boolean openIntentInApp(final Context context,
final boolean showToast) {
final String defaultPackageName = getDefaultAppPackageName(context, intent);
- if (defaultPackageName.equals("android")) {
- // No app set as default (doesn't work on some devices)
- openAppChooser(context, intent, true);
- } else {
- if (defaultPackageName.isEmpty()) {
- // No app installed to open the intent
- if (showToast) {
- Toast.makeText(context, R.string.no_app_to_open_intent, Toast.LENGTH_LONG)
- .show();
- }
- return false;
- } else {
- try {
- intent.setPackage(defaultPackageName);
- context.startActivity(intent);
- } catch (final ActivityNotFoundException e) {
- // Not an app to open the intent but an app chooser because of OEMs changes
- intent.setPackage(null);
- openAppChooser(context, intent, true);
- }
+ if (defaultPackageName.isEmpty()) {
+ // No app installed to open the intent
+ if (showToast) {
+ Toast.makeText(context, R.string.no_app_to_open_intent, Toast.LENGTH_LONG)
+ .show();
}
+ return false;
+ } else {
+ context.startActivity(intent);
}
return true;
@@ -256,6 +243,7 @@ public static void shareText(final Context context,
final String imagePreviewUrl) {
final Intent shareIntent = new Intent(Intent.ACTION_SEND);
shareIntent.setType("text/plain");
+ shareIntent.putExtra(Intent.EXTRA_TEXT, content);
if (!title.isEmpty()) {
shareIntent.putExtra(Intent.EXTRA_TITLE, title);
}
@@ -267,7 +255,6 @@ public static void shareText(final Context context,
if (!imagePreviewUrl.isEmpty()) {
//shareIntent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
}*/
- shareIntent.putExtra(Intent.EXTRA_TEXT, content);
openAppChooser(context, shareIntent, false);
}
From a79badd783c72f06afca7eaae6d5b0297beab4ed Mon Sep 17 00:00:00 2001
From: TiA4f8R <74829229+TiA4f8R@users.noreply.github.com>
Date: Sat, 3 Apr 2021 14:46:30 +0200
Subject: [PATCH 12/20] Adress requested changes and try some cleanup in
handleUrl method of InternalUrlsHandler class
---
.../util/CommentTextOnTouchListener.java | 11 ++---
.../InternalUrlsHandler.java | 9 ++--
.../external_communication/TextLinkifier.java | 46 ++++++++++---------
.../giga/ui/adapter/MissionAdapter.java | 2 +
4 files changed, 34 insertions(+), 34 deletions(-)
diff --git a/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java b/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java
index 8fe59b03655..d0edc4bc4c1 100644
--- a/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java
+++ b/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java
@@ -48,14 +48,11 @@ public boolean onTouch(final View v, final MotionEvent event) {
if (link.length != 0) {
if (action == MotionEvent.ACTION_UP) {
- boolean handled = false;
if (link[0] instanceof URLSpan) {
- handled = InternalUrlsHandler.handleUrl(v.getContext(),
- ((URLSpan) link[0]).getURL(), 1);
- }
- if (!handled) {
- ShareUtils.openUrlInBrowser(v.getContext(),
- ((URLSpan) link[0]).getURL(), false);
+ final String url = ((URLSpan) link[0]).getURL();
+ if (!InternalUrlsHandler.handleUrl(v.getContext(), url, 1)) {
+ ShareUtils.openUrlInBrowser(v.getContext(), url, false);
+ }
}
} else if (action == MotionEvent.ACTION_DOWN) {
Selection.setSelection(buffer,
diff --git a/app/src/main/java/org/schabi/newpipe/util/external_communication/InternalUrlsHandler.java b/app/src/main/java/org/schabi/newpipe/util/external_communication/InternalUrlsHandler.java
index 7b81538a3cd..a234f620316 100644
--- a/app/src/main/java/org/schabi/newpipe/util/external_communication/InternalUrlsHandler.java
+++ b/app/src/main/java/org/schabi/newpipe/util/external_communication/InternalUrlsHandler.java
@@ -49,22 +49,20 @@ public static boolean handleUrl(final Context context,
final int timestampType) {
String matchedUrl = "";
int seconds = -1;
- final Pattern timestampPattern;
+ final Matcher matcher;
if (timestampType == 0) {
- timestampPattern = AMPERSAND_TIMESTAMP_PATTERN;
+ matcher = AMPERSAND_TIMESTAMP_PATTERN.matcher(url);
} else if (timestampType == 1) {
- timestampPattern = HASHTAG_TIMESTAMP_PATTERN;
+ matcher = HASHTAG_TIMESTAMP_PATTERN.matcher(url);
} else {
return false;
}
- final Matcher matcher = timestampPattern.matcher(url);
if (matcher.matches()) {
matchedUrl = matcher.group(1);
seconds = Integer.parseInt(matcher.group(2));
}
-
if (matchedUrl == null || matchedUrl.isEmpty()) {
return false;
}
@@ -78,7 +76,6 @@ public static boolean handleUrl(final Context context,
} catch (final ExtractionException e) {
return false;
}
-
if (linkType == StreamingService.LinkType.NONE) {
return false;
}
diff --git a/app/src/main/java/org/schabi/newpipe/util/external_communication/TextLinkifier.java b/app/src/main/java/org/schabi/newpipe/util/external_communication/TextLinkifier.java
index 1859d42d855..d63b8c5bbdd 100644
--- a/app/src/main/java/org/schabi/newpipe/util/external_communication/TextLinkifier.java
+++ b/app/src/main/java/org/schabi/newpipe/util/external_communication/TextLinkifier.java
@@ -144,13 +144,18 @@ private static void addClickListenersOnHashtags(final Context context,
final int hashtagEnd = hashtagsMatches.end(1);
final String parsedHashtag = descriptionText.substring(hashtagStart, hashtagEnd);
- spannableDescription.setSpan(new ClickableSpan() {
- @Override
- public void onClick(@NonNull final View view) {
- NavigationHelper.openSearch(context, streamingService.getServiceId(),
- parsedHashtag);
- }
- }, hashtagStart, hashtagEnd, 0);
+ // don't add a ClickableSpan if there is already one, which should be a part of an URL,
+ // already parsed before
+ if (spannableDescription.getSpans(hashtagStart, hashtagEnd,
+ ClickableSpan.class).length == 0) {
+ spannableDescription.setSpan(new ClickableSpan() {
+ @Override
+ public void onClick(@NonNull final View view) {
+ NavigationHelper.openSearch(context, streamingService.getServiceId(),
+ parsedHashtag);
+ }
+ }, hashtagStart, hashtagEnd, 0);
+ }
}
}
@@ -181,26 +186,24 @@ private static void addClickListenersOnTimestamps(final Context context,
final String parsedTimestamp = descriptionText.substring(timestampStart, timestampEnd);
final String[] timestampParts = parsedTimestamp.split(":");
final int time;
+
if (timestampParts.length == 3) { // timestamp format: XX:XX:XX
time = Integer.parseInt(timestampParts[0]) * 3600 // hours
+ Integer.parseInt(timestampParts[1]) * 60 // minutes
+ Integer.parseInt(timestampParts[2]); // seconds
- spannableDescription.setSpan(new ClickableSpan() {
- @Override
- public void onClick(@NonNull final View view) {
- playOnPopup(context, contentUrl, streamingService, time);
- }
- }, timestampStart, timestampEnd, 0);
} else if (timestampParts.length == 2) { // timestamp format: XX:XX
time = Integer.parseInt(timestampParts[0]) * 60 // minutes
+ Integer.parseInt(timestampParts[1]); // seconds
- spannableDescription.setSpan(new ClickableSpan() {
- @Override
- public void onClick(@NonNull final View view) {
- playOnPopup(context, contentUrl, streamingService, time);
- }
- }, timestampStart, timestampEnd, 0);
+ } else {
+ continue;
}
+
+ spannableDescription.setSpan(new ClickableSpan() {
+ @Override
+ public void onClick(@NonNull final View view) {
+ playOnPopup(context, contentUrl, streamingService, time);
+ }
+ }, timestampStart, timestampEnd, 0);
}
}
@@ -239,10 +242,11 @@ private static Disposable changeIntentsOfDescriptionLinks(final Context context,
final URLSpan[] urls = textBlockLinked.getSpans(0, chars.length(), URLSpan.class);
for (final URLSpan span : urls) {
+ final String url = span.getURL();
final ClickableSpan clickableSpan = new ClickableSpan() {
public void onClick(@NonNull final View view) {
- if (!InternalUrlsHandler.handleUrl(context, span.getURL(), 0)) {
- ShareUtils.openUrlInBrowser(context, span.getURL(), false);
+ if (!InternalUrlsHandler.handleUrl(context, url, 0)) {
+ ShareUtils.openUrlInBrowser(context, url, false);
}
}
};
diff --git a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java
index d77afde9707..e06485fdf9f 100644
--- a/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java
+++ b/app/src/main/java/us/shandian/giga/ui/adapter/MissionAdapter.java
@@ -375,6 +375,8 @@ private void shareFile(Mission mission) {
final Intent intent = new Intent(Intent.ACTION_CHOOSER);
intent.putExtra(Intent.EXTRA_INTENT, shareIntent);
+ // unneeded to set a title to the chooser on Android P and higher because the system
+ // ignores this title on these versions
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.O_MR1) {
intent.putExtra(Intent.EXTRA_TITLE, mContext.getString(R.string.share_dialog_title));
}
From f13f4cc5d21397e92e59d2d609788d34ab5d1c8d Mon Sep 17 00:00:00 2001
From: TiA4f8R <74829229+TiA4f8R@users.noreply.github.com>
Date: Sat, 3 Apr 2021 18:57:16 +0200
Subject: [PATCH 13/20] Split handleUrl method into two methods
Split handleURL method, now private, into two methods:
handleUrlCommentsTimestamp and handleUrlDescriptionTimestamp. Code is
now more proper.
---
.../util/CommentTextOnTouchListener.java | 3 +-
.../InternalUrlsHandler.java | 78 ++++++++++++-------
.../external_communication/TextLinkifier.java | 2 +-
3 files changed, 53 insertions(+), 30 deletions(-)
diff --git a/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java b/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java
index d0edc4bc4c1..dfe5e8ad0eb 100644
--- a/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java
+++ b/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java
@@ -50,7 +50,8 @@ public boolean onTouch(final View v, final MotionEvent event) {
if (action == MotionEvent.ACTION_UP) {
if (link[0] instanceof URLSpan) {
final String url = ((URLSpan) link[0]).getURL();
- if (!InternalUrlsHandler.handleUrl(v.getContext(), url, 1)) {
+ if (!InternalUrlsHandler.handleUrlCommentsTimestamp(v.getContext(),
+ url)) {
ShareUtils.openUrlInBrowser(v.getContext(), url, false);
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/util/external_communication/InternalUrlsHandler.java b/app/src/main/java/org/schabi/newpipe/util/external_communication/InternalUrlsHandler.java
index a234f620316..086ba001557 100644
--- a/app/src/main/java/org/schabi/newpipe/util/external_communication/InternalUrlsHandler.java
+++ b/app/src/main/java/org/schabi/newpipe/util/external_communication/InternalUrlsHandler.java
@@ -20,6 +20,8 @@
import io.reactivex.rxjava3.core.Single;
import io.reactivex.rxjava3.schedulers.Schedulers;
+import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
+
public final class InternalUrlsHandler {
private static final Pattern AMPERSAND_TIMESTAMP_PATTERN = Pattern.compile("(.*)&t=(\\d+)");
private static final Pattern HASHTAG_TIMESTAMP_PATTERN =
@@ -28,48 +30,68 @@ public final class InternalUrlsHandler {
private InternalUrlsHandler() {
}
+ /**
+ * Handle a YouTube timestamp comment URL in NewPipe.
+ *
+ * This method will check if the provided url is a YouTube comment description URL ({@code
+ * https://www.youtube.com/watch?v=}video_id{@code #timestamp=}time_in_seconds). If yes, the
+ * popup player will be opened when the user will click on the timestamp in the comment,
+ * at the time and for the video indicated in the timestamp.
+ *
+ * @param context the context to use
+ * @param url the URL to check if it can be handled
+ * @return true if the URL can be handled by NewPipe, false if it cannot
+ */
+ public static boolean handleUrlCommentsTimestamp(final Context context, final String url) {
+ return handleUrl(context, url, HASHTAG_TIMESTAMP_PATTERN);
+ }
+
+ /**
+ * Handle a YouTube timestamp description URL in NewPipe.
+ *
+ * This method will check if the provided url is a YouTube timestamp description URL ({@code
+ * https://www.youtube.com/watch?v=}video_id{@code &t=}time_in_seconds). If yes, the popup
+ * player will be opened when the user will click on the timestamp in the video description,
+ * at the time and for the video indicated in the timestamp.
+ *
+ * @param context the context to use
+ * @param url the URL to check if it can be handled
+ * @return true if the URL can be handled by NewPipe, false if it cannot
+ */
+ public static boolean handleUrlDescriptionTimestamp(final Context context, final String url) {
+ return handleUrl(context, url, AMPERSAND_TIMESTAMP_PATTERN);
+ }
+
/**
* Handle an URL in NewPipe.
*
* This method will check if the provided url can be handled in NewPipe or not. If this is a
- * service URL with a timestamp, the popup player will be opened.
- *
- * The timestamp param accepts two integers, corresponding to two timestamps types:
- * 0 for {@code &t=} (used for timestamps in descriptions),
- * 1 for {@code #timestamp=} (used for timestamps in comments).
- * Any other value of this integer will return false.
+ * service URL with a timestamp, the popup player will be opened and true will be returned;
+ * else, false will be returned.
*
- * @param context the context to be used
+ * @param context the context to use
* @param url the URL to check if it can be handled
- * @param timestampType the type of timestamp
+ * @param pattern the pattern
* @return true if the URL can be handled by NewPipe, false if it cannot
*/
- public static boolean handleUrl(final Context context,
- final String url,
- final int timestampType) {
- String matchedUrl = "";
- int seconds = -1;
- final Matcher matcher;
-
- if (timestampType == 0) {
- matcher = AMPERSAND_TIMESTAMP_PATTERN.matcher(url);
- } else if (timestampType == 1) {
- matcher = HASHTAG_TIMESTAMP_PATTERN.matcher(url);
- } else {
- return false;
- }
-
+ private static boolean handleUrl(final Context context,
+ final String url,
+ final Pattern pattern) {
+ final String matchedUrl;
+ final StreamingService service;
+ final StreamingService.LinkType linkType;
+ final int seconds;
+ final Matcher matcher = pattern.matcher(url);
if (matcher.matches()) {
matchedUrl = matcher.group(1);
seconds = Integer.parseInt(matcher.group(2));
- }
- if (matchedUrl == null || matchedUrl.isEmpty()) {
+ } else {
return false;
}
- final StreamingService service;
- final StreamingService.LinkType linkType;
-
+ if (isNullOrEmpty(matchedUrl)) {
+ return false;
+ }
try {
service = NewPipe.getServiceByUrl(matchedUrl);
linkType = service.getLinkTypeByUrl(matchedUrl);
diff --git a/app/src/main/java/org/schabi/newpipe/util/external_communication/TextLinkifier.java b/app/src/main/java/org/schabi/newpipe/util/external_communication/TextLinkifier.java
index d63b8c5bbdd..50a453e358c 100644
--- a/app/src/main/java/org/schabi/newpipe/util/external_communication/TextLinkifier.java
+++ b/app/src/main/java/org/schabi/newpipe/util/external_communication/TextLinkifier.java
@@ -245,7 +245,7 @@ private static Disposable changeIntentsOfDescriptionLinks(final Context context,
final String url = span.getURL();
final ClickableSpan clickableSpan = new ClickableSpan() {
public void onClick(@NonNull final View view) {
- if (!InternalUrlsHandler.handleUrl(context, url, 0)) {
+ if (!InternalUrlsHandler.handleUrlDescriptionTimestamp(context, url)) {
ShareUtils.openUrlInBrowser(context, url, false);
}
}
From da4d379b227534245aa5cd0a4fdc95ca781edf51 Mon Sep 17 00:00:00 2001
From: TiA4f8R <74829229+TiA4f8R@users.noreply.github.com>
Date: Sun, 4 Apr 2021 16:37:09 +0200
Subject: [PATCH 14/20] Initial work: use disposables for timestamps parsing in
YouTube video descriptions and YouTube comments
---
.../util/CommentTextOnTouchListener.java | 6 ++-
.../InternalUrlsHandler.java | 51 +++++++++++--------
.../external_communication/TextLinkifier.java | 7 ++-
3 files changed, 40 insertions(+), 24 deletions(-)
diff --git a/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java b/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java
index dfe5e8ad0eb..7c87e664ba8 100644
--- a/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java
+++ b/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java
@@ -13,6 +13,8 @@
import org.schabi.newpipe.util.external_communication.ShareUtils;
import org.schabi.newpipe.util.external_communication.InternalUrlsHandler;
+import io.reactivex.rxjava3.disposables.CompositeDisposable;
+
public class CommentTextOnTouchListener implements View.OnTouchListener {
public static final CommentTextOnTouchListener INSTANCE = new CommentTextOnTouchListener();
@@ -50,8 +52,8 @@ public boolean onTouch(final View v, final MotionEvent event) {
if (action == MotionEvent.ACTION_UP) {
if (link[0] instanceof URLSpan) {
final String url = ((URLSpan) link[0]).getURL();
- if (!InternalUrlsHandler.handleUrlCommentsTimestamp(v.getContext(),
- url)) {
+ if (!InternalUrlsHandler.handleUrlCommentsTimestamp(
+ new CompositeDisposable(), v.getContext(), url)) {
ShareUtils.openUrlInBrowser(v.getContext(), url, false);
}
}
diff --git a/app/src/main/java/org/schabi/newpipe/util/external_communication/InternalUrlsHandler.java b/app/src/main/java/org/schabi/newpipe/util/external_communication/InternalUrlsHandler.java
index 086ba001557..58a06089dbd 100644
--- a/app/src/main/java/org/schabi/newpipe/util/external_communication/InternalUrlsHandler.java
+++ b/app/src/main/java/org/schabi/newpipe/util/external_communication/InternalUrlsHandler.java
@@ -18,6 +18,7 @@
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.core.Single;
+import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.schedulers.Schedulers;
import static org.schabi.newpipe.extractor.utils.Utils.isNullOrEmpty;
@@ -38,12 +39,15 @@ private InternalUrlsHandler() {
* popup player will be opened when the user will click on the timestamp in the comment,
* at the time and for the video indicated in the timestamp.
*
- * @param context the context to use
- * @param url the URL to check if it can be handled
+ * @param disposables a field of the Activity/Fragment class that calls this method
+ * @param context the context to use
+ * @param url the URL to check if it can be handled
* @return true if the URL can be handled by NewPipe, false if it cannot
*/
- public static boolean handleUrlCommentsTimestamp(final Context context, final String url) {
- return handleUrl(context, url, HASHTAG_TIMESTAMP_PATTERN);
+ public static boolean handleUrlCommentsTimestamp(final CompositeDisposable disposables,
+ final Context context,
+ final String url) {
+ return handleUrl(disposables, context, url, HASHTAG_TIMESTAMP_PATTERN);
}
/**
@@ -54,12 +58,15 @@ public static boolean handleUrlCommentsTimestamp(final Context context, final St
* player will be opened when the user will click on the timestamp in the video description,
* at the time and for the video indicated in the timestamp.
*
- * @param context the context to use
- * @param url the URL to check if it can be handled
+ * @param disposables a field of the Activity/Fragment class that calls this method
+ * @param context the context to use
+ * @param url the URL to check if it can be handled
* @return true if the URL can be handled by NewPipe, false if it cannot
*/
- public static boolean handleUrlDescriptionTimestamp(final Context context, final String url) {
- return handleUrl(context, url, AMPERSAND_TIMESTAMP_PATTERN);
+ public static boolean handleUrlDescriptionTimestamp(final CompositeDisposable disposables,
+ final Context context,
+ final String url) {
+ return handleUrl(disposables, context, url, AMPERSAND_TIMESTAMP_PATTERN);
}
/**
@@ -69,12 +76,14 @@ public static boolean handleUrlDescriptionTimestamp(final Context context, final
* service URL with a timestamp, the popup player will be opened and true will be returned;
* else, false will be returned.
*
- * @param context the context to use
- * @param url the URL to check if it can be handled
- * @param pattern the pattern
+ * @param disposables a field of the Activity/Fragment class that calls this method
+ * @param context the context to use
+ * @param url the URL to check if it can be handled
+ * @param pattern the pattern to use
* @return true if the URL can be handled by NewPipe, false if it cannot
*/
- private static boolean handleUrl(final Context context,
+ private static boolean handleUrl(final CompositeDisposable disposables,
+ final Context context,
final String url,
final Pattern pattern) {
final String matchedUrl;
@@ -102,7 +111,7 @@ private static boolean handleUrl(final Context context,
return false;
}
if (linkType == StreamingService.LinkType.STREAM && seconds != -1) {
- return playOnPopup(context, matchedUrl, service, seconds);
+ return playOnPopup(disposables, context, matchedUrl, service, seconds);
} else {
NavigationHelper.openRouterActivity(context, matchedUrl);
return true;
@@ -112,13 +121,15 @@ private static boolean handleUrl(final Context context,
/**
* Play a content in the floating player.
*
- * @param context the context to be used
- * @param url the URL of the content
- * @param service the service of the content
- * @param seconds the position in seconds at which the floating player will start
+ * @param disposables a field of the Activity/Fragment class that calls this method
+ * @param context the context to be used
+ * @param url the URL of the content
+ * @param service the service of the content
+ * @param seconds the position in seconds at which the floating player will start
* @return true if the playback of the content has successfully started or false if not
*/
- public static boolean playOnPopup(final Context context,
+ public static boolean playOnPopup(final CompositeDisposable disposables,
+ final Context context,
final String url,
final StreamingService service,
final int seconds) {
@@ -133,13 +144,13 @@ public static boolean playOnPopup(final Context context,
final Single
+ * This calls {@link #shareText(Context, String, String, String)} with an empty string for the
+ * imagePreviewUrl parameter.
+ *
+ * @param context the context to use
+ * @param title the title of the content
+ * @param content the content to share
+ */
+ public static void shareText(final Context context, final String title, final String content) {
+ shareText(context, title, content, "");
+ }
+
/**
* Copy the text to clipboard, and indicate to the user whether the operation was completed
* successfully using a Toast.
From 218f25c171a4e361c072348469baac152b83a6da Mon Sep 17 00:00:00 2001
From: TiA4f8R <74829229+TiA4f8R@users.noreply.github.com>
Date: Fri, 21 May 2021 14:52:21 +0200
Subject: [PATCH 16/20] Annotate params and methods with NonNull
---
.../InternalUrlsHandler.java | 22 +++++++++------
.../external_communication/KoreUtils.java | 6 ++--
.../external_communication/ShareUtils.java | 28 +++++++++++--------
.../external_communication/TextLinkifier.java | 12 +++++---
4 files changed, 41 insertions(+), 27 deletions(-)
diff --git a/app/src/main/java/org/schabi/newpipe/util/external_communication/InternalUrlsHandler.java b/app/src/main/java/org/schabi/newpipe/util/external_communication/InternalUrlsHandler.java
index 58a06089dbd..69c846dbc62 100644
--- a/app/src/main/java/org/schabi/newpipe/util/external_communication/InternalUrlsHandler.java
+++ b/app/src/main/java/org/schabi/newpipe/util/external_communication/InternalUrlsHandler.java
@@ -2,6 +2,8 @@
import android.content.Context;
+import androidx.annotation.NonNull;
+
import org.schabi.newpipe.extractor.NewPipe;
import org.schabi.newpipe.extractor.StreamingService;
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
@@ -44,9 +46,10 @@ private InternalUrlsHandler() {
* @param url the URL to check if it can be handled
* @return true if the URL can be handled by NewPipe, false if it cannot
*/
- public static boolean handleUrlCommentsTimestamp(final CompositeDisposable disposables,
+ public static boolean handleUrlCommentsTimestamp(@NonNull final CompositeDisposable
+ disposables,
final Context context,
- final String url) {
+ @NonNull final String url) {
return handleUrl(disposables, context, url, HASHTAG_TIMESTAMP_PATTERN);
}
@@ -63,9 +66,10 @@ public static boolean handleUrlCommentsTimestamp(final CompositeDisposable dispo
* @param url the URL to check if it can be handled
* @return true if the URL can be handled by NewPipe, false if it cannot
*/
- public static boolean handleUrlDescriptionTimestamp(final CompositeDisposable disposables,
+ public static boolean handleUrlDescriptionTimestamp(@NonNull final CompositeDisposable
+ disposables,
final Context context,
- final String url) {
+ @NonNull final String url) {
return handleUrl(disposables, context, url, AMPERSAND_TIMESTAMP_PATTERN);
}
@@ -82,10 +86,10 @@ public static boolean handleUrlDescriptionTimestamp(final CompositeDisposable di
* @param pattern the pattern to use
* @return true if the URL can be handled by NewPipe, false if it cannot
*/
- private static boolean handleUrl(final CompositeDisposable disposables,
+ private static boolean handleUrl(@NonNull final CompositeDisposable disposables,
final Context context,
- final String url,
- final Pattern pattern) {
+ @NonNull final String url,
+ @NonNull final Pattern pattern) {
final String matchedUrl;
final StreamingService service;
final StreamingService.LinkType linkType;
@@ -128,10 +132,10 @@ private static boolean handleUrl(final CompositeDisposable disposables,
* @param seconds the position in seconds at which the floating player will start
* @return true if the playback of the content has successfully started or false if not
*/
- public static boolean playOnPopup(final CompositeDisposable disposables,
+ public static boolean playOnPopup(@NonNull final CompositeDisposable disposables,
final Context context,
final String url,
- final StreamingService service,
+ @NonNull final StreamingService service,
final int seconds) {
final LinkHandlerFactory factory = service.getStreamLHFactory();
final String cleanUrl;
diff --git a/app/src/main/java/org/schabi/newpipe/util/external_communication/KoreUtils.java b/app/src/main/java/org/schabi/newpipe/util/external_communication/KoreUtils.java
index 5844a0c6c41..6801f24ef2d 100644
--- a/app/src/main/java/org/schabi/newpipe/util/external_communication/KoreUtils.java
+++ b/app/src/main/java/org/schabi/newpipe/util/external_communication/KoreUtils.java
@@ -2,6 +2,7 @@
import android.content.Context;
+import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.preference.PreferenceManager;
@@ -17,13 +18,14 @@ public static boolean isServiceSupportedByKore(final int serviceId) {
|| serviceId == ServiceList.SoundCloud.getServiceId());
}
- public static boolean shouldShowPlayWithKodi(final Context context, final int serviceId) {
+ public static boolean shouldShowPlayWithKodi(@NonNull final Context context,
+ final int serviceId) {
return isServiceSupportedByKore(serviceId)
&& PreferenceManager.getDefaultSharedPreferences(context)
.getBoolean(context.getString(R.string.show_play_with_kodi_key), false);
}
- public static void showInstallKoreDialog(final Context context) {
+ public static void showInstallKoreDialog(@NonNull final Context context) {
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setMessage(R.string.kore_not_found)
.setPositiveButton(R.string.install, (dialog, which) ->
diff --git a/app/src/main/java/org/schabi/newpipe/util/external_communication/ShareUtils.java b/app/src/main/java/org/schabi/newpipe/util/external_communication/ShareUtils.java
index b9f96746ff2..e49cd6ea2d4 100644
--- a/app/src/main/java/org/schabi/newpipe/util/external_communication/ShareUtils.java
+++ b/app/src/main/java/org/schabi/newpipe/util/external_communication/ShareUtils.java
@@ -11,6 +11,7 @@
import android.os.Build;
import android.widget.Toast;
+import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import org.schabi.newpipe.R;
@@ -33,7 +34,7 @@ private ShareUtils() {
* @param context the context to use
* @param packageId the package id of the app to be installed
*/
- public static void installApp(final Context context, final String packageId) {
+ public static void installApp(@NonNull final Context context, final String packageId) {
// Try market scheme
final boolean marketSchemeResult = openIntentInApp(context, new Intent(Intent.ACTION_VIEW,
Uri.parse("market://details?id=" + packageId))
@@ -57,7 +58,7 @@ public static void installApp(final Context context, final String packageId) {
* for HTTP protocol or for the created intent
* @return true if the URL can be opened or false if it cannot
*/
- public static boolean openUrlInBrowser(final Context context,
+ public static boolean openUrlInBrowser(@NonNull final Context context,
final String url,
final boolean httpDefaultBrowserTest) {
final String defaultPackageName;
@@ -107,7 +108,7 @@ public static boolean openUrlInBrowser(final Context context,
* @param url the url to browse
* @return true if the URL can be opened or false if it cannot be
**/
- public static boolean openUrlInBrowser(final Context context, final String url) {
+ public static boolean openUrlInBrowser(@NonNull final Context context, final String url) {
return openUrlInBrowser(context, url, true);
}
@@ -126,8 +127,8 @@ public static boolean openUrlInBrowser(final Context context, final String url)
* to open the intent (true) or not (false)
* @return true if the intent can be opened or false if it cannot be
*/
- public static boolean openIntentInApp(final Context context,
- final Intent intent,
+ public static boolean openIntentInApp(@NonNull final Context context,
+ @NonNull final Intent intent,
final boolean showToast) {
final String defaultPackageName = getDefaultAppPackageName(context, intent);
@@ -159,8 +160,8 @@ public static boolean openIntentInApp(final Context context,
* @param intent the intent to open
* @param setTitleChooser set the title "Open with" to the chooser if true, else not
*/
- private static void openAppChooser(final Context context,
- final Intent intent,
+ private static void openAppChooser(@NonNull final Context context,
+ @NonNull final Intent intent,
final boolean setTitleChooser) {
final Intent chooserIntent = new Intent(Intent.ACTION_CHOOSER);
chooserIntent.putExtra(Intent.EXTRA_INTENT, intent);
@@ -214,7 +215,8 @@ private static void openAppChooser(final Context context,
* @return the package name of the default app, an empty string if there's no app installed to
* handle the intent or the app chooser if there's no default
*/
- private static String getDefaultAppPackageName(final Context context, final Intent intent) {
+ private static String getDefaultAppPackageName(@NonNull final Context context,
+ @NonNull final Intent intent) {
final ResolveInfo resolveInfo = context.getPackageManager().resolveActivity(intent,
PackageManager.MATCH_DEFAULT_ONLY);
@@ -237,8 +239,8 @@ private static String getDefaultAppPackageName(final Context context, final Inte
* @param content the content to share
* @param imagePreviewUrl the image of the subject
*/
- public static void shareText(final Context context,
- final String title,
+ public static void shareText(@NonNull final Context context,
+ @NonNull final String title,
final String content,
final String imagePreviewUrl) {
final Intent shareIntent = new Intent(Intent.ACTION_SEND);
@@ -272,7 +274,9 @@ public static void shareText(final Context context,
* @param title the title of the content
* @param content the content to share
*/
- public static void shareText(final Context context, final String title, final String content) {
+ public static void shareText(@NonNull final Context context,
+ @NonNull final String title,
+ final String content) {
shareText(context, title, content, "");
}
@@ -283,7 +287,7 @@ public static void shareText(final Context context, final String title, final St
* @param context the context to use
* @param text the text to copy
*/
- public static void copyToClipboard(final Context context, final String text) {
+ public static void copyToClipboard(@NonNull final Context context, final String text) {
final ClipboardManager clipboardManager =
ContextCompat.getSystemService(context, ClipboardManager.class);
diff --git a/app/src/main/java/org/schabi/newpipe/util/external_communication/TextLinkifier.java b/app/src/main/java/org/schabi/newpipe/util/external_communication/TextLinkifier.java
index 6c5cfb4997f..d9d9875abd9 100644
--- a/app/src/main/java/org/schabi/newpipe/util/external_communication/TextLinkifier.java
+++ b/app/src/main/java/org/schabi/newpipe/util/external_communication/TextLinkifier.java
@@ -55,6 +55,7 @@ private TextLinkifier() {
* will be called
* @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed
*/
+ @NonNull
public static Disposable createLinksFromHtmlBlock(final Context context,
final String htmlBlock,
final TextView textView,
@@ -82,9 +83,10 @@ public static Disposable createLinksFromHtmlBlock(final Context context,
* @param contentUrl the URL of the content
* @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed
*/
+ @NonNull
public static Disposable createLinksFromPlainText(final Context context,
final String plainTextBlock,
- final TextView textView,
+ @NonNull final TextView textView,
final StreamingService streamingService,
final String contentUrl) {
textView.setAutoLinkMask(Linkify.WEB_URLS);
@@ -109,6 +111,7 @@ public static Disposable createLinksFromPlainText(final Context context,
* @param contentUrl the URL of the content
* @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed
*/
+ @NonNull
public static Disposable createLinksFromMarkdownText(final Context context,
final String markdownBlock,
final TextView textView,
@@ -134,7 +137,7 @@ public static Disposable createLinksFromMarkdownText(final Context context,
* @param streamingService the {@link StreamingService} of the content
*/
private static void addClickListenersOnHashtags(final Context context,
- final SpannableStringBuilder
+ @NonNull final SpannableStringBuilder
spannableDescription,
final StreamingService streamingService) {
final String descriptionText = spannableDescription.toString();
@@ -174,7 +177,7 @@ public void onClick(@NonNull final View view) {
* @param streamingService the {@link StreamingService} of the content
*/
private static void addClickListenersOnTimestamps(final Context context,
- final SpannableStringBuilder
+ @NonNull final SpannableStringBuilder
spannableDescription,
final String contentUrl,
final StreamingService streamingService) {
@@ -232,6 +235,7 @@ public void onClick(@NonNull final View view) {
* @param contentUrl the URL of the content
* @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed
*/
+ @NonNull
private static Disposable changeIntentsOfDescriptionLinks(final Context context,
final CharSequence chars,
final TextView textView,
@@ -279,7 +283,7 @@ public void onClick(@NonNull final View view) {
});
}
- private static void setTextViewCharSequence(final TextView textView,
+ private static void setTextViewCharSequence(@NonNull final TextView textView,
final CharSequence charSequence) {
textView.setText(charSequence);
textView.setMovementMethod(LinkMovementMethod.getInstance());
From eef418a757795abae2255fcba236927b5784677a Mon Sep 17 00:00:00 2001
From: Stypox
* This will call
- * {@link TextLinkifier#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView,
- * StreamingService, String)}
+ * {@link TextLinkifier#changeIntentsOfDescriptionLinks(TextView, CharSequence, Info)}
* after having linked the URLs with {@link HtmlCompat#fromHtml(String, int)}.
*
- * @param context the context to use
- * @param htmlBlock the htmlBlock to be linked
- * @param textView the TextView to set the htmlBlock linked
- * @param streamingService the {@link StreamingService} of the content
- * @param contentUrl the URL of the content
- * @param htmlCompatFlag the int flag to be set when {@link HtmlCompat#fromHtml(String, int)}
- * will be called
+ * @param textView the TextView to set the htmlBlock linked
+ * @param htmlBlock the htmlBlock to be linked
+ * @param htmlCompatFlag the int flag to be set when {@link HtmlCompat#fromHtml(String, int)}
+ * will be called
+ * @param relatedInfo if given, handle timestamps to open the stream in the popup player at
+ * the specific time, and hashtags to search for the term in the correct
+ * service
* @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed
*/
@NonNull
- public static Disposable createLinksFromHtmlBlock(final Context context,
+ public static Disposable createLinksFromHtmlBlock(@NonNull final TextView textView,
final String htmlBlock,
- final TextView textView,
- final StreamingService streamingService,
- final String contentUrl,
- final int htmlCompatFlag) {
- return changeIntentsOfDescriptionLinks(context,
- HtmlCompat.fromHtml(htmlBlock, htmlCompatFlag), textView, streamingService,
- contentUrl);
+ final int htmlCompatFlag,
+ @Nullable final Info relatedInfo) {
+ return changeIntentsOfDescriptionLinks(
+ textView, HtmlCompat.fromHtml(htmlBlock, htmlCompatFlag), relatedInfo);
}
/**
* Create web links for contents with a plain text description.
*
* This will call
- * {@link TextLinkifier#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView,
- * StreamingService, String)}
+ * {@link TextLinkifier#changeIntentsOfDescriptionLinks(TextView, CharSequence, Info)}
* after having linked the URLs with {@link TextView#setAutoLinkMask(int)} and
* {@link TextView#setText(CharSequence, TextView.BufferType)}.
*
- * @param context the context to use
- * @param plainTextBlock the block of plain text to be linked
- * @param textView the TextView to set the plain text block linked
- * @param streamingService the {@link StreamingService} of the content
- * @param contentUrl the URL of the content
+ * @param textView the TextView to set the plain text block linked
+ * @param plainTextBlock the block of plain text to be linked
+ * @param relatedInfo if given, handle timestamps to open the stream in the popup player at
+ * the specific time, and hashtags to search for the term in the correct
+ * service
* @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed
*/
@NonNull
- public static Disposable createLinksFromPlainText(final Context context,
+ public static Disposable createLinksFromPlainText(@NonNull final TextView textView,
final String plainTextBlock,
- @NonNull final TextView textView,
- final StreamingService streamingService,
- final String contentUrl) {
+ @Nullable final Info relatedInfo) {
textView.setAutoLinkMask(Linkify.WEB_URLS);
textView.setText(plainTextBlock, TextView.BufferType.SPANNABLE);
- return changeIntentsOfDescriptionLinks(context, textView.getText(), textView,
- streamingService, contentUrl);
+ return changeIntentsOfDescriptionLinks(textView, textView.getText(), relatedInfo);
}
/**
* Create web links for contents with a markdown description.
*
* This will call
- * {@link TextLinkifier#changeIntentsOfDescriptionLinks(Context, CharSequence, TextView,
- * StreamingService, String)}
+ * {@link TextLinkifier#changeIntentsOfDescriptionLinks(TextView, CharSequence, Info)}
* after creating an {@link Markwon} object and using
* {@link Markwon#setMarkdown(TextView, String)}.
*
- * @param context the context to use
- * @param markdownBlock the block of markdown text to be linked
- * @param textView the TextView to set the plain text block linked
- * @param streamingService the {@link StreamingService} of the content
- * @param contentUrl the URL of the content
+ * @param textView the TextView to set the plain text block linked
+ * @param markdownBlock the block of markdown text to be linked
+ * @param relatedInfo if given, handle timestamps to open the stream in the popup player at
+ * the specific time, and hashtags to search for the term in the correct
+ * service
* @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed
*/
@NonNull
- public static Disposable createLinksFromMarkdownText(final Context context,
+ public static Disposable createLinksFromMarkdownText(@NonNull final TextView textView,
final String markdownBlock,
- final TextView textView,
- final StreamingService streamingService,
- final String contentUrl) {
- final Markwon markwon = Markwon.builder(context).usePlugin(LinkifyPlugin.create()).build();
+ @Nullable final Info relatedInfo) {
+ final Markwon markwon = Markwon.builder(textView.getContext())
+ .usePlugin(LinkifyPlugin.create()).build();
markwon.setMarkdown(textView, markdownBlock);
- return changeIntentsOfDescriptionLinks(context, textView.getText(), textView,
- streamingService, contentUrl);
+ return changeIntentsOfDescriptionLinks(textView, textView.getText(), relatedInfo);
}
/**
@@ -134,12 +124,12 @@ public static Disposable createLinksFromMarkdownText(final Context context,
* @param context the context to use
* @param spannableDescription the SpannableStringBuilder with the text of the
* content description
- * @param streamingService the {@link StreamingService} of the content
+ * @param relatedInfo used to search for the term in the correct service
*/
private static void addClickListenersOnHashtags(final Context context,
@NonNull final SpannableStringBuilder
spannableDescription,
- final StreamingService streamingService) {
+ final Info relatedInfo) {
final String descriptionText = spannableDescription.toString();
final Matcher hashtagsMatches = HASHTAGS_PATTERN.matcher(descriptionText);
@@ -155,7 +145,7 @@ private static void addClickListenersOnHashtags(final Context context,
spannableDescription.setSpan(new ClickableSpan() {
@Override
public void onClick(@NonNull final View view) {
- NavigationHelper.openSearch(context, streamingService.getServiceId(),
+ NavigationHelper.openSearch(context, relatedInfo.getServiceId(),
parsedHashtag);
}
}, hashtagStart, hashtagEnd, 0);
@@ -173,14 +163,12 @@ public void onClick(@NonNull final View view) {
* @param context the context to use
* @param spannableDescription the SpannableStringBuilder with the text of the
* content description
- * @param contentUrl the URL of the content
- * @param streamingService the {@link StreamingService} of the content
+ * @param relatedInfo what to open in the popup player when timestamps are clicked
*/
private static void addClickListenersOnTimestamps(final Context context,
@NonNull final SpannableStringBuilder
spannableDescription,
- final String contentUrl,
- final StreamingService streamingService) {
+ final Info relatedInfo) {
final String descriptionText = spannableDescription.toString();
final Matcher timestampsMatches = TIMESTAMPS_PATTERN.matcher(descriptionText);
@@ -189,14 +177,14 @@ private static void addClickListenersOnTimestamps(final Context context,
final int timestampEnd = timestampsMatches.end(3);
final String parsedTimestamp = descriptionText.substring(timestampStart, timestampEnd);
final String[] timestampParts = parsedTimestamp.split(":");
- final int time;
+ final int seconds;
if (timestampParts.length == 3) { // timestamp format: XX:XX:XX
- time = Integer.parseInt(timestampParts[0]) * 3600 // hours
+ seconds = Integer.parseInt(timestampParts[0]) * 3600 // hours
+ Integer.parseInt(timestampParts[1]) * 60 // minutes
+ Integer.parseInt(timestampParts[2]); // seconds
} else if (timestampParts.length == 2) { // timestamp format: XX:XX
- time = Integer.parseInt(timestampParts[0]) * 60 // minutes
+ seconds = Integer.parseInt(timestampParts[0]) * 60 // minutes
+ Integer.parseInt(timestampParts[1]); // seconds
} else {
continue;
@@ -205,8 +193,8 @@ private static void addClickListenersOnTimestamps(final Context context,
spannableDescription.setSpan(new ClickableSpan() {
@Override
public void onClick(@NonNull final View view) {
- playOnPopup(new CompositeDisposable(), context, contentUrl, streamingService,
- time);
+ playOnPopup(new CompositeDisposable(), context, relatedInfo.getUrl(),
+ relatedInfo.getService(), seconds);
}
}, timestampStart, timestampEnd, 0);
}
@@ -221,28 +209,28 @@ public void onClick(@NonNull final View view) {
* with {@link ShareUtils#openUrlInBrowser(Context, String, boolean)}.
* This method will also add click listeners on timestamps in this description, which will play
* the content in the popup player at the time indicated in the timestamp, by using
- * {@link TextLinkifier#addClickListenersOnTimestamps(Context, SpannableStringBuilder, String,
- * StreamingService)} method and click listeners on hashtags, which will open a search
- * on the current service with the hashtag.
+ * {@link TextLinkifier#addClickListenersOnTimestamps(Context, SpannableStringBuilder, Info)}
+ * method and click listeners on hashtags, by using
+ * {@link TextLinkifier#addClickListenersOnHashtags(Context, SpannableStringBuilder, Info)},
+ * which will open a search on the current service with the hashtag.
*
* This method is required in order to intercept links and e.g. show a confirmation dialog
* before opening a web link.
*
- * @param context the context to use
- * @param chars the CharSequence to be parsed
- * @param textView the TextView in which the converted CharSequence will be applied
- * @param streamingService the {@link StreamingService} of the content
- * @param contentUrl the URL of the content
+ * @param textView the TextView in which the converted CharSequence will be applied
+ * @param chars the CharSequence to be parsed
+ * @param relatedInfo if given, handle timestamps to open the stream in the popup player at
+ * the specific time, and hashtags to search for the term in the correct
+ * service
* @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed
*/
@NonNull
- private static Disposable changeIntentsOfDescriptionLinks(final Context context,
+ private static Disposable changeIntentsOfDescriptionLinks(final TextView textView,
final CharSequence chars,
- final TextView textView,
- final StreamingService
- streamingService,
- final String contentUrl) {
+ @Nullable final Info relatedInfo) {
return Single.fromCallable(() -> {
+ final Context context = textView.getContext();
+
// add custom click actions on web links
final SpannableStringBuilder textBlockLinked = new SpannableStringBuilder(chars);
final URLSpan[] urls = textBlockLinked.getSpans(0, chars.length(), URLSpan.class);
@@ -264,11 +252,10 @@ public void onClick(@NonNull final View view) {
}
// add click actions on plain text timestamps only for description of contents,
- // unneeded for metainfo TextViews
- if (contentUrl != null || streamingService != null) {
- addClickListenersOnTimestamps(context, textBlockLinked, contentUrl,
- streamingService);
- addClickListenersOnHashtags(context, textBlockLinked, streamingService);
+ // unneeded for meta-info or other TextViews
+ if (relatedInfo != null) {
+ addClickListenersOnTimestamps(context, textBlockLinked, relatedInfo);
+ addClickListenersOnHashtags(context, textBlockLinked, relatedInfo);
}
return textBlockLinked;
From edfe0f9c3060c30e208f96d6c043ffc26feb2e2d Mon Sep 17 00:00:00 2001
From: Stypox
- * This will call
- * {@link TextLinkifier#changeIntentsOfDescriptionLinks(TextView, CharSequence, Info)}
- * after having linked the URLs with {@link HtmlCompat#fromHtml(String, int)}.
+ * This will call {@link TextLinkifier#changeIntentsOfDescriptionLinks(TextView, CharSequence,
+ * Info, CompositeDisposable)} after having linked the URLs with
+ * {@link HtmlCompat#fromHtml(String, int)}.
*
* @param textView the TextView to set the htmlBlock linked
* @param htmlBlock the htmlBlock to be linked
@@ -53,23 +52,24 @@ private TextLinkifier() {
* @param relatedInfo if given, handle timestamps to open the stream in the popup player at
* the specific time, and hashtags to search for the term in the correct
* service
- * @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed
+ * @param disposables disposables created by the method are added here and their lifecycle
+ * should be handled by the calling class
*/
- @NonNull
- public static Disposable createLinksFromHtmlBlock(@NonNull final TextView textView,
- final String htmlBlock,
- final int htmlCompatFlag,
- @Nullable final Info relatedInfo) {
- return changeIntentsOfDescriptionLinks(
- textView, HtmlCompat.fromHtml(htmlBlock, htmlCompatFlag), relatedInfo);
+ public static void createLinksFromHtmlBlock(@NonNull final TextView textView,
+ final String htmlBlock,
+ final int htmlCompatFlag,
+ @Nullable final Info relatedInfo,
+ final CompositeDisposable disposables) {
+ changeIntentsOfDescriptionLinks(
+ textView, HtmlCompat.fromHtml(htmlBlock, htmlCompatFlag), relatedInfo, disposables);
}
/**
* Create web links for contents with a plain text description.
*
- * This will call
- * {@link TextLinkifier#changeIntentsOfDescriptionLinks(TextView, CharSequence, Info)}
- * after having linked the URLs with {@link TextView#setAutoLinkMask(int)} and
+ * This will call {@link TextLinkifier#changeIntentsOfDescriptionLinks(TextView, CharSequence,
+ * Info, CompositeDisposable)} after having linked the URLs with
+ * {@link TextView#setAutoLinkMask(int)} and
* {@link TextView#setText(CharSequence, TextView.BufferType)}.
*
* @param textView the TextView to set the plain text block linked
@@ -77,40 +77,40 @@ public static Disposable createLinksFromHtmlBlock(@NonNull final TextView textVi
* @param relatedInfo if given, handle timestamps to open the stream in the popup player at
* the specific time, and hashtags to search for the term in the correct
* service
- * @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed
+ * @param disposables disposables created by the method are added here and their lifecycle
+ * should be handled by the calling class
*/
- @NonNull
- public static Disposable createLinksFromPlainText(@NonNull final TextView textView,
- final String plainTextBlock,
- @Nullable final Info relatedInfo) {
+ public static void createLinksFromPlainText(@NonNull final TextView textView,
+ final String plainTextBlock,
+ @Nullable final Info relatedInfo,
+ final CompositeDisposable disposables) {
textView.setAutoLinkMask(Linkify.WEB_URLS);
textView.setText(plainTextBlock, TextView.BufferType.SPANNABLE);
- return changeIntentsOfDescriptionLinks(textView, textView.getText(), relatedInfo);
+ changeIntentsOfDescriptionLinks(textView, textView.getText(), relatedInfo, disposables);
}
/**
* Create web links for contents with a markdown description.
*
- * This will call
- * {@link TextLinkifier#changeIntentsOfDescriptionLinks(TextView, CharSequence, Info)}
- * after creating an {@link Markwon} object and using
+ * This will call {@link TextLinkifier#changeIntentsOfDescriptionLinks(TextView, CharSequence,
+ * Info, CompositeDisposable)} after creating an {@link Markwon} object and using
* {@link Markwon#setMarkdown(TextView, String)}.
*
* @param textView the TextView to set the plain text block linked
* @param markdownBlock the block of markdown text to be linked
* @param relatedInfo if given, handle timestamps to open the stream in the popup player at
* the specific time, and hashtags to search for the term in the correct
- * service
- * @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed
+ * @param disposables disposables created by the method are added here and their lifecycle
+ * should be handled by the calling class
*/
- @NonNull
- public static Disposable createLinksFromMarkdownText(@NonNull final TextView textView,
- final String markdownBlock,
- @Nullable final Info relatedInfo) {
+ public static void createLinksFromMarkdownText(@NonNull final TextView textView,
+ final String markdownBlock,
+ @Nullable final Info relatedInfo,
+ final CompositeDisposable disposables) {
final Markwon markwon = Markwon.builder(textView.getContext())
.usePlugin(LinkifyPlugin.create()).build();
- markwon.setMarkdown(textView, markdownBlock);
- return changeIntentsOfDescriptionLinks(textView, textView.getText(), relatedInfo);
+ changeIntentsOfDescriptionLinks(textView, markwon.toMarkdown(markdownBlock), relatedInfo,
+ disposables);
}
/**
@@ -164,11 +164,14 @@ public void onClick(@NonNull final View view) {
* @param spannableDescription the SpannableStringBuilder with the text of the
* content description
* @param relatedInfo what to open in the popup player when timestamps are clicked
+ * @param disposables disposables created by the method are added here and their
+ * lifecycle should be handled by the calling class
*/
private static void addClickListenersOnTimestamps(final Context context,
@NonNull final SpannableStringBuilder
spannableDescription,
- final Info relatedInfo) {
+ final Info relatedInfo,
+ final CompositeDisposable disposables) {
final String descriptionText = spannableDescription.toString();
final Matcher timestampsMatches = TIMESTAMPS_PATTERN.matcher(descriptionText);
@@ -193,8 +196,8 @@ private static void addClickListenersOnTimestamps(final Context context,
spannableDescription.setSpan(new ClickableSpan() {
@Override
public void onClick(@NonNull final View view) {
- playOnPopup(new CompositeDisposable(), context, relatedInfo.getUrl(),
- relatedInfo.getService(), seconds);
+ playOnPopup(context, relatedInfo.getUrl(), relatedInfo.getService(), seconds,
+ disposables);
}
}, timestampStart, timestampEnd, 0);
}
@@ -209,8 +212,8 @@ public void onClick(@NonNull final View view) {
* with {@link ShareUtils#openUrlInBrowser(Context, String, boolean)}.
* This method will also add click listeners on timestamps in this description, which will play
* the content in the popup player at the time indicated in the timestamp, by using
- * {@link TextLinkifier#addClickListenersOnTimestamps(Context, SpannableStringBuilder, Info)}
- * method and click listeners on hashtags, by using
+ * {@link TextLinkifier#addClickListenersOnTimestamps(Context, SpannableStringBuilder, Info,
+ * CompositeDisposable)} method and click listeners on hashtags, by using
* {@link TextLinkifier#addClickListenersOnHashtags(Context, SpannableStringBuilder, Info)},
* which will open a search on the current service with the hashtag.
*
@@ -222,13 +225,14 @@ public void onClick(@NonNull final View view) {
* @param relatedInfo if given, handle timestamps to open the stream in the popup player at
* the specific time, and hashtags to search for the term in the correct
* service
- * @return a disposable to be stored somewhere and disposed when activity/fragment is destroyed
+ * @param disposables disposables created by the method are added here and their lifecycle
+ * should be handled by the calling class
*/
- @NonNull
- private static Disposable changeIntentsOfDescriptionLinks(final TextView textView,
- final CharSequence chars,
- @Nullable final Info relatedInfo) {
- return Single.fromCallable(() -> {
+ private static void changeIntentsOfDescriptionLinks(final TextView textView,
+ final CharSequence chars,
+ @Nullable final Info relatedInfo,
+ final CompositeDisposable disposables) {
+ disposables.add(Single.fromCallable(() -> {
final Context context = textView.getContext();
// add custom click actions on web links
@@ -254,7 +258,7 @@ public void onClick(@NonNull final View view) {
// add click actions on plain text timestamps only for description of contents,
// unneeded for meta-info or other TextViews
if (relatedInfo != null) {
- addClickListenersOnTimestamps(context, textBlockLinked, relatedInfo);
+ addClickListenersOnTimestamps(context, textBlockLinked, relatedInfo, disposables);
addClickListenersOnHashtags(context, textBlockLinked, relatedInfo);
}
@@ -267,7 +271,7 @@ public void onClick(@NonNull final View view) {
Log.e(TAG, "Unable to linkify text", throwable);
// this should never happen, but if it does, just fallback to it
setTextViewCharSequence(textView, chars);
- });
+ }));
}
private static void setTextViewCharSequence(@NonNull final TextView textView,
From 32b0bdb98c2a6ae7902e2c6c5d3c891a8b712ad9 Mon Sep 17 00:00:00 2001
From: TiA4f8R <74829229+TiA4f8R@users.noreply.github.com>
Date: Fri, 11 Jun 2021 12:19:29 +0200
Subject: [PATCH 19/20] Fix the compile error
---
.../main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt b/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt
index f24116e23ec..7617ef451ea 100644
--- a/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt
+++ b/app/src/main/java/org/schabi/newpipe/about/LicenseFragmentHelper.kt
@@ -10,8 +10,8 @@ import io.reactivex.rxjava3.disposables.Disposable
import io.reactivex.rxjava3.schedulers.Schedulers
import org.schabi.newpipe.R
import org.schabi.newpipe.util.Localization
-import org.schabi.newpipe.util.ShareUtils
import org.schabi.newpipe.util.ThemeHelper
+import org.schabi.newpipe.util.external_communication.ShareUtils
import java.io.BufferedReader
import java.io.IOException
import java.io.InputStreamReader
From 4eef498d241bf395415dac6b1b49114101de94ad Mon Sep 17 00:00:00 2001
From: TiA4f8R <74829229+TiA4f8R@users.noreply.github.com>
Date: Fri, 11 Jun 2021 14:08:08 +0200
Subject: [PATCH 20/20] Only call addClickListenersOnTimestamps if relatedInfo
is instance of StreamInfo
---
.../newpipe/util/external_communication/TextLinkifier.java | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/app/src/main/java/org/schabi/newpipe/util/external_communication/TextLinkifier.java b/app/src/main/java/org/schabi/newpipe/util/external_communication/TextLinkifier.java
index f4b423e4191..76da096094b 100644
--- a/app/src/main/java/org/schabi/newpipe/util/external_communication/TextLinkifier.java
+++ b/app/src/main/java/org/schabi/newpipe/util/external_communication/TextLinkifier.java
@@ -15,6 +15,7 @@
import androidx.core.text.HtmlCompat;
import org.schabi.newpipe.extractor.Info;
+import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.util.NavigationHelper;
import java.util.regex.Matcher;
@@ -258,7 +259,10 @@ public void onClick(@NonNull final View view) {
// add click actions on plain text timestamps only for description of contents,
// unneeded for meta-info or other TextViews
if (relatedInfo != null) {
- addClickListenersOnTimestamps(context, textBlockLinked, relatedInfo, disposables);
+ if (relatedInfo instanceof StreamInfo) {
+ addClickListenersOnTimestamps(context, textBlockLinked, relatedInfo,
+ disposables);
+ }
addClickListenersOnHashtags(context, textBlockLinked, relatedInfo);
}