From c0004e988a64b241a916b83370ab16290ff83f59 Mon Sep 17 00:00:00 2001 From: Ritvik Saraf <13ritvik@gmail.com> Date: Fri, 1 Mar 2019 13:28:32 +0530 Subject: [PATCH 1/3] make links in comments clickable, increase text size --- .../holder/CommentsMiniInfoItemHolder.java | 57 ++++++++++++----- .../util/CommentTextOnTouchListener.java | 62 +++++++++++++++++++ .../main/res/layout/list_comments_item.xml | 6 +- .../res/layout/list_comments_mini_item.xml | 4 +- app/src/main/res/values/dimens.xml | 4 ++ 5 files changed, 111 insertions(+), 22 deletions(-) create mode 100644 app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java 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 c2bc86691..0e6321f35 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 @@ -1,7 +1,7 @@ package org.schabi.newpipe.info_list.holder; import android.support.v7.app.AppCompatActivity; -import android.text.TextUtils; +import android.text.util.Linkify; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; @@ -11,6 +11,7 @@ import org.schabi.newpipe.extractor.InfoItem; import org.schabi.newpipe.extractor.comments.CommentsInfoItem; import org.schabi.newpipe.info_list.InfoItemBuilder; import org.schabi.newpipe.report.ErrorActivity; +import org.schabi.newpipe.util.CommentTextOnTouchListener; import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.NavigationHelper; @@ -26,6 +27,9 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder { private static final int commentDefaultLines = 2; private static final int commentExpandedLines = 1000; + private String commentText; + private boolean containsLinks = false; + CommentsMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) { super(infoItemBuilder, layoutId, parent); @@ -66,34 +70,57 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder { } }); - // ellipsize if not already ellipsized - if (null == itemContentView.getEllipsize()) { - itemContentView.setEllipsize(TextUtils.TruncateAt.END); - itemContentView.setMaxLines(commentDefaultLines); + itemContentView.setMaxLines(commentDefaultLines); + commentText = item.getCommentText(); + itemContentView.setText(commentText); + containsLinks = linkify(); + itemContentView.setOnTouchListener(CommentTextOnTouchListener.INSTANCE); + + if(itemContentView.getLineCount() == 0){ + itemContentView.post(() -> ellipsize()); + }else{ + ellipsize(); } - itemContentView.setText(item.getCommentText()); if (null != item.getLikeCount()) { itemLikesCountView.setText(String.valueOf(item.getLikeCount())); } itemPublishedTime.setText(item.getPublishedTime()); itemView.setOnClickListener(view -> { - toggleEllipsize(item.getCommentText()); + toggleEllipsize(); if (itemBuilder.getOnCommentsSelectedListener() != null) { itemBuilder.getOnCommentsSelectedListener().selected(item); } }); } - private void toggleEllipsize(String text) { - // toggle ellipsize - if (null == itemContentView.getEllipsize()) { - itemContentView.setEllipsize(TextUtils.TruncateAt.END); - itemContentView.setMaxLines(commentDefaultLines); - } else { - itemContentView.setEllipsize(null); - itemContentView.setMaxLines(commentExpandedLines); + private void ellipsize() { + if (itemContentView.getLineCount() > commentDefaultLines){ + int endOfLastLine = itemContentView.getLayout().getLineEnd(commentDefaultLines - 1); + String newVal = itemContentView.getText().subSequence(0, endOfLastLine - 3) + "..."; + itemContentView.setText(newVal); + if(containsLinks) linkify(); } } + + private void toggleEllipsize() { + if (itemContentView.getText().toString().equals(commentText)) { + ellipsize(); + } else { + expand(); + } + } + + private void expand() { + itemContentView.setMaxLines(commentExpandedLines); + itemContentView.setText(commentText); + if(containsLinks) linkify(); + } + + private boolean linkify(){ + boolean res = Linkify.addLinks(itemContentView, Linkify.WEB_URLS); + itemContentView.setMovementMethod(null); + return res; + } } diff --git a/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java b/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java new file mode 100644 index 000000000..2934145ff --- /dev/null +++ b/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java @@ -0,0 +1,62 @@ +package org.schabi.newpipe.util; + +import android.text.Layout; +import android.text.Selection; +import android.text.Spannable; +import android.text.Spanned; +import android.text.style.ClickableSpan; +import android.view.MotionEvent; +import android.view.View; +import android.widget.TextView; + +public class CommentTextOnTouchListener implements View.OnTouchListener { + + public static final CommentTextOnTouchListener INSTANCE = new CommentTextOnTouchListener(); + + @Override + public boolean onTouch(View v, MotionEvent event) { + if(!(v instanceof TextView)){ + return false; + } + TextView widget = (TextView) v; + Object text = widget.getText(); + if (text instanceof Spanned) { + Spannable buffer = (Spannable) text; + + int action = event.getAction(); + + if (action == MotionEvent.ACTION_UP + || action == MotionEvent.ACTION_DOWN) { + int x = (int) event.getX(); + int y = (int) event.getY(); + + x -= widget.getTotalPaddingLeft(); + y -= widget.getTotalPaddingTop(); + + x += widget.getScrollX(); + y += widget.getScrollY(); + + Layout layout = widget.getLayout(); + int line = layout.getLineForVertical(y); + int off = layout.getOffsetForHorizontal(line, x); + + ClickableSpan[] link = buffer.getSpans(off, off, + ClickableSpan.class); + + if (link.length != 0) { + if (action == MotionEvent.ACTION_UP) { + link[0].onClick(widget); + } else if (action == MotionEvent.ACTION_DOWN) { + Selection.setSelection(buffer, + buffer.getSpanStart(link[0]), + buffer.getSpanEnd(link[0])); + } + return true; + } + } + + } + + return false; + } +} diff --git a/app/src/main/res/layout/list_comments_item.xml b/app/src/main/res/layout/list_comments_item.xml index a9b091329..393d7d1b4 100644 --- a/app/src/main/res/layout/list_comments_item.xml +++ b/app/src/main/res/layout/list_comments_item.xml @@ -32,7 +32,7 @@ android:ellipsize="end" android:lines="1" android:textAppearance="?android:attr/textAppearanceSmall" - android:textSize="@dimen/video_item_search_title_text_size" + android:textSize="@dimen/comment_item_title_text_size" tools:text="Author Name, Lorem ipsum" /> diff --git a/app/src/main/res/layout/list_comments_mini_item.xml b/app/src/main/res/layout/list_comments_mini_item.xml index 36f3e2e6e..3f3c6c468 100644 --- a/app/src/main/res/layout/list_comments_mini_item.xml +++ b/app/src/main/res/layout/list_comments_mini_item.xml @@ -27,10 +27,8 @@ android:layout_height="wrap_content" android:layout_marginBottom="@dimen/channel_item_description_to_details_margin" android:layout_toRightOf="@+id/itemThumbnailView" - android:ellipsize="end" - android:lines="2" android:textAppearance="?android:attr/textAppearanceSmall" - android:textSize="@dimen/video_item_search_uploader_text_size" + android:textSize="@dimen/comment_item_content_text_size" tools:text="Channel description, Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blanditLorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc tristique vitae sem vitae blandit" /> 14sp + + + 12sp + 12sp From 67d2b9131e7871505454d12dd1d245528b251228 Mon Sep 17 00:00:00 2001 From: Ritvik Saraf <13ritvik@gmail.com> Date: Sat, 2 Mar 2019 05:12:06 +0530 Subject: [PATCH 2/3] handling timestamp links in comments --- .../org/schabi/newpipe/RouterActivity.java | 12 +++- .../holder/CommentsMiniInfoItemHolder.java | 35 ++++++++-- .../util/CommentTextOnTouchListener.java | 68 ++++++++++++++++++- .../schabi/newpipe/util/NavigationHelper.java | 12 +++- 4 files changed, 114 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/org/schabi/newpipe/RouterActivity.java b/app/src/main/java/org/schabi/newpipe/RouterActivity.java index b8941670f..9977ef56c 100644 --- a/app/src/main/java/org/schabi/newpipe/RouterActivity.java +++ b/app/src/main/java/org/schabi/newpipe/RouterActivity.java @@ -36,7 +36,6 @@ import org.schabi.newpipe.extractor.exceptions.ExtractionException; import org.schabi.newpipe.extractor.playlist.PlaylistInfo; import org.schabi.newpipe.extractor.stream.StreamInfo; import org.schabi.newpipe.extractor.stream.VideoStream; -import org.schabi.newpipe.player.helper.PlayerHelper; import org.schabi.newpipe.player.playqueue.ChannelPlayQueue; import org.schabi.newpipe.player.playqueue.PlayQueue; import org.schabi.newpipe.player.playqueue.PlaylistPlayQueue; @@ -81,10 +80,13 @@ public class RouterActivity extends AppCompatActivity { protected int selectedPreviously = -1; protected String currentUrl; + protected boolean internalRoute = false; protected final CompositeDisposable disposables = new CompositeDisposable(); private boolean selectionIsDownload = false; + public static final String internalRouteKey = "internalRoute"; + @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -99,6 +101,8 @@ public class RouterActivity extends AppCompatActivity { } } + internalRoute = getIntent().getBooleanExtra(internalRouteKey, false); + setTheme(ThemeHelper.isLightThemeSelected(this) ? R.style.RouterActivityThemeLight : R.style.RouterActivityThemeDark); } @@ -383,8 +387,10 @@ public class RouterActivity extends AppCompatActivity { .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(intent -> { - intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + if(!internalRoute){ + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK); + } startActivity(intent); finish(); 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 0e6321f35..9be272198 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 @@ -15,6 +15,9 @@ import org.schabi.newpipe.util.CommentTextOnTouchListener; import org.schabi.newpipe.util.ImageDisplayConstants; import org.schabi.newpipe.util.NavigationHelper; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + import de.hdodenhof.circleimageview.CircleImageView; public class CommentsMiniInfoItemHolder extends InfoItemHolder { @@ -28,7 +31,23 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder { private static final int commentExpandedLines = 1000; private String commentText; - private boolean containsLinks = false; + private String streamUrl; + + private static final Pattern pattern = Pattern.compile("(\\d+:)?(\\d+)?:(\\d+)"); + + private final Linkify.TransformFilter timestampLink = new Linkify.TransformFilter() { + @Override + public String transformUrl(Matcher match, String url) { + int timestamp = 0; + String hours = match.group(1); + String minutes = match.group(2); + String seconds = match.group(3); + if(hours != null) timestamp += (Integer.parseInt(hours.replace(":", ""))*3600); + if(minutes != null) timestamp += (Integer.parseInt(minutes.replace(":", ""))*60); + if(seconds != null) timestamp += (Integer.parseInt(seconds)); + return streamUrl + url.replace(match.group(0), "&t=" + String.valueOf(timestamp)); + } + }; CommentsMiniInfoItemHolder(InfoItemBuilder infoItemBuilder, int layoutId, ViewGroup parent) { super(infoItemBuilder, layoutId, parent); @@ -70,10 +89,12 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder { } }); + streamUrl = item.getUrl(); + itemContentView.setMaxLines(commentDefaultLines); commentText = item.getCommentText(); itemContentView.setText(commentText); - containsLinks = linkify(); + linkify(); itemContentView.setOnTouchListener(CommentTextOnTouchListener.INSTANCE); if(itemContentView.getLineCount() == 0){ @@ -100,7 +121,7 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder { int endOfLastLine = itemContentView.getLayout().getLineEnd(commentDefaultLines - 1); String newVal = itemContentView.getText().subSequence(0, endOfLastLine - 3) + "..."; itemContentView.setText(newVal); - if(containsLinks) linkify(); + linkify(); } } @@ -115,12 +136,12 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder { private void expand() { itemContentView.setMaxLines(commentExpandedLines); itemContentView.setText(commentText); - if(containsLinks) linkify(); + linkify(); } - private boolean linkify(){ - boolean res = Linkify.addLinks(itemContentView, Linkify.WEB_URLS); + private void linkify(){ + Linkify.addLinks(itemContentView, Linkify.WEB_URLS); + Linkify.addLinks(itemContentView, pattern, null, null, timestampLink); itemContentView.setMovementMethod(null); - return res; } } 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 2934145ff..570b5f8b2 100644 --- a/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java +++ b/app/src/main/java/org/schabi/newpipe/util/CommentTextOnTouchListener.java @@ -1,18 +1,38 @@ package org.schabi.newpipe.util; +import android.content.Context; import android.text.Layout; import android.text.Selection; import android.text.Spannable; import android.text.Spanned; import android.text.style.ClickableSpan; +import android.text.style.URLSpan; import android.view.MotionEvent; 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.Single; +import io.reactivex.android.schedulers.AndroidSchedulers; +import io.reactivex.schedulers.Schedulers; + public class CommentTextOnTouchListener implements View.OnTouchListener { public static final CommentTextOnTouchListener INSTANCE = new CommentTextOnTouchListener(); + private static final Pattern timestampPattern = Pattern.compile(".*&t=(\\d+)"); + @Override public boolean onTouch(View v, MotionEvent event) { if(!(v instanceof TextView)){ @@ -45,7 +65,11 @@ public class CommentTextOnTouchListener implements View.OnTouchListener { if (link.length != 0) { if (action == MotionEvent.ACTION_UP) { - link[0].onClick(widget); + boolean handled = false; + if(link[0] instanceof URLSpan){ + handled = handleUrl(v.getContext(), (URLSpan) link[0]); + } + if(!handled) link[0].onClick(widget); } else if (action == MotionEvent.ACTION_DOWN) { Selection.setSelection(buffer, buffer.getSpanStart(link[0]), @@ -59,4 +83,46 @@ public class CommentTextOnTouchListener implements View.OnTouchListener { return false; } + + private boolean handleUrl(Context context, URLSpan urlSpan) { + String url = urlSpan.getURL(); + StreamingService service; + StreamingService.LinkType linkType; + try { + service = NewPipe.getServiceByUrl(url); + linkType = service.getLinkTypeByUrl(url); + } catch (ExtractionException e) { + return false; + } + if(linkType == StreamingService.LinkType.NONE){ + return false; + } + Matcher matcher = timestampPattern.matcher(url); + if(linkType == StreamingService.LinkType.STREAM && matcher.matches()){ + int seconds = Integer.parseInt(matcher.group(1)); + return playOnPopup(context, url, service, seconds); + }else{ + NavigationHelper.openRouterActivity(context, url); + return true; + } + } + + private boolean playOnPopup(Context context, String url, StreamingService service, int seconds) { + LinkHandlerFactory factory = service.getStreamLHFactory(); + String cleanUrl = null; + try { + cleanUrl = factory.getUrl(factory.getId(url)); + } catch (ParsingException e) { + return false; + } + Single single = ExtractorHelper.getStreamInfo(service.getServiceId(), cleanUrl, false); + single.subscribeOn(Schedulers.io()) + .observeOn(AndroidSchedulers.mainThread()) + .subscribe(info -> { + PlayQueue playQueue = new SinglePlayQueue((StreamInfo) info); + ((StreamInfo) info).setStartPosition(seconds); + NavigationHelper.enqueueOnPopupPlayer(context, playQueue, true); + }); + return true; + } } 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 4b93600ce..98ae3a88a 100644 --- a/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java +++ b/app/src/main/java/org/schabi/newpipe/util/NavigationHelper.java @@ -21,6 +21,7 @@ import com.nostra13.universalimageloader.core.ImageLoader; import org.schabi.newpipe.MainActivity; import org.schabi.newpipe.R; +import org.schabi.newpipe.RouterActivity; import org.schabi.newpipe.about.AboutActivity; import org.schabi.newpipe.download.DownloadActivity; import org.schabi.newpipe.extractor.NewPipe; @@ -34,11 +35,11 @@ import org.schabi.newpipe.fragments.MainFragment; import org.schabi.newpipe.fragments.detail.VideoDetailFragment; import org.schabi.newpipe.fragments.list.channel.ChannelFragment; import org.schabi.newpipe.fragments.list.comments.CommentsFragment; -import org.schabi.newpipe.local.bookmark.BookmarkFragment; -import org.schabi.newpipe.local.feed.FeedFragment; import org.schabi.newpipe.fragments.list.kiosk.KioskFragment; import org.schabi.newpipe.fragments.list.playlist.PlaylistFragment; import org.schabi.newpipe.fragments.list.search.SearchFragment; +import org.schabi.newpipe.local.bookmark.BookmarkFragment; +import org.schabi.newpipe.local.feed.FeedFragment; import org.schabi.newpipe.local.history.StatisticsPlaylistFragment; import org.schabi.newpipe.local.playlist.LocalPlaylistFragment; import org.schabi.newpipe.local.subscription.SubscriptionFragment; @@ -422,6 +423,13 @@ public class NavigationHelper { context.startActivity(mIntent); } + public static void openRouterActivity(Context context, String url) { + Intent mIntent = new Intent(context, RouterActivity.class); + mIntent.setData(Uri.parse(url)); + mIntent.putExtra(RouterActivity.internalRouteKey, true); + context.startActivity(mIntent); + } + public static void openAbout(Context context) { Intent intent = new Intent(context, AboutActivity.class); context.startActivity(intent); From 8491035b2f9657318d7e30b5044161234ab038ac Mon Sep 17 00:00:00 2001 From: Ritvik Saraf <13ritvik@gmail.com> Date: Sun, 3 Mar 2019 04:34:50 +0530 Subject: [PATCH 3/3] updated extractor --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 47cb031d7..114828bf3 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -57,7 +57,7 @@ dependencies { exclude module: 'support-annotations' }) - implementation 'com.github.TeamNewPipe:NewPipeExtractor:e7e411dc296d8633' + implementation 'com.github.TeamNewPipe:NewPipeExtractor:aa4f03a' testImplementation 'junit:junit:4.12' testImplementation 'org.mockito:mockito-core:2.23.0'