NewPipe/app/src/main/java/org/schabi/newpipe/info_list/holder/CommentsMiniInfoItemHolder....

280 lines
11 KiB
Java
Raw Normal View History

2018-09-03 01:22:59 +02:00
package org.schabi.newpipe.info_list.holder;
2023-02-10 06:40:43 +01:00
import static android.text.TextUtils.isEmpty;
import android.graphics.Paint;
import android.text.Layout;
import android.text.method.LinkMovementMethod;
import android.text.style.URLSpan;
import android.util.Log;
import android.view.View;
2018-09-03 01:22:59 +02:00
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.RelativeLayout;
2018-09-03 01:22:59 +02:00
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.text.HtmlCompat;
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;
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;
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;
import org.schabi.newpipe.local.history.HistoryRecordManager;
import org.schabi.newpipe.util.DeviceUtils;
import org.schabi.newpipe.util.Localization;
2018-09-03 01:22:59 +02:00
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.text.CommentTextOnTouchListener;
import org.schabi.newpipe.util.text.TextLinkifier;
2018-09-03 01:22:59 +02: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 {
private static final String TAG = "CommentsMiniIIHolder";
private static final String ELLIPSIS = "";
private static final int COMMENT_DEFAULT_LINES = 2;
private static final int COMMENT_EXPANDED_LINES = 1000;
private final int commentHorizontalPadding;
private final int commentVerticalPadding;
2023-01-15 14:57:34 +01:00
private final Paint paintAtContentSize;
private final float ellipsisWidthPx;
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;
private final TextView itemPublishedTime;
2018-09-03 01:22:59 +02:00
private final CompositeDisposable disposables = new CompositeDisposable();
2023-02-10 06:40:43 +01:00
@Nullable private Description commentText;
@Nullable private StreamingService streamService;
@Nullable private String streamUrl;
2019-03-02 00:42:06 +01:00
CommentsMiniInfoItemHolder(final InfoItemBuilder infoItemBuilder, final int layoutId,
final ViewGroup parent) {
2018-09-03 01:22:59 +02:00
super(infoItemBuilder, layoutId, parent);
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);
itemPublishedTime = itemView.findViewById(R.id.itemPublishedTime);
2018-12-07 02:15:33 +01:00
itemContentView = itemView.findViewById(R.id.itemCommentContentView);
commentHorizontalPadding = (int) infoItemBuilder.getContext()
.getResources().getDimension(R.dimen.comments_horizontal_padding);
commentVerticalPadding = (int) infoItemBuilder.getContext()
.getResources().getDimension(R.dimen.comments_vertical_padding);
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
}
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
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;
PicassoHelper.loadAvatar(item.getUploaderAvatarUrl()).into(itemThumbnailView);
if (PicassoHelper.getShouldLoadImages()) {
itemThumbnailView.setVisibility(View.VISIBLE);
itemRoot.setPadding(commentVerticalPadding, commentVerticalPadding,
commentVerticalPadding, commentVerticalPadding);
} else {
itemThumbnailView.setVisibility(View.GONE);
itemRoot.setPadding(commentHorizontalPadding, commentVerticalPadding,
commentHorizontalPadding, commentVerticalPadding);
}
itemThumbnailView.setOnClickListener(view -> openCommentAuthor(item));
2018-09-03 01:22:59 +02: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();
commentText = item.getCommentText();
ellipsize();
//noinspection ClickableViewAccessibility
itemContentView.setOnTouchListener(CommentTextOnTouchListener.INSTANCE);
2018-12-07 02:15:33 +01:00
if (item.getLikeCount() >= 0) {
itemLikesCountView.setText(
Localization.shortCount(
itemBuilder.getContext(),
item.getLikeCount()));
} else {
itemLikesCountView.setText("-");
}
if (item.getUploadDate() != null) {
itemPublishedTime.setText(Localization.relativeTime(item.getUploadDate()
.offsetDateTime()));
} else {
itemPublishedTime.setText(item.getTextualUploadDate());
2018-09-03 01:22:59 +02:00
}
itemView.setOnClickListener(view -> {
toggleEllipsize();
2018-09-03 01:22:59 +02:00
if (itemBuilder.getOnCommentsSelectedListener() != null) {
itemBuilder.getOnCommentsSelectedListener().selected(item);
}
});
itemView.setOnLongClickListener(view -> {
2020-07-21 00:43:49 +02:00
if (DeviceUtils.isTv(itemBuilder.getContext())) {
openCommentAuthor(item);
2020-06-11 18:36:05 +02:00
} else {
2023-02-10 06:40:43 +01:00
final CharSequence text = itemContentView.getText();
if (text != null) {
ShareUtils.copyToClipboard(itemBuilder.getContext(), text.toString());
}
2020-02-04 13:31:39 +01:00
}
return true;
2020-02-04 13:31:39 +01:00
});
}
private void openCommentAuthor(final CommentsInfoItem item) {
2023-02-10 06:40:43 +01:00
if (isEmpty(item.getUploaderUrl())) {
return;
}
final AppCompatActivity activity = (AppCompatActivity) itemBuilder.getContext();
try {
NavigationHelper.openChannelFragment(
activity.getSupportFragmentManager(),
item.getServiceId(),
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);
}
2018-09-03 01:22:59 +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() {
if (itemView.isInTouchMode()) {
2019-11-14 14:51:35 +01:00
return false;
}
2020-08-16 10:24:58 +02:00
final URLSpan[] urls = itemContentView.getUrls();
2019-11-14 14:51:35 +01:00
return urls != null && urls.length != 0;
}
private void determineMovementMethod() {
2019-11-14 14:51:35 +01:00
if (shouldFocusLinks()) {
allowLinkFocus();
} else {
denyLinkFocus();
}
}
private void ellipsize() {
2023-01-15 14:57:34 +01:00
itemContentView.setMaxLines(COMMENT_EXPANDED_LINES);
linkifyCommentContentView(v -> {
boolean hasEllipsis = false;
2023-02-10 06:40:43 +01:00
final CharSequence charSeqText = itemContentView.getText();
if (charSeqText != null && 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.
2023-02-10 06:40:43 +01:00
final String text = charSeqText.toString();
2023-01-15 14:57:34 +01:00
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-15 14:57:34 +01:00
final String newVal = text.substring(0, end) + ELLIPSIS;
itemContentView.setText(newVal);
hasEllipsis = true;
}
itemContentView.setMaxLines(COMMENT_DEFAULT_LINES);
if (hasEllipsis) {
denyLinkFocus();
} else {
determineMovementMethod();
}
});
}
private void toggleEllipsize() {
final CharSequence text = itemContentView.getText();
2023-02-10 06:40:43 +01:00
if (!isEmpty(text) && text.charAt(text.length() - 1) == ELLIPSIS.charAt(0)) {
expand();
} else if (itemContentView.getLineCount() > COMMENT_DEFAULT_LINES) {
ellipsize();
2018-09-03 01:22:59 +02:00
}
}
private void expand() {
itemContentView.setMaxLines(COMMENT_EXPANDED_LINES);
linkifyCommentContentView(v -> determineMovementMethod());
}
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);
}
}
2018-09-03 01:22:59 +02:00
}