diff --git a/packages/core/application/application-common.ts b/packages/core/application/application-common.ts index 30412a3f5..9b0a702b6 100644 --- a/packages/core/application/application-common.ts +++ b/packages/core/application/application-common.ts @@ -97,11 +97,6 @@ interface ApplicationEvents { */ on(event: 'livesync', callback: (args: ApplicationEventData) => void, thisArg?: any): void; - /** - * This event is raised when application css is changed. - */ - on(event: 'cssChanged', callback: (args: CssChangedEventData) => void, thisArg?: any): void; - /** * This event is raised on application launchEvent. */ diff --git a/packages/core/image-asset/image-asset-common.ts b/packages/core/image-asset/image-asset-common.ts index 798727dd3..029fc8807 100644 --- a/packages/core/image-asset/image-asset-common.ts +++ b/packages/core/image-asset/image-asset-common.ts @@ -19,7 +19,7 @@ export class ImageAssetBase extends Observable implements ImageAssetDefinition { } set options(value: ImageAssetOptions) { - this._options = value; + this._options = normalizeImageAssetOptions(value); } get nativeImage(): any { @@ -35,6 +35,35 @@ export class ImageAssetBase extends Observable implements ImageAssetDefinition { } } +function toPositiveInt(value: any): number { + if (value == null) { + return 0; + } + if (typeof value === 'number') { + return value > 0 ? Math.floor(value) : 0; + } + if (typeof value === 'string') { + const parsed = parseInt(value, 10); + return isNaN(parsed) || parsed <= 0 ? 0 : parsed; + } + return 0; +} + +function normalizeImageAssetOptions(options: ImageAssetOptions): ImageAssetOptions { + const normalized = options ? { ...options } : ({} as ImageAssetOptions); + // Coerce potential string values to positive integers; fallback to 0 + // to trigger default sizing downstream + (normalized as any).width = toPositiveInt((options as any)?.width); + (normalized as any).height = toPositiveInt((options as any)?.height); + if (typeof normalized.keepAspectRatio !== 'boolean') { + normalized.keepAspectRatio = true; + } + if (typeof normalized.autoScaleFactor !== 'boolean') { + normalized.autoScaleFactor = true; + } + return normalized; +} + export function getAspectSafeDimensions(sourceWidth, sourceHeight, reqWidth, reqHeight) { const widthCoef = sourceWidth / reqWidth; const heightCoef = sourceHeight / reqHeight; @@ -47,8 +76,9 @@ export function getAspectSafeDimensions(sourceWidth, sourceHeight, reqWidth, req } export function getRequestedImageSize(src: { width: number; height: number }, options: ImageAssetOptions): { width: number; height: number } { - let reqWidth = options.width || Math.min(src.width, Screen.mainScreen.widthPixels); - let reqHeight = options.height || Math.min(src.height, Screen.mainScreen.heightPixels); + const normalized = normalizeImageAssetOptions(options); + let reqWidth = normalized.width || Math.min(src.width, Screen.mainScreen.widthPixels); + let reqHeight = normalized.height || Math.min(src.height, Screen.mainScreen.heightPixels); if (options && options.keepAspectRatio) { const safeAspectSize = getAspectSafeDimensions(src.width, src.height, reqWidth, reqHeight); diff --git a/packages/core/image-asset/image-asset-options.spec.ts b/packages/core/image-asset/image-asset-options.spec.ts new file mode 100644 index 000000000..d7a10649f --- /dev/null +++ b/packages/core/image-asset/image-asset-options.spec.ts @@ -0,0 +1,27 @@ +import { getRequestedImageSize } from './image-asset-common'; + +describe('ImageAssetOptions normalization', () => { + it('coerces string width/height to numbers', () => { + const src = { width: 2000, height: 1500 }; + const result = getRequestedImageSize(src as any, { width: '300' as any, height: '200' as any, keepAspectRatio: false, autoScaleFactor: true } as any); + expect(result.width).toBe(300); + expect(result.height).toBe(200); + }); + + it('falls back to defaults when invalid strings provided', () => { + const src = { width: 800, height: 600 }; + const result = getRequestedImageSize(src as any, { width: 'abc' as any, height: '' as any, keepAspectRatio: false } as any); + // should fall back to screen pixel defaults via getRequestedImageSize, but since + // we cannot easily control Screen.mainScreen here, we at least assert they are > 0 + expect(result.width).toBeGreaterThan(0); + expect(result.height).toBeGreaterThan(0); + }); + + it('respects keepAspectRatio by adjusting to safe dimensions', () => { + const src = { width: 2000, height: 1000 }; + const result = getRequestedImageSize(src as any, { width: '500' as any, height: '500' as any, keepAspectRatio: true } as any); + // current implementation scales using the smaller coefficient (min), so expect 1000x500 + expect(result.width).toBe(1000); + expect(result.height).toBe(500); + }); +}); diff --git a/packages/core/platforms/android/widgets-release.aar b/packages/core/platforms/android/widgets-release.aar index c9fc4e1f5..b0cb2cd98 100644 Binary files a/packages/core/platforms/android/widgets-release.aar and b/packages/core/platforms/android/widgets-release.aar differ diff --git a/packages/ui-mobile-base/android/build.gradle b/packages/ui-mobile-base/android/build.gradle index e7cd8d342..3cb150366 100644 --- a/packages/ui-mobile-base/android/build.gradle +++ b/packages/ui-mobile-base/android/build.gradle @@ -6,7 +6,8 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.1.0' + // Updated for Java 21 compatibility via Gradle 8.4 + classpath 'com.android.tools.build:gradle:8.3.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files diff --git a/packages/ui-mobile-base/android/gradle/wrapper/gradle-wrapper.properties b/packages/ui-mobile-base/android/gradle/wrapper/gradle-wrapper.properties index 41dfb8790..e411586a5 100644 --- a/packages/ui-mobile-base/android/gradle/wrapper/gradle-wrapper.properties +++ b/packages/ui-mobile-base/android/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/packages/ui-mobile-base/android/widgets/build.gradle b/packages/ui-mobile-base/android/widgets/build.gradle index a17206f3d..1208c04a1 100644 --- a/packages/ui-mobile-base/android/widgets/build.gradle +++ b/packages/ui-mobile-base/android/widgets/build.gradle @@ -6,7 +6,7 @@ def isWinOs = System.properties['os.name'].toLowerCase().contains('windows') apply plugin: 'com.android.library' -def computeCompileSdkVersion () { +def computeCompileSdkValue () { if(project.hasProperty("compileSdk")) { return compileSdk } @@ -15,15 +15,6 @@ def computeCompileSdkVersion () { } } -def computeBuildToolsVersion() { - if(project.hasProperty("buildToolsVersion")) { - return buildToolsVersion - } - else { - return "32.0.0" - } -} - def computeTargetSdkVersion() { if(project.hasProperty("targetSdk")) { return targetSdk @@ -34,12 +25,13 @@ def computeTargetSdkVersion() { } android { - compileSdkVersion computeCompileSdkVersion() - buildToolsVersion computeBuildToolsVersion() + // AGP 8 DSL: use 'compileSdk' and specify namespace + compileSdk computeCompileSdkValue() + namespace "org.nativescript.widgets" defaultConfig { - minSdkVersion 17 - targetSdkVersion computeTargetSdkVersion() + minSdk 17 + targetSdk computeTargetSdkVersion() versionCode 1 versionName "1.0" } diff --git a/packages/ui-mobile-base/android/widgets/src/main/java/org/nativescript/widgets/Utils.java b/packages/ui-mobile-base/android/widgets/src/main/java/org/nativescript/widgets/Utils.java index d32fed9f4..29a3da916 100644 --- a/packages/ui-mobile-base/android/widgets/src/main/java/org/nativescript/widgets/Utils.java +++ b/packages/ui-mobile-base/android/widgets/src/main/java/org/nativescript/widgets/Utils.java @@ -170,6 +170,36 @@ public class Utils { boolean autoScaleFactor; } + private static int parsePositiveInt(JSONObject object, String key) { + if (object == null || key == null) { + return 0; + } + try { + if (!object.has(key) || object.isNull(key)) { + return 0; + } + Object value = object.get(key); + if (value instanceof Number) { + int parsed = (int) Math.floor(((Number) value).doubleValue()); + return parsed > 0 ? parsed : 0; + } + if (value instanceof String) { + String s = ((String) value).trim(); + if (s.length() == 0) { + return 0; + } + try { + int parsed = Integer.parseInt(s); + return parsed > 0 ? parsed : 0; + } catch (NumberFormatException ignored) { + return 0; + } + } + } catch (JSONException ignored) { + } + return 0; + } + private static final Executor executors = Executors.newCachedThreadPool(); @@ -297,8 +327,9 @@ public class Utils { try { JSONObject object = new JSONObject(options); - opts.width = object.optInt("width", 0); - opts.height = object.optInt("height", 0); + // Coerce numeric strings or numbers; fallback to 0 for invalid values + opts.width = parsePositiveInt(object, "width"); + opts.height = parsePositiveInt(object, "height"); opts.keepAspectRatio = object.optBoolean("keepAspectRatio", true); opts.autoScaleFactor = object.optBoolean("autoScaleFactor", true); } catch (JSONException ignored) { diff --git a/packages/ui-mobile-base/build.android.sh b/packages/ui-mobile-base/build.android.sh index 64401f524..2c58a8c94 100755 --- a/packages/ui-mobile-base/build.android.sh +++ b/packages/ui-mobile-base/build.android.sh @@ -6,6 +6,33 @@ set -e echo "Use dumb gradle terminal" export TERM=dumb +# Prefer running with JDK 21 for Gradle 8.4+/AGP 8.3+ +if [ "$(uname)" = "Darwin" ]; then + # On macOS, prefer JDK 21, fall back to JDK 17 + if /usr/libexec/java_home -v 21 >/dev/null 2>&1; then + export JAVA_HOME=$(/usr/libexec/java_home -v 21) + export PATH="$JAVA_HOME/bin:$PATH" + echo "Using Java (21) at $JAVA_HOME" + java -version 2>&1 | head -n 1 + elif /usr/libexec/java_home -v 17 >/dev/null 2>&1; then + export JAVA_HOME=$(/usr/libexec/java_home -v 17) + export PATH="$JAVA_HOME/bin:$PATH" + echo "Using Java (17) at $JAVA_HOME" + java -version 2>&1 | head -n 1 + else + echo "Warning: JDK 21 or 17 not found via /usr/libexec/java_home. Using current JAVA_HOME: ${JAVA_HOME:-unset}" + java -version 2>&1 || true + echo "Install JDK 21 (preferred) or JDK 17." + fi +else + # Non-macOS: print Java version for visibility + if command -v java >/dev/null 2>&1; then + echo "Detected Java runtime: $(java -version 2>&1 | head -n 1)" + else + echo "Warning: 'java' not found on PATH. Build will likely fail." + fi +fi + rm -rf dist/package/platforms/android || true mkdir -p dist/package/platforms/android