From 3a7d6c7352669833981ef45f88f12cc165321d03 Mon Sep 17 00:00:00 2001 From: Eduardo Speroni Date: Thu, 21 Sep 2023 18:32:25 -0300 Subject: [PATCH 1/7] perf: improve attribute selectors by adding single listeners --- packages/core/ui/styling/style-scope.ts | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/packages/core/ui/styling/style-scope.ts b/packages/core/ui/styling/style-scope.ts index a71ff6f9ec..c933383db6 100644 --- a/packages/core/ui/styling/style-scope.ts +++ b/packages/core/ui/styling/style-scope.ts @@ -22,6 +22,7 @@ import * as capm from './css-animation-parser'; import { sanitizeModuleName } from '../../utils/common'; import { resolveModuleName } from '../../module-name-resolver'; import { cleanupImportantFlags } from './css-utils'; +import { Observable, PropertyChangeData } from '../../data/observable'; let cssAnimationParserModule: typeof capm; function ensureCssAnimationParserModule() { @@ -418,6 +419,8 @@ export class CssState { _matchInvalid: boolean; _playsKeyframeAnimations: boolean; + private _dynamicUpdateListenerMap: Map void> = new Map(); + constructor(private viewRef: WeakRef) { this._onDynamicStateChangeHandler = () => this.updateDynamicState(); } @@ -650,9 +653,14 @@ export class CssState { const changeMap = this._match.changeMap; changeMap.forEach((changes, view) => { if (changes.attributes) { - changes.attributes.forEach((attribute) => { - view.addEventListener(attribute + 'Change', this._onDynamicStateChangeHandler); - }); + const attributes = changes.attributes; + const listener = (args: PropertyChangeData) => { + if (attributes.has(args.propertyName)) { + this._onDynamicStateChangeHandler(); + } + }; + this._dynamicUpdateListenerMap.set(view, listener); + view.addEventListener(Observable.propertyChangeEvent, listener); } if (changes.pseudoClasses) { changes.pseudoClasses.forEach((pseudoClass) => { @@ -669,10 +677,8 @@ export class CssState { private unsubscribeFromDynamicUpdates(): void { this._appliedChangeMap.forEach((changes, view) => { - if (changes.attributes) { - changes.attributes.forEach((attribute) => { - view.removeEventListener(attribute + 'Change', this._onDynamicStateChangeHandler); - }); + if (this._dynamicUpdateListenerMap.has(view)) { + view.removeEventListener(Observable.propertyChangeEvent, this._dynamicUpdateListenerMap.get(view)); } if (changes.pseudoClasses) { changes.pseudoClasses.forEach((pseudoClass) => { @@ -684,6 +690,7 @@ export class CssState { }); } }); + this._dynamicUpdateListenerMap.clear(); this._appliedChangeMap = CssState.emptyChangeMap; } From 150f623d65f19c1f5950a1a770e710070d9df07a Mon Sep 17 00:00:00 2001 From: Eduardo Speroni Date: Thu, 21 Sep 2023 18:41:36 -0300 Subject: [PATCH 2/7] feat: add a way to globally ignore CSS attributes --- packages/core/css/system-classes.ts | 1 + packages/core/ui/styling/css-selector.ts | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/packages/core/css/system-classes.ts b/packages/core/css/system-classes.ts index 3c1edadcb9..a40e6a6607 100644 --- a/packages/core/css/system-classes.ts +++ b/packages/core/css/system-classes.ts @@ -6,6 +6,7 @@ export namespace CSSUtils { export const CLASS_PREFIX = 'ns-'; export const MODAL_ROOT_VIEW_CSS_CLASS = `${CLASS_PREFIX}${MODAL}`; export const ROOT_VIEW_CSS_CLASS = `${CLASS_PREFIX}${ROOT}`; + export const IgnoredCssDynamicAttributeTracking = new Set(); export function getSystemCssClasses(): string[] { return cssClasses; diff --git a/packages/core/ui/styling/css-selector.ts b/packages/core/ui/styling/css-selector.ts index bccd3971dc..580b8b0df6 100644 --- a/packages/core/ui/styling/css-selector.ts +++ b/packages/core/ui/styling/css-selector.ts @@ -4,6 +4,7 @@ import { isNullOrUndefined } from '../../utils/types'; import * as ReworkCSS from '../../css'; import { Combinator as ICombinator, SimpleSelectorSequence as ISimpleSelectorSequence, Selector as ISelector, SimpleSelector as ISimpleSelector, parseSelector } from '../../css/parser'; +import { CSSUtils } from '../../css/system-classes'; /** * An interface describing the shape of a type on which the selectors may apply. @@ -675,6 +676,9 @@ export class SelectorsMatch implements ChangeAccumulator { public selectors: SelectorCore[]; public addAttribute(node: T, attribute: string): void { + if (CSSUtils.IgnoredCssDynamicAttributeTracking.has(attribute)) { + return; + } const deps: Changes = this.properties(node); if (!deps.attributes) { deps.attributes = new Set(); From 877500a518993b3dda3c23bdccee2a76024f37b5 Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Fri, 26 Apr 2024 11:41:36 -0700 Subject: [PATCH 3/7] release: 8.8.0-alpha.0 --- packages/core/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/package.json b/packages/core/package.json index 4137a43172..58fd232a69 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@nativescript/core", - "version": "8.7.3", + "version": "8.8.0-alpha.0", "description": "A JavaScript library providing an easy to use api for interacting with iOS and Android platform APIs.", "main": "index", "types": "index.d.ts", From 1c7878c6abc83011e6647e985a4d200be5ebbe16 Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Thu, 2 May 2024 12:04:22 -0700 Subject: [PATCH 4/7] release: 8.8.0-alpha.1 --- packages/core/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/package.json b/packages/core/package.json index 58fd232a69..a21af8251d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@nativescript/core", - "version": "8.8.0-alpha.0", + "version": "8.8.0-alpha.1", "description": "A JavaScript library providing an easy to use api for interacting with iOS and Android platform APIs.", "main": "index", "types": "index.d.ts", From 9d6e9fe144349e9b0dab5837c34d790f7f8d49ea Mon Sep 17 00:00:00 2001 From: Eduardo Speroni Date: Tue, 28 May 2024 10:22:18 -0300 Subject: [PATCH 5/7] feat: add `sys://` ios images --- packages/core/image-source/index.android.ts | 14 ++++++++--- packages/core/image-source/index.d.ts | 12 +++++++++ packages/core/image-source/index.ios.ts | 28 +++++++++++++++++++-- packages/core/ui/image/image-common.ts | 12 +++++++-- packages/core/utils/common.ts | 8 +++--- 5 files changed, 64 insertions(+), 10 deletions(-) diff --git a/packages/core/image-source/index.android.ts b/packages/core/image-source/index.android.ts index ca89f85247..ca36e9f4bd 100644 --- a/packages/core/image-source/index.android.ts +++ b/packages/core/image-source/index.android.ts @@ -149,6 +149,14 @@ export class ImageSource implements ImageSourceDefinition { return ImageSource.fromFileSync(path); } + static fromSystemImageSync(name: string): ImageSource { + return ImageSource.fromResourceSync(name); + } + + static fromSystemImage(name: string): Promise { + return ImageSource.fromResource(name); + } + static fromDataSync(data: any): ImageSource { const bitmap = android.graphics.BitmapFactory.decodeStream(data); @@ -335,7 +343,7 @@ export class ImageSource implements ImageSourceDefinition { reject(); } }, - }) + }), ); }); } @@ -375,7 +383,7 @@ export class ImageSource implements ImageSourceDefinition { reject(); } }, - }) + }), ); }); } @@ -404,7 +412,7 @@ export class ImageSource implements ImageSourceDefinition { reject(); } }, - }) + }), ); }); } diff --git a/packages/core/image-source/index.d.ts b/packages/core/image-source/index.d.ts index 55037abc92..57e533595e 100644 --- a/packages/core/image-source/index.d.ts +++ b/packages/core/image-source/index.d.ts @@ -54,6 +54,18 @@ export class ImageSource { */ static fromResource(name: string): Promise; + /** + * Loads this instance from the specified system image name. + * @param name the name of the system image + */ + static fromSystemImageSync(name: string): ImageSource; + + /** + * Loads this instance from the specified system image name asynchronously. + * @param name the name of the system image + */ + static fromSystemImage(name: string): Promise; + /** * Loads this instance from the specified file. * @param path The location of the file on the file system. diff --git a/packages/core/image-source/index.ios.ts b/packages/core/image-source/index.ios.ts index fd8fbf6df4..f6d854ec8a 100644 --- a/packages/core/image-source/index.ios.ts +++ b/packages/core/image-source/index.ios.ts @@ -8,7 +8,7 @@ import { Trace } from '../trace'; // Types. import { path as fsPath, knownFolders } from '../file-system'; -import { isFileOrResourcePath, RESOURCE_PREFIX, layout, releaseNativeObject } from '../utils'; +import { isFileOrResourcePath, RESOURCE_PREFIX, layout, releaseNativeObject, SYSTEM_PREFIX } from '../utils'; import { getScaledDimensions } from './image-source-common'; @@ -73,6 +73,27 @@ export class ImageSource implements ImageSourceDefinition { return http.getImage(url); } + static fromSystemImageSync(name: string): ImageSource { + const image = UIImage.systemImageNamed(name); + + return image ? new ImageSource(image) : null; + } + + static fromSystemImage(name: string): Promise { + return new Promise((resolve, reject) => { + try { + const image = UIImage.systemImageNamed(name); + if (image) { + resolve(new ImageSource(image)); + } else { + reject(new Error(`Failed to load system icon with name: ${name}`)); + } + } catch (ex) { + reject(ex); + } + }); + } + static fromResourceSync(name: string): ImageSource { const nativeSource = (UIImage).tns_safeImageNamed(name) || (UIImage).tns_safeImageNamed(`${name}.jpg`); @@ -126,7 +147,10 @@ export class ImageSource implements ImageSourceDefinition { } if (path.indexOf(RESOURCE_PREFIX) === 0) { - return ImageSource.fromResourceSync(path.substr(RESOURCE_PREFIX.length)); + return ImageSource.fromResourceSync(path.slice(RESOURCE_PREFIX.length)); + } + if (path.indexOf(SYSTEM_PREFIX) === 0) { + return ImageSource.fromSystemImageSync(path.slice(SYSTEM_PREFIX.length)); } return ImageSource.fromFileSync(path); diff --git a/packages/core/ui/image/image-common.ts b/packages/core/ui/image/image-common.ts index 91bb9a1f99..73bc24d30d 100644 --- a/packages/core/ui/image/image-common.ts +++ b/packages/core/ui/image/image-common.ts @@ -4,7 +4,7 @@ import { booleanConverter } from '../core/view-base'; import { CoreTypes } from '../../core-types'; import { ImageAsset } from '../../image-asset'; import { ImageSource } from '../../image-source'; -import { isDataURI, isFontIconURI, isFileOrResourcePath, RESOURCE_PREFIX } from '../../utils'; +import { isDataURI, isFontIconURI, isFileOrResourcePath, RESOURCE_PREFIX, SYSTEM_PREFIX } from '../../utils'; import { Color } from '../../color'; import { Style } from '../styling/style'; import { Length } from '../styling/style-properties'; @@ -75,13 +75,21 @@ export abstract class ImageBase extends View implements ImageDefinition { } } else if (isFileOrResourcePath(value)) { if (value.indexOf(RESOURCE_PREFIX) === 0) { - const resPath = value.substr(RESOURCE_PREFIX.length); + const resPath = value.slice(RESOURCE_PREFIX.length); if (sync) { imageLoaded(ImageSource.fromResourceSync(resPath)); } else { this.imageSource = null; ImageSource.fromResource(resPath).then(imageLoaded); } + } else if (value.indexOf(SYSTEM_PREFIX) === 0) { + const sysPath = value.slice(SYSTEM_PREFIX.length); + if (sync) { + imageLoaded(ImageSource.fromSystemImageSync(sysPath)); + } else { + this.imageSource = null; + ImageSource.fromSystemImage(sysPath).then(imageLoaded); + } } else { if (sync) { imageLoaded(ImageSource.fromFileSync(value)); diff --git a/packages/core/utils/common.ts b/packages/core/utils/common.ts index 273f5eb90c..2f8f46379d 100644 --- a/packages/core/utils/common.ts +++ b/packages/core/utils/common.ts @@ -8,6 +8,7 @@ export * from './mainthread-helper'; export * from './macrotask-scheduler'; export const RESOURCE_PREFIX = 'res://'; +export const SYSTEM_PREFIX = 'sys://'; export const FILE_PREFIX = 'file:///'; export function escapeRegexSymbols(source: string): string { @@ -75,7 +76,8 @@ export function isFileOrResourcePath(path: string): boolean { return ( path.indexOf('~/') === 0 || // relative to AppRoot path.indexOf('/') === 0 || // absolute path - path.indexOf(RESOURCE_PREFIX) === 0 + path.indexOf(RESOURCE_PREFIX) === 0 || + path.indexOf(SYSTEM_PREFIX) === 0 ); // resource } @@ -215,7 +217,7 @@ export function queueGC(delay = 900, useThrottle?: boolean) { if (!throttledGC.get(delay)) { throttledGC.set( delay, - throttle(() => GC(), delay) + throttle(() => GC(), delay), ); } throttledGC.get(delay)(); @@ -226,7 +228,7 @@ export function queueGC(delay = 900, useThrottle?: boolean) { if (!debouncedGC.get(delay)) { debouncedGC.set( delay, - debounce(() => GC(), delay) + debounce(() => GC(), delay), ); } debouncedGC.get(delay)(); From 56dda3b8051c6bd983921748a4cd975f555c704b Mon Sep 17 00:00:00 2001 From: Nathan Walker Date: Sun, 16 Jun 2024 14:21:09 -0700 Subject: [PATCH 6/7] feat(ios): SF Symbol effects via symbolEffect property --- apps/toolbox/src/pages/image-handling.ts | 28 ++++++++++++++++++++++- apps/toolbox/src/pages/image-handling.xml | 18 +++++++++++++++ packages/core/references.d.ts | 1 + packages/core/ui/image/image-common.ts | 19 +++++++++++++++ packages/core/ui/image/index.d.ts | 1 + packages/core/ui/image/index.ios.ts | 12 +++++++++- packages/core/ui/index.ts | 2 +- 7 files changed, 78 insertions(+), 3 deletions(-) diff --git a/apps/toolbox/src/pages/image-handling.ts b/apps/toolbox/src/pages/image-handling.ts index 528712f8b1..71d1381b15 100644 --- a/apps/toolbox/src/pages/image-handling.ts +++ b/apps/toolbox/src/pages/image-handling.ts @@ -1,4 +1,4 @@ -import { Observable, EventData, Page, ImageSource, knownFolders, path } from '@nativescript/core'; +import { Observable, EventData, Page, ImageSource, knownFolders, path, ImageSymbolEffect } from '@nativescript/core'; import { create, ImagePickerMediaType } from '@nativescript/imagepicker'; let page: Page; @@ -10,6 +10,32 @@ export function navigatingTo(args: EventData) { export class DemoModel extends Observable { addingPhoto = false; + symbolWiggleEffect: ImageSymbolEffect; + symbolBounceEffect: ImageSymbolEffect; + symbolBreathEffect: ImageSymbolEffect; + symbolRotateEffect: ImageSymbolEffect; + + constructor() { + super(); + if (__APPLE__) { + this.symbolWiggleEffect = { + effect: NSSymbolWiggleEffect.effect(), + start: true, + }; + this.symbolBounceEffect = { + effect: NSSymbolBounceEffect.effect(), + start: true, + }; + this.symbolBreathEffect = { + effect: NSSymbolBreatheEffect.effect(), + start: true, + }; + this.symbolRotateEffect = { + effect: NSSymbolRotateEffect.effect(), + start: true, + }; + } + } pickImage() { const context = create({ diff --git a/apps/toolbox/src/pages/image-handling.xml b/apps/toolbox/src/pages/image-handling.xml index 0b67d295dd..ad735e19b8 100644 --- a/apps/toolbox/src/pages/image-handling.xml +++ b/apps/toolbox/src/pages/image-handling.xml @@ -5,9 +5,27 @@ +