Skip to content

Commit 50819dc

Browse files
authored
Merge branch 'main' into fix-proxyview-hidden
2 parents 15fbca8 + cce8a22 commit 50819dc

File tree

6 files changed

+185
-5
lines changed

6 files changed

+185
-5
lines changed

apps/automated/src/ui/layouts/stack-layout-tests.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,11 +44,16 @@ export class StackLayoutTest extends testModule.UITest<StackLayout> {
4444

4545
TKUnit.assertEqual(this.rootLayout.orientation, CoreTypes.Orientation.vertical, 'Default orientation should be Vertical.');
4646

47+
// Capture current counts to assert on deltas instead of absolute values.
48+
// Some platforms may perform extra layout passes, so we only require at least one new pass.
49+
const beforeMeasure = this.rootLayout.measureCount;
50+
const beforeArrange = this.rootLayout.arrangeCount;
51+
4752
this.rootLayout.orientation = 'horizontal';
4853
this.waitUntilTestElementLayoutIsValid();
4954

50-
TKUnit.assertEqual(this.rootLayout.measureCount, 2, 'Orientation change should invalidate measure.');
51-
TKUnit.assertEqual(this.rootLayout.arrangeCount, 2, 'Orientation change should invalidate arrange.');
55+
TKUnit.assert(this.rootLayout.measureCount > beforeMeasure, `Orientation change should trigger a new measure. Before: ${beforeMeasure}, After: ${this.rootLayout.measureCount}`);
56+
TKUnit.assert(this.rootLayout.arrangeCount > beforeArrange, `Orientation change should trigger a new arrange. Before: ${beforeArrange}, After: ${this.rootLayout.arrangeCount}`);
5257
}
5358

5459
public test_ShouldMeasureWith_AtMost_OnVertical() {

packages/core/references.d.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
/// <reference path="../types-ios/src/lib/ios/objc-x86_64/objc!Darwin.d.ts" />
55
/// <reference path="../types-ios/src/lib/ios/objc-x86_64/objc!DarwinFoundation.d.ts" />
66
/// <reference path="../types-ios/src/lib/ios/objc-x86_64/objc!Symbols.d.ts" />
7-
/// <reference path="../types-android/src/lib/android-30.d.ts" />
7+
/// <reference path="../types-android/src/lib/android-33.d.ts" />
88
/// <reference path="./platforms/ios/typings/objc!MaterialComponents.d.ts" />
99
/// <reference path="./platforms/ios/typings/objc!NativeScriptUtils.d.ts" />
1010
/// <reference path="./global-types.d.ts" />

packages/core/ui/core/view/index.android.ts

Lines changed: 60 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,51 @@ interface DialogOptions {
7272
dismissCallback: () => void;
7373
}
7474

75+
let OnBackPressedCallback;
76+
77+
if (SDK_VERSION >= 33) {
78+
OnBackPressedCallback = (androidx.activity.OnBackPressedCallback as any).extend({
79+
handleOnBackPressed() {
80+
console.log('OnBackPressedCallback handleOnBackPressed called');
81+
const dialog = this['_dialog']?.get();
82+
83+
if (!dialog) {
84+
// disable the callback and call super to avoid infinite loop
85+
86+
this.setEnabled(false);
87+
88+
return;
89+
}
90+
91+
const view = dialog.fragment.owner;
92+
93+
const args: AndroidActivityBackPressedEventData = {
94+
eventName: 'activityBackPressed',
95+
object: view,
96+
activity: view._context,
97+
cancel: false,
98+
};
99+
100+
// Fist fire application.android global event
101+
getNativeScriptGlobals().events.notify(args);
102+
103+
if (args.cancel) {
104+
return;
105+
}
106+
107+
view.notify(args);
108+
109+
if (!args.cancel) {
110+
this.setEnabled(false);
111+
112+
dialog.getOnBackPressedDispatcher().onBackPressed();
113+
114+
this.setEnabled(true);
115+
}
116+
},
117+
});
118+
}
119+
75120
interface TouchListener {
76121
new (owner: View): android.view.View.OnTouchListener;
77122
}
@@ -121,14 +166,24 @@ function initializeDialogFragment() {
121166
}
122167

123168
@NativeClass
124-
class DialogImpl extends android.app.Dialog {
169+
class DialogImpl extends androidx.appcompat.app.AppCompatDialog {
125170
constructor(
126171
public fragment: DialogFragmentImpl,
127172
context: android.content.Context,
128173
themeResId: number,
129174
) {
130175
super(context, themeResId);
131176

177+
if (SDK_VERSION >= 33 && OnBackPressedCallback) {
178+
const callback = new OnBackPressedCallback(true);
179+
180+
callback['_dialog'] = new WeakRef(this);
181+
182+
// @ts-ignore
183+
184+
this.getOnBackPressedDispatcher().addCallback(this, callback);
185+
}
186+
132187
return global.__native(this);
133188
}
134189

@@ -138,6 +193,10 @@ function initializeDialogFragment() {
138193
}
139194

140195
public onBackPressed(): void {
196+
if (SDK_VERSION >= 33) {
197+
super.onBackPressed();
198+
return;
199+
}
141200
const view = this.fragment.owner;
142201
const args = <AndroidActivityBackPressedEventData>{
143202
eventName: 'activityBackPressed',

packages/core/ui/frame/index.android.ts

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import { AndroidActivityBackPressedEventData, AndroidActivityNewIntentEventData,
1717
import { Application } from '../../application/application';
1818
import { isEmbedded, setEmbeddedView } from '../embedding';
1919
import { CALLBACKS, FRAMEID, framesCache, setFragmentCallbacks } from './frame-helper-for-android';
20+
import { Device } from '../../platform';
2021

2122
export * from './frame-common';
2223
export { setFragmentClass } from './fragment';
@@ -762,6 +763,78 @@ function startActivity(activity: androidx.appcompat.app.AppCompatActivity, frame
762763
activity.startActivity(intent);
763764
}
764765

766+
let OnBackPressedCallback;
767+
768+
if (parseInt(Device.sdkVersion) >= 33) {
769+
OnBackPressedCallback = (<any>androidx.activity.OnBackPressedCallback).extend('com.tns.OnBackPressedCallback', {
770+
handleOnBackPressed() {
771+
if (Trace.isEnabled()) {
772+
Trace.write('NativeScriptActivity.onBackPressed;', Trace.categories.NativeLifecycle);
773+
}
774+
775+
const activity = this['_activity']?.get();
776+
777+
if (!activity) {
778+
if (Trace.isEnabled()) {
779+
Trace.write('NativeScriptActivity.onBackPressed; Activity is null, calling super', Trace.categories.NativeLifecycle);
780+
}
781+
782+
this.setEnabled(false);
783+
784+
return;
785+
}
786+
787+
const args = <AndroidActivityBackPressedEventData>{
788+
eventName: 'activityBackPressed',
789+
790+
object: Application,
791+
792+
android: Application.android,
793+
794+
activity: activity,
795+
796+
cancel: false,
797+
};
798+
799+
Application.android.notify(args);
800+
801+
if (args.cancel) {
802+
return;
803+
}
804+
805+
const view = activity._rootView;
806+
807+
let callSuper = false;
808+
809+
const viewArgs = <AndroidActivityBackPressedEventData>{
810+
eventName: 'activityBackPressed',
811+
812+
object: view,
813+
814+
activity: activity,
815+
816+
cancel: false,
817+
};
818+
819+
view?.notify(viewArgs);
820+
821+
// In the case of Frame, use this callback only if it was overridden, since the original will cause navigation issues
822+
823+
if (!viewArgs.cancel && (view?.onBackPressed === Frame.prototype.onBackPressed || !view?.onBackPressed())) {
824+
callSuper = view instanceof Frame ? !Frame.goBack() : true;
825+
}
826+
827+
if (callSuper) {
828+
this.setEnabled(false);
829+
830+
activity.getOnBackPressedDispatcher().onBackPressed();
831+
832+
this.setEnabled(true);
833+
}
834+
},
835+
});
836+
}
837+
765838
const activityRootViewsMap = new Map<number, WeakRef<View>>();
766839
const ROOT_VIEW_ID_EXTRA = 'com.tns.activity.rootViewId';
767840

@@ -1055,4 +1128,13 @@ export class ActivityCallbacksImplementation implements AndroidActivityCallbacks
10551128

10561129
export function setActivityCallbacks(activity: androidx.appcompat.app.AppCompatActivity): void {
10571130
activity[CALLBACKS] = new ActivityCallbacksImplementation();
1131+
if (OnBackPressedCallback && !activity['_onBackPressed']) {
1132+
const callback = new OnBackPressedCallback(true);
1133+
1134+
callback['_activity'] = new WeakRef(activity);
1135+
1136+
activity['_onBackPressed'] = true;
1137+
1138+
(activity as androidx.activity.ComponentActivity).getOnBackPressedDispatcher().addCallback(activity, callback);
1139+
}
10581140
}

packages/core/utils/native-helper.android.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { platformCheck } from './platform-check';
22

33
// importing this helper as a separate file avoids "android" symbol clash with the global android object
4-
import { resources, collections, getWindow, getApplication, getCurrentActivity, getApplicationContext, getResources, getPackageName, getInputMethodManager, showSoftInput, dismissSoftInput } from './native-helper-for-android';
4+
import { resources, collections, getWindow, getApplication, getCurrentActivity, getApplicationContext, getResources, getPackageName, getInputMethodManager, showSoftInput, dismissSoftInput, enableEdgeToEdge, setDarkModeHandler, setNavigationBarColor, setStatusBarColor } from './native-helper-for-android';
55
export { dataSerialize, dataDeserialize } from './native-helper-for-android';
66

77
export { getWindow } from './native-helper-for-android';
@@ -18,6 +18,10 @@ export const android = {
1818
getInputMethodManager,
1919
showSoftInput,
2020
dismissSoftInput,
21+
enableEdgeToEdge,
22+
setStatusBarColor,
23+
setNavigationBarColor,
24+
setDarkModeHandler,
2125
};
2226

2327
/**

packages/core/utils/native-helper.d.ts

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,36 @@ export const android: {
107107
* Hides the soft input method, usually a soft keyboard.
108108
*/
109109
dismissSoftInput: (nativeView?: android.view.View) => void;
110+
/**
111+
* Sets the dark mode handler for the application.
112+
* @param options Options to set dark mode handler.
113+
*/
114+
setDarkModeHandler(options?: { activity?: androidx.appcompat.app.AppCompatActivity; handler: (bar: 'status' | 'navigation', resources: android.content.res.Resources) => boolean }): void;
115+
/**
116+
* Sets the navigation bar color for the application.
117+
* @param options Options to set navigation bar color.
118+
*/
119+
setNavigationBarColor(options?: { activity?: androidx.appcompat.app.AppCompatActivity; lightColor?: Color; darkColor?: Color }): void;
120+
/**
121+
* Sets the status bar color for the application.
122+
* @param options Options to set status bar color.
123+
*/
124+
setStatusBarColor(options?: { activity?: androidx.appcompat.app.AppCompatActivity; lightColor?: Color; darkColor?: Color }): void;
125+
/**
126+
* Enables edge-to-edge navigation for the provided activity.
127+
* @param activity The activity to enable edge-to-edge navigation for.
128+
* @param options Optional configuration for status and navigation bar colors.
129+
*/
130+
enableEdgeToEdge(
131+
activity: androidx.appcompat.app.AppCompatActivity,
132+
options?: {
133+
statusBarLightColor?: Color;
134+
statusBarDarkColor?: Color;
135+
navigationBarLightColor?: Color;
136+
navigationBarDarkColor?: Color;
137+
handleDarkMode?: (bar: 'status' | 'navigation', resources: android.content.res.Resources) => boolean;
138+
},
139+
): void;
110140
};
111141

112142
/**

0 commit comments

Comments
 (0)