11package org .schabi .newpipe .info_list .holder ;
22
3+ import android .graphics .Paint ;
4+ import android .text .Layout ;
35import android .text .TextUtils ;
46import android .text .method .LinkMovementMethod ;
57import android .text .style .URLSpan ;
6- import android .text .util .Linkify ;
78import android .util .Log ;
89import android .view .View ;
910import android .view .ViewGroup ;
1011import android .widget .ImageView ;
1112import android .widget .RelativeLayout ;
1213import android .widget .TextView ;
1314
15+ import androidx .annotation .Nullable ;
1416import androidx .appcompat .app .AppCompatActivity ;
15- import androidx .core .text .util . LinkifyCompat ;
17+ import androidx .core .text .HtmlCompat ;
1618
1719import org .schabi .newpipe .R ;
1820import org .schabi .newpipe .error .ErrorUtil ;
1921import org .schabi .newpipe .extractor .InfoItem ;
22+ import org .schabi .newpipe .extractor .NewPipe ;
23+ import org .schabi .newpipe .extractor .ServiceList ;
24+ import org .schabi .newpipe .extractor .StreamingService ;
2025import org .schabi .newpipe .extractor .comments .CommentsInfoItem ;
26+ import org .schabi .newpipe .extractor .exceptions .ExtractionException ;
27+ import org .schabi .newpipe .extractor .stream .Description ;
2128import org .schabi .newpipe .info_list .InfoItemBuilder ;
2229import org .schabi .newpipe .local .history .HistoryRecordManager ;
23- import org .schabi .newpipe .util .text .CommentTextOnTouchListener ;
2430import org .schabi .newpipe .util .DeviceUtils ;
2531import org .schabi .newpipe .util .Localization ;
2632import org .schabi .newpipe .util .NavigationHelper ;
2733import org .schabi .newpipe .util .PicassoHelper ;
2834import org .schabi .newpipe .util .external_communication .ShareUtils ;
29- import org .schabi .newpipe .util .text .TimestampExtractor ;
35+ import org .schabi .newpipe .util .text .CommentTextOnTouchListener ;
36+ import org .schabi .newpipe .util .text .TextLinkifier ;
3037
31- import java .util .Objects ;
38+ import java .util .function .Consumer ;
39+
40+ import io .reactivex .rxjava3 .disposables .CompositeDisposable ;
3241
3342public class CommentsMiniInfoItemHolder extends InfoItemHolder {
3443 private static final String TAG = "CommentsMiniIIHolder" ;
44+ private static final String ELLIPSIS = "…" ;
3545
3646 private static final int COMMENT_DEFAULT_LINES = 2 ;
3747 private static final int COMMENT_EXPANDED_LINES = 1000 ;
3848
3949 private final int commentHorizontalPadding ;
4050 private final int commentVerticalPadding ;
51+ private final float ellipsisWidthPx ;
4152
4253 private final RelativeLayout itemRoot ;
4354 private final ImageView itemThumbnailView ;
4455 private final TextView itemContentView ;
4556 private final TextView itemLikesCountView ;
4657 private final TextView itemPublishedTime ;
4758
48- private String commentText ;
59+ private final CompositeDisposable disposables = new CompositeDisposable ();
60+ private Description commentText ;
61+ private StreamingService streamService ;
4962 private String streamUrl ;
5063
5164 CommentsMiniInfoItemHolder (final InfoItemBuilder infoItemBuilder , final int layoutId ,
@@ -62,6 +75,10 @@ public class CommentsMiniInfoItemHolder extends InfoItemHolder {
6275 .getResources ().getDimension (R .dimen .comments_horizontal_padding );
6376 commentVerticalPadding = (int ) infoItemBuilder .getContext ()
6477 .getResources ().getDimension (R .dimen .comments_vertical_padding );
78+
79+ final Paint paint = new Paint ();
80+ paint .setTextSize (itemContentView .getTextSize ());
81+ ellipsisWidthPx = paint .measureText (ELLIPSIS );
6582 }
6683
6784 public CommentsMiniInfoItemHolder (final InfoItemBuilder infoItemBuilder ,
@@ -91,18 +108,20 @@ public void updateFromItem(final InfoItem infoItem,
91108
92109 itemThumbnailView .setOnClickListener (view -> openCommentAuthor (item ));
93110
111+ try {
112+ streamService = NewPipe .getService (item .getServiceId ());
113+ } catch (final ExtractionException e ) {
114+ // should never happen
115+ ErrorUtil .showUiErrorSnackbar (itemBuilder .getContext (), "Getting StreamingService" , e );
116+ Log .w (TAG , "Cannot obtain service from comment service id, defaulting to YouTube" , e );
117+ streamService = ServiceList .YouTube ;
118+ }
94119 streamUrl = item .getUrl ();
95-
96- itemContentView .setLines (COMMENT_DEFAULT_LINES );
97120 commentText = item .getCommentText ();
98- itemContentView .setText (commentText , TextView .BufferType .SPANNABLE );
99- itemContentView .setOnTouchListener (CommentTextOnTouchListener .INSTANCE );
121+ ellipsize ();
100122
101- if (itemContentView .getLineCount () == 0 ) {
102- itemContentView .post (this ::ellipsize );
103- } else {
104- ellipsize ();
105- }
123+ //noinspection ClickableViewAccessibility
124+ itemContentView .setOnTouchListener (CommentTextOnTouchListener .INSTANCE );
106125
107126 if (item .getLikeCount () >= 0 ) {
108127 itemLikesCountView .setText (
@@ -132,7 +151,8 @@ public void updateFromItem(final InfoItem infoItem,
132151 if (DeviceUtils .isTv (itemBuilder .getContext ())) {
133152 openCommentAuthor (item );
134153 } else {
135- ShareUtils .copyToClipboard (itemBuilder .getContext (), commentText );
154+ ShareUtils .copyToClipboard (itemBuilder .getContext (),
155+ itemContentView .getText ().toString ());
136156 }
137157 return true ;
138158 });
@@ -172,7 +192,7 @@ private boolean shouldFocusLinks() {
172192 return urls != null && urls .length != 0 ;
173193 }
174194
175- private void determineLinkFocus () {
195+ private void determineMovementMethod () {
176196 if (shouldFocusLinks ()) {
177197 allowLinkFocus ();
178198 } else {
@@ -181,63 +201,51 @@ private void determineLinkFocus() {
181201 }
182202
183203 private void ellipsize () {
184- boolean hasEllipsis = false ;
185-
186- if (itemContentView .getLineCount () > COMMENT_DEFAULT_LINES ) {
187- final int endOfLastLine = itemContentView
188- .getLayout ()
189- .getLineEnd (COMMENT_DEFAULT_LINES - 1 );
190- int end = itemContentView .getText ().toString ().lastIndexOf (' ' , endOfLastLine - 2 );
191- if (end == -1 ) {
192- end = Math .max (endOfLastLine - 2 , 0 );
193- }
194- final String newVal = itemContentView .getText ().subSequence (0 , end ) + " …" ;
195- itemContentView .setText (newVal );
196- hasEllipsis = true ;
197- }
204+ linkifyCommentContentView (v -> {
205+ boolean hasEllipsis = false ;
198206
199- linkify ();
207+ if (itemContentView .getLineCount () > COMMENT_DEFAULT_LINES ) {
208+ final int endOfLastLine = itemContentView
209+ .getLayout ()
210+ .getLineEnd (COMMENT_DEFAULT_LINES - 1 );
211+ int end = itemContentView .getText ().toString ().lastIndexOf (' ' , endOfLastLine - 2 );
212+ if (end == -1 ) {
213+ end = Math .max (endOfLastLine - 2 , 0 );
214+ }
215+ final String newVal = itemContentView .getText ().subSequence (0 , end ) + " …" ;
216+ itemContentView .setText (newVal );
217+ hasEllipsis = true ;
218+ }
200219
201- if (hasEllipsis ) {
202- denyLinkFocus ();
203- } else {
204- determineLinkFocus ();
205- }
220+ itemContentView .setMaxLines (COMMENT_DEFAULT_LINES );
221+ if (hasEllipsis ) {
222+ denyLinkFocus ();
223+ } else {
224+ determineMovementMethod ();
225+ }
226+ });
206227 }
207228
208229 private void toggleEllipsize () {
209- if (itemContentView .getText ().toString ().equals (commentText )) {
210- if (itemContentView .getLineCount () > COMMENT_DEFAULT_LINES ) {
211- ellipsize ();
212- }
213- } else {
230+ final CharSequence text = itemContentView .getText ();
231+ if (text .charAt (text .length () - 1 ) == ELLIPSIS .charAt (0 )) {
214232 expand ();
233+ } else if (itemContentView .getLineCount () > COMMENT_DEFAULT_LINES ) {
234+ ellipsize ();
215235 }
216236 }
217237
218238 private void expand () {
219239 itemContentView .setMaxLines (COMMENT_EXPANDED_LINES );
220- itemContentView .setText (commentText );
221- linkify ();
222- determineLinkFocus ();
240+ linkifyCommentContentView (v -> determineMovementMethod ());
223241 }
224242
225- private void linkify () {
226- LinkifyCompat .addLinks (itemContentView , Linkify .WEB_URLS );
227- LinkifyCompat .addLinks (itemContentView , TimestampExtractor .TIMESTAMPS_PATTERN , null , null ,
228- (match , url ) -> {
229- try {
230- final var timestampMatch = TimestampExtractor
231- .getTimestampFromMatcher (match , commentText );
232- if (timestampMatch == null ) {
233- return url ;
234- }
235- return streamUrl + url .replace (Objects .requireNonNull (match .group (0 )),
236- "#timestamp=" + timestampMatch .seconds ());
237- } catch (final Exception ex ) {
238- Log .e (TAG , "Unable to process url='" + url + "' as timestampLink" , ex );
239- return url ;
240- }
241- });
243+ private void linkifyCommentContentView (@ Nullable final Consumer <TextView > onCompletion ) {
244+ disposables .clear ();
245+ if (commentText != null ) {
246+ TextLinkifier .fromDescription (itemContentView , commentText ,
247+ HtmlCompat .FROM_HTML_MODE_LEGACY , streamService , streamUrl , disposables ,
248+ onCompletion );
249+ }
242250 }
243251}
0 commit comments