diff --git a/api-reports/NativeScript.api.md b/api-reports/NativeScript.api.md index d2d7d93604..3c1d17ad55 100644 --- a/api-reports/NativeScript.api.md +++ b/api-reports/NativeScript.api.md @@ -413,6 +413,7 @@ export class Color { public b: number; public equals(value: Color): boolean; public static equals(value1: Color, value2: Color): boolean; + public static fromIosColor(value: any /* UIColor */): Color; public g: number; public hex: string; ios: any /* UIColor */; diff --git a/e2e/ui-tests-app/app/css/all-non-uniform-border-page.css b/e2e/ui-tests-app/app/css/all-non-uniform-border-page.css index 3529e9d294..a0f54831f2 100644 --- a/e2e/ui-tests-app/app/css/all-non-uniform-border-page.css +++ b/e2e/ui-tests-app/app/css/all-non-uniform-border-page.css @@ -5,3 +5,10 @@ height: 80; font-size: 6; } + +.html-view { + font-size: 10; + link-color: red; + color: green; + font-family: 'Courier New', Courier, monospace; +} diff --git a/e2e/ui-tests-app/app/css/all-non-uniform-border-page.xml b/e2e/ui-tests-app/app/css/all-non-uniform-border-page.xml index 64efc27d91..ed2837afdf 100644 --- a/e2e/ui-tests-app/app/css/all-non-uniform-border-page.xml +++ b/e2e/ui-tests-app/app/css/all-non-uniform-border-page.xml @@ -11,7 +11,7 @@ - + diff --git a/nativescript-core/color/color-common.ts b/nativescript-core/color/color-common.ts index 0a8e12fb42..d929e50558 100644 --- a/nativescript-core/color/color-common.ts +++ b/nativescript-core/color/color-common.ts @@ -157,6 +157,10 @@ export class Color implements definition.Color { public toString(): string { return this.hex; } + + public static fromIosColor(value: UIColor): Color { + return undefined; + } } function isRgbOrRgba(value: string): boolean { diff --git a/nativescript-core/color/color.d.ts b/nativescript-core/color/color.d.ts index ee75d7941a..cb6712cf38 100644 --- a/nativescript-core/color/color.d.ts +++ b/nativescript-core/color/color.d.ts @@ -75,4 +75,9 @@ export class Color { * @param value Input string. */ public static isValid(value: any): boolean; + + /** + * Creates color from iOS-specific UIColor value representation. + */ + public static fromIosColor(value: any /* UIColor */): Color; } diff --git a/nativescript-core/color/color.ios.ts b/nativescript-core/color/color.ios.ts index a0aad04377..648a3759f9 100644 --- a/nativescript-core/color/color.ios.ts +++ b/nativescript-core/color/color.ios.ts @@ -11,4 +11,15 @@ export class Color extends common.Color { return this._ios; } + + public static fromIosColor(value: UIColor): Color { + const rgba = CGColorGetComponents(value.CGColor); + + return new Color( + Math.round(rgba[3] * 255), + Math.round(rgba[0] * 255), + Math.round(rgba[1] * 255), + Math.round(rgba[2] * 255), + ); + } } diff --git a/nativescript-core/ui/html-view/html-view-common.ts b/nativescript-core/ui/html-view/html-view-common.ts index e6c564e865..9c7df2d504 100644 --- a/nativescript-core/ui/html-view/html-view-common.ts +++ b/nativescript-core/ui/html-view/html-view-common.ts @@ -1,5 +1,7 @@ -import { HtmlView as HtmlViewDefinition } from "."; +import { Color } from "../../color"; +import { Style, CssProperty } from "../core/properties"; import { View, Property, CSSType } from "../core/view"; +import { HtmlView as HtmlViewDefinition } from "."; export * from "../core/view"; @@ -13,3 +15,11 @@ HtmlViewBase.prototype.recycleNativeView = "auto"; // TODO: Can we use Label.ios optimization for affectsLayout??? export const htmlProperty = new Property({ name: "html", defaultValue: "", affectsLayout: true }); htmlProperty.register(HtmlViewBase); + +export const linkColorProperty = new CssProperty({ + name: "linkColor", + cssName: "link-color", + equalityComparer: Color.equals, + valueConverter: (value) => new Color(value), +}); +linkColorProperty.register(Style); diff --git a/nativescript-core/ui/html-view/html-view.android.ts b/nativescript-core/ui/html-view/html-view.android.ts index 139735bb66..67379dc807 100644 --- a/nativescript-core/ui/html-view/html-view.android.ts +++ b/nativescript-core/ui/html-view/html-view.android.ts @@ -1,5 +1,13 @@ -import { - HtmlViewBase, htmlProperty +import { Color } from "../../color"; +import { Font } from "../styling/font"; +import { + colorProperty, + fontSizeProperty, + fontInternalProperty, +} from "../styling/style-properties"; +import { + HtmlViewBase, htmlProperty, + linkColorProperty, } from "./html-view-common"; export * from "./html-view-common"; @@ -15,6 +23,9 @@ export class HtmlView extends HtmlViewBase { super.initNativeView(); const nativeView = this.nativeViewProtected; + // Allow text selection + nativeView.setTextIsSelectable(true); + // This makes the html work nativeView.setLinksClickable(true); nativeView.setMovementMethod(android.text.method.LinkMovementMethod.getInstance()); @@ -39,4 +50,46 @@ export class HtmlView extends HtmlViewBase { this.nativeViewProtected.setAutoLinkMask(mask); this.nativeViewProtected.setText(android.text.Html.fromHtml(value)); } + + [colorProperty.getDefault](): android.content.res.ColorStateList { + return this.nativeViewProtected.getTextColors(); + } + [colorProperty.setNative](value: Color | android.content.res.ColorStateList) { + if (value instanceof Color) { + this.nativeViewProtected.setTextColor(value.android); + } else { + this.nativeViewProtected.setTextColor(value); + } + } + + [linkColorProperty.getDefault](): android.content.res.ColorStateList { + return this.nativeViewProtected.getLinkTextColors(); + } + [linkColorProperty.setNative](value: Color | android.content.res.ColorStateList) { + const color = value instanceof Color ? value.android : value; + if (value instanceof Color) { + this.nativeViewProtected.setLinkTextColor(value.android); + } else { + this.nativeViewProtected.setLinkTextColor(value); + } + } + + [fontInternalProperty.getDefault](): android.graphics.Typeface { + return this.nativeViewProtected.getTypeface(); + } + [fontInternalProperty.setNative](value: Font | android.graphics.Typeface) { + const font = value instanceof Font ? value.getAndroidTypeface() : value; + this.nativeViewProtected.setTypeface(font); + } + + [fontSizeProperty.getDefault](): {nativeSize: number} { + return {nativeSize: this.nativeViewProtected.getTextSize()}; + } + [fontSizeProperty.setNative](value: number | {nativeSize: number}) { + if (typeof value === "number") { + this.nativeViewProtected.setTextSize(value); + } else { + this.nativeViewProtected.setTextSize(android.util.TypedValue.COMPLEX_UNIT_PX, value.nativeSize); + } + } } diff --git a/nativescript-core/ui/html-view/html-view.ios.ts b/nativescript-core/ui/html-view/html-view.ios.ts index d5c1d5b6e7..89156326fd 100644 --- a/nativescript-core/ui/html-view/html-view.ios.ts +++ b/nativescript-core/ui/html-view/html-view.ios.ts @@ -1,5 +1,12 @@ +import { Color } from "../../color"; +import { Font } from "../styling/font"; import { - HtmlViewBase, View, layout, htmlProperty + colorProperty, + fontInternalProperty, +} from "../styling/style-properties"; +import { + HtmlViewBase, View, layout, htmlProperty, fontSizeProperty, + linkColorProperty, } from "./html-view-common"; import { ios } from "../../utils/utils"; @@ -9,6 +16,7 @@ const majorVersion = ios.MajorVersion; export class HtmlView extends HtmlViewBase { nativeViewProtected: UITextView; + private currentHtml: string; public createNativeView() { const view = UITextView.new(); @@ -21,6 +29,14 @@ export class HtmlView extends HtmlViewBase { return view; } + public initNativeView(): void { + super.initNativeView(); + + // Remove extra padding + this.nativeViewProtected.textContainer.lineFragmentPadding = 0; + this.nativeViewProtected.textContainerInset = (UIEdgeInsets as any).zero; + } + get ios(): UITextView { return this.nativeViewProtected; } @@ -50,8 +66,22 @@ export class HtmlView extends HtmlViewBase { [htmlProperty.getDefault](): string { return ""; } - [htmlProperty.setNative](value: string) { - const htmlString = NSString.stringWithString(value + ""); + + private renderWithStyles() { + let html = this.currentHtml; + const styles = []; + if (this.nativeViewProtected.font) { + styles.push(`font-family: '${this.nativeViewProtected.font.fontName}';`); + styles.push(`font-size: ${this.nativeViewProtected.font.pointSize}px;`); + } + if (this.nativeViewProtected.textColor) { + const textColor = Color.fromIosColor(this.nativeViewProtected.textColor); + styles.push(`color: ${textColor.hex};`); + } + if (styles.length > 0) { + html += ``; + } + const htmlString = NSString.stringWithString(html + ""); const nsData = htmlString.dataUsingEncoding(NSUnicodeStringEncoding); this.nativeViewProtected.attributedText = NSAttributedString.alloc().initWithDataOptionsDocumentAttributesError( nsData, @@ -63,4 +93,38 @@ export class HtmlView extends HtmlViewBase { this.nativeViewProtected.textColor = UIColor.labelColor; } } + + [htmlProperty.setNative](value: string) { + this.currentHtml = value; + this.renderWithStyles(); + } + + [colorProperty.getDefault](): UIColor { + return this.nativeViewProtected.textColor; + } + [colorProperty.setNative](value: Color | UIColor) { + const color = value instanceof Color ? value.ios : value; + this.nativeViewProtected.textColor = color; + this.renderWithStyles(); + } + + [linkColorProperty.getDefault](): UIColor { + return this.nativeViewProtected.linkTextAttributes[NSForegroundColorAttributeName]; + } + [linkColorProperty.setNative](value: Color | UIColor) { + const color = value instanceof Color ? value.ios : value; + const linkTextAttributes = NSDictionary.dictionaryWithObjectForKey( + color, + NSForegroundColorAttributeName, + ); + this.nativeViewProtected.linkTextAttributes = linkTextAttributes; + } + [fontInternalProperty.getDefault](): UIFont { + return this.nativeViewProtected.font; + } + [fontInternalProperty.setNative](value: Font | UIFont) { + const font = value instanceof Font ? value.getUIFont(this.nativeViewProtected.font) : value; + this.nativeViewProtected.font = font; + this.renderWithStyles(); + } }