Skip to content

Commit fea5249

Browse files
committed
fix(android): faster formattedText / html creation
1 parent 15f747c commit fea5249

File tree

1 file changed

+67
-82
lines changed

1 file changed

+67
-82
lines changed

src/label.android.ts

Lines changed: 67 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ export { createNativeAttributedString } from '@nativescript-community/text';
5959
export * from './label-common';
6060
const sdkVersion = lazy(() => parseInt(Device.sdkVersion, 10));
6161

62-
let TextView: typeof com.nativescript.label.EllipsizingTextView;
62+
let TextView: typeof com.nativescript.text.TextView;
6363

6464
const CHILD_FORMATTED_TEXT = 'formattedText';
6565

@@ -140,53 +140,6 @@ function initializeClickableSpan(): void {
140140
ClickableSpan = ClickableSpanImpl;
141141
}
142142

143-
type URLClickableSpan = new (url: string, owner: Label) => android.text.style.URLSpan;
144-
145-
// eslint-disable-next-line no-redeclare
146-
let URLClickableSpan: URLClickableSpan;
147-
148-
function initializeURLClickableSpan(): void {
149-
if (URLClickableSpan) {
150-
return;
151-
}
152-
153-
@NativeClass
154-
class URLClickableSpanImpl extends android.text.style.URLSpan {
155-
owner: WeakRef<Label>;
156-
constructor(url: string, owner: Label) {
157-
super(url);
158-
this.owner = new WeakRef(owner);
159-
160-
return global.__native(this);
161-
}
162-
onClick(view: android.view.View): void {
163-
const owner = this.owner.get();
164-
if (owner) {
165-
owner.notify({ eventName: Span.linkTapEvent, object: owner, link: this.getURL() });
166-
}
167-
168-
view.clearFocus();
169-
view.invalidate();
170-
}
171-
updateDrawState(tp: android.text.TextPaint): void {
172-
const owner = this.owner.get();
173-
super.updateDrawState(tp);
174-
175-
if (owner) {
176-
if (owner.linkUnderline === false) {
177-
tp.setUnderlineText(false);
178-
}
179-
if (owner.linkColor) {
180-
const color = owner.linkColor instanceof Color ? owner.linkColor : new Color(owner.linkColor);
181-
tp.setColor(color.android);
182-
}
183-
}
184-
}
185-
}
186-
187-
URLClickableSpan = URLClickableSpanImpl;
188-
}
189-
190143
@CSSType('HTMLLabel')
191144
abstract class LabelBase extends View implements LabelViewDefinition {
192145
//@ts-ignore
@@ -310,7 +263,7 @@ abstract class LabelBase extends View implements LabelViewDefinition {
310263
}
311264

312265
export class Label extends LabelBase {
313-
nativeViewProtected: com.nativescript.label.EllipsizingTextView;
266+
nativeViewProtected: com.nativescript.text.TextView;
314267
mHandleFontSize = true;
315268
mTappable = false;
316269
private mAutoFontSize = false;
@@ -323,10 +276,18 @@ export class Label extends LabelBase {
323276
@profile
324277
public createNativeView() {
325278
if (!TextView) {
326-
TextView = androidx.appcompat.widget.AppCompatTextView;
279+
TextView = com.nativescript.text.TextView;
327280
}
328281
return new TextView(this._context);
329282
}
283+
urlSpanClickListener: com.nativescript.text.URLSpanClickListener;
284+
public disposeNativeView() {
285+
super.disposeNativeView();
286+
this.nativeTextViewProtected.urlSpanClickListener = null;
287+
if (this.urlSpanClickListener) {
288+
this.urlSpanClickListener = null;
289+
}
290+
}
330291

331292
[maxLinesProperty.setNative](value: number | string) {
332293
// this.nativeViewProtected.setMinLines(1);
@@ -573,23 +534,25 @@ export class Label extends LabelBase {
573534
createFormattedTextNative(value: any) {
574535
const result = createNativeAttributedString(value, this, this.autoFontSize);
575536

576-
let indexSearch = 0;
537+
// let indexSearch = 0;
577538
let str: string;
578-
value.spans.forEach((s) => {
579-
if (s.tappable) {
580-
if (!str) {
581-
str = value.toString();
582-
this._setTappableState(true);
583-
}
584-
initializeClickableSpan();
585-
const text = s.text;
586-
const start = str.indexOf(text, indexSearch);
587-
if (start !== -1) {
588-
indexSearch = start + text.length;
589-
result.setSpan(new ClickableSpan(s), start, indexSearch, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
590-
}
591-
}
592-
});
539+
// const spans = value.spans;
540+
this._setTappableState(value.spans.some((s) => s.tappable));
541+
// value.spans.forEach((s) => {
542+
// if (s.tappable) {
543+
// if (!str) {
544+
// // str = value.toString();
545+
// this._setTappableState(true);
546+
// }
547+
// // initializeClickableSpan();
548+
// // const text = s.text;
549+
// // const start = str.indexOf(text, indexSearch);
550+
// // if (start !== -1) {
551+
// // indexSearch = start + text.length;
552+
// // result.setSpan(new ClickableSpan(s), start, indexSearch, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
553+
// // }
554+
// }
555+
// });
593556
return result;
594557
}
595558
@profile
@@ -603,33 +566,55 @@ export class Label extends LabelBase {
603566
// no need to check for urlspan if we dont have a listener
604567
// the only issue might happen if the listener is set afterward. Is that really an issue?
605568
// if (this.linkColor || this.linkUnderline this.hasListeners(Span.linkTapEvent)) {
606-
const urlSpan = result.getSpans(0, result.length(), android.text.style.URLSpan.class);
607-
if (urlSpan.length > 0) {
608-
this._setTappableState(true);
609-
initializeURLClickableSpan();
610-
for (let index = 0; index < urlSpan.length; index++) {
611-
const span = urlSpan[index];
612-
const text = span.getURL();
613-
const start = result.getSpanStart(span);
614-
const end = result.getSpanEnd(span);
615-
result.removeSpan(span);
616-
result.setSpan(new URLClickableSpan(text, this), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
617-
}
618-
}
569+
// const urlSpan = result.getSpans(0, result.length(), android.text.style.URLSpan.class);
570+
// if (urlSpan.length > 0) {
571+
this._setTappableState(TextView.attributedStringHasSpan(result, android.text.style.URLSpan.class));
572+
// initializeURLClickableSpan();
573+
// for (let index = 0; index < urlSpan.length; index++) {
574+
// const span = urlSpan[index];
575+
// const text = span.getURL();
576+
// const start = result.getSpanStart(span);
577+
// const end = result.getSpanEnd(span);
578+
// result.removeSpan(span);
579+
// result.setSpan(new URLClickableSpan(text, this), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
580+
// }
581+
// }
619582
// }
620583
return result;
621584
}
622585
_setTappableState(tappable: boolean) {
623586
if (this.mTappable !== tappable) {
624587
this.mTappable = tappable;
588+
const nativeView = this.nativeTextViewProtected;
625589
if (this.mTappable) {
626-
this.nativeViewProtected.setMovementMethod(android.text.method.LinkMovementMethod.getInstance());
627-
this.nativeViewProtected.setHighlightColor(null);
590+
nativeView.setMovementMethod(android.text.method.LinkMovementMethod.getInstance());
591+
nativeView.setHighlightColor(null);
592+
if (!this.urlSpanClickListener) {
593+
this.urlSpanClickListener = new com.nativescript.text.URLSpanClickListener({
594+
onClick: this.onURLClick.bind(this)
595+
});
596+
}
597+
nativeView.urlSpanClickListener = this.urlSpanClickListener;
628598
} else {
629-
this.nativeViewProtected.setMovementMethod(this.mDefaultMovementMethod);
599+
nativeView.setMovementMethod(this.mDefaultMovementMethod);
600+
nativeView.urlSpanClickListener = null;
630601
}
631602
}
632603
}
604+
onURLClick(span: android.text.style.URLSpan) {
605+
const link = span.getURL();
606+
if (this.formattedText) {
607+
const spanIndex = parseInt(link, 10);
608+
if (!isNaN(spanIndex) && spanIndex >= 0 && spanIndex < this.formattedText?.spans.length) {
609+
this.formattedText?.spans.getItem(spanIndex).notify({ eventName: Span.linkTapEvent });
610+
}
611+
}
612+
this.notify({ eventName: Span.linkTapEvent, link });
613+
// const nativeView = this.nativeTextViewProtected;
614+
615+
// nativeView.clearFocus();
616+
// nativeView.invalidate();
617+
}
633618

634619
@profile
635620
_setNativeText(reset: boolean = false): void {

0 commit comments

Comments
 (0)