Files
Alexander Vakrilov cc97a16800 feat: Scoped Packages (#7911)
* chore: move tns-core-modules to nativescript-core

* chore: preparing compat generate script

* chore: add missing definitions

* chore: no need for http-request to be private

* chore: packages chore

* test: generate tests for tns-core-modules

* chore: add anroid module for consistency

* chore: add .npmignore

* chore: added privateModulesWhitelist

* chore(webpack): added bundle-entry-points

* chore: scripts

* chore: tests changed to use @ns/core

* test: add scoped-packages test project

* test: fix types

* test: update test project

* chore: build scripts

* chore: update build script

* chore: npm scripts cleanup

* chore: make the compat pgk work with old wp config

* test: generate diff friendly tests

* chore: create barrel exports

* chore: move files after rebase

* chore: typedoc config

* chore: compat mode

* chore: review of barrels

* chore: remove tns-core-modules import after rebase

* chore: dev workflow setup

* chore: update developer-workflow

* docs: experiment with API extractor

* chore: api-extractor and barrel exports

* chore: api-extractor configs

* chore: generate d.ts rollup with api-extractor

* refactor: move methods inside Frame

* chore: fic tests to use Frame static methods

* refactor: create Builder class

* refactor: use Builder class in tests

* refactor: include Style in ui barrel

* chore: separate compat build script

* chore: fix tslint errors

* chore: update NATIVESCRIPT_CORE_ARGS

* chore: fix compat pack

* chore: fix ui-test-app build with linked modules

* chore: Application, ApplicationSettings, Connectivity and Http

* chore: export Trace, Profiling and Utils

* refactor: Static create methods for ImageSource

* chore: fix deprecated usages of ImageSource

* chore: move Span and FormattedString to ui

* chore: add events-args and ImageSource to index files

* chore: check for CLI >= 6.2 when building for IOS

* chore: update travis build

* chore: copy Pod file to compat package

* chore: update error msg ui-tests-app

* refactor: Apply suggestions from code review

Co-Authored-By: Martin Yankov <m.i.yankov@gmail.com>

* chore: typings and refs

* chore: add missing d.ts files for public API

* chore: adress code review FB

* chore: update api-report

* chore: dev-workflow for other apps

* chore: api update

* chore: update api-report
2019-10-17 00:45:33 +03:00

454 lines
18 KiB
TypeScript

import { TextDecoration, TextAlignment, TextTransform, WhiteSpace } from "./text-base";
import { Font } from "../styling/font";
import { backgroundColorProperty } from "../styling/style-properties";
import {
TextBaseCommon, formattedTextProperty, textAlignmentProperty, textDecorationProperty, fontSizeProperty,
textProperty, textTransformProperty, letterSpacingProperty, colorProperty, fontInternalProperty,
paddingLeftProperty, paddingTopProperty, paddingRightProperty, paddingBottomProperty, Length,
whiteSpaceProperty, lineHeightProperty, FormattedString, layout, Span, Color, isBold, resetSymbol
} from "./text-base-common";
import { isString } from "../../utils/types";
export * from "./text-base-common";
interface TextTransformation {
new(owner: TextBase): android.text.method.TransformationMethod;
}
let TextTransformation: TextTransformation;
function initializeTextTransformation(): void {
if (TextTransformation) {
return;
}
@Interfaces([android.text.method.TransformationMethod])
class TextTransformationImpl extends java.lang.Object implements android.text.method.TransformationMethod {
constructor(public textBase: TextBase) {
super();
return global.__native(this);
}
public getTransformation(charSeq: any, view: android.view.View): any {
// NOTE: Do we need to transform the new text here?
const formattedText = this.textBase.formattedText;
if (formattedText) {
return createSpannableStringBuilder(formattedText);
}
else {
const text = this.textBase.text;
const stringValue = (text === null || text === undefined) ? "" : text.toString();
return getTransformedText(stringValue, this.textBase.textTransform);
}
}
public onFocusChanged(view: android.view.View, sourceText: string, focused: boolean, direction: number, previouslyFocusedRect: android.graphics.Rect): void {
// Do nothing for now.
}
}
TextTransformation = TextTransformationImpl;
}
export class TextBase extends TextBaseCommon {
nativeViewProtected: android.widget.TextView;
nativeTextViewProtected: android.widget.TextView;
private _defaultTransformationMethod: android.text.method.TransformationMethod;
private _paintFlags: number;
private _minHeight: number;
private _maxHeight: number;
private _minLines: number;
private _maxLines: number;
public initNativeView(): void {
super.initNativeView();
initializeTextTransformation();
const nativeView = this.nativeTextViewProtected;
this._defaultTransformationMethod = nativeView.getTransformationMethod();
this._minHeight = nativeView.getMinHeight();
this._maxHeight = nativeView.getMaxHeight();
this._minLines = nativeView.getMinLines();
this._maxLines = nativeView.getMaxLines();
}
public resetNativeView(): void {
super.resetNativeView();
const nativeView = this.nativeTextViewProtected;
// We reset it here too because this could be changed by multiple properties - whiteSpace, secure, textTransform
nativeView.setSingleLine(this._isSingleLine);
nativeView.setTransformationMethod(this._defaultTransformationMethod);
this._defaultTransformationMethod = null;
if (this._paintFlags !== undefined) {
nativeView.setPaintFlags(this._paintFlags);
this._paintFlags = undefined;
}
if (this._minLines !== -1) {
nativeView.setMinLines(this._minLines);
} else {
nativeView.setMinHeight(this._minHeight);
}
this._minHeight = this._minLines = undefined;
if (this._maxLines !== -1) {
nativeView.setMaxLines(this._maxLines);
} else {
nativeView.setMaxHeight(this._maxHeight);
}
this._maxHeight = this._maxLines = undefined;
}
[textProperty.getDefault](): symbol | number {
return resetSymbol;
}
[textProperty.setNative](value: string | number | symbol) {
const reset = value === resetSymbol;
if (!reset && this.formattedText) {
return;
}
this._setNativeText(reset);
}
[formattedTextProperty.setNative](value: FormattedString) {
const nativeView = this.nativeTextViewProtected;
if (!value) {
if (nativeView instanceof android.widget.Button &&
nativeView.getTransformationMethod() instanceof TextTransformation) {
nativeView.setTransformationMethod(this._defaultTransformationMethod);
}
}
// Don't change the transformation method if this is secure TextField or we'll lose the hiding characters.
if ((<any>this).secure) {
return;
}
const spannableStringBuilder = createSpannableStringBuilder(value);
nativeView.setText(<any>spannableStringBuilder);
textProperty.nativeValueChange(this, (value === null || value === undefined) ? "" : value.toString());
if (spannableStringBuilder && nativeView instanceof android.widget.Button &&
!(nativeView.getTransformationMethod() instanceof TextTransformation)) {
// Replace Android Button's default transformation (in case the developer has not already specified a text-transform) method
// with our transformation method which can handle formatted text.
// Otherwise, the default tranformation method of the Android Button will overwrite and ignore our spannableStringBuilder.
nativeView.setTransformationMethod(new TextTransformation(this));
}
}
[textTransformProperty.setNative](value: TextTransform) {
if (value === "initial") {
this.nativeTextViewProtected.setTransformationMethod(this._defaultTransformationMethod);
return;
}
// Don't change the transformation method if this is secure TextField or we'll lose the hiding characters.
if ((<any>this).secure) {
return;
}
this.nativeTextViewProtected.setTransformationMethod(new TextTransformation(this));
}
[textAlignmentProperty.getDefault](): TextAlignment {
return "initial";
}
[textAlignmentProperty.setNative](value: TextAlignment) {
let verticalGravity = this.nativeTextViewProtected.getGravity() & android.view.Gravity.VERTICAL_GRAVITY_MASK;
switch (value) {
case "initial":
case "left":
this.nativeTextViewProtected.setGravity(android.view.Gravity.START | verticalGravity);
break;
case "center":
this.nativeTextViewProtected.setGravity(android.view.Gravity.CENTER_HORIZONTAL | verticalGravity);
break;
case "right":
this.nativeTextViewProtected.setGravity(android.view.Gravity.END | verticalGravity);
break;
}
}
// Overridden in TextField because setSingleLine(false) will remove methodTransformation.
// and we don't want to allow TextField to be multiline
[whiteSpaceProperty.setNative](value: WhiteSpace) {
const nativeView = this.nativeTextViewProtected;
switch (value) {
case "initial":
case "normal":
nativeView.setSingleLine(false);
nativeView.setEllipsize(null);
break;
case "nowrap":
nativeView.setSingleLine(true);
nativeView.setEllipsize(android.text.TextUtils.TruncateAt.END);
break;
}
}
[colorProperty.getDefault](): android.content.res.ColorStateList {
return this.nativeTextViewProtected.getTextColors();
}
[colorProperty.setNative](value: Color | android.content.res.ColorStateList) {
if (!this.formattedText || !(value instanceof Color)) {
if (value instanceof Color) {
this.nativeTextViewProtected.setTextColor(value.android);
} else {
this.nativeTextViewProtected.setTextColor(value);
}
}
}
[fontSizeProperty.getDefault](): { nativeSize: number } {
return { nativeSize: this.nativeTextViewProtected.getTextSize() };
}
[fontSizeProperty.setNative](value: number | { nativeSize: number }) {
if (!this.formattedText || (typeof value !== "number")) {
if (typeof value === "number") {
this.nativeTextViewProtected.setTextSize(value);
} else {
this.nativeTextViewProtected.setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, value.nativeSize);
}
}
}
[lineHeightProperty.getDefault](): number {
return this.nativeTextViewProtected.getLineSpacingExtra() / layout.getDisplayDensity();
}
[lineHeightProperty.setNative](value: number) {
this.nativeTextViewProtected.setLineSpacing(value * layout.getDisplayDensity(), 1);
}
[fontInternalProperty.getDefault](): android.graphics.Typeface {
return this.nativeTextViewProtected.getTypeface();
}
[fontInternalProperty.setNative](value: Font | android.graphics.Typeface) {
if (!this.formattedText || !(value instanceof Font)) {
this.nativeTextViewProtected.setTypeface(value instanceof Font ? value.getAndroidTypeface() : value);
}
}
[textDecorationProperty.getDefault](value: number) {
return this._paintFlags = this.nativeTextViewProtected.getPaintFlags();
}
[textDecorationProperty.setNative](value: number | TextDecoration) {
switch (value) {
case "none":
this.nativeTextViewProtected.setPaintFlags(0);
break;
case "underline":
this.nativeTextViewProtected.setPaintFlags(android.graphics.Paint.UNDERLINE_TEXT_FLAG);
break;
case "line-through":
this.nativeTextViewProtected.setPaintFlags(android.graphics.Paint.STRIKE_THRU_TEXT_FLAG);
break;
case "underline line-through":
this.nativeTextViewProtected.setPaintFlags(android.graphics.Paint.UNDERLINE_TEXT_FLAG | android.graphics.Paint.STRIKE_THRU_TEXT_FLAG);
break;
default:
this.nativeTextViewProtected.setPaintFlags(value);
break;
}
}
[letterSpacingProperty.getDefault](): number {
return org.nativescript.widgets.ViewHelper.getLetterspacing(this.nativeTextViewProtected);
}
[letterSpacingProperty.setNative](value: number) {
org.nativescript.widgets.ViewHelper.setLetterspacing(this.nativeTextViewProtected, value);
}
[paddingTopProperty.getDefault](): Length {
return { value: this._defaultPaddingTop, unit: "px" };
}
[paddingTopProperty.setNative](value: Length) {
org.nativescript.widgets.ViewHelper.setPaddingTop(this.nativeTextViewProtected, Length.toDevicePixels(value, 0) + Length.toDevicePixels(this.style.borderTopWidth, 0));
}
[paddingRightProperty.getDefault](): Length {
return { value: this._defaultPaddingRight, unit: "px" };
}
[paddingRightProperty.setNative](value: Length) {
org.nativescript.widgets.ViewHelper.setPaddingRight(this.nativeTextViewProtected, Length.toDevicePixels(value, 0) + Length.toDevicePixels(this.style.borderRightWidth, 0));
}
[paddingBottomProperty.getDefault](): Length {
return { value: this._defaultPaddingBottom, unit: "px" };
}
[paddingBottomProperty.setNative](value: Length) {
org.nativescript.widgets.ViewHelper.setPaddingBottom(this.nativeTextViewProtected, Length.toDevicePixels(value, 0) + Length.toDevicePixels(this.style.borderBottomWidth, 0));
}
[paddingLeftProperty.getDefault](): Length {
return { value: this._defaultPaddingLeft, unit: "px" };
}
[paddingLeftProperty.setNative](value: Length) {
org.nativescript.widgets.ViewHelper.setPaddingLeft(this.nativeTextViewProtected, Length.toDevicePixels(value, 0) + Length.toDevicePixels(this.style.borderLeftWidth, 0));
}
_setNativeText(reset: boolean = false): void {
if (reset) {
this.nativeTextViewProtected.setText(null);
return;
}
let transformedText: any;
if (this.formattedText) {
transformedText = createSpannableStringBuilder(this.formattedText);
} else {
const text = this.text;
const stringValue = (text === null || text === undefined) ? "" : text.toString();
transformedText = getTransformedText(stringValue, this.textTransform);
}
this.nativeTextViewProtected.setText(<any>transformedText);
}
}
function getCapitalizedString(str: string): string {
let words = str.split(" ");
let newWords = [];
for (let i = 0, length = words.length; i < length; i++) {
let word = words[i].toLowerCase();
newWords.push(word.substr(0, 1).toUpperCase() + word.substring(1));
}
return newWords.join(" ");
}
export function getTransformedText(text: string, textTransform: TextTransform): string {
if (!text || !isString(text)) {
return "";
}
switch (textTransform) {
case "uppercase":
return text.toUpperCase();
case "lowercase":
return text.toLowerCase();
case "capitalize":
return getCapitalizedString(text);
case "none":
default:
return text;
}
}
function createSpannableStringBuilder(formattedString: FormattedString): android.text.SpannableStringBuilder {
if (!formattedString || !formattedString.parent) {
return null;
}
const ssb = new android.text.SpannableStringBuilder();
for (let i = 0, spanStart = 0, spanLength = 0, length = formattedString.spans.length; i < length; i++) {
const span = formattedString.spans.getItem(i);
const text = span.text;
const textTransform = (<TextBase>formattedString.parent).textTransform;
let spanText = (text === null || text === undefined) ? "" : text.toString();
if (textTransform && textTransform !== "none") {
spanText = getTransformedText(spanText, textTransform);
}
spanLength = spanText.length;
if (spanLength > 0) {
ssb.insert(spanStart, spanText);
setSpanModifiers(ssb, span, spanStart, spanStart + spanLength);
spanStart += spanLength;
}
}
return ssb;
}
function setSpanModifiers(ssb: android.text.SpannableStringBuilder, span: Span, start: number, end: number): void {
const spanStyle = span.style;
const bold = isBold(spanStyle.fontWeight);
const italic = spanStyle.fontStyle === "italic";
if (bold && italic) {
ssb.setSpan(new android.text.style.StyleSpan(android.graphics.Typeface.BOLD_ITALIC), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
else if (bold) {
ssb.setSpan(new android.text.style.StyleSpan(android.graphics.Typeface.BOLD), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
else if (italic) {
ssb.setSpan(new android.text.style.StyleSpan(android.graphics.Typeface.ITALIC), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
const fontFamily = span.fontFamily;
if (fontFamily) {
const font = new Font(fontFamily, 0, (italic) ? "italic" : "normal", (bold) ? "bold" : "normal");
const typeface = font.getAndroidTypeface() || android.graphics.Typeface.create(fontFamily, 0);
const typefaceSpan: android.text.style.TypefaceSpan = new org.nativescript.widgets.CustomTypefaceSpan(fontFamily, typeface);
ssb.setSpan(typefaceSpan, start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
const realFontSize = span.fontSize;
if (realFontSize) {
ssb.setSpan(new android.text.style.AbsoluteSizeSpan(realFontSize * layout.getDisplayDensity()), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
const color = span.color;
if (color) {
ssb.setSpan(new android.text.style.ForegroundColorSpan(color.android), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
let backgroundColor: Color;
if (backgroundColorProperty.isSet(spanStyle)) {
backgroundColor = spanStyle.backgroundColor;
} else if (backgroundColorProperty.isSet(span.parent.style)) {
// parent is FormattedString
backgroundColor = span.parent.style.backgroundColor;
} else if (backgroundColorProperty.isSet(span.parent.parent.style)) {
// parent.parent is TextBase
backgroundColor = span.parent.parent.style.backgroundColor;
}
if (backgroundColor) {
ssb.setSpan(new android.text.style.BackgroundColorSpan(backgroundColor.android), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
let valueSource: typeof spanStyle;
if (textDecorationProperty.isSet(spanStyle)) {
valueSource = spanStyle;
} else if (textDecorationProperty.isSet(span.parent.style)) {
// span.parent is FormattedString
valueSource = span.parent.style;
} else if (textDecorationProperty.isSet(span.parent.parent.style)) {
// span.parent.parent is TextBase
valueSource = span.parent.parent.style;
}
if (valueSource) {
const textDecorations = valueSource.textDecoration;
const underline = textDecorations.indexOf("underline") !== -1;
if (underline) {
ssb.setSpan(new android.text.style.UnderlineSpan(), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
const strikethrough = textDecorations.indexOf("line-through") !== -1;
if (strikethrough) {
ssb.setSpan(new android.text.style.StrikethroughSpan(), start, end, android.text.Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
// TODO: Implement letterSpacing for Span here.
// const letterSpacing = formattedString.parent.style.letterSpacing;
// if (letterSpacing > 0) {
// ssb.setSpan(new android.text.style.ScaleXSpan((letterSpacing + 1) / 10), start, end, android.text.Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
// }
}