2018-09-03 01:22:59 +02:00
|
|
|
package org.schabi.newpipe.info_list.holder;
|
|
|
|
|
2023-01-05 19:01:38 +01:00
|
|
|
import android.graphics.Paint;
|
|
|
|
import android.text.Layout;
|
2020-05-03 11:54:37 +02:00
|
|
|
import android.text.TextUtils;
|
2020-04-20 13:37:35 +02:00
|
|
|
import android.text.method.LinkMovementMethod;
|
|
|
|
import android.text.style.URLSpan;
|
2021-08-04 22:35:41 +02:00
|
|
|
import android.util.Log;
|
2020-09-29 17:47:39 +02:00
|
|
|
import android.view.View;
|
2018-09-03 01:22:59 +02:00
|
|
|
import android.view.ViewGroup;
|
2022-02-23 21:11:25 +01:00
|
|
|
import android.widget.ImageView;
|
2020-09-29 17:47:39 +02:00
|
|
|
import android.widget.RelativeLayout;
|
2018-09-03 01:22:59 +02:00
|
|
|
import android.widget.TextView;
|
|
|
|
|
2023-01-05 19:01:38 +01:00
|
|
|
import androidx.annotation.Nullable;
|
2020-04-20 13:37:35 +02:00
|
|
|
import androidx.appcompat.app.AppCompatActivity;
|
2023-01-05 19:01:38 +01:00
|
|
|
import androidx.core.text.HtmlCompat;
|
2020-09-29 17:47:39 +02:00
|
|
|
|
2018-09-03 01:22:59 +02:00
|
|
|
import org.schabi.newpipe.R;
|
2021-12-01 09:43:24 +01:00
|
|
|
import org.schabi.newpipe.error.ErrorUtil;
|
2018-09-03 01:22:59 +02:00
|
|
|
import org.schabi.newpipe.extractor.InfoItem;
|
2023-01-05 19:01:38 +01:00
|
|
|
import org.schabi.newpipe.extractor.NewPipe;
|
|
|
|
import org.schabi.newpipe.extractor.ServiceList;
|
|
|
|
import org.schabi.newpipe.extractor.StreamingService;
|
2018-09-03 01:22:59 +02:00
|
|
|
import org.schabi.newpipe.extractor.comments.CommentsInfoItem;
|
2023-01-05 19:01:38 +01:00
|
|
|
import org.schabi.newpipe.extractor.exceptions.ExtractionException;
|
|
|
|
import org.schabi.newpipe.extractor.stream.Description;
|
2018-09-03 01:22:59 +02:00
|
|
|
import org.schabi.newpipe.info_list.InfoItemBuilder;
|
2019-08-14 11:42:39 +02:00
|
|
|
import org.schabi.newpipe.local.history.HistoryRecordManager;
|
2021-01-15 17:11:04 +01:00
|
|
|
import org.schabi.newpipe.util.DeviceUtils;
|
2019-10-28 03:35:51 +01:00
|
|
|
import org.schabi.newpipe.util.Localization;
|
2018-09-03 01:22:59 +02:00
|
|
|
import org.schabi.newpipe.util.NavigationHelper;
|
2021-03-27 14:37:44 +01:00
|
|
|
import org.schabi.newpipe.util.PicassoHelper;
|
2022-07-10 14:19:58 +02:00
|
|
|
import org.schabi.newpipe.util.external_communication.ShareUtils;
|
2023-01-05 19:01:38 +01:00
|
|
|
import org.schabi.newpipe.util.text.CommentTextOnTouchListener;
|
|
|
|
import org.schabi.newpipe.util.text.TextLinkifier;
|
2018-09-03 01:22:59 +02:00
|
|
|
|
2023-01-05 19:01:38 +01:00
|
|
|
import java.util.function.Consumer;
|
|
|
|
|
|
|
|
import io.reactivex.rxjava3.disposables.CompositeDisposable;
|
2019-03-02 00:42:06 +01:00
|
|
|
|
2018-09-03 01:22:59 +02:00
|
|
|
public class CommentsMiniInfoItemHolder extends InfoItemHolder {
|
2021-08-04 22:35:41 +02:00
|
|
|
private static final String TAG = "CommentsMiniIIHolder";
|
2023-01-05 19:01:38 +01:00
|
|
|
private static final String ELLIPSIS = "…";
|
2021-08-04 22:35:41 +02:00
|
|
|
|
2020-03-31 19:20:15 +02:00
|
|
|
private static final int COMMENT_DEFAULT_LINES = 2;
|
|
|
|
private static final int COMMENT_EXPANDED_LINES = 1000;
|
2021-08-06 22:08:29 +02:00
|
|
|
|
2020-09-29 17:47:39 +02:00
|
|
|
private final int commentHorizontalPadding;
|
|
|
|
private final int commentVerticalPadding;
|
2023-01-15 14:57:34 +01:00
|
|
|
|
|
|
|
private final Paint paintAtContentSize;
|
2023-01-05 19:01:38 +01:00
|
|
|
private final float ellipsisWidthPx;
|
2020-03-31 19:20:15 +02:00
|
|
|
|
2020-09-29 17:47:39 +02:00
|
|
|
private final RelativeLayout itemRoot;
|
2022-09-06 05:50:52 +02:00
|
|
|
private final ImageView itemThumbnailView;
|
2018-09-03 01:22:59 +02:00
|
|
|
private final TextView itemContentView;
|
|
|
|
private final TextView itemLikesCountView;
|
2018-12-08 16:02:28 +01:00
|
|
|
private final TextView itemPublishedTime;
|
2018-09-03 01:22:59 +02:00
|
|
|
|
2023-01-05 19:01:38 +01:00
|
|
|
private final CompositeDisposable disposables = new CompositeDisposable();
|
|
|
|
private Description commentText;
|
|
|
|
private StreamingService streamService;
|
2019-03-02 00:42:06 +01:00
|
|
|
private String streamUrl;
|
|
|
|
|
2020-03-31 19:20:15 +02:00
|
|
|
CommentsMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder, final int layoutId,
|
|
|
|
final ViewGroup parent) {
|
2018-09-03 01:22:59 +02:00
|
|
|
super(infoItemBuilder, layoutId, parent);
|
|
|
|
|
2020-09-29 17:47:39 +02:00
|
|
|
itemRoot = itemView.findViewById(R.id.itemRoot);
|
2018-09-03 01:22:59 +02:00
|
|
|
itemThumbnailView = itemView.findViewById(R.id.itemThumbnailView);
|
|
|
|
itemLikesCountView = itemView.findViewById(R.id.detail_thumbs_up_count_view);
|
2018-12-08 16:02:28 +01:00
|
|
|
itemPublishedTime = itemView.findViewById(R.id.itemPublishedTime);
|
2018-12-07 02:15:33 +01:00
|
|
|
itemContentView = itemView.findViewById(R.id.itemCommentContentView);
|
2020-09-29 17:47:39 +02:00
|
|
|
|
|
|
|
commentHorizontalPadding = (int) infoItemBuilder.getContext()
|
|
|
|
.getResources().getDimension(R.dimen.comments_horizontal_padding);
|
|
|
|
commentVerticalPadding = (int) infoItemBuilder.getContext()
|
|
|
|
.getResources().getDimension(R.dimen.comments_vertical_padding);
|
2023-01-05 19:01:38 +01:00
|
|
|
|
2023-01-15 14:57:34 +01:00
|
|
|
paintAtContentSize = new Paint();
|
|
|
|
paintAtContentSize.setTextSize(itemContentView.getTextSize());
|
|
|
|
ellipsisWidthPx = paintAtContentSize.measureText(ELLIPSIS);
|
2018-09-03 01:22:59 +02:00
|
|
|
}
|
|
|
|
|
2020-03-31 19:20:15 +02:00
|
|
|
public CommentsMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder,
|
|
|
|
final ViewGroup parent) {
|
2018-09-03 01:22:59 +02:00
|
|
|
this(infoItemBuilder, R.layout.list_comments_mini_item, parent);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2020-03-31 19:20:15 +02:00
|
|
|
public void updateFromItem(final InfoItem infoItem,
|
|
|
|
final HistoryRecordManager historyRecordManager) {
|
|
|
|
if (!(infoItem instanceof CommentsInfoItem)) {
|
|
|
|
return;
|
|
|
|
}
|
2018-09-03 01:22:59 +02:00
|
|
|
final CommentsInfoItem item = (CommentsInfoItem) infoItem;
|
|
|
|
|
2021-03-27 14:37:44 +01:00
|
|
|
PicassoHelper.loadAvatar(item.getUploaderAvatarUrl()).into(itemThumbnailView);
|
|
|
|
if (PicassoHelper.getShouldLoadImages()) {
|
2020-09-29 17:47:39 +02:00
|
|
|
itemThumbnailView.setVisibility(View.VISIBLE);
|
|
|
|
itemRoot.setPadding(commentVerticalPadding, commentVerticalPadding,
|
|
|
|
commentVerticalPadding, commentVerticalPadding);
|
|
|
|
} else {
|
|
|
|
itemThumbnailView.setVisibility(View.GONE);
|
|
|
|
itemRoot.setPadding(commentHorizontalPadding, commentVerticalPadding,
|
|
|
|
commentHorizontalPadding, commentVerticalPadding);
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2020-04-20 13:37:35 +02:00
|
|
|
itemThumbnailView.setOnClickListener(view -> openCommentAuthor(item));
|
2018-09-03 01:22:59 +02:00
|
|
|
|
2023-01-05 19:01:38 +01:00
|
|
|
try {
|
|
|
|
streamService = NewPipe.getService(item.getServiceId());
|
|
|
|
} catch (final ExtractionException e) {
|
|
|
|
// should never happen
|
|
|
|
ErrorUtil.showUiErrorSnackbar(itemBuilder.getContext(), "Getting StreamingService", e);
|
|
|
|
Log.w(TAG, "Cannot obtain service from comment service id, defaulting to YouTube", e);
|
|
|
|
streamService = ServiceList.YouTube;
|
|
|
|
}
|
2019-03-02 00:42:06 +01:00
|
|
|
streamUrl = item.getUrl();
|
2019-03-01 08:58:32 +01:00
|
|
|
commentText = item.getCommentText();
|
2023-01-05 19:01:38 +01:00
|
|
|
ellipsize();
|
2019-03-01 08:58:32 +01:00
|
|
|
|
2023-01-05 19:01:38 +01:00
|
|
|
//noinspection ClickableViewAccessibility
|
|
|
|
itemContentView.setOnTouchListener(CommentTextOnTouchListener.INSTANCE);
|
2018-12-07 02:15:33 +01:00
|
|
|
|
2019-10-28 03:35:51 +01:00
|
|
|
if (item.getLikeCount() >= 0) {
|
2021-06-03 14:40:00 +02:00
|
|
|
itemLikesCountView.setText(
|
|
|
|
Localization.shortCount(
|
|
|
|
itemBuilder.getContext(),
|
|
|
|
item.getLikeCount()));
|
2019-10-28 03:35:51 +01:00
|
|
|
} else {
|
|
|
|
itemLikesCountView.setText("-");
|
|
|
|
}
|
|
|
|
|
2020-04-21 20:08:47 +02:00
|
|
|
if (item.getUploadDate() != null) {
|
2020-11-19 14:05:17 +01:00
|
|
|
itemPublishedTime.setText(Localization.relativeTime(item.getUploadDate()
|
|
|
|
.offsetDateTime()));
|
2019-10-28 03:35:51 +01:00
|
|
|
} else {
|
2020-04-21 20:08:47 +02:00
|
|
|
itemPublishedTime.setText(item.getTextualUploadDate());
|
2018-09-03 01:22:59 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
itemView.setOnClickListener(view -> {
|
2019-03-01 08:58:32 +01:00
|
|
|
toggleEllipsize();
|
2018-09-03 01:22:59 +02:00
|
|
|
if (itemBuilder.getOnCommentsSelectedListener() != null) {
|
|
|
|
itemBuilder.getOnCommentsSelectedListener().selected(item);
|
|
|
|
}
|
|
|
|
});
|
2020-02-04 12:10:57 +01:00
|
|
|
|
|
|
|
|
2020-04-20 13:37:35 +02:00
|
|
|
itemView.setOnLongClickListener(view -> {
|
2020-07-21 00:43:49 +02:00
|
|
|
if (DeviceUtils.isTv(itemBuilder.getContext())) {
|
2020-04-20 13:37:35 +02:00
|
|
|
openCommentAuthor(item);
|
2020-06-11 18:36:05 +02:00
|
|
|
} else {
|
2023-01-05 19:01:38 +01:00
|
|
|
ShareUtils.copyToClipboard(itemBuilder.getContext(),
|
|
|
|
itemContentView.getText().toString());
|
2020-02-04 13:31:39 +01:00
|
|
|
}
|
2020-04-20 13:37:35 +02:00
|
|
|
return true;
|
2020-02-04 13:31:39 +01:00
|
|
|
});
|
2020-04-20 13:37:35 +02:00
|
|
|
}
|
2020-02-04 12:10:57 +01:00
|
|
|
|
2020-04-20 13:37:35 +02:00
|
|
|
private void openCommentAuthor(final CommentsInfoItem item) {
|
2020-05-03 11:54:37 +02:00
|
|
|
if (TextUtils.isEmpty(item.getUploaderUrl())) {
|
2020-04-20 13:37:35 +02:00
|
|
|
return;
|
|
|
|
}
|
2020-12-11 14:55:47 +01:00
|
|
|
final AppCompatActivity activity = (AppCompatActivity) itemBuilder.getContext();
|
2020-04-20 13:37:35 +02:00
|
|
|
try {
|
|
|
|
NavigationHelper.openChannelFragment(
|
|
|
|
activity.getSupportFragmentManager(),
|
|
|
|
item.getServiceId(),
|
2020-04-21 20:08:47 +02:00
|
|
|
item.getUploaderUrl(),
|
|
|
|
item.getUploaderName());
|
2020-08-16 10:24:58 +02:00
|
|
|
} catch (final Exception e) {
|
2021-12-01 09:43:24 +01:00
|
|
|
ErrorUtil.showUiErrorSnackbar(activity, "Opening channel fragment", e);
|
2020-04-20 13:37:35 +02:00
|
|
|
}
|
2018-09-03 01:22:59 +02:00
|
|
|
}
|
|
|
|
|
2019-10-09 12:09:07 +02:00
|
|
|
private void allowLinkFocus() {
|
2019-11-14 14:51:35 +01:00
|
|
|
itemContentView.setMovementMethod(LinkMovementMethod.getInstance());
|
|
|
|
}
|
|
|
|
|
|
|
|
private void denyLinkFocus() {
|
|
|
|
itemContentView.setMovementMethod(null);
|
|
|
|
}
|
|
|
|
|
|
|
|
private boolean shouldFocusLinks() {
|
2019-10-09 12:09:07 +02:00
|
|
|
if (itemView.isInTouchMode()) {
|
2019-11-14 14:51:35 +01:00
|
|
|
return false;
|
2019-10-09 12:09:07 +02:00
|
|
|
}
|
|
|
|
|
2020-08-16 10:24:58 +02:00
|
|
|
final URLSpan[] urls = itemContentView.getUrls();
|
2019-10-09 12:09:07 +02:00
|
|
|
|
2019-11-14 14:51:35 +01:00
|
|
|
return urls != null && urls.length != 0;
|
|
|
|
}
|
|
|
|
|
2023-01-05 19:01:38 +01:00
|
|
|
private void determineMovementMethod() {
|
2019-11-14 14:51:35 +01:00
|
|
|
if (shouldFocusLinks()) {
|
|
|
|
allowLinkFocus();
|
|
|
|
} else {
|
|
|
|
denyLinkFocus();
|
2019-10-09 12:09:07 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-03-01 08:58:32 +01:00
|
|
|
private void ellipsize() {
|
2023-01-15 14:57:34 +01:00
|
|
|
itemContentView.setMaxLines(COMMENT_EXPANDED_LINES);
|
2023-01-05 19:01:38 +01:00
|
|
|
linkifyCommentContentView(v -> {
|
|
|
|
boolean hasEllipsis = false;
|
2019-10-09 12:09:07 +02:00
|
|
|
|
2023-01-05 19:01:38 +01:00
|
|
|
if (itemContentView.getLineCount() > COMMENT_DEFAULT_LINES) {
|
2023-01-15 14:57:34 +01:00
|
|
|
// Note that converting to String removes spans (i.e. links), but that's something
|
|
|
|
// we actually want since when the text is ellipsized we want all clicks on the
|
|
|
|
// comment to expand the comment, not to open links.
|
|
|
|
final String text = itemContentView.getText().toString();
|
|
|
|
|
|
|
|
final Layout layout = itemContentView.getLayout();
|
|
|
|
final float lineWidth = layout.getLineWidth(COMMENT_DEFAULT_LINES - 1);
|
|
|
|
final float layoutWidth = layout.getWidth();
|
|
|
|
final int lineStart = layout.getLineStart(COMMENT_DEFAULT_LINES - 1);
|
|
|
|
final int lineEnd = layout.getLineEnd(COMMENT_DEFAULT_LINES - 1);
|
|
|
|
|
|
|
|
// remove characters up until there is enough space for the ellipsis
|
|
|
|
// (also summing 2 more pixels, just to be sure to avoid float rounding errors)
|
|
|
|
int end = lineEnd;
|
|
|
|
float removedCharactersWidth = 0.0f;
|
|
|
|
while (lineWidth - removedCharactersWidth + ellipsisWidthPx + 2.0f > layoutWidth
|
|
|
|
&& end >= lineStart) {
|
|
|
|
end -= 1;
|
|
|
|
// recalculate each time to account for ligatures or other similar things
|
|
|
|
removedCharactersWidth = paintAtContentSize.measureText(
|
|
|
|
text.substring(end, lineEnd));
|
|
|
|
}
|
|
|
|
|
|
|
|
// remove trailing spaces and newlines
|
|
|
|
while (end > 0 && Character.isWhitespace(text.charAt(end - 1))) {
|
|
|
|
end -= 1;
|
2023-01-05 19:01:38 +01:00
|
|
|
}
|
2023-01-15 14:57:34 +01:00
|
|
|
|
|
|
|
final String newVal = text.substring(0, end) + ELLIPSIS;
|
2023-01-05 19:01:38 +01:00
|
|
|
itemContentView.setText(newVal);
|
|
|
|
hasEllipsis = true;
|
|
|
|
}
|
2019-10-09 12:09:07 +02:00
|
|
|
|
2023-01-05 19:01:38 +01:00
|
|
|
itemContentView.setMaxLines(COMMENT_DEFAULT_LINES);
|
|
|
|
if (hasEllipsis) {
|
|
|
|
denyLinkFocus();
|
|
|
|
} else {
|
|
|
|
determineMovementMethod();
|
|
|
|
}
|
|
|
|
});
|
2019-03-01 08:58:32 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
private void toggleEllipsize() {
|
2023-01-05 19:01:38 +01:00
|
|
|
final CharSequence text = itemContentView.getText();
|
|
|
|
if (text.charAt(text.length() - 1) == ELLIPSIS.charAt(0)) {
|
2019-03-01 08:58:32 +01:00
|
|
|
expand();
|
2023-01-05 19:01:38 +01:00
|
|
|
} else if (itemContentView.getLineCount() > COMMENT_DEFAULT_LINES) {
|
|
|
|
ellipsize();
|
2018-09-03 01:22:59 +02:00
|
|
|
}
|
|
|
|
}
|
2019-03-01 08:58:32 +01:00
|
|
|
|
|
|
|
private void expand() {
|
2020-03-31 19:20:15 +02:00
|
|
|
itemContentView.setMaxLines(COMMENT_EXPANDED_LINES);
|
2023-01-05 19:01:38 +01:00
|
|
|
linkifyCommentContentView(v -> determineMovementMethod());
|
2019-03-01 08:58:32 +01:00
|
|
|
}
|
|
|
|
|
2023-01-05 19:01:38 +01:00
|
|
|
private void linkifyCommentContentView(@Nullable final Consumer<TextView> onCompletion) {
|
|
|
|
disposables.clear();
|
|
|
|
if (commentText != null) {
|
|
|
|
TextLinkifier.fromDescription(itemContentView, commentText,
|
|
|
|
HtmlCompat.FROM_HTML_MODE_LEGACY, streamService, streamUrl, disposables,
|
|
|
|
onCompletion);
|
|
|
|
}
|
2019-03-01 08:58:32 +01:00
|
|
|
}
|
2018-09-03 01:22:59 +02:00
|
|
|
}
|