feat: handle android back button on android (#1656)

This commit is contained in:
Aman Harwara
2022-09-28 12:12:55 +05:30
committed by GitHub
parent 04245dfeeb
commit 981d8a7497
17 changed files with 413 additions and 101 deletions

View File

@@ -0,0 +1,21 @@
import { AbstractService, InternalEventBus, ReactNativeToWebEvent } from '@standardnotes/snjs'
import { BackHandler, NativeEventSubscription } from 'react-native'
export class AndroidBackHandlerService extends AbstractService<ReactNativeToWebEvent> {
private removeListener: NativeEventSubscription
constructor() {
const internalEventBus = new InternalEventBus()
super(internalEventBus)
this.removeListener = BackHandler.addEventListener('hardwareBackPress', () => {
void this.notifyEvent(ReactNativeToWebEvent.AndroidBackButtonPressed)
return true
})
}
deinit() {
this.removeListener.remove()
}
}

View File

@@ -1,4 +1,5 @@
import AsyncStorage from '@react-native-community/async-storage'
import { AndroidBackHandlerService } from '@Root/AndroidBackHandlerService'
import SNReactNative from '@standardnotes/react-native-utils'
import {
ApplicationIdentifier,
@@ -80,13 +81,18 @@ export class MobileDevice implements MobileDeviceInterface {
public isDarkMode = false
private crypto: SNReactNativeCrypto
constructor(private stateObserverService?: AppStateObserverService) {
constructor(
private stateObserverService?: AppStateObserverService,
private androidBackHandlerService?: AndroidBackHandlerService,
) {
this.crypto = new SNReactNativeCrypto()
}
deinit() {
this.stateObserverService?.deinit()
;(this.stateObserverService as unknown) = undefined
this.androidBackHandlerService?.deinit()
;(this.androidBackHandlerService as unknown) = undefined
}
consoleLog(...args: any[]): void {
@@ -542,4 +548,29 @@ export class MobileDevice implements MobileDeviceInterface {
this.consoleLog(`${error}`)
}
}
confirmAndExit() {
Alert.alert(
'Close app',
'Do you want to close the app?',
[
{
text: 'Cancel',
style: 'cancel',
// eslint-disable-next-line @typescript-eslint/no-empty-function
onPress: async () => {},
},
{
text: 'Close',
style: 'destructive',
onPress: async () => {
SNReactNative.exitApp()
},
},
],
{
cancelable: true,
},
)
}
}

View File

@@ -6,6 +6,7 @@ import { Keyboard, Platform } from 'react-native'
import VersionInfo from 'react-native-version-info'
import { WebView, WebViewMessageEvent } from 'react-native-webview'
import pjson from '../package.json'
import { AndroidBackHandlerService } from './AndroidBackHandlerService'
import { AppStateObserverService } from './AppStateObserverService'
const LoggingEnabled = IsDev
@@ -24,13 +25,23 @@ const MobileWebAppContents = ({ destroyAndReload }: { destroyAndReload: () => vo
const webViewRef = useRef<WebView>(null)
const sourceUri = (Platform.OS === 'android' ? 'file:///android_asset/' : '') + 'Web.bundle/src/index.html'
const stateService = useMemo(() => new AppStateObserverService(), [])
const device = useMemo(() => new MobileDevice(stateService), [stateService])
const androidBackHandlerService = useMemo(() => new AndroidBackHandlerService(), [])
const device = useMemo(
() => new MobileDevice(stateService, androidBackHandlerService),
[androidBackHandlerService, stateService],
)
useEffect(() => {
const removeListener = stateService.addEventObserver((event: ReactNativeToWebEvent) => {
const removeStateServiceListener = stateService.addEventObserver((event: ReactNativeToWebEvent) => {
webViewRef.current?.postMessage(JSON.stringify({ reactNativeEvent: event, messageType: 'event' }))
})
const removeBackHandlerServiceListener = androidBackHandlerService.addEventObserver(
(event: ReactNativeToWebEvent) => {
webViewRef.current?.postMessage(JSON.stringify({ reactNativeEvent: event, messageType: 'event' }))
},
)
const keyboardShowListener = Keyboard.addListener('keyboardWillShow', () => {
device.reloadStatusBarStyle(false)
})
@@ -40,11 +51,12 @@ const MobileWebAppContents = ({ destroyAndReload }: { destroyAndReload: () => vo
})
return () => {
removeListener()
removeStateServiceListener()
removeBackHandlerServiceListener()
keyboardShowListener.remove()
keyboardHideListener.remove()
}
}, [webViewRef, stateService])
}, [webViewRef, stateService, device, androidBackHandlerService])
useEffect(() => {
const observer = device.addMobileWebEventReceiver((event) => {

View File

@@ -12,4 +12,6 @@ export interface WebApplicationInterface extends ApplicationInterface {
handleMobileResumingFromBackgroundEvent(): Promise<void>
isNativeMobileWeb(): boolean
get mobileDevice(): MobileDeviceInterface
handleAndroidBackButtonPressed(): void
addAndroidBackHandlerEventListener(listener: () => boolean): (() => void) | undefined
}

View File

@@ -15,4 +15,5 @@ export interface MobileDeviceInterface extends DeviceInterface {
handleThemeSchemeChange(isDark: boolean): void
shareBase64AsFile(base64: string, filename: string): Promise<void>
downloadBase64AsFile(base64: string, filename: string, saveInTempLocation?: boolean): Promise<string | undefined>
confirmAndExit(): void
}

View File

@@ -3,4 +3,5 @@ export enum ReactNativeToWebEvent {
ResumingFromBackground = 'ResumingFromBackground',
GainingFocus = 'GainingFocus',
LosingFocus = 'LosingFocus',
AndroidBackButtonPressed = 'AndroidBackButtonPressed',
}

View File

@@ -26,6 +26,7 @@ import { isDesktopApplication } from '@/Utils'
import { DesktopManager } from './Device/DesktopManager'
import { ArchiveManager, AutolockService, IOService, WebAlertService, ThemeManager } from '@standardnotes/ui-services'
import { MobileWebReceiver } from './MobileWebReceiver'
import { AndroidBackHandler } from '@/NativeMobileWeb/AndroidBackHandler'
type WebServices = {
viewControllerManager: ViewControllerManager
@@ -45,6 +46,7 @@ export class WebApplication extends SNApplication implements WebApplicationInter
public iconsController: IconsController
private onVisibilityChange: () => void
private mobileWebReceiver?: MobileWebReceiver
private androidBackHandler?: AndroidBackHandler
constructor(
deviceInterface: WebOrDesktopDevice,
@@ -76,6 +78,7 @@ export class WebApplication extends SNApplication implements WebApplicationInter
if (this.isNativeMobileWeb()) {
this.mobileWebReceiver = new MobileWebReceiver(this)
this.androidBackHandler = new AndroidBackHandler()
// eslint-disable-next-line no-console
console.log = (...args) => {
@@ -264,4 +267,17 @@ export class WebApplication extends SNApplication implements WebApplicationInter
await this.lock()
}
}
handleAndroidBackButtonPressed(): void {
if (typeof this.androidBackHandler !== 'undefined') {
this.androidBackHandler.notifyEvent()
}
}
addAndroidBackHandlerEventListener(listener: () => boolean) {
if (typeof this.androidBackHandler !== 'undefined') {
return this.androidBackHandler.addEventListener(listener)
}
return
}
}

View File

@@ -53,6 +53,9 @@ export class MobileWebReceiver {
case ReactNativeToWebEvent.ResumingFromBackground:
void this.application.handleMobileResumingFromBackgroundEvent()
break
case ReactNativeToWebEvent.AndroidBackButtonPressed:
void this.application.handleAndroidBackButtonPressed()
break
default:
break

View File

@@ -25,6 +25,7 @@ import { PanelResizedData } from '@/Types/PanelResizedData'
import TagContextMenuWrapper from '@/Components/Tags/TagContextMenuWrapper'
import FileDragNDropProvider from '../FileDragNDropProvider/FileDragNDropProvider'
import ResponsivePaneProvider from '../ResponsivePane/ResponsivePaneProvider'
import AndroidBackHandlerProvider from '@/NativeMobileWeb/useAndroidBackHandler'
import ConfirmDeleteAccountContainer from '@/Components/ConfirmDeleteAccountModal/ConfirmDeleteAccountModal'
type Props = {
@@ -175,77 +176,79 @@ const ApplicationView: FunctionComponent<Props> = ({ application, mainApplicatio
}
return (
<PremiumModalProvider application={application} viewControllerManager={viewControllerManager}>
<AndroidBackHandlerProvider application={application}>
<ResponsivePaneProvider>
<div className={platformString + ' main-ui-view sn-component'}>
<div id="app" className={appClass + ' app app-column-container'}>
<FileDragNDropProvider
application={application}
featuresController={viewControllerManager.featuresController}
filesController={viewControllerManager.filesController}
>
<Navigation application={application} />
<ContentListView
<PremiumModalProvider application={application} viewControllerManager={viewControllerManager}>
<div className={platformString + ' main-ui-view sn-component'}>
<div id="app" className={appClass + ' app app-column-container'}>
<FileDragNDropProvider
application={application}
accountMenuController={viewControllerManager.accountMenuController}
featuresController={viewControllerManager.featuresController}
filesController={viewControllerManager.filesController}
itemListController={viewControllerManager.itemListController}
navigationController={viewControllerManager.navigationController}
noAccountWarningController={viewControllerManager.noAccountWarningController}
noteTagsController={viewControllerManager.noteTagsController}
>
<Navigation application={application} />
<ContentListView
application={application}
accountMenuController={viewControllerManager.accountMenuController}
filesController={viewControllerManager.filesController}
itemListController={viewControllerManager.itemListController}
navigationController={viewControllerManager.navigationController}
noAccountWarningController={viewControllerManager.noAccountWarningController}
noteTagsController={viewControllerManager.noteTagsController}
notesController={viewControllerManager.notesController}
selectionController={viewControllerManager.selectionController}
searchOptionsController={viewControllerManager.searchOptionsController}
/>
<NoteGroupView application={application} />
</FileDragNDropProvider>
</div>
<>
<Footer application={application} applicationGroup={mainApplicationGroup} />
<SessionsModal application={application} viewControllerManager={viewControllerManager} />
<PreferencesViewWrapper viewControllerManager={viewControllerManager} application={application} />
<RevisionHistoryModal
application={application}
historyModalController={viewControllerManager.historyModalController}
notesController={viewControllerManager.notesController}
selectionController={viewControllerManager.selectionController}
searchOptionsController={viewControllerManager.searchOptionsController}
subscriptionController={viewControllerManager.subscriptionController}
/>
<NoteGroupView application={application} />
</FileDragNDropProvider>
</>
{renderChallenges()}
<>
<NotesContextMenu
application={application}
navigationController={viewControllerManager.navigationController}
notesController={viewControllerManager.notesController}
noteTagsController={viewControllerManager.noteTagsController}
historyModalController={viewControllerManager.historyModalController}
/>
<TagContextMenuWrapper
navigationController={viewControllerManager.navigationController}
featuresController={viewControllerManager.featuresController}
/>
<FileContextMenuWrapper
filesController={viewControllerManager.filesController}
selectionController={viewControllerManager.selectionController}
/>
<PurchaseFlowWrapper application={application} viewControllerManager={viewControllerManager} />
<ConfirmSignoutContainer
applicationGroup={mainApplicationGroup}
viewControllerManager={viewControllerManager}
application={application}
/>
<ToastContainer />
<FilePreviewModalWrapper application={application} viewControllerManager={viewControllerManager} />
<PermissionsModalWrapper application={application} />
<ConfirmDeleteAccountContainer application={application} viewControllerManager={viewControllerManager} />
</>
</div>
<>
<Footer application={application} applicationGroup={mainApplicationGroup} />
<SessionsModal application={application} viewControllerManager={viewControllerManager} />
<PreferencesViewWrapper viewControllerManager={viewControllerManager} application={application} />
<RevisionHistoryModal
application={application}
historyModalController={viewControllerManager.historyModalController}
notesController={viewControllerManager.notesController}
selectionController={viewControllerManager.selectionController}
subscriptionController={viewControllerManager.subscriptionController}
/>
</>
{renderChallenges()}
<>
<NotesContextMenu
application={application}
navigationController={viewControllerManager.navigationController}
notesController={viewControllerManager.notesController}
noteTagsController={viewControllerManager.noteTagsController}
historyModalController={viewControllerManager.historyModalController}
/>
<TagContextMenuWrapper
navigationController={viewControllerManager.navigationController}
featuresController={viewControllerManager.featuresController}
/>
<FileContextMenuWrapper
filesController={viewControllerManager.filesController}
selectionController={viewControllerManager.selectionController}
/>
<PurchaseFlowWrapper application={application} viewControllerManager={viewControllerManager} />
<ConfirmSignoutContainer
applicationGroup={mainApplicationGroup}
viewControllerManager={viewControllerManager}
application={application}
/>
<ToastContainer />
<FilePreviewModalWrapper application={application} viewControllerManager={viewControllerManager} />
<PermissionsModalWrapper application={application} />
<ConfirmDeleteAccountContainer application={application} viewControllerManager={viewControllerManager} />
</>
</div>
</PremiumModalProvider>
</ResponsivePaneProvider>
</PremiumModalProvider>
</AndroidBackHandlerProvider>
)
}

View File

@@ -184,6 +184,20 @@ const ChallengeModal: FunctionComponent<Props> = ({
}
}, [hasBiometricPromptValue, hasOnlyBiometricPrompt, submit])
useEffect(() => {
const removeListener = application.addAndroidBackHandlerEventListener(() => {
if (challenge.cancelable) {
cancelChallenge()
}
return true
})
return () => {
if (removeListener) {
removeListener()
}
}
}, [application, cancelChallenge, challenge.cancelable])
if (!challenge.prompts) {
return null
}

View File

@@ -1,3 +1,4 @@
import { useAndroidBackHandler } from '@/NativeMobileWeb/useAndroidBackHandler'
import { UuidGenerator } from '@standardnotes/snjs'
import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react'
import PositionedPopoverContent from './PositionedPopoverContent'
@@ -41,6 +42,8 @@ const Popover = ({
}: Props) => {
const popoverId = useRef(UuidGenerator.GenerateUuid())
const addAndroidBackHandler = useAndroidBackHandler()
useRegisterPopoverToParent(popoverId.current)
const [childPopovers, setChildPopovers] = useState<Set<string>>(new Set())
@@ -64,6 +67,23 @@ const Popover = ({
[registerChildPopover, unregisterChildPopover],
)
useEffect(() => {
let removeListener: (() => void) | undefined
if (open) {
removeListener = addAndroidBackHandler(() => {
togglePopover()
return true
})
}
return () => {
if (removeListener) {
removeListener()
}
}
}, [addAndroidBackHandler, open, togglePopover])
return open ? (
<PopoverContext.Provider value={contextValue}>
<PositionedPopoverContent

View File

@@ -8,31 +8,52 @@ import { isIOS } from '@/Utils'
import { useDisableBodyScrollOnMobile } from '@/Hooks/useDisableBodyScrollOnMobile'
import { classNames } from '@/Utils/ConcatenateClassNames'
import { MediaQueryBreakpoints, useMediaQuery } from '@/Hooks/useMediaQuery'
import { useAndroidBackHandler } from '@/NativeMobileWeb/useAndroidBackHandler'
const PreferencesView: FunctionComponent<PreferencesProps> = (props) => {
const PreferencesView: FunctionComponent<PreferencesProps> = ({
application,
viewControllerManager,
closePreferences,
userProvider,
mfaProvider,
}) => {
const isDesktopScreen = useMediaQuery(MediaQueryBreakpoints.md)
const menu = useMemo(
() => new PreferencesMenu(props.application, props.viewControllerManager.enableUnfinishedFeatures),
[props.viewControllerManager.enableUnfinishedFeatures, props.application],
() => new PreferencesMenu(application, viewControllerManager.enableUnfinishedFeatures),
[viewControllerManager.enableUnfinishedFeatures, application],
)
useEffect(() => {
menu.selectPane(props.viewControllerManager.preferencesController.currentPane)
const removeEscKeyObserver = props.application.io.addKeyObserver({
menu.selectPane(viewControllerManager.preferencesController.currentPane)
const removeEscKeyObserver = application.io.addKeyObserver({
key: 'Escape',
onKeyDown: (event) => {
event.preventDefault()
props.closePreferences()
closePreferences()
},
})
return () => {
removeEscKeyObserver()
}
}, [props, menu])
}, [menu, viewControllerManager.preferencesController.currentPane, application.io, closePreferences])
useDisableBodyScrollOnMobile()
const addAndroidBackHandler = useAndroidBackHandler()
useEffect(() => {
const removeListener = addAndroidBackHandler(() => {
closePreferences()
return true
})
return () => {
if (removeListener) {
removeListener()
}
}
}, [addAndroidBackHandler, closePreferences])
return (
<div
className={classNames(
@@ -48,13 +69,20 @@ const PreferencesView: FunctionComponent<PreferencesProps> = (props) => {
<h1 className="text-base font-bold md:text-lg">Your preferences for Standard Notes</h1>
<RoundIconButton
onClick={() => {
props.closePreferences()
closePreferences()
}}
type="normal"
icon="close"
/>
</div>
<PreferencesCanvas {...props} menu={menu} />
<PreferencesCanvas
menu={menu}
application={application}
viewControllerManager={viewControllerManager}
closePreferences={closePreferences}
userProvider={userProvider}
mfaProvider={mfaProvider}
/>
</div>
)
}

View File

@@ -1,6 +1,19 @@
import { ElementIds } from '@/Constants/ElementIDs'
import { useAndroidBackHandler } from '@/NativeMobileWeb/useAndroidBackHandler'
import { isMobileScreen } from '@/Utils'
import { useEffect, ReactNode, useMemo, createContext, useCallback, useContext, useState, memo } from 'react'
import {
useEffect,
ReactNode,
useMemo,
createContext,
useCallback,
useContext,
useState,
memo,
useRef,
useLayoutEffect,
MutableRefObject,
} from 'react'
import { AppPaneId } from './AppPaneMetadata'
type ResponsivePaneData = {
@@ -20,16 +33,27 @@ export const useResponsiveAppPane = () => {
return value
}
type Props = {
type ChildrenProps = {
children: ReactNode
}
const MemoizedChildren = memo(({ children }: Props) => <div>{children}</div>)
function useStateRef<State>(state: State): MutableRefObject<State> {
const ref = useRef<State>(state)
const ResponsivePaneProvider = ({ children }: Props) => {
useLayoutEffect(() => {
ref.current = state
}, [state])
return ref
}
const MemoizedChildren = memo(({ children }: ChildrenProps) => <div>{children}</div>)
const ResponsivePaneProvider = ({ children }: ChildrenProps) => {
const [currentSelectedPane, setCurrentSelectedPane] = useState<AppPaneId>(
isMobileScreen() ? AppPaneId.Items : AppPaneId.Editor,
)
const currentSelectedPaneRef = useStateRef<AppPaneId>(currentSelectedPane)
const [previousSelectedPane, setPreviousSelectedPane] = useState<AppPaneId>(
isMobileScreen() ? AppPaneId.Items : AppPaneId.Editor,
)
@@ -57,6 +81,27 @@ const ResponsivePaneProvider = ({ children }: Props) => {
currentPaneElement?.classList.add('selected')
}, [currentSelectedPane, previousSelectedPane])
const addAndroidBackHandler = useAndroidBackHandler()
useEffect(() => {
const removeListener = addAndroidBackHandler(() => {
if (
currentSelectedPaneRef.current === AppPaneId.Editor ||
currentSelectedPaneRef.current === AppPaneId.Navigation
) {
toggleAppPane(AppPaneId.Items)
return true
} else {
return false
}
})
return () => {
if (removeListener) {
removeListener()
}
}
}, [addAndroidBackHandler, currentSelectedPaneRef, toggleAppPane])
const contextValue = useMemo(
() => ({
selectedPane: currentSelectedPane,

View File

@@ -1,8 +1,9 @@
import { observer } from 'mobx-react-lite'
import { FunctionComponent } from 'react'
import { FunctionComponent, useEffect } from 'react'
import HistoryModalDialogContent from './HistoryModalDialogContent'
import HistoryModalDialog from './HistoryModalDialog'
import { RevisionHistoryModalProps } from './RevisionHistoryModalProps'
import { useAndroidBackHandler } from '@/NativeMobileWeb/useAndroidBackHandler'
const RevisionHistoryModal: FunctionComponent<RevisionHistoryModalProps> = ({
application,
@@ -11,6 +12,27 @@ const RevisionHistoryModal: FunctionComponent<RevisionHistoryModalProps> = ({
selectionController,
subscriptionController,
}) => {
const addAndroidBackHandler = useAndroidBackHandler()
const isOpen = !!historyModalController.note
useEffect(() => {
let removeListener: (() => void) | undefined
if (isOpen) {
removeListener = addAndroidBackHandler(() => {
historyModalController.dismissModal()
return true
})
}
return () => {
if (removeListener) {
removeListener()
}
}
}, [addAndroidBackHandler, historyModalController, isOpen])
if (!historyModalController.note) {
return null
}

View File

@@ -1,7 +1,8 @@
import { FunctionComponent, ReactNode } from 'react'
import { FunctionComponent, ReactNode, useEffect } from 'react'
import { AlertDialogLabel } from '@reach/alert-dialog'
import Icon from '@/Components/Icon/Icon'
import { classNames } from '@/Utils/ConcatenateClassNames'
import { useAndroidBackHandler } from '@/NativeMobileWeb/useAndroidBackHandler'
type Props = {
closeDialog: () => void
@@ -10,24 +11,40 @@ type Props = {
children?: ReactNode
}
const ModalDialogLabel: FunctionComponent<Props> = ({ children, closeDialog, className, headerButtons }) => (
<AlertDialogLabel
className={classNames(
'flex flex-shrink-0 items-center justify-between rounded-t border-b border-solid border-border bg-default px-4.5 py-3 text-text',
className,
)}
>
<div className="flex w-full flex-row items-center justify-between">
<div className="flex-grow text-lg font-semibold text-text">{children}</div>
<div className="flex items-center gap-2">
{headerButtons}
<button tabIndex={0} className="rounded p-1 font-bold hover:bg-contrast" onClick={closeDialog}>
<Icon type="close" />
</button>
const ModalDialogLabel: FunctionComponent<Props> = ({ children, closeDialog, className, headerButtons }) => {
const addAndroidBackHandler = useAndroidBackHandler()
useEffect(() => {
const removeListener = addAndroidBackHandler(() => {
closeDialog()
return true
})
return () => {
if (removeListener) {
removeListener()
}
}
}, [addAndroidBackHandler, closeDialog])
return (
<AlertDialogLabel
className={classNames(
'flex flex-shrink-0 items-center justify-between rounded-t border-b border-solid border-border bg-default px-4.5 py-3 text-text',
className,
)}
>
<div className="flex w-full flex-row items-center justify-between">
<div className="flex-grow text-lg font-semibold text-text">{children}</div>
<div className="flex items-center gap-2">
{headerButtons}
<button tabIndex={0} className="rounded p-1 font-bold hover:bg-contrast" onClick={closeDialog}>
<Icon type="close" />
</button>
</div>
</div>
</div>
<hr className="h-1px no-border m-0 bg-border" />
</AlertDialogLabel>
)
<hr className="h-1px no-border m-0 bg-border" />
</AlertDialogLabel>
)
}
export default ModalDialogLabel

View File

@@ -0,0 +1,22 @@
type Listener = () => boolean
type RemoveListener = () => void
export class AndroidBackHandler {
private listeners = new Set<Listener>()
addEventListener(listener: Listener): RemoveListener {
this.listeners.add(listener)
return () => {
this.listeners.delete(listener)
}
}
notifyEvent() {
for (const listener of Array.from(this.listeners).reverse()) {
if (listener()) {
return
}
}
}
}

View File

@@ -0,0 +1,54 @@
import { WebApplication } from '@/Application/Application'
import { observer } from 'mobx-react-lite'
import { createContext, memo, ReactNode, useCallback, useContext, useEffect } from 'react'
type BackHandlerContextData = WebApplication['addAndroidBackHandlerEventListener']
const BackHandlerContext = createContext<BackHandlerContextData | null>(null)
export const useAndroidBackHandler = () => {
const value = useContext(BackHandlerContext)
if (!value) {
throw new Error('Component must be a child of <AndroidBackHandlerProvider />')
}
return value
}
type ChildrenProps = {
children: ReactNode
}
type ProviderProps = {
application: WebApplication
} & ChildrenProps
const MemoizedChildren = memo(({ children }: ChildrenProps) => <div>{children}</div>)
const AndroidBackHandlerProvider = ({ application, children }: ProviderProps) => {
const addAndroidBackHandler = useCallback(
(listener: () => boolean) => application.addAndroidBackHandlerEventListener(listener),
[application],
)
useEffect(() => {
const removeListener = addAndroidBackHandler(() => {
application.mobileDevice.confirmAndExit()
return true
})
return () => {
if (removeListener) {
removeListener()
}
}
}, [addAndroidBackHandler, application.mobileDevice])
return (
<BackHandlerContext.Provider value={addAndroidBackHandler}>
<MemoizedChildren children={children} />
</BackHandlerContext.Provider>
)
}
export default observer(AndroidBackHandlerProvider)