Merge remote-tracking branch 'origin/main' into refactor/circular-deps

This commit is contained in:
Nathan Walker
2025-07-24 22:49:06 -07:00
21 changed files with 437 additions and 332 deletions

View File

@@ -159,8 +159,8 @@ export function test_ChainingAnimations(done) {
.then(() => label.animate({ translate: { x: 0, y: 0 }, duration: duration }))
.then(() => label.animate({ scale: { x: 5, y: 5 }, duration: duration }))
.then(() => label.animate({ scale: { x: 1, y: 1 }, duration: duration }))
.then(() => label.animate({ rotate: { x: 90, y: 0, z: 180 }, duration: duration }))
.then(() => label.animate({ rotate: { x: 0, y: 0, z: 0 }, duration: duration }))
.then(() => label.animate({ rotate: 180, duration: duration }))
.then(() => label.animate({ rotate: 0, duration: duration }))
.then(() => {
//console.log("Animation finished");
// >> (hide)

View File

@@ -109,16 +109,11 @@ export function testBackgroundInternalChangedOnceOnResize() {
TKUnit.assertEqual(trackCount(), 1, 'Expected background to be re-applied at most once when the view is laid-out on 0 0 200 200.');
// Ignore safe area as it may result in re-calculating view frame, thus trigger a size change regardless
layout.iosIgnoreSafeArea = true;
layout.requestLayout();
layout.layout(50, 50, 250, 250);
TKUnit.assertEqual(trackCount(), 0, 'Expected background to NOT change when view is laid-out from 0 0 200 200 to 50 50 250 250.');
layout.iosIgnoreSafeArea = false;
layout.requestLayout();
layout.layout(0, 0, 250, 250);

View File

Binary file not shown.

View File

@@ -79,8 +79,8 @@ class AnimationDelegateImpl extends NSObject implements CAAnimationDelegate {
targetStyle[setLocal ? widthProperty.name : widthProperty.keyframe] = value;
break;
case Properties.scale:
targetStyle[setLocal ? scaleXProperty.name : scaleXProperty.keyframe] = value.x === 0 ? 1e-6 : value.x;
targetStyle[setLocal ? scaleYProperty.name : scaleYProperty.keyframe] = value.y === 0 ? 1e-6 : value.y;
targetStyle[setLocal ? scaleXProperty.name : scaleXProperty.keyframe] = value.x === 0 ? 0.001 : value.x;
targetStyle[setLocal ? scaleYProperty.name : scaleYProperty.keyframe] = value.y === 0 ? 0.001 : value.y;
break;
case _transform:
if (value[Properties.translate] !== undefined) {
@@ -95,8 +95,8 @@ class AnimationDelegateImpl extends NSObject implements CAAnimationDelegate {
if (value[Properties.scale] !== undefined) {
const x = value[Properties.scale].x;
const y = value[Properties.scale].y;
targetStyle[setLocal ? scaleXProperty.name : scaleXProperty.keyframe] = x === 0 ? 1e-6 : x;
targetStyle[setLocal ? scaleYProperty.name : scaleYProperty.keyframe] = y === 0 ? 1e-6 : y;
targetStyle[setLocal ? scaleXProperty.name : scaleXProperty.keyframe] = x === 0 ? 0.001 : x;
targetStyle[setLocal ? scaleYProperty.name : scaleYProperty.keyframe] = y === 0 ? 0.001 : y;
}
break;
}
@@ -309,6 +309,7 @@ export class Animation extends AnimationBase {
const parent = view.parent as View;
let propertyNameToAnimate = animation.property;
let subPropertyNameToAnimate;
let toValue = animation.value;
let fromValue;
if (nativeView) {
@@ -334,6 +335,8 @@ export class Animation extends AnimationBase {
};
fromValue = nativeView.layer.opacity;
break;
// In the case of rotation, avoid animating affine transform directly as it will animate to the closest result
// that is visually the same. For example, 0 -> 360 will leave view as is.
case Properties.rotate:
animation._originalValue = {
x: view.rotateX,
@@ -346,9 +349,30 @@ export class Animation extends AnimationBase {
style[setLocal ? rotateYProperty.name : rotateYProperty.keyframe] = value.y;
};
propertyNameToAnimate = 'transform';
fromValue = NSValue.valueWithCATransform3D(nativeView.layer.transform);
toValue = NSValue.valueWithCATransform3D(iosHelper.applyRotateTransform(nativeView.layer.transform, toValue.x, toValue.y, toValue.z));
propertyNameToAnimate = 'transform.rotation';
subPropertyNameToAnimate = ['x', 'y', 'z'];
fromValue = {
x: nativeView.layer.valueForKeyPath('transform.rotation.x'),
y: nativeView.layer.valueForKeyPath('transform.rotation.y'),
z: nativeView.layer.valueForKeyPath('transform.rotation.z'),
};
if (animation.target.rotateX !== undefined && animation.target.rotateX !== 0 && Math.floor(toValue / 360) - toValue / 360 === 0) {
fromValue.x = (animation.target.rotateX * Math.PI) / 180;
}
if (animation.target.rotateY !== undefined && animation.target.rotateY !== 0 && Math.floor(toValue / 360) - toValue / 360 === 0) {
fromValue.y = (animation.target.rotateY * Math.PI) / 180;
}
if (animation.target.rotate !== undefined && animation.target.rotate !== 0 && Math.floor(toValue / 360) - toValue / 360 === 0) {
fromValue.z = (animation.target.rotate * Math.PI) / 180;
}
// Respect only value.z for back-compat until 3D rotations are implemented
toValue = {
x: (toValue.x * Math.PI) / 180,
y: (toValue.y * Math.PI) / 180,
z: (toValue.z * Math.PI) / 180,
};
break;
case Properties.translate:
animation._originalValue = {
@@ -365,10 +389,10 @@ export class Animation extends AnimationBase {
break;
case Properties.scale:
if (toValue.x === 0) {
toValue.x = 1e-6;
toValue.x = 0.001;
}
if (toValue.y === 0) {
toValue.y = 1e-6;
toValue.y = 0.001;
}
animation._originalValue = { x: view.scaleX, y: view.scaleY };
animation._propertyResetCallback = (value, valueSource) => {
@@ -451,6 +475,7 @@ export class Animation extends AnimationBase {
return {
propertyNameToAnimate: propertyNameToAnimate,
fromValue: fromValue,
subPropertiesToAnimate: subPropertyNameToAnimate,
toValue: toValue,
duration: duration,
repeatCount: repeatCount,
@@ -496,10 +521,10 @@ export class Animation extends AnimationBase {
}
private static _createGroupAnimation(args: AnimationInfo, animation: PropertyAnimation) {
const animations = NSMutableArray.alloc<CAAnimation>().initWithCapacity(args.subPropertiesToAnimate.length);
const groupAnimation = CAAnimationGroup.new();
groupAnimation.duration = args.duration;
const animations = NSMutableArray.alloc<CAAnimation>().initWithCapacity(args.subPropertiesToAnimate.length);
groupAnimation.duration = args.duration;
if (args.repeatCount !== undefined) {
groupAnimation.repeatCount = args.repeatCount;
}
@@ -651,30 +676,16 @@ export class Animation extends AnimationBase {
}
if (value[Properties.scale] !== undefined) {
const x = value[Properties.scale].x || 1e-6;
const y = value[Properties.scale].y || 1e-6;
result = CATransform3DScale(result, x, y, 1);
}
if (value[Properties.rotate] !== undefined) {
const x = value[Properties.rotate].x;
const y = value[Properties.rotate].y;
const z = value[Properties.rotate].z;
const perspective = animation.target.perspective || 300;
// Set perspective in case of rotation since we use z
if (x || y) {
result.m34 = -1 / perspective;
}
result = iosHelper.applyRotateTransform(result, x, y, z);
const x = value[Properties.scale].x;
const y = value[Properties.scale].y;
result = CATransform3DScale(result, x === 0 ? 0.001 : x, y === 0 ? 0.001 : y, 1);
}
return result;
}
private static _isAffineTransform(property: string): boolean {
return property === _transform || property === Properties.translate || property === Properties.scale || property === Properties.rotate;
return property === _transform || property === Properties.translate || property === Properties.scale;
}
private static _canBeMerged(animation1: PropertyAnimation, animation2: PropertyAnimation) {
@@ -941,8 +952,7 @@ function calculateTransform(view: View): CATransform3D {
// Order is important: translate, rotate, scale
let expectedTransform = new CATransform3D(CATransform3DIdentity);
// TODO: Add perspective property to transform animations (not just rotation)
// Set perspective in case of rotation since we use z
// Only set perspective if there is 3D rotation
if (view.rotateX || view.rotateY) {
expectedTransform.m34 = -1 / perspective;
}

View File

@@ -150,7 +150,7 @@ export class Property<T extends ViewBase, U> implements TypedPropertyDescriptor<
public readonly key: symbol;
public readonly getDefault: symbol;
public readonly setNative: any;
public readonly setNative: symbol;
public readonly defaultValueKey: symbol;
public readonly defaultValue: U;
@@ -351,7 +351,7 @@ export class CoercibleProperty<T extends ViewBase, U> extends Property<T, U> imp
const propertyName = options.name;
const key = this.key;
const getDefault: symbol = this.getDefault;
const setNative: any = this.setNative;
const setNative: symbol = this.setNative;
const defaultValueKey = this.defaultValueKey;
const defaultValue: U = this.defaultValue;
@@ -566,7 +566,7 @@ export class CssProperty<T extends Style, U> {
public readonly key: symbol;
public readonly getDefault: symbol;
public readonly setNative: any;
public readonly setNative: symbol;
public readonly sourceKey: symbol;
public readonly defaultValueKey: symbol;
public readonly defaultValue: U;
@@ -833,7 +833,7 @@ export class CssAnimationProperty<T extends Style, U> implements CssAnimationPro
public readonly cssLocalName: string;
public readonly getDefault: symbol;
public readonly setNative: any;
public readonly setNative: symbol;
public readonly register: (cls: { prototype }) => void;

View File

@@ -105,12 +105,13 @@ export interface ShowModalOptions {
* @param criterion - The type of ancestor view we are looking for. Could be a string containing a class name or an actual type.
* Returns an instance of a view (if found), otherwise undefined.
*/
export function getAncestor(view: ViewBase, criterion: string | { new () }): ViewBase {
let matcher: (view: ViewBase) => boolean = null;
export function getAncestor<T extends ViewBase = ViewBase>(view: T, criterion: string | { new () }): T {
let matcher: (view: ViewBase) => view is T;
if (typeof criterion === 'string') {
matcher = (view: ViewBase) => view.typeName === criterion;
matcher = (view: ViewBase): view is T => view.typeName === criterion;
} else {
matcher = (view: ViewBase) => view instanceof criterion;
matcher = (view: ViewBase): view is T => view instanceof criterion;
}
for (let parent = view.parent; parent != null; parent = parent.parent) {

View File

@@ -809,6 +809,117 @@ export class View extends ViewCommon {
this.nativeViewProtected.setAlpha(float(value));
}
[accessibilityRoleProperty.setNative](value: AccessibilityRole): void {
this.accessibilityRole = value as AccessibilityRole;
updateA11yPropertiesCallback(this);
if (SDK_VERSION >= 28) {
this.nativeViewProtected?.setAccessibilityHeading(value === AccessibilityRole.Header);
}
}
[accessibilityLiveRegionProperty.setNative](value: AccessibilityLiveRegion): void {
switch (value as AccessibilityLiveRegion) {
case AccessibilityLiveRegion.Assertive: {
this.nativeViewProtected.setAccessibilityLiveRegion(android.view.View.ACCESSIBILITY_LIVE_REGION_ASSERTIVE);
break;
}
case AccessibilityLiveRegion.Polite: {
this.nativeViewProtected.setAccessibilityLiveRegion(android.view.View.ACCESSIBILITY_LIVE_REGION_POLITE);
break;
}
default: {
this.nativeViewProtected.setAccessibilityLiveRegion(android.view.View.ACCESSIBILITY_LIVE_REGION_NONE);
break;
}
}
}
[accessibilityStateProperty.setNative](value: AccessibilityState): void {
this.accessibilityState = value as AccessibilityState;
updateA11yPropertiesCallback(this);
}
[horizontalAlignmentProperty.getDefault](): CoreTypes.HorizontalAlignmentType {
return <CoreTypes.HorizontalAlignmentType>org.nativescript.widgets.ViewHelper.getHorizontalAlignment(this.nativeViewProtected);
}
[horizontalAlignmentProperty.setNative](value: CoreTypes.HorizontalAlignmentType) {
const nativeView = this.nativeViewProtected;
const lp: any = nativeView.getLayoutParams() || new org.nativescript.widgets.CommonLayoutParams();
const gravity = lp.gravity;
const weight = lp.weight;
// Set only if params gravity exists.
if (gravity !== undefined) {
switch (value) {
case 'left':
lp.gravity = GRAVITY_LEFT | (gravity & VERTICAL_GRAVITY_MASK);
if (weight < 0) {
lp.weight = -2;
}
break;
case 'center':
lp.gravity = GRAVITY_CENTER_HORIZONTAL | (gravity & VERTICAL_GRAVITY_MASK);
if (weight < 0) {
lp.weight = -2;
}
break;
case 'right':
lp.gravity = GRAVITY_RIGHT | (gravity & VERTICAL_GRAVITY_MASK);
if (weight < 0) {
lp.weight = -2;
}
break;
case 'stretch':
lp.gravity = GRAVITY_FILL_HORIZONTAL | (gravity & VERTICAL_GRAVITY_MASK);
if (weight < 0) {
lp.weight = -1;
}
break;
}
nativeView.setLayoutParams(lp);
}
}
[verticalAlignmentProperty.getDefault](): CoreTypes.VerticalAlignmentType {
return <CoreTypes.VerticalAlignmentType>org.nativescript.widgets.ViewHelper.getVerticalAlignment(this.nativeViewProtected);
}
[verticalAlignmentProperty.setNative](value: CoreTypes.VerticalAlignmentType) {
const nativeView = this.nativeViewProtected;
const lp: any = nativeView.getLayoutParams() || new org.nativescript.widgets.CommonLayoutParams();
const gravity = lp.gravity;
const height = lp.height;
// Set only if params gravity exists.
if (gravity !== undefined) {
switch (value) {
case 'top':
lp.gravity = GRAVITY_TOP | (gravity & HORIZONTAL_GRAVITY_MASK);
if (height < 0) {
lp.height = -2;
}
break;
case 'middle':
lp.gravity = GRAVITY_CENTER_VERTICAL | (gravity & HORIZONTAL_GRAVITY_MASK);
if (height < 0) {
lp.height = -2;
}
break;
case 'bottom':
lp.gravity = GRAVITY_BOTTOM | (gravity & HORIZONTAL_GRAVITY_MASK);
if (height < 0) {
lp.height = -2;
}
break;
case 'stretch':
lp.gravity = GRAVITY_FILL_VERTICAL | (gravity & HORIZONTAL_GRAVITY_MASK);
if (height < 0) {
lp.height = -1;
}
break;
}
nativeView.setLayoutParams(lp);
}
}
[testIDProperty.setNative](value: string) {
this.setAccessibilityIdentifier(this.nativeViewProtected, value);
}
@@ -835,27 +946,17 @@ export class View extends ViewCommon {
this.setAccessibilityIdentifier(this.nativeViewProtected, value);
}
// @ts-expect-error
[accessibilityRoleProperty.setNative](value: AccessibilityRole): void {
this.accessibilityRole = value;
updateA11yPropertiesCallback(this);
if (SDK_VERSION >= 28) {
this.nativeViewProtected?.setAccessibilityHeading(value === AccessibilityRole.Header);
}
}
[accessibilityValueProperty.setNative](): void {
[accessibilityValueProperty.setNative](value: string): void {
this._androidContentDescriptionUpdated = true;
updateContentDescription(this);
}
[accessibilityLabelProperty.setNative](): void {
[accessibilityLabelProperty.setNative](value: string): void {
this._androidContentDescriptionUpdated = true;
updateContentDescription(this);
}
[accessibilityHintProperty.setNative](): void {
[accessibilityHintProperty.setNative](value: string): void {
this._androidContentDescriptionUpdated = true;
updateContentDescription(this);
}
@@ -868,31 +969,7 @@ export class View extends ViewCommon {
}
}
// @ts-expect-error
[accessibilityLiveRegionProperty.setNative](value: AccessibilityLiveRegion): void {
switch (value) {
case AccessibilityLiveRegion.Assertive: {
this.nativeViewProtected.setAccessibilityLiveRegion(android.view.View.ACCESSIBILITY_LIVE_REGION_ASSERTIVE);
break;
}
case AccessibilityLiveRegion.Polite: {
this.nativeViewProtected.setAccessibilityLiveRegion(android.view.View.ACCESSIBILITY_LIVE_REGION_POLITE);
break;
}
default: {
this.nativeViewProtected.setAccessibilityLiveRegion(android.view.View.ACCESSIBILITY_LIVE_REGION_NONE);
break;
}
}
}
// @ts-expect-error
[accessibilityStateProperty.setNative](value: AccessibilityState): void {
this.accessibilityState = value;
updateA11yPropertiesCallback(this);
}
[accessibilityMediaSessionProperty.setNative](): void {
[accessibilityMediaSessionProperty.setNative](value: string): void {
updateA11yPropertiesCallback(this);
}
@@ -976,88 +1053,6 @@ export class View extends ViewCommon {
nativeView.setStateListAnimator(stateListAnimator);
}
[horizontalAlignmentProperty.getDefault](): CoreTypes.HorizontalAlignmentType {
return <CoreTypes.HorizontalAlignmentType>org.nativescript.widgets.ViewHelper.getHorizontalAlignment(this.nativeViewProtected);
}
// @ts-expect-error
[horizontalAlignmentProperty.setNative](value: CoreTypes.HorizontalAlignmentType) {
const nativeView = this.nativeViewProtected;
const lp: any = nativeView.getLayoutParams() || new org.nativescript.widgets.CommonLayoutParams();
const gravity = lp.gravity;
const weight = lp.weight;
// Set only if params gravity exists.
if (gravity !== undefined) {
switch (value) {
case 'left':
lp.gravity = GRAVITY_LEFT | (gravity & VERTICAL_GRAVITY_MASK);
if (weight < 0) {
lp.weight = -2;
}
break;
case 'center':
lp.gravity = GRAVITY_CENTER_HORIZONTAL | (gravity & VERTICAL_GRAVITY_MASK);
if (weight < 0) {
lp.weight = -2;
}
break;
case 'right':
lp.gravity = GRAVITY_RIGHT | (gravity & VERTICAL_GRAVITY_MASK);
if (weight < 0) {
lp.weight = -2;
}
break;
case 'stretch':
lp.gravity = GRAVITY_FILL_HORIZONTAL | (gravity & VERTICAL_GRAVITY_MASK);
if (weight < 0) {
lp.weight = -1;
}
break;
}
nativeView.setLayoutParams(lp);
}
}
[verticalAlignmentProperty.getDefault](): CoreTypes.VerticalAlignmentType {
return <CoreTypes.VerticalAlignmentType>org.nativescript.widgets.ViewHelper.getVerticalAlignment(this.nativeViewProtected);
}
// @ts-expect-error
[verticalAlignmentProperty.setNative](value: CoreTypes.VerticalAlignmentType) {
const nativeView = this.nativeViewProtected;
const lp: any = nativeView.getLayoutParams() || new org.nativescript.widgets.CommonLayoutParams();
const gravity = lp.gravity;
const height = lp.height;
// Set only if params gravity exists.
if (gravity !== undefined) {
switch (value) {
case 'top':
lp.gravity = GRAVITY_TOP | (gravity & HORIZONTAL_GRAVITY_MASK);
if (height < 0) {
lp.height = -2;
}
break;
case 'middle':
lp.gravity = GRAVITY_CENTER_VERTICAL | (gravity & HORIZONTAL_GRAVITY_MASK);
if (height < 0) {
lp.height = -2;
}
break;
case 'bottom':
lp.gravity = GRAVITY_BOTTOM | (gravity & HORIZONTAL_GRAVITY_MASK);
if (height < 0) {
lp.height = -2;
}
break;
case 'stretch':
lp.gravity = GRAVITY_FILL_VERTICAL | (gravity & HORIZONTAL_GRAVITY_MASK);
if (height < 0) {
lp.height = -1;
}
break;
}
nativeView.setLayoutParams(lp);
}
}
[rotateProperty.setNative](value: number) {
org.nativescript.widgets.ViewHelper.setRotate(this.nativeViewProtected, float(value));
}

View File

@@ -104,14 +104,13 @@ export class View extends ViewCommon {
@profile
public layout(left: number, top: number, right: number, bottom: number, setFrame = true): void {
const result = this._setCurrentLayoutBounds(left, top, right, bottom);
let { sizeChanged } = result;
const { boundsChanged, sizeChanged } = this._setCurrentLayoutBounds(left, top, right, bottom);
if (setFrame) {
this.layoutNativeView(left, top, right, bottom);
}
const needsLayout = result.boundsChanged || (this._privateFlags & PFLAG_LAYOUT_REQUIRED) === PFLAG_LAYOUT_REQUIRED;
const needsLayout = boundsChanged || (this._privateFlags & PFLAG_LAYOUT_REQUIRED) === PFLAG_LAYOUT_REQUIRED;
if (needsLayout) {
let position: Position;
@@ -119,14 +118,6 @@ export class View extends ViewCommon {
// on iOS 11+ it is possible to have a changed layout frame due to safe area insets
// get the frame and adjust the position, so that onLayout works correctly
position = IOSHelper.getPositionFromFrame(this.nativeViewProtected.frame);
if (!sizeChanged) {
// If frame has actually changed, there is the need to update view background and border styles as they depend on native view bounds
// To trigger the needed visual update, mark size as changed
if (position.left !== left || position.top !== top || position.right !== right || position.bottom !== bottom) {
sizeChanged = true;
}
}
} else {
position = { left, top, right, bottom };
}

View File

@@ -1,5 +1,5 @@
// Definitions.
import { NavigationType } from './frame-common';
import { NavigationType, TransitionState } from './frame-common';
import { NavigationTransition, BackstackEntry } from '.';
// Types.
@@ -152,7 +152,7 @@ export function _setAndroidFragmentTransitions(animated: boolean, navigationTran
setupCurrentFragmentExplodeTransition(navigationTransition, currentEntry);
}
} else if (name.indexOf('flip') === 0) {
const direction = name.substr('flip'.length) || 'right'; //Extract the direction from the string
const direction = name.substring('flip'.length) || 'right'; //Extract the direction from the string
const flipTransition = new FlipTransition(direction, navigationTransition.duration, navigationTransition.curve);
setupNewFragmentCustomTransition(navigationTransition, newEntry, flipTransition);
@@ -282,23 +282,28 @@ export function _getAnimatedEntries(frameId: number): Set<BackstackEntry> {
export function _updateTransitions(entry: ExpandedEntry): void {
const fragment = entry.fragment;
if (!fragment) {
return;
}
const enterTransitionListener = entry.enterTransitionListener;
if (enterTransitionListener && fragment) {
if (enterTransitionListener) {
fragment.setEnterTransition(enterTransitionListener.transition);
}
const exitTransitionListener = entry.exitTransitionListener;
if (exitTransitionListener && fragment) {
if (exitTransitionListener) {
fragment.setExitTransition(exitTransitionListener.transition);
}
const reenterTransitionListener = entry.reenterTransitionListener;
if (reenterTransitionListener && fragment) {
if (reenterTransitionListener) {
fragment.setReenterTransition(reenterTransitionListener.transition);
}
const returnTransitionListener = entry.returnTransitionListener;
if (returnTransitionListener && fragment) {
if (returnTransitionListener) {
fragment.setReturnTransition(returnTransitionListener.transition);
}
}
@@ -428,6 +433,16 @@ function addToWaitingQueue(entry: ExpandedEntry): void {
entries.add(entry);
}
function cloneExpandedTransitionListener(expandedTransitionListener: ExpandedTransitionListener) {
if (!expandedTransitionListener) {
return null;
}
const cloneTransition = expandedTransitionListener.transition.clone();
return addNativeTransitionListener(expandedTransitionListener.entry, cloneTransition);
}
function clearExitAndReenterTransitions(entry: ExpandedEntry, removeListener: boolean): void {
const fragment: androidx.fragment.app.Fragment = entry.fragment;
const exitListener = entry.exitTransitionListener;
@@ -469,15 +484,56 @@ function clearExitAndReenterTransitions(entry: ExpandedEntry, removeListener: bo
}
}
export function _getTransitionState(entry: ExpandedEntry): TransitionState {
let transitionState: TransitionState;
if (entry.enterTransitionListener && entry.exitTransitionListener) {
transitionState = {
enterTransitionListener: cloneExpandedTransitionListener(entry.enterTransitionListener),
exitTransitionListener: cloneExpandedTransitionListener(entry.exitTransitionListener),
reenterTransitionListener: cloneExpandedTransitionListener(entry.reenterTransitionListener),
returnTransitionListener: cloneExpandedTransitionListener(entry.returnTransitionListener),
transitionName: entry.transitionName,
entry,
};
} else {
transitionState = null;
}
return transitionState;
}
export function _restoreTransitionState(snapshot: TransitionState): void {
const entry = snapshot.entry as ExpandedEntry;
if (snapshot.enterTransitionListener) {
entry.enterTransitionListener = snapshot.enterTransitionListener;
}
if (snapshot.exitTransitionListener) {
entry.exitTransitionListener = snapshot.exitTransitionListener;
}
if (snapshot.reenterTransitionListener) {
entry.reenterTransitionListener = snapshot.reenterTransitionListener;
}
if (snapshot.returnTransitionListener) {
entry.returnTransitionListener = snapshot.returnTransitionListener;
}
entry.transitionName = snapshot.transitionName;
}
export function _clearFragment(entry: ExpandedEntry): void {
clearEntry(entry, false);
clearTransitions(entry, false);
}
export function _clearEntry(entry: ExpandedEntry): void {
clearEntry(entry, true);
clearTransitions(entry, true);
}
function clearEntry(entry: ExpandedEntry, removeListener: boolean): void {
function clearTransitions(entry: ExpandedEntry, removeListener: boolean): void {
clearExitAndReenterTransitions(entry, removeListener);
const fragment: androidx.fragment.app.Fragment = entry.fragment;
@@ -569,7 +625,7 @@ function setReturnTransition(navigationTransition: NavigationTransition, entry:
function setupNewFragmentSlideTransition(navTransition: NavigationTransition, entry: ExpandedEntry, name: string): void {
setupCurrentFragmentSlideTransition(navTransition, entry, name);
const direction = name.substr('slide'.length) || 'left'; //Extract the direction from the string
const direction = name.substring('slide'.length) || 'left'; //Extract the direction from the string
switch (direction) {
case 'left':
setEnterTransition(navTransition, entry, new androidx.transition.Slide(android.view.Gravity.RIGHT));
@@ -594,7 +650,7 @@ function setupNewFragmentSlideTransition(navTransition: NavigationTransition, en
}
function setupCurrentFragmentSlideTransition(navTransition: NavigationTransition, entry: ExpandedEntry, name: string): void {
const direction = name.substr('slide'.length) || 'left'; //Extract the direction from the string
const direction = name.substring('slide'.length) || 'left'; //Extract the direction from the string
switch (direction) {
case 'left':
setExitTransition(navTransition, entry, new androidx.transition.Slide(android.view.Gravity.LEFT));

View File

@@ -1,4 +1,4 @@
import { NavigationTransition, BackstackEntry } from '.';
import { NavigationTransition, BackstackEntry, TransitionState } from '.';
/**
* @private
@@ -20,6 +20,14 @@ export function _updateTransitions(entry: BackstackEntry): void;
* Reverse transitions from entry to fragment if any.
*/
export function _reverseTransitions(previousEntry: BackstackEntry, currentEntry: BackstackEntry): boolean;
/**
* @private
*/
export function _getTransitionState(entry: BackstackEntry): TransitionState;
/**
* @private
*/
export function _restoreTransitionState(snapshot: TransitionState): void;
/**
* @private
* Called when entry is removed from backstack (either back navigation or

View File

@@ -76,13 +76,13 @@ export class FrameBase extends CustomLayoutView {
return true;
} else if (top) {
let parentFrameCanGoBack = false;
let parentFrame = <FrameBase>getAncestor(top, 'Frame');
let parentFrame = getAncestor(top, 'Frame');
while (parentFrame && !parentFrameCanGoBack) {
if (parentFrame && parentFrame.canGoBack()) {
parentFrameCanGoBack = true;
} else {
parentFrame = <FrameBase>getAncestor(parentFrame, 'Frame');
parentFrame = getAncestor(parentFrame, 'Frame');
}
}
@@ -122,7 +122,6 @@ export class FrameBase extends CustomLayoutView {
@profile
public onLoaded() {
super.onLoaded();
this._processNextNavigationEntry();
}
@@ -323,9 +322,10 @@ export class FrameBase extends CustomLayoutView {
}
private isNestedWithin(parentFrameCandidate: FrameBase): boolean {
let frameAncestor: FrameBase = this;
let frameAncestor = this as FrameBase;
while (frameAncestor) {
frameAncestor = <FrameBase>getAncestor(frameAncestor, FrameBase);
frameAncestor = getAncestor(frameAncestor, FrameBase);
if (frameAncestor === parentFrameCandidate) {
return true;
}

View File

@@ -5,7 +5,7 @@ import { Observable } from '../../data/observable';
import { Trace } from '../../trace';
import { View } from '../core/view';
import { _stack, FrameBase, NavigationType } from './frame-common';
import { _clearEntry, _clearFragment, _getAnimatedEntries, _reverseTransitions, _setAndroidFragmentTransitions, _updateTransitions, addNativeTransitionListener } from './fragment.transitions';
import { _clearEntry, _clearFragment, _getAnimatedEntries, _getTransitionState, _restoreTransitionState, _reverseTransitions, _setAndroidFragmentTransitions, _updateTransitions, addNativeTransitionListener } from './fragment.transitions';
import { profile } from '../../profiling';
import { android as androidUtils } from '../../utils/native-helper';
import type { ExpandedEntry } from './fragment.transitions.android';
@@ -23,6 +23,7 @@ export { setFragmentClass } from './fragment';
const INTENT_EXTRA = 'com.tns.activity';
const ownerSymbol = Symbol('_owner');
const isPendingDetachSymbol = Symbol('_isPendingDetach');
let navDepth = -1;
let fragmentId = -1;
@@ -51,6 +52,12 @@ function getAttachListener(): android.view.View.OnAttachStateChangeListener {
if (owner) {
owner._onDetachedFromWindow();
}
if (view[isPendingDetachSymbol]) {
delete view[isPendingDetachSymbol];
view.removeOnAttachStateChangeListener(this);
view[ownerSymbol] = null;
}
},
});
@@ -66,7 +73,10 @@ export class Frame extends FrameBase {
private _containerViewId = -1;
private _tearDownPending = false;
private _attachedToWindow = false;
private _wasReset = false;
/**
* This property indicates that the view is to be reused as a root view or has been previously disposed.
*/
private _isReset = false;
private _cachedTransitionState: TransitionState;
private _frameCreateTimeout: NodeJS.Timeout;
@@ -136,8 +146,9 @@ export class Frame extends FrameBase {
}
this._attachedToWindow = true;
this._wasReset = false;
this._isReset = false;
this._processNextNavigationEntry();
this._ensureEntryFragment();
}
_onDetachedFromWindow(): void {
@@ -155,12 +166,12 @@ export class Frame extends FrameBase {
return;
}
// in case the activity is "reset" using resetRootView we must wait for
// in case the activity is "reset" using resetRootView or disposed we must wait for
// the attachedToWindow event to make the first navigation or it will crash
// https://github.com/NativeScript/NativeScript/commit/9dd3e1a8076e5022e411f2f2eeba34aabc68d112
// though we should not do it on app "start"
// or it will create a "flash" to activity background color
if (this._wasReset && !this._attachedToWindow) {
if (this._isReset && !this._attachedToWindow) {
return;
}
@@ -187,7 +198,7 @@ export class Frame extends FrameBase {
// simulated navigation (NoTransition, zero duration animator) and thus the fragment immediately disappears;
// the user only sees the animation of the entering fragment as per its specific enter animation settings.
// NOTE: we are restoring the animation settings in Frame.setCurrent(...) as navigation completes asynchronously
const cachedTransitionState = getTransitionState(this._currentEntry);
const cachedTransitionState = _getTransitionState(this._currentEntry);
if (cachedTransitionState) {
this._cachedTransitionState = cachedTransitionState;
@@ -221,7 +232,7 @@ export class Frame extends FrameBase {
public _onRootViewReset(): void {
super._onRootViewReset();
// used to handle the "first" navigate differently on first run and on reset
this._wasReset = true;
this._isReset = true;
// call this AFTER the super call to ensure descendants apply their rootview-reset logic first
// i.e. in a scenario with nested frames / frame with tabview let the descendandt cleanup the inner
// fragments first, and then cleanup the parent fragments
@@ -234,6 +245,33 @@ export class Frame extends FrameBase {
this.backgroundColor = this._originalBackground;
this._originalBackground = null;
}
this._ensureEntryFragment();
super.onLoaded();
}
onUnloaded() {
super.onUnloaded();
if (typeof this._frameCreateTimeout === 'number') {
clearTimeout(this._frameCreateTimeout);
this._frameCreateTimeout = null;
}
}
/**
* TODO: Check if this fragment precaution is still needed
*/
private _ensureEntryFragment(): void {
// in case the activity is "reset" using resetRootView or disposed we must wait for
// the attachedToWindow event to make the first navigation or it will crash
// https://github.com/NativeScript/NativeScript/commit/9dd3e1a8076e5022e411f2f2eeba34aabc68d112
// though we should not do it on app "start"
// or it will create a "flash" to activity background color
if (this._isReset && !this._attachedToWindow) {
return;
}
this._frameCreateTimeout = setTimeout(() => {
// there's a bug with nested frames where sometimes the nested fragment is not recreated at all
// so we manually check on loaded event if the fragment is not recreated and recreate it
@@ -248,17 +286,9 @@ export class Frame extends FrameBase {
transaction.commitAllowingStateLoss();
}
}
}, 0);
super.onLoaded();
}
onUnloaded() {
super.onUnloaded();
if (typeof this._frameCreateTimeout === 'number') {
clearTimeout(this._frameCreateTimeout);
this._frameCreateTimeout = null;
}
}, 0);
}
private disposeCurrentFragment(): void {
@@ -345,7 +375,7 @@ export class Frame extends FrameBase {
// restore cached animation settings if we just completed simulated first navigation (no animation)
if (this._cachedTransitionState) {
restoreTransitionState(this._currentEntry, this._cachedTransitionState);
_restoreTransitionState(this._cachedTransitionState);
this._cachedTransitionState = null;
}
@@ -384,7 +414,7 @@ export class Frame extends FrameBase {
// HACK: This @profile decorator creates a circular dependency
// HACK: because the function parameter type is evaluated with 'typeof'
@profile
public _navigateCore(newEntry: any) {
public _navigateCore(newEntry: BackstackEntry) {
// should be (newEntry: BackstackEntry)
super._navigateCore(newEntry);
@@ -480,19 +510,19 @@ export class Frame extends FrameBase {
if (removed.fragment) {
_clearEntry(removed);
removed.fragment = null;
}
removed.fragment = null;
removed.viewSavedState = null;
}
protected _disposeBackstackEntry(entry: BackstackEntry): void {
if (entry.fragment) {
_clearFragment(entry);
entry.fragment = null;
}
entry.recreated = false;
entry.fragment = null;
super._disposeBackstackEntry(entry);
}
@@ -511,9 +541,12 @@ export class Frame extends FrameBase {
public initNativeView(): void {
super.initNativeView();
const listener = getAttachListener();
this.nativeViewProtected.addOnAttachStateChangeListener(listener);
this.nativeViewProtected[ownerSymbol] = this;
this._android.rootViewGroup = this.nativeViewProtected;
const nativeView = this.nativeViewProtected as android.view.ViewGroup;
nativeView.addOnAttachStateChangeListener(listener);
nativeView[ownerSymbol] = this;
this._android.rootViewGroup = nativeView;
if (this._containerViewId < 0) {
this._containerViewId = android.view.View.generateViewId();
}
@@ -521,12 +554,23 @@ export class Frame extends FrameBase {
}
public disposeNativeView() {
const nativeView = this.nativeViewProtected as android.view.ViewGroup;
const listener = getAttachListener();
this.nativeViewProtected.removeOnAttachStateChangeListener(listener);
this.nativeViewProtected[ownerSymbol] = null;
// There are cases like root view when detach listener is not called upon removing view from view-tree
// so mark those views as pending and remove listener once the view is detached
if (nativeView.isAttachedToWindow()) {
nativeView[isPendingDetachSymbol] = true;
} else {
nativeView.removeOnAttachStateChangeListener(listener);
nativeView[ownerSymbol] = null;
}
this._tearDownPending = !!this._executingContext;
const current = this._currentEntry;
const executingEntry = this._executingContext ? this._executingContext.entry : null;
this.backStack.forEach((entry) => {
// Don't destroy current and executing entries or UI will look blank.
// We will do it in setCurrent.
@@ -539,6 +583,12 @@ export class Frame extends FrameBase {
this._disposeBackstackEntry(current);
}
// Dispose cached transition and store it again if view ever gets re-used
this._cachedTransitionState = null;
// Mark as reset in order to properly re-initialize fragments if view ever gets re-used
this._isReset = true;
this._android.rootViewGroup = null;
this._removeFromFrameStack();
super.disposeNativeView();
@@ -596,55 +646,6 @@ export function reloadPage(context?: ModuleContext): void {
// attach on global, so it can be overwritten in NativeScript Angular
global.__onLiveSyncCore = Frame.reloadPage;
function cloneExpandedTransitionListener(expandedTransitionListener: any) {
if (!expandedTransitionListener) {
return null;
}
const cloneTransition = expandedTransitionListener.transition.clone();
return addNativeTransitionListener(expandedTransitionListener.entry, cloneTransition);
}
function getTransitionState(entry: BackstackEntry): TransitionState {
const expandedEntry = <any>entry;
const transitionState = <TransitionState>{};
if (expandedEntry.enterTransitionListener && expandedEntry.exitTransitionListener) {
transitionState.enterTransitionListener = cloneExpandedTransitionListener(expandedEntry.enterTransitionListener);
transitionState.exitTransitionListener = cloneExpandedTransitionListener(expandedEntry.exitTransitionListener);
transitionState.reenterTransitionListener = cloneExpandedTransitionListener(expandedEntry.reenterTransitionListener);
transitionState.returnTransitionListener = cloneExpandedTransitionListener(expandedEntry.returnTransitionListener);
transitionState.transitionName = expandedEntry.transitionName;
transitionState.entry = entry;
} else {
return null;
}
return transitionState;
}
function restoreTransitionState(entry: BackstackEntry, snapshot: TransitionState): void {
const expandedEntry = <any>entry;
if (snapshot.enterTransitionListener) {
expandedEntry.enterTransitionListener = snapshot.enterTransitionListener;
}
if (snapshot.exitTransitionListener) {
expandedEntry.exitTransitionListener = snapshot.exitTransitionListener;
}
if (snapshot.reenterTransitionListener) {
expandedEntry.reenterTransitionListener = snapshot.reenterTransitionListener;
}
if (snapshot.returnTransitionListener) {
expandedEntry.returnTransitionListener = snapshot.returnTransitionListener;
}
expandedEntry.transitionName = snapshot.transitionName;
}
let framesCounter = 0;
class AndroidFrame extends Observable implements AndroidFrameDefinition {

View File

@@ -97,7 +97,7 @@ export class Frame extends FrameBase {
// !!! THIS PROFILE DECORATOR CREATES A CIRCULAR DEPENDENCY
// !!! BECAUSE THE PARAMETER TYPE IS EVALUATED WITH TYPEOF
@profile
public _navigateCore(backstackEntry: any) {
public _navigateCore(backstackEntry: BackstackEntry) {
super._navigateCore(backstackEntry);
const viewController: UIViewController = backstackEntry.resolvedPage.ios;
@@ -507,7 +507,7 @@ class UINavigationControllerImpl extends UINavigationController {
}
}
private animateWithDuration(navigationTransition: NavigationTransition, nativeTransition: UIViewAnimationTransition, transitionType: string, baseCallback: Function): void {
private animateWithDuration(navigationTransition: NavigationTransition, nativeTransition: UIViewAnimationTransition, transitionType: string, baseCallback: () => void): void {
const duration = navigationTransition.duration ? navigationTransition.duration / 1000 : CORE_ANIMATION_DEFAULTS.duration;
const curve = _getNativeCurve(navigationTransition);

View File

@@ -1,5 +1,5 @@
import { ImageBase, stretchProperty, imageSourceProperty, tintColorProperty, srcProperty, iosSymbolEffectProperty, ImageSymbolEffect, ImageSymbolEffects, iosSymbolScaleProperty } from './image-common';
import { ImageSource, iosSymbolScaleType } from '../../image-source';
import { ImageSource } from '../../image-source';
import { ImageAsset } from '../../image-asset';
import { Color } from '../../color';
import { Trace } from '../../trace';
@@ -219,8 +219,7 @@ export class Image extends ImageBase {
}
}
// @ts-expect-error
[iosSymbolScaleProperty.setNative](value: iosSymbolScaleType) {
[iosSymbolScaleProperty.setNative](value: string) {
// reset src to configure scale
this._setSrc(this.src);
}

View File

@@ -68,6 +68,7 @@ class FlexLine {
_dividerLengthInMainSize = 0;
_crossSize = 0;
_itemCount = 0;
_goneItemCount = 0;
_totalFlexGrow = 0;
_totalFlexShrink = 0;
_maxBaseline = 0;
@@ -96,6 +97,9 @@ class FlexLine {
get itemCount(): number {
return this._itemCount;
}
get layoutVisibleItemCount(): number {
return this._itemCount - this._goneItemCount;
}
get totalFlexGrow(): number {
return this._totalFlexGrow;
}
@@ -250,6 +254,7 @@ export class FlexboxLayout extends FlexboxLayoutBase {
continue;
} else if (child.isCollapsed) {
flexLine._itemCount++;
flexLine._goneItemCount++;
this._addFlexLineIfLastFlexItem(i, childCount, flexLine);
continue;
}
@@ -277,7 +282,7 @@ export class FlexboxLayout extends FlexboxLayoutBase {
largestHeightInRow = Math.max(largestHeightInRow, child.getMeasuredHeight() + lp.effectiveMarginTop + lp.effectiveMarginBottom);
if (this._isWrapRequired(child, widthMode, widthSize, flexLine._mainSize, child.getMeasuredWidth() + lp.effectiveMarginLeft + lp.effectiveMarginRight, i, indexInFlexLine)) {
if (flexLine.itemCount > 0) {
if (flexLine.layoutVisibleItemCount > 0) {
this._addFlexLine(flexLine);
}
@@ -319,11 +324,11 @@ export class FlexboxLayout extends FlexboxLayoutBase {
if (this.flexWrap !== FlexWrap.WRAP_REVERSE) {
let marginTop = flexLine._maxBaseline - FlexboxLayout.getBaseline(child);
marginTop = Math.max(marginTop, lp.effectiveMarginTop);
largestHeightInLine = Math.max(largestHeightInLine, child.getActualSize().height + marginTop + lp.effectiveMarginBottom);
largestHeightInLine = Math.max(largestHeightInLine, child.getMeasuredHeight() + marginTop + lp.effectiveMarginBottom);
} else {
let marginBottom = flexLine._maxBaseline - child.getMeasuredHeight() + FlexboxLayout.getBaseline(child);
marginBottom = Math.max(marginBottom, lp.effectiveMarginBottom);
largestHeightInLine = Math.max(largestHeightInLine, child.getActualSize().height + lp.effectiveMarginTop + marginBottom);
largestHeightInLine = Math.max(largestHeightInLine, child.getMeasuredHeight() + lp.effectiveMarginTop + marginBottom);
}
}
flexLine._crossSize = largestHeightInLine;
@@ -360,6 +365,7 @@ export class FlexboxLayout extends FlexboxLayoutBase {
continue;
} else if (child.isCollapsed) {
flexLine._itemCount++;
flexLine._goneItemCount++;
this._addFlexLineIfLastFlexItem(i, childCount, flexLine);
continue;
}
@@ -386,7 +392,7 @@ export class FlexboxLayout extends FlexboxLayoutBase {
largestWidthInColumn = Math.max(largestWidthInColumn, child.getMeasuredWidth() + lp.effectiveMarginLeft + lp.effectiveMarginRight);
if (this._isWrapRequired(child, heightMode, heightSize, flexLine.mainSize, child.getMeasuredHeight() + lp.effectiveMarginTop + lp.effectiveMarginBottom, i, indexInFlexLine)) {
if (flexLine._itemCount > 0) {
if (flexLine.layoutVisibleItemCount > 0) {
this._addFlexLine(flexLine);
}
@@ -448,7 +454,7 @@ export class FlexboxLayout extends FlexboxLayoutBase {
}
private _addFlexLineIfLastFlexItem(childIndex: number, childCount: number, flexLine: FlexLine) {
if (childIndex === childCount - 1 && flexLine.itemCount !== 0) {
if (childIndex === childCount - 1 && flexLine.layoutVisibleItemCount !== 0) {
this._addFlexLine(flexLine);
}
}
@@ -1013,16 +1019,20 @@ export class FlexboxLayout extends FlexboxLayoutBase {
childLeft = paddingLeft + (width - insets.left - insets.right - flexLine._mainSize) / 2.0;
childRight = width - paddingRight - (width - insets.left - insets.right - flexLine._mainSize) / 2.0;
break;
case JustifyContent.SPACE_AROUND:
if (flexLine._itemCount !== 0) {
spaceBetweenItem = (width - insets.left - insets.right - flexLine.mainSize) / flexLine._itemCount;
case JustifyContent.SPACE_AROUND: {
const visibleCount = flexLine.layoutVisibleItemCount;
if (visibleCount !== 0) {
spaceBetweenItem = (width - insets.left - insets.right - flexLine.mainSize) / visibleCount;
}
childLeft = paddingLeft + spaceBetweenItem / 2.0;
childRight = width - paddingRight - spaceBetweenItem / 2.0;
break;
}
case JustifyContent.SPACE_BETWEEN: {
const visibleCount = flexLine.layoutVisibleItemCount;
const denominator = visibleCount !== 1 ? visibleCount - 1 : 1.0;
childLeft = paddingLeft;
const denominator = flexLine.itemCount !== 1 ? flexLine.itemCount - 1 : 1.0;
spaceBetweenItem = (width - insets.left - insets.right - flexLine.mainSize) / denominator;
childRight = width - paddingRight;
break;
@@ -1157,16 +1167,20 @@ export class FlexboxLayout extends FlexboxLayoutBase {
childTop = paddingTop + (height - insets.top - insets.bottom - flexLine._mainSize) / 2.0;
childBottom = height - paddingBottom - (height - insets.top - insets.bottom - flexLine._mainSize) / 2.0;
break;
case JustifyContent.SPACE_AROUND:
if (flexLine._itemCount !== 0) {
spaceBetweenItem = (height - insets.top - insets.bottom - flexLine._mainSize) / flexLine.itemCount;
case JustifyContent.SPACE_AROUND: {
const visibleCount = flexLine.layoutVisibleItemCount;
if (visibleCount !== 0) {
spaceBetweenItem = (height - insets.top - insets.bottom - flexLine._mainSize) / visibleCount;
}
childTop = paddingTop + spaceBetweenItem / 2.0;
childBottom = height - paddingBottom - spaceBetweenItem / 2.0;
break;
}
case JustifyContent.SPACE_BETWEEN: {
const visibleCount = flexLine.layoutVisibleItemCount;
const denominator = visibleCount !== 1 ? visibleCount - 1 : 1.0;
childTop = paddingTop;
const denominator = flexLine.itemCount !== 1 ? flexLine.itemCount - 1 : 1.0;
spaceBetweenItem = (height - insets.top - insets.bottom - flexLine.mainSize) / denominator;
childBottom = height - paddingBottom;
break;

View File

@@ -4,6 +4,7 @@ import { isUserInteractionEnabledProperty, isEnabledProperty } from '../core/vie
import { ad } from '../../utils';
import { Color } from '../../color';
import { colorProperty, backgroundColorProperty, backgroundInternalProperty, fontInternalProperty, fontSizeProperty } from '../styling/style-properties';
import { Background } from '../styling/background';
export * from './search-bar-common';
@@ -227,14 +228,13 @@ export class SearchBar extends SearchBarBase {
[backgroundInternalProperty.getDefault](): any {
return null;
}
[backgroundInternalProperty.setNative](value: any) {
[backgroundInternalProperty.setNative](value: android.graphics.drawable.Drawable | Background) {
//
}
[textProperty.getDefault](): string {
return '';
}
// @ts-expect-error
[textProperty.setNative](value: string) {
const text = value === null || value === undefined ? '' : value.toString();
this.nativeViewProtected.setQuery(text, false);
@@ -242,7 +242,6 @@ export class SearchBar extends SearchBarBase {
[hintProperty.getDefault](): string {
return null;
}
// @ts-expect-error
[hintProperty.setNative](value: string) {
if (value === null || value === undefined) {
this.nativeViewProtected.setQueryHint(null);

View File

@@ -158,14 +158,13 @@ export class SearchBar extends SearchBarBase {
[backgroundInternalProperty.getDefault](): any {
return null;
}
[backgroundInternalProperty.setNative](value: any) {
[backgroundInternalProperty.setNative](value: UIColor) {
//
}
[textProperty.getDefault](): string {
return '';
}
// @ts-expect-error
[textProperty.setNative](value: string) {
const text = value === null || value === undefined ? '' : value.toString();
this.ios.text = text;
@@ -174,7 +173,6 @@ export class SearchBar extends SearchBarBase {
[hintProperty.getDefault](): string {
return '';
}
// @ts-expect-error
[hintProperty.setNative](value: string) {
this._updateAttributedPlaceholder();
}

View File

@@ -386,13 +386,6 @@ export class TextBase extends TextBaseCommon {
}
}
[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();
}
@@ -446,17 +439,9 @@ export class TextBase extends TextBaseCommon {
);
}
[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](): CoreTypes.LengthType {
return { value: this._defaultPaddingTop, unit: 'px' };
}
// @ts-expect-error
[paddingTopProperty.setNative](value: CoreTypes.LengthType) {
org.nativescript.widgets.ViewHelper.setPaddingTop(this.nativeTextViewProtected, Length.toDevicePixels(value, 0) + Length.toDevicePixels(this.style.borderTopWidth, 0));
}
@@ -464,7 +449,6 @@ export class TextBase extends TextBaseCommon {
[paddingRightProperty.getDefault](): CoreTypes.LengthType {
return { value: this._defaultPaddingRight, unit: 'px' };
}
// @ts-expect-error
[paddingRightProperty.setNative](value: CoreTypes.LengthType) {
org.nativescript.widgets.ViewHelper.setPaddingRight(this.nativeTextViewProtected, Length.toDevicePixels(value, 0) + Length.toDevicePixels(this.style.borderRightWidth, 0));
}
@@ -472,7 +456,6 @@ export class TextBase extends TextBaseCommon {
[paddingBottomProperty.getDefault](): CoreTypes.LengthType {
return { value: this._defaultPaddingBottom, unit: 'px' };
}
// @ts-expect-error
[paddingBottomProperty.setNative](value: CoreTypes.LengthType) {
org.nativescript.widgets.ViewHelper.setPaddingBottom(this.nativeTextViewProtected, Length.toDevicePixels(value, 0) + Length.toDevicePixels(this.style.borderBottomWidth, 0));
}
@@ -480,11 +463,24 @@ export class TextBase extends TextBaseCommon {
[paddingLeftProperty.getDefault](): CoreTypes.LengthType {
return { value: this._defaultPaddingLeft, unit: 'px' };
}
// @ts-expect-error
[paddingLeftProperty.setNative](value: CoreTypes.LengthType) {
org.nativescript.widgets.ViewHelper.setPaddingLeft(this.nativeTextViewProtected, Length.toDevicePixels(value, 0) + Length.toDevicePixels(this.style.borderLeftWidth, 0));
}
[lineHeightProperty.getDefault](): number {
return this.nativeTextViewProtected.getLineSpacingExtra() / layout.getDisplayDensity();
}
[lineHeightProperty.setNative](value: number) {
this.nativeTextViewProtected.setLineSpacing(value * layout.getDisplayDensity(), 1);
}
[letterSpacingProperty.getDefault](): number {
return org.nativescript.widgets.ViewHelper.getLetterspacing(this.nativeTextViewProtected);
}
[letterSpacingProperty.setNative](value: number) {
org.nativescript.widgets.ViewHelper.setLetterspacing(this.nativeTextViewProtected, value);
}
[testIDProperty.setNative](value: string): void {
this.setAccessibilityIdentifier(this.nativeTextViewProtected, value);
}
@@ -493,7 +489,7 @@ export class TextBase extends TextBaseCommon {
this.setAccessibilityIdentifier(this.nativeTextViewProtected, value);
}
[maxLinesProperty.setNative](value: number) {
[maxLinesProperty.setNative](value: CoreTypes.MaxLinesType) {
const nativeTextViewProtected = this.nativeTextViewProtected;
if (value <= 0) {
nativeTextViewProtected.setMaxLines(Number.MAX_SAFE_INTEGER);

View File

@@ -350,7 +350,7 @@ export class TextField extends TextFieldBase {
}
if (paragraphStyle) {
let attributedString = NSMutableAttributedString.alloc().initWithString(this.nativeViewProtected.text || '');
const attributedString = NSMutableAttributedString.alloc().initWithString(this.nativeViewProtected.text || '');
attributedString.addAttributeValueRange(NSParagraphStyleAttributeName, paragraphStyle, NSRangeFromString(`{0,${attributedString.length}}`));
this.nativeViewProtected.attributedText = attributedString;

View File

@@ -70,6 +70,9 @@ public class FlexLine {
*/
int mItemCount;
/** Holds the count of the views whose visibilities are gone */
int mGoneItemCount;
/**
* @see {@link #getTotalFlexGrow()}
*/
@@ -151,6 +154,13 @@ public class FlexLine {
return mItemCount;
}
/**
* @return the count of the views whose visibilities are not gone in this flex line.
*/
public int getLayoutVisibleItemCount() {
return mItemCount - mGoneItemCount;
}
/**
* @return the sum of the flexGrow properties of the children included in this flex line
*/

View File

@@ -583,6 +583,7 @@ public class FlexboxLayout extends LayoutBase {
continue;
} else if (child.getVisibility() == View.GONE) {
flexLine.mItemCount++;
flexLine.mGoneItemCount++;
addFlexLineIfLastFlexItem(i, childCount, flexLine);
continue;
}
@@ -627,7 +628,7 @@ public class FlexboxLayout extends LayoutBase {
if (isWrapRequired(widthMode, widthSize, flexLine.mMainSize,
child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin, lp,
i, indexInFlexLine)) {
if (flexLine.mItemCount > 0) {
if (flexLine.getLayoutVisibleItemCount() > 0) {
addFlexLine(flexLine);
}
@@ -681,17 +682,18 @@ public class FlexboxLayout extends LayoutBase {
for (int i = viewIndex; i < viewIndex + flexLine.mItemCount; i++) {
View child = getReorderedChildAt(i);
LayoutParams lp = (LayoutParams) child.getLayoutParams();
if (mFlexWrap != FLEX_WRAP_WRAP_REVERSE) {
int marginTop = flexLine.mMaxBaseline - child.getBaseline();
marginTop = Math.max(marginTop, lp.topMargin);
largestHeightInLine = Math.max(largestHeightInLine,
child.getHeight() + marginTop + lp.bottomMargin);
child.getMeasuredHeight() + marginTop + lp.bottomMargin);
} else {
int marginBottom = flexLine.mMaxBaseline - child.getMeasuredHeight() +
child.getBaseline();
marginBottom = Math.max(marginBottom, lp.bottomMargin);
largestHeightInLine = Math.max(largestHeightInLine,
child.getHeight() + lp.topMargin + marginBottom);
child.getMeasuredHeight() + lp.topMargin + marginBottom);
}
}
flexLine.mCrossSize = largestHeightInLine;
@@ -745,6 +747,7 @@ public class FlexboxLayout extends LayoutBase {
continue;
} else if (child.getVisibility() == View.GONE) {
flexLine.mItemCount++;
flexLine.mGoneItemCount++;
addFlexLineIfLastFlexItem(i, childCount, flexLine);
continue;
}
@@ -790,7 +793,7 @@ public class FlexboxLayout extends LayoutBase {
if (isWrapRequired(heightMode, heightSize, flexLine.mMainSize,
child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin, lp,
i, indexInFlexLine)) {
if (flexLine.mItemCount > 0) {
if (flexLine.getLayoutVisibleItemCount() > 0) {
addFlexLine(flexLine);
}
@@ -862,7 +865,7 @@ public class FlexboxLayout extends LayoutBase {
}
private void addFlexLineIfLastFlexItem(int childIndex, int childCount, FlexLine flexLine) {
if (childIndex == childCount - 1 && flexLine.mItemCount != 0) {
if (childIndex == childCount - 1 && flexLine.getLayoutVisibleItemCount() != 0) {
// Add the flex line if this item is the last item
addFlexLine(flexLine);
}
@@ -1661,20 +1664,25 @@ public class FlexboxLayout extends LayoutBase {
childLeft = paddingLeft + (width - flexLine.mMainSize) / 2f;
childRight = width - paddingRight - (width - flexLine.mMainSize) / 2f;
break;
case JUSTIFY_CONTENT_SPACE_AROUND:
if (flexLine.mItemCount != 0) {
case JUSTIFY_CONTENT_SPACE_AROUND: {
int visibleCount = flexLine.getLayoutVisibleItemCount();
if (visibleCount != 0) {
spaceBetweenItem = (width - flexLine.mMainSize)
/ (float) flexLine.mItemCount;
/ (float) visibleCount;
}
childLeft = paddingLeft + spaceBetweenItem / 2f;
childRight = width - paddingRight - spaceBetweenItem / 2f;
break;
case JUSTIFY_CONTENT_SPACE_BETWEEN:
}
case JUSTIFY_CONTENT_SPACE_BETWEEN: {
int visibleCount = flexLine.getLayoutVisibleItemCount();
float denominator = visibleCount != 1 ? visibleCount - 1 : 1f;
childLeft = paddingLeft;
float denominator = flexLine.mItemCount != 1 ? flexLine.mItemCount - 1 : 1f;
spaceBetweenItem = (width - flexLine.mMainSize) / denominator;
childRight = width - paddingRight;
break;
}
default:
throw new IllegalStateException(
"Invalid justifyContent is set: " + mJustifyContent);
@@ -1878,20 +1886,25 @@ public class FlexboxLayout extends LayoutBase {
childTop = paddingTop + (height - flexLine.mMainSize) / 2f;
childBottom = height - paddingBottom - (height - flexLine.mMainSize) / 2f;
break;
case JUSTIFY_CONTENT_SPACE_AROUND:
if (flexLine.mItemCount != 0) {
case JUSTIFY_CONTENT_SPACE_AROUND: {
int visibleCount = flexLine.getLayoutVisibleItemCount();
if (visibleCount != 0) {
spaceBetweenItem = (height - flexLine.mMainSize)
/ (float) flexLine.mItemCount;
/ (float) visibleCount;
}
childTop = paddingTop + spaceBetweenItem / 2f;
childBottom = height - paddingBottom - spaceBetweenItem / 2f;
break;
case JUSTIFY_CONTENT_SPACE_BETWEEN:
}
case JUSTIFY_CONTENT_SPACE_BETWEEN: {
int visibleCount = flexLine.getLayoutVisibleItemCount();
float denominator = visibleCount != 1 ? visibleCount - 1 : 1f;
childTop = paddingTop;
float denominator = flexLine.mItemCount != 1 ? flexLine.mItemCount - 1 : 1f;
spaceBetweenItem = (height - flexLine.mMainSize) / denominator;
childBottom = height - paddingBottom;
break;
}
default:
throw new IllegalStateException(
"Invalid justifyContent is set: " + mJustifyContent);
@@ -2085,6 +2098,11 @@ public class FlexboxLayout extends LayoutBase {
FlexLine flexLine = mFlexLines.get(i);
for (int j = 0; j < flexLine.mItemCount; j++) {
View view = getReorderedChildAt(currentViewIndex);
if (view == null || view.getVisibility() == View.GONE) {
continue;
}
LayoutParams lp = (LayoutParams) view.getLayoutParams();
// Judge if the beginning or middle divider is needed
@@ -2165,6 +2183,11 @@ public class FlexboxLayout extends LayoutBase {
// Draw horizontal dividers if needed
for (int j = 0; j < flexLine.mItemCount; j++) {
View view = getReorderedChildAt(currentViewIndex);
if (view == null || view.getVisibility() == View.GONE) {
continue;
}
LayoutParams lp = (LayoutParams) view.getLayoutParams();
// Judge if the beginning or middle divider is needed
@@ -2332,11 +2355,20 @@ public class FlexboxLayout extends LayoutBase {
}
/**
* @return the flex lines composing this flex container. This method returns an unmodifiable
* list. Thus any changes of the returned list are not supported.
* @return the flex lines composing this flex container. This method returns a copy of the
* original list excluding a dummy flex line (flex line that doesn't have any flex items in it
* but used for the alignment along the cross axis).
* Thus any changes of the returned list are not reflected to the original list.
*/
public List<FlexLine> getFlexLines() {
return Collections.unmodifiableList(mFlexLines);
List<FlexLine> result = new ArrayList<>(mFlexLines.size());
for (FlexLine flexLine : mFlexLines) {
if (flexLine.getLayoutVisibleItemCount() == 0) {
continue;
}
result.add(flexLine);
}
return result;
}
/**
@@ -2535,7 +2567,7 @@ public class FlexboxLayout extends LayoutBase {
private boolean allFlexLinesAreDummyBefore(int flexLineIndex) {
for (int i = 0; i < flexLineIndex; i++) {
if (mFlexLines.get(i).mItemCount > 0) {
if (mFlexLines.get(i).getLayoutVisibleItemCount() > 0) {
return false;
}
}
@@ -2554,7 +2586,7 @@ public class FlexboxLayout extends LayoutBase {
}
for (int i = flexLineIndex + 1; i < mFlexLines.size(); i++) {
if (mFlexLines.get(i).mItemCount > 0) {
if (mFlexLines.get(i).getLayoutVisibleItemCount() > 0) {
return false;
}
}