diff --git a/packages/core/ui/core/view/index.android.ts b/packages/core/ui/core/view/index.android.ts index 695214260..4d33c12cf 100644 --- a/packages/core/ui/core/view/index.android.ts +++ b/packages/core/ui/core/view/index.android.ts @@ -24,7 +24,8 @@ import * as Utils from '../../../utils'; import { SDK_VERSION } from '../../../utils/constants'; import { BoxShadow } from '../../styling/box-shadow'; import { NativeScriptAndroidView } from '../../utils'; - +import { Device } from '../../../platform'; +import { Frame } from '../../frame'; export * from './view-common'; // helpers (these are okay re-exported here) export * from './view-helper'; @@ -52,6 +53,42 @@ const GRAVITY_FILL_VERTICAL = 112; // android.view.Gravity.FILL_VERTICAL const modalMap = new Map(); +let OnBackPressedCallback; +if (parseInt(Device.sdkVersion) >= 33) { + OnBackPressedCallback = (androidx.activity.OnBackPressedCallback).extend({ + handleOnBackPressed() { + const dialog = this['_dialog']?.get(); + if (!dialog) { + // disable the callback and call super to avoid infinite loop + this.setEnabled(false); + return; + } + + const view = dialog.fragment.owner; + const args = { + eventName: 'activityBackPressed', + object: view, + activity: view._context, + cancel: false, + }; + + // Fist fire application.android global event + Application.android.notify(args); + if (args.cancel) { + return; + } + + view.notify(args); + + if (!args.cancel) { + this.setEnabled(false); + dialog.getOnBackPressedDispatcher().onBackPressed(); + this.setEnabled(true); + } + }, + }); +} + let TouchListener: TouchListener; let DialogFragment: DialogFragment; @@ -115,13 +152,21 @@ function initializeDialogFragment() { } @NativeClass - class DialogImpl extends android.app.Dialog { + class DialogImpl extends androidx.appcompat.app.AppCompatDialog { constructor( public fragment: DialogFragmentImpl, context: android.content.Context, themeResId: number, ) { super(context, themeResId); + // @ts-ignore + + if (parseInt(Device.sdkVersion) >= 33 && OnBackPressedCallback) { + const callback = new OnBackPressedCallback(true); + callback['_dialog'] = new WeakRef(this); + // @ts-ignore + this.getOnBackPressedDispatcher().addCallback(this, callback); + } return global.__native(this); } @@ -132,6 +177,10 @@ function initializeDialogFragment() { } public onBackPressed(): void { + if (parseInt(Device.sdkVersion) >= 33) { + super.onBackPressed(); + return; + } const view = this.fragment.owner; const args = { eventName: 'activityBackPressed', diff --git a/packages/core/ui/frame/index.android.ts b/packages/core/ui/frame/index.android.ts index 0f0529e17..8be62066d 100644 --- a/packages/core/ui/frame/index.android.ts +++ b/packages/core/ui/frame/index.android.ts @@ -4,7 +4,7 @@ import { Page } from '../page'; import { TransitionState } from './frame-common'; // Types. -import { Application } from '../../application'; +import { Application, AndroidActivityBackPressedEventData } from '../../application'; import { Observable } from '../../data/observable'; import { Trace } from '../../trace'; @@ -19,6 +19,7 @@ import type { ExpandedEntry } from './fragment.transitions.android'; import { ensureFragmentClass, fragmentClass } from './fragment'; import { FragmentCallbacksImplementation } from './callbacks/fragment-callbacks'; import { ActivityCallbacksImplementation } from './callbacks/activity-callbacks'; +import { Device } from '../../platform'; export * from './frame-common'; export { setFragmentClass } from './fragment'; @@ -782,8 +783,69 @@ export function getFrameByNumberId(frameId: number): Frame { return null; } +let OnBackPressedCallback; +if (parseInt(Device.sdkVersion) >= 33) { + OnBackPressedCallback = (androidx.activity.OnBackPressedCallback).extend('com.tns.OnBackPressedCallback', { + handleOnBackPressed() { + if (Trace.isEnabled()) { + Trace.write('NativeScriptActivity.onBackPressed;', Trace.categories.NativeLifecycle); + } + + const activity = this['_activity']?.get(); + if (!activity) { + if (Trace.isEnabled()) { + Trace.write('NativeScriptActivity.onBackPressed; Activity is null, calling super', Trace.categories.NativeLifecycle); + } + this.setEnabled(false); + return; + } + + const args = { + eventName: 'activityBackPressed', + object: Application, + android: Application.android, + activity: activity, + cancel: false, + }; + Application.android.notify(args); + if (args.cancel) { + return; + } + + const view = activity._rootView; + let callSuper = false; + + const viewArgs = { + eventName: 'activityBackPressed', + object: view, + activity: activity, + cancel: false, + }; + view?.notify(viewArgs); + + // In the case of Frame, use this callback only if it was overridden, since the original will cause navigation issues + if (!viewArgs.cancel && (view?.onBackPressed === Frame.prototype.onBackPressed || !view?.onBackPressed())) { + callSuper = view instanceof Frame ? !Frame.goBack() : true; + } + + if (callSuper) { + this.setEnabled(false); + activity.getOnBackPressedDispatcher().onBackPressed(); + this.setEnabled(true); + } + }, + }); +} + export function setActivityCallbacks(activity: androidx.appcompat.app.AppCompatActivity): void { activity[CALLBACKS] = new ActivityCallbacksImplementation(); + + if (OnBackPressedCallback && !activity['_onBackPressed']) { + const callback = new OnBackPressedCallback(true); + callback['_activity'] = new WeakRef(activity); + activity['_onBackPressed'] = true; + (activity as androidx.activity.ComponentActivity).getOnBackPressedDispatcher().addCallback(activity, callback); + } } export function setFragmentCallbacks(fragment: androidx.fragment.app.Fragment): void { diff --git a/packages/types-android/src/lib/android/androidx-activity.d.ts b/packages/types-android/src/lib/android/androidx-activity.d.ts index 43293f6bb..5e49d74bb 100644 --- a/packages/types-android/src/lib/android/androidx-activity.d.ts +++ b/packages/types-android/src/lib/android/androidx-activity.d.ts @@ -205,7 +205,7 @@ declare module androidx { declare module androidx { export module activity { - export class ComponentDialog implements androidx.activity.OnBackPressedDispatcherOwner { + export class ComponentDialog extends android.app.Dialog implements androidx.activity.OnBackPressedDispatcherOwner { public static class: java.lang.Class; public onStart(): void; public constructor(context: globalAndroid.content.Context);