Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Add long press action on hashtags and web links in descriptions #7725

Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import static android.text.TextUtils.isEmpty;
import static org.schabi.newpipe.extractor.stream.StreamExtractor.NO_AGE_LIMIT;
import static org.schabi.newpipe.extractor.utils.Utils.isBlank;
import static org.schabi.newpipe.util.Localization.getAppLocale;

import android.os.Bundle;
import android.view.LayoutInflater;
Expand All @@ -28,7 +29,7 @@
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.external_communication.ShareUtils;
import org.schabi.newpipe.util.external_communication.TextLinkifier;
import org.schabi.newpipe.util.text.TextLinkifier;

import icepick.State;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
Expand Down Expand Up @@ -134,7 +135,8 @@ private void loadDescriptionContent() {
TextLinkifier.createLinksFromMarkdownText(binding.detailDescriptionView,
description.getContent(), streamInfo, descriptionDisposables);
break;
case Description.PLAIN_TEXT: default:
case Description.PLAIN_TEXT:
default:
TextLinkifier.createLinksFromPlainText(binding.detailDescriptionView,
description.getContent(), streamInfo, descriptionDisposables);
break;
Expand All @@ -144,30 +146,30 @@ private void loadDescriptionContent() {

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

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

addPrivacyMetadataItem(inflater, layout);

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

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

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

addTagsMetadataItem(inflater, layout);
}
Expand All @@ -191,12 +193,14 @@ private void addMetadataItem(final LayoutInflater inflater,
});

if (linkifyContent) {
TextLinkifier.createLinksFromPlainText(itemBinding.metadataContentView, content, null,
descriptionDisposables);
TextLinkifier.createLinksFromPlainText(itemBinding.metadataContentView, content,
null, descriptionDisposables);
} else {
itemBinding.metadataContentView.setText(content);
}

itemBinding.metadataContentView.setClickable(true);

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

Expand Down Expand Up @@ -245,14 +249,15 @@ private void addPrivacyMetadataItem(final LayoutInflater inflater, final LinearL
case INTERNAL:
contentRes = R.string.metadata_privacy_internal;
break;
case OTHER: default:
case OTHER:
default:
contentRes = 0;
break;
}

if (contentRes != 0) {
addMetadataItem(inflater, layout, false,
R.string.metadata_privacy, getString(contentRes));
addMetadataItem(inflater, layout, false, R.string.metadata_privacy,
getString(contentRes));
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,13 @@
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
import org.schabi.newpipe.info_list.InfoItemBuilder;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.util.CommentTextOnTouchListener;
import org.schabi.newpipe.util.text.CommentTextOnTouchListener;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.Localization;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.PicassoHelper;
import org.schabi.newpipe.util.external_communication.ShareUtils;
import org.schabi.newpipe.util.external_communication.TimestampExtractor;
import org.schabi.newpipe.util.text.TimestampExtractor;

import java.util.Objects;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@
import org.schabi.newpipe.extractor.stream.StreamInfo;
import org.schabi.newpipe.extractor.stream.StreamInfoItem;
import org.schabi.newpipe.extractor.suggestion.SuggestionExtractor;
import org.schabi.newpipe.util.external_communication.TextLinkifier;
import org.schabi.newpipe.util.text.TextLinkifier;

import java.util.Collections;
import java.util.List;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -313,10 +313,15 @@ public static void copyToClipboard(@NonNull final Context context, final String
return;
}

clipboardManager.setPrimaryClip(ClipData.newPlainText(null, text));
if (Build.VERSION.SDK_INT < 33) {
// Android 13 has its own "copied to clipboard" dialog
Toast.makeText(context, R.string.msg_copied, Toast.LENGTH_SHORT).show();
try {
clipboardManager.setPrimaryClip(ClipData.newPlainText(null, text));
if (Build.VERSION.SDK_INT < 33) {
// Android 13 has its own "copied to clipboard" dialog
Toast.makeText(context, R.string.msg_copied, Toast.LENGTH_SHORT).show();
}
} catch (final Exception e) {
Log.e(TAG, "Error when trying to copy text to clipboard", e);
Toast.makeText(context, R.string.msg_failed_to_copy, Toast.LENGTH_SHORT).show();
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package org.schabi.newpipe.util;
package org.schabi.newpipe.util.text;

import static org.schabi.newpipe.util.text.TouchUtils.getOffsetForHorizontalLine;

import android.text.Layout;
import android.text.Selection;
import android.text.Spannable;
import android.text.Spanned;
Expand All @@ -11,7 +12,6 @@
import android.widget.TextView;

import org.schabi.newpipe.util.external_communication.ShareUtils;
import org.schabi.newpipe.util.external_communication.InternalUrlsHandler;

import io.reactivex.rxjava3.disposables.CompositeDisposable;

Expand All @@ -30,23 +30,9 @@ public boolean onTouch(final View v, final MotionEvent event) {

final 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();

final Layout layout = widget.getLayout();
final int line = layout.getLineForVertical(y);
final int off = layout.getOffsetForHorizontal(line, x);

final ClickableSpan[] link = buffer.getSpans(off, off,
ClickableSpan.class);
if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
final int offset = getOffsetForHorizontalLine(widget, event);
final ClickableSpan[] link = buffer.getSpans(offset, offset, ClickableSpan.class);

if (link.length != 0) {
if (action == MotionEvent.ACTION_UP) {
Expand All @@ -58,8 +44,7 @@ public boolean onTouch(final View v, final MotionEvent event) {
}
}
} else if (action == MotionEvent.ACTION_DOWN) {
Selection.setSelection(buffer,
buffer.getSpanStart(link[0]),
Selection.setSelection(buffer, buffer.getSpanStart(link[0]),
buffer.getSpanEnd(link[0]));
}
return true;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package org.schabi.newpipe.util.text;

import android.content.Context;
import android.view.View;

import androidx.annotation.NonNull;

import org.schabi.newpipe.extractor.Info;
import org.schabi.newpipe.util.NavigationHelper;
import org.schabi.newpipe.util.external_communication.ShareUtils;

final class HashtagLongPressClickableSpan extends LongPressClickableSpan {

@NonNull
private final Context context;
@NonNull
private final String parsedHashtag;
@NonNull
private final Info relatedInfo;

HashtagLongPressClickableSpan(@NonNull final Context context,
@NonNull final String parsedHashtag,
@NonNull final Info relatedInfo) {
this.context = context;
this.parsedHashtag = parsedHashtag;
this.relatedInfo = relatedInfo;
}

@Override
public void onClick(@NonNull final View view) {
NavigationHelper.openSearch(context, relatedInfo.getServiceId(), parsedHashtag);
}

@Override
public void onLongClick(@NonNull final View view) {
ShareUtils.copyToClipboard(context, parsedHashtag);
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package org.schabi.newpipe.util.external_communication;
package org.schabi.newpipe.util.text;

import android.content.Context;
import android.util.Log;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.schabi.newpipe.util.text;

import android.text.style.ClickableSpan;
import android.view.View;

import androidx.annotation.NonNull;

public abstract class LongPressClickableSpan extends ClickableSpan {

public abstract void onLongClick(@NonNull View view);

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
package org.schabi.newpipe.util.text;

import static org.schabi.newpipe.util.text.TouchUtils.getOffsetForHorizontalLine;

import android.os.Handler;
import android.os.Looper;
import android.text.Selection;
import android.text.Spannable;
import android.text.method.LinkMovementMethod;
import android.text.method.MovementMethod;
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.widget.TextView;

import androidx.annotation.NonNull;

// Class adapted from https://stackoverflow.com/a/31786969

public class LongPressLinkMovementMethod extends LinkMovementMethod {

private static final int LONG_PRESS_TIME = ViewConfiguration.getLongPressTimeout();

private static LongPressLinkMovementMethod instance;

private Handler longClickHandler;
private boolean isLongPressed = false;

@Override
public boolean onTouchEvent(@NonNull final TextView widget,
@NonNull final Spannable buffer,
@NonNull final MotionEvent event) {
final int action = event.getAction();

if (action == MotionEvent.ACTION_CANCEL && longClickHandler != null) {
longClickHandler.removeCallbacksAndMessages(null);
}

if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_DOWN) {
final int offset = getOffsetForHorizontalLine(widget, event);
final LongPressClickableSpan[] link = buffer.getSpans(offset, offset,
LongPressClickableSpan.class);

if (link.length != 0) {
if (action == MotionEvent.ACTION_UP) {
if (longClickHandler != null) {
longClickHandler.removeCallbacksAndMessages(null);
}
if (!isLongPressed) {
link[0].onClick(widget);
}
isLongPressed = false;
} else {
Selection.setSelection(buffer, buffer.getSpanStart(link[0]),
buffer.getSpanEnd(link[0]));
if (longClickHandler != null) {
longClickHandler.postDelayed(() -> {
link[0].onLongClick(widget);
isLongPressed = true;
}, LONG_PRESS_TIME);
}
}
return true;
}
}

return super.onTouchEvent(widget, buffer, event);
}

public static MovementMethod getInstance() {
if (instance == null) {
instance = new LongPressLinkMovementMethod();
instance.longClickHandler = new Handler(Looper.myLooper());
}

return instance;
}
}
Loading