1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-01-23 18:53:36 +02:00

Mobile: Accessibility: Improve dialog accessibility (#11395)

This commit is contained in:
Henry Heino 2024-11-16 13:09:50 -08:00 committed by GitHub
parent 6eac8d9ccf
commit 84eab775c3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
26 changed files with 478 additions and 417 deletions

View File

@ -575,7 +575,6 @@ packages/app-mobile/commands/openNote.js
packages/app-mobile/commands/scrollToHash.js
packages/app-mobile/commands/util/goToNote.js
packages/app-mobile/commands/util/showResource.js
packages/app-mobile/components/BackButtonDialogBox.js
packages/app-mobile/components/BetaChip.js
packages/app-mobile/components/CameraView/ActionButtons.js
packages/app-mobile/components/CameraView/Camera/index.jest.js
@ -588,7 +587,10 @@ packages/app-mobile/components/CameraView/types.js
packages/app-mobile/components/CameraView/utils/fitRectIntoBounds.js
packages/app-mobile/components/CameraView/utils/useBarcodeScanner.js
packages/app-mobile/components/Checkbox.js
packages/app-mobile/components/DialogManager.js
packages/app-mobile/components/DialogManager/PromptDialog.js
packages/app-mobile/components/DialogManager/hooks/useDialogControl.js
packages/app-mobile/components/DialogManager/index.js
packages/app-mobile/components/DialogManager/types.js
packages/app-mobile/components/DismissibleDialog.js
packages/app-mobile/components/Dropdown.test.js
packages/app-mobile/components/Dropdown.js
@ -760,6 +762,7 @@ packages/app-mobile/components/screens/ShareManager/IncomingShareItem.js
packages/app-mobile/components/screens/ShareManager/index.test.js
packages/app-mobile/components/screens/ShareManager/index.js
packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js
packages/app-mobile/components/screens/dropbox-login.js
packages/app-mobile/components/screens/encryption-config.js
packages/app-mobile/components/screens/status.js
packages/app-mobile/components/side-menu-content.js

7
.gitignore vendored
View File

@ -552,7 +552,6 @@ packages/app-mobile/commands/openNote.js
packages/app-mobile/commands/scrollToHash.js
packages/app-mobile/commands/util/goToNote.js
packages/app-mobile/commands/util/showResource.js
packages/app-mobile/components/BackButtonDialogBox.js
packages/app-mobile/components/BetaChip.js
packages/app-mobile/components/CameraView/ActionButtons.js
packages/app-mobile/components/CameraView/Camera/index.jest.js
@ -565,7 +564,10 @@ packages/app-mobile/components/CameraView/types.js
packages/app-mobile/components/CameraView/utils/fitRectIntoBounds.js
packages/app-mobile/components/CameraView/utils/useBarcodeScanner.js
packages/app-mobile/components/Checkbox.js
packages/app-mobile/components/DialogManager.js
packages/app-mobile/components/DialogManager/PromptDialog.js
packages/app-mobile/components/DialogManager/hooks/useDialogControl.js
packages/app-mobile/components/DialogManager/index.js
packages/app-mobile/components/DialogManager/types.js
packages/app-mobile/components/DismissibleDialog.js
packages/app-mobile/components/Dropdown.test.js
packages/app-mobile/components/Dropdown.js
@ -737,6 +739,7 @@ packages/app-mobile/components/screens/ShareManager/IncomingShareItem.js
packages/app-mobile/components/screens/ShareManager/index.test.js
packages/app-mobile/components/screens/ShareManager/index.js
packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js
packages/app-mobile/components/screens/dropbox-login.js
packages/app-mobile/components/screens/encryption-config.js
packages/app-mobile/components/screens/status.js
packages/app-mobile/components/side-menu-content.js

View File

@ -42,7 +42,7 @@ export const runtime = (): CommandRuntime => {
} else {
const errorMessage = _('Unsupported link or message: %s', link);
logger.error(errorMessage);
await shim.showMessageBox(errorMessage);
await shim.showErrorDialog(errorMessage);
}
},
};

View File

@ -1,25 +0,0 @@
import BackButtonService from '../services/BackButtonService';
const DialogBox = require('react-native-dialogbox').default;
export default class BackButtonDialogBox extends DialogBox {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
public constructor(props: any) {
super(props);
this.backHandler_ = () => {
if (this.state.isVisible) {
this.close();
return true;
}
return false;
};
}
public async componentDidUpdate() {
if (this.state.isVisible) {
BackButtonService.addHandler(this.backHandler_);
} else {
BackButtonService.removeHandler(this.backHandler_);
}
}
}

View File

@ -1,150 +0,0 @@
import * as React from 'react';
import { createContext, useEffect, useMemo, useRef, useState } from 'react';
import { Alert, Platform, StyleSheet } from 'react-native';
import { Button, Dialog, Portal, Text } from 'react-native-paper';
import Modal from './Modal';
import { _ } from '@joplin/lib/locale';
import shim from '@joplin/lib/shim';
import makeShowMessageBox from '../utils/makeShowMessageBox';
export interface PromptButton {
text: string;
onPress?: ()=> void;
style?: 'cancel'|'default'|'destructive';
}
interface PromptOptions {
cancelable?: boolean;
}
export interface DialogControl {
prompt(title: string, message: string, buttons?: PromptButton[], options?: PromptOptions): void;
}
export const DialogContext = createContext<DialogControl>(null);
interface Props {
children: React.ReactNode;
}
interface PromptDialogData {
key: string;
title: string;
message: string;
buttons: PromptButton[];
onDismiss: (()=> void)|null;
}
const styles = StyleSheet.create({
dialogContainer: {
maxWidth: 400,
minWidth: '50%',
alignSelf: 'center',
},
modalContainer: {
marginTop: 'auto',
marginBottom: 'auto',
},
});
const DialogManager: React.FC<Props> = props => {
const [dialogModels, setPromptDialogs] = useState<PromptDialogData[]>([]);
const nextDialogIdRef = useRef(0);
const dialogControl: DialogControl = useMemo(() => {
const defaultButtons = [{ text: _('OK') }];
return {
prompt: (title: string, message: string, buttons: PromptButton[] = defaultButtons, options?: PromptOptions) => {
if (Platform.OS !== 'web') {
// Alert.alert provides a more native style on iOS.
Alert.alert(title, message, buttons, options);
// Alert.alert doesn't work on web.
} else {
const onDismiss = () => {
setPromptDialogs(dialogs => dialogs.filter(d => d !== dialog));
};
const cancelable = options?.cancelable ?? true;
const dialog: PromptDialogData = {
key: `dialog-${nextDialogIdRef.current++}`,
title,
message,
buttons: buttons.map(button => ({
...button,
onPress: () => {
onDismiss();
button.onPress?.();
},
})),
onDismiss: cancelable ? onDismiss : null,
};
setPromptDialogs(dialogs => {
return [
...dialogs,
dialog,
];
});
}
},
};
}, []);
const dialogControlRef = useRef(dialogControl);
dialogControlRef.current = dialogControl;
useEffect(() => {
shim.showMessageBox = makeShowMessageBox(dialogControlRef);
return () => {
dialogControlRef.current = null;
};
}, []);
const dialogComponents: React.ReactNode[] = [];
for (const dialog of dialogModels) {
const buttons = dialog.buttons.map((button, index) => {
return (
<Button key={`${index}-${button.text}`} onPress={button.onPress}>{button.text}</Button>
);
});
dialogComponents.push(
<Dialog
testID={'prompt-dialog'}
style={styles.dialogContainer}
key={dialog.key}
visible={true}
onDismiss={dialog.onDismiss}
>
<Dialog.Title>{dialog.title}</Dialog.Title>
<Dialog.Content>
<Text variant='bodyMedium'>{dialog.message}</Text>
</Dialog.Content>
<Dialog.Actions>
{buttons}
</Dialog.Actions>
</Dialog>,
);
}
// Web: Use a <Modal> wrapper for better keyboard focus handling.
return <>
<DialogContext.Provider value={dialogControl}>
{props.children}
</DialogContext.Provider>
<Portal>
<Modal
visible={!!dialogComponents.length}
containerStyle={styles.modalContainer}
animationType='none'
backgroundColor='rgba(0, 0, 0, 0.1)'
transparent={true}
onRequestClose={dialogModels[dialogComponents.length - 1]?.onDismiss}
>
{dialogComponents}
</Modal>
</Portal>
</>;
};
export default DialogManager;

View File

@ -0,0 +1,93 @@
import * as React from 'react';
import { Button, Dialog, Divider, Surface, Text } from 'react-native-paper';
import { DialogType, PromptDialogData } from './types';
import { StyleSheet } from 'react-native';
import { useMemo } from 'react';
import { themeStyle } from '../global-style';
interface Props {
dialog: PromptDialogData;
themeId: number;
}
const useStyles = (themeId: number, isMenu: boolean) => {
return useMemo(() => {
const theme = themeStyle(themeId);
return StyleSheet.create({
dialogContainer: {
backgroundColor: theme.backgroundColor,
borderRadius: 24,
paddingTop: 24,
marginLeft: 4,
marginRight: 4,
},
buttonScrollerContent: {
flexDirection: 'row',
justifyContent: 'flex-end',
flexWrap: 'wrap',
},
dialogContent: {
paddingBottom: 14,
},
dialogActions: {
paddingBottom: 14,
paddingTop: 4,
...(isMenu ? {
flexDirection: 'column',
alignItems: 'stretch',
} : {}),
},
dialogLabel: {
textAlign: isMenu ? 'center' : undefined,
},
});
}, [themeId, isMenu]);
};
const PromptDialog: React.FC<Props> = ({ dialog, themeId }) => {
const isMenu = dialog.type === DialogType.Menu;
const styles = useStyles(themeId, isMenu);
const buttons = dialog.buttons.map((button, index) => {
return (
<Button
key={`${index}-${button.text}`}
onPress={button.onPress}
>{button.text}</Button>
);
});
const titleComponent = <Text
variant='titleMedium'
accessibilityRole='header'
style={styles.dialogLabel}
>{dialog.title}</Text>;
return (
<Surface
testID={'prompt-dialog'}
style={styles.dialogContainer}
key={dialog.key}
elevation={1}
>
<Dialog.Content style={styles.dialogContent}>
{dialog.title ? titleComponent : null}
<Text
variant='bodyMedium'
style={styles.dialogLabel}
>{dialog.message}</Text>
</Dialog.Content>
{isMenu ? <Divider/> : null}
<Dialog.Actions
style={styles.dialogActions}
>
{buttons}
</Dialog.Actions>
</Surface>
);
};
export default PromptDialog;

View File

@ -0,0 +1,98 @@
import * as React from 'react';
import { Alert, Platform } from 'react-native';
import { DialogControl, DialogType, MenuChoice, PromptButton, PromptDialogData, PromptOptions } from '../types';
import { _ } from '@joplin/lib/locale';
import { useMemo, useRef } from 'react';
type SetPromptDialogs = React.Dispatch<React.SetStateAction<PromptDialogData[]>>;
const useDialogControl = (setPromptDialogs: SetPromptDialogs) => {
const nextDialogIdRef = useRef(0);
const dialogControl: DialogControl = useMemo(() => {
const onDismiss = (dialog: PromptDialogData) => {
setPromptDialogs(dialogs => dialogs.filter(d => d !== dialog));
};
const defaultButtons = [{ text: _('OK') }];
const control: DialogControl = {
info: (message: string) => {
return new Promise<void>((resolve) => {
control.prompt(_('Info'), message, [{
text: _('OK'),
onPress: () => resolve(),
}]);
});
},
error: (message: string) => {
return new Promise<void>((resolve) => {
control.prompt(_('Error'), message, [{
text: _('OK'),
onPress: () => resolve(),
}]);
});
},
prompt: (title: string, message: string, buttons: PromptButton[] = defaultButtons, options?: PromptOptions) => {
// Alert.alert doesn't work on web.
if (Platform.OS !== 'web') {
// Note: Alert.alert provides a more native style on iOS.
Alert.alert(title, message, buttons, options);
} else {
const cancelable = options?.cancelable ?? true;
const dialog: PromptDialogData = {
type: DialogType.Prompt,
key: `dialog-${nextDialogIdRef.current++}`,
title,
message,
buttons: buttons.map(button => ({
...button,
onPress: () => {
onDismiss(dialog);
button.onPress?.();
},
})),
onDismiss: cancelable ? () => onDismiss(dialog) : null,
};
setPromptDialogs(dialogs => {
return [
...dialogs,
dialog,
];
});
}
},
showMenu: function<T>(title: string, choices: MenuChoice<T>[]) {
return new Promise<T>((resolve) => {
const dismiss = () => onDismiss(dialog);
const dialog: PromptDialogData = {
type: DialogType.Menu,
key: `menu-dialog-${nextDialogIdRef.current++}`,
title: '',
message: title,
buttons: choices.map(choice => ({
text: choice.text,
onPress: () => {
dismiss();
resolve(choice.id);
},
})),
onDismiss: dismiss,
};
setPromptDialogs(dialogs => {
return [
...dialogs,
dialog,
];
});
});
},
};
return control;
}, [setPromptDialogs]);
return dialogControl;
};
export default useDialogControl;

View File

@ -0,0 +1,86 @@
import * as React from 'react';
import { createContext, useEffect, useMemo, useRef, useState } from 'react';
import { StyleSheet, useWindowDimensions } from 'react-native';
import { Portal } from 'react-native-paper';
import Modal from '../Modal';
import shim from '@joplin/lib/shim';
import makeShowMessageBox from '../../utils/makeShowMessageBox';
import { DialogControl, PromptDialogData } from './types';
import useDialogControl from './hooks/useDialogControl';
import PromptDialog from './PromptDialog';
export type { DialogControl } from './types';
export const DialogContext = createContext<DialogControl>(null);
interface Props {
themeId: number;
children: React.ReactNode;
}
const useStyles = () => {
const windowSize = useWindowDimensions();
return useMemo(() => {
return StyleSheet.create({
modalContainer: {
marginLeft: 'auto',
marginRight: 'auto',
marginTop: 'auto',
marginBottom: 'auto',
width: Math.max(windowSize.width / 2, 400),
maxWidth: '100%',
},
});
}, [windowSize.width]);
};
const DialogManager: React.FC<Props> = props => {
const [dialogModels, setPromptDialogs] = useState<PromptDialogData[]>([]);
const dialogControl = useDialogControl(setPromptDialogs);
const dialogControlRef = useRef(dialogControl);
dialogControlRef.current = dialogControl;
useEffect(() => {
shim.showMessageBox = makeShowMessageBox(dialogControlRef);
return () => {
dialogControlRef.current = null;
};
}, []);
const styles = useStyles();
const dialogComponents: React.ReactNode[] = [];
for (const dialog of dialogModels) {
dialogComponents.push(
<PromptDialog
key={dialog.key}
dialog={dialog}
themeId={props.themeId}
/>,
);
}
// Web: Use a <Modal> wrapper for better keyboard focus handling.
return <>
<DialogContext.Provider value={dialogControl}>
{props.children}
</DialogContext.Provider>
<Portal>
<Modal
visible={!!dialogComponents.length}
scrollOverflow={true}
containerStyle={styles.modalContainer}
animationType='fade'
backgroundColor='rgba(0, 0, 0, 0.1)'
transparent={true}
onRequestClose={dialogModels[dialogComponents.length - 1]?.onDismiss}
>
{dialogComponents}
</Modal>
</Portal>
</>;
};
export default DialogManager;

View File

@ -0,0 +1,37 @@
export interface PromptButton {
text: string;
onPress?: ()=> void;
style?: 'cancel'|'default'|'destructive';
}
export interface PromptOptions {
cancelable?: boolean;
}
export interface MenuChoice<IdType> {
text: string;
id: IdType;
}
export interface DialogControl {
info(message: string): Promise<void>;
error(message: string): Promise<void>;
prompt(title: string, message: string, buttons?: PromptButton[], options?: PromptOptions): void;
showMenu<IdType>(title: string, choices: MenuChoice<IdType>[]): Promise<IdType>;
}
export enum DialogType {
Prompt,
Menu,
}
export interface PromptDialogData {
type: DialogType;
key: string;
title: string;
message: string;
buttons: PromptButton[];
onDismiss: (()=> void)|null;
}

View File

@ -1,15 +1,20 @@
import * as React from 'react';
import { RefObject, useCallback, useMemo, useRef } from 'react';
import { GestureResponderEvent, Modal, ModalProps, StyleSheet, View, ViewStyle, useWindowDimensions } from 'react-native';
import { GestureResponderEvent, Modal, ModalProps, ScrollView, StyleSheet, View, ViewStyle, useWindowDimensions } from 'react-native';
import { hasNotch } from 'react-native-device-info';
interface ModalElementProps extends ModalProps {
children: React.ReactNode;
containerStyle?: ViewStyle;
backgroundColor?: string;
// If scrollOverflow is provided, the modal is wrapped in a vertical
// ScrollView. This allows the user to scroll parts of dialogs into
// view that would otherwise be clipped by the screen edge.
scrollOverflow?: boolean;
}
const useStyles = (backgroundColor?: string) => {
const useStyles = (hasScrollView: boolean, backgroundColor: string|undefined) => {
const { width: windowWidth, height: windowHeight } = useWindowDimensions();
const isLandscape = windowWidth > windowHeight;
return useMemo(() => {
@ -25,12 +30,27 @@ const useStyles = (backgroundColor?: string) => {
return StyleSheet.create({
modalBackground: {
...backgroundPadding,
flexGrow: 1,
flexShrink: 1,
// When hasScrollView, the modal background is wrapped in a ScrollView. In this case, it's
// possible to scroll content outside the background into view. To prevent the edge of the
// background from being visible, the background color is applied to the ScrollView container
// instead:
backgroundColor: hasScrollView ? null : backgroundColor,
},
modalScrollView: {
backgroundColor,
flexGrow: 1,
flexShrink: 1,
},
modalScrollViewContent: {
// Make the scroll view's scrolling region at least as tall as its container.
// This makes it possible to vertically center the content of scrollable modals.
flexGrow: 1,
},
});
}, [isLandscape, backgroundColor]);
}, [hasScrollView, isLandscape, backgroundColor]);
};
const useBackgroundTouchListeners = (onRequestClose: (event: GestureResponderEvent)=> void, backdropRef: RefObject<View>) => {
@ -51,9 +71,10 @@ const ModalElement: React.FC<ModalElementProps> = ({
children,
containerStyle,
backgroundColor,
scrollOverflow,
...modalProps
}) => {
const styles = useStyles(backgroundColor);
const styles = useStyles(scrollOverflow, backgroundColor);
// contentWrapper adds padding. To allow styling the region outside of the modal
// (e.g. to add a background), the content is wrapped twice.
@ -66,18 +87,25 @@ const ModalElement: React.FC<ModalElementProps> = ({
const backgroundRef = useRef<View>();
const { onShouldBackgroundCaptureTouch, onBackgroundTouchFinished } = useBackgroundTouchListeners(modalProps.onRequestClose, backgroundRef);
const contentAndBackdrop = <View
ref={backgroundRef}
style={styles.modalBackground}
onStartShouldSetResponder={onShouldBackgroundCaptureTouch}
onResponderRelease={onBackgroundTouchFinished}
>{content}</View>;
// supportedOrientations: On iOS, this allows the dialog to be shown in non-portrait orientations.
return (
<Modal
supportedOrientations={['portrait', 'portrait-upside-down', 'landscape', 'landscape-left', 'landscape-right']}
{...modalProps}
>
<View
ref={backgroundRef}
style={styles.modalBackground}
onStartShouldSetResponder={onShouldBackgroundCaptureTouch}
onResponderRelease={onBackgroundTouchFinished}
>{content}</View>
{scrollOverflow ? (
<ScrollView
style={styles.modalScrollView}
contentContainerStyle={styles.modalScrollViewContent}
>{contentAndBackdrop}</ScrollView>
) : contentAndBackdrop}
</Modal>
);
};

View File

@ -3,7 +3,6 @@ import * as React from 'react';
import useOnMessage, { HandleMessageCallback, OnMarkForDownloadCallback } from './hooks/useOnMessage';
import { useRef, useCallback, useState, useMemo } from 'react';
import { View, ViewStyle } from 'react-native';
import BackButtonDialogBox from '../BackButtonDialogBox';
import ExtendedWebView from '../ExtendedWebView';
import { WebViewControl } from '../ExtendedWebView/types';
import useOnResourceLongPress from './hooks/useOnResourceLongPress';
@ -37,7 +36,6 @@ interface Props {
}
export default function NoteBodyViewer(props: Props) {
const dialogBoxRef = useRef(null);
const webviewRef = useRef<WebViewControl>(null);
const onScroll = useCallback(async (scrollTop: number) => {
@ -49,7 +47,6 @@ export default function NoteBodyViewer(props: Props) {
onJoplinLinkClick: props.onJoplinLinkClick,
onRequestEditResource: props.onRequestEditResource,
},
dialogBoxRef,
);
const onPostMessage = useOnMessage(props.noteBody, {
@ -101,9 +98,6 @@ export default function NoteBodyViewer(props: Props) {
if (props.onLoadEnd) props.onLoadEnd();
}, [props.onLoadEnd]);
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const BackButtonDialogBox_ = BackButtonDialogBox as any;
const { html, injectedJs } = useSource(tempDir, props.themeId);
return (
@ -119,7 +113,6 @@ export default function NoteBodyViewer(props: Props) {
onLoadEnd={onLoadEnd}
onMessage={onWebViewMessage}
/>
<BackButtonDialogBox_ ref={dialogBoxRef}/>
</View>
);
}

View File

@ -1,13 +1,13 @@
import { useCallback } from 'react';
import { useCallback, useContext } from 'react';
const { _ } = require('@joplin/lib/locale.js');
const { dialogs } = require('../../../utils/dialogs.js');
import Resource from '@joplin/lib/models/Resource';
import { copyToCache } from '../../../utils/ShareUtils';
import isEditableResource from '../../NoteEditor/ImageEditor/isEditableResource';
import shim from '@joplin/lib/shim';
import shareFile from '../../../utils/shareFile';
import Logger from '@joplin/utils/Logger';
import { DialogContext } from '../../DialogManager';
const logger = Logger.create('useOnResourceLongPress');
@ -16,10 +16,11 @@ interface Callbacks {
onRequestEditResource: (message: string)=> void;
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
export default function useOnResourceLongPress(callbacks: Callbacks, dialogBoxRef: any) {
export default function useOnResourceLongPress(callbacks: Callbacks) {
const { onJoplinLinkClick, onRequestEditResource } = callbacks;
const dialogManager = useContext(DialogContext);
return useCallback(async (msg: string) => {
try {
const resourceId = msg.split(':')[1];
@ -42,7 +43,7 @@ export default function useOnResourceLongPress(callbacks: Callbacks, dialogBoxRe
}
actions.push({ text: _('Share'), id: 'share' });
const action = await dialogs.pop({ dialogbox: dialogBoxRef.current }, name, actions);
const action = await dialogManager.showMenu(name, actions);
if (action === 'open') {
onJoplinLinkClick(`joplin://${resourceId}`);
@ -54,7 +55,7 @@ export default function useOnResourceLongPress(callbacks: Callbacks, dialogBoxRe
}
} catch (e) {
logger.error('Could not handle link long press', e);
void shim.showMessageBox(`An error occurred, check log for details: ${e}`);
void shim.showErrorDialog(`An error occurred, check log for details: ${e}`);
}
}, [onJoplinLinkClick, onRequestEditResource, dialogBoxRef]);
}, [onJoplinLinkClick, onRequestEditResource, dialogManager]);
}

View File

@ -10,7 +10,6 @@ import Note from '@joplin/lib/models/Note';
import Folder from '@joplin/lib/models/Folder';
import { themeStyle } from '../global-style';
import { OnValueChangedListener } from '../Dropdown';
const DialogBox = require('react-native-dialogbox').default;
import { FolderEntity } from '@joplin/lib/services/database/types';
import { State } from '@joplin/lib/reducer';
import IconButton from '../IconButton';
@ -84,7 +83,6 @@ interface ScreenHeaderState {
class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeaderState> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
private cachedStyles: any;
public dialogbox?: typeof DialogBox;
public constructor(props: ScreenHeaderProps) {
super(props);
this.cachedStyles = {};
@ -645,11 +643,6 @@ class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeade
<WarningBanner
showShouldUpgradeSyncTargetMessage={this.props.showShouldUpgradeSyncTargetMessage}
/>
<DialogBox
ref={(dialogbox: typeof DialogBox) => {
this.dialogbox = dialogbox;
}}
/>
</View>
);
}

View File

@ -105,7 +105,7 @@ class LogScreenComponent extends BaseScreenComponent<Props, State> {
logger.error('Unable to share log data:', e);
// Display a message to the user (e.g. in the case where the user is out of disk space).
void shim.showMessageBox(_('Error'), _('Unable to share log data. Reason: %s', e.toString()));
void shim.showErrorDialog(_('Unable to share log data. Reason: %s', e.toString()));
} finally {
if (fileToShare) {
await shim.fsDriver().remove(fileToShare);

View File

@ -32,8 +32,6 @@ import { reg } from '@joplin/lib/registry';
import ResourceFetcher from '@joplin/lib/services/ResourceFetcher';
import { BaseScreenComponent } from '../base-screen';
import { themeStyle, editorFont } from '../global-style';
const { dialogs } = require('../../utils/dialogs.js');
const DialogBox = require('react-native-dialogbox').default;
import shared, { BaseNoteScreenComponent, Props as BaseProps } from '@joplin/lib/components/shared/note-screen-shared';
import { Asset, ImagePickerResponse, launchImageLibrary } from 'react-native-image-picker';
import SelectDateTimeDialog from '../SelectDateTimeDialog';
@ -49,7 +47,7 @@ import { isSupportedLanguage } from '../../services/voiceTyping/vosk';
import { ChangeEvent as EditorChangeEvent, SelectionRangeChangeEvent, UndoRedoDepthChangeEvent } from '@joplin/editor/events';
import { join } from 'path';
import { Dispatch } from 'redux';
import { RefObject } from 'react';
import { RefObject, useContext } from 'react';
import { SelectionRange } from '../NoteEditor/types';
import { getNoteCallbackUrl } from '@joplin/lib/callbackUrlUtils';
import { AppState } from '../../utils/types';
@ -64,6 +62,7 @@ import { ResourceInfo } from '../NoteBodyViewer/hooks/useRerenderHandler';
import getImageDimensions from '../../utils/image/getImageDimensions';
import resizeImage from '../../utils/image/resizeImage';
import { CameraResult } from '../CameraView/types';
import { DialogContext, DialogControl } from '../DialogManager';
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const emptyArray: any[] = [];
@ -87,6 +86,10 @@ interface Props extends BaseProps {
toolbarEnabled: boolean;
}
interface ComponentProps extends Props {
dialogs: DialogControl;
}
interface State {
note: NoteEntity;
mode: 'view'|'edit';
@ -117,7 +120,7 @@ interface State {
voiceTypingDialogShown: boolean;
}
class NoteScreenComponent extends BaseScreenComponent<Props, State> implements BaseNoteScreenComponent {
class NoteScreenComponent extends BaseScreenComponent<ComponentProps, State> implements BaseNoteScreenComponent {
// This isn't in this.state because we don't want changing scroll to trigger
// a re-render.
private lastBodyScroll: number|undefined = undefined;
@ -153,7 +156,7 @@ class NoteScreenComponent extends BaseScreenComponent<Props, State> implements B
return { header: null };
}
public constructor(props: Props) {
public constructor(props: ComponentProps) {
super(props);
this.state = {
@ -206,7 +209,10 @@ class NoteScreenComponent extends BaseScreenComponent<Props, State> implements B
const saveDialog = async () => {
if (this.isModified()) {
const buttonId = await dialogs.pop(this, _('This note has been modified:'), [{ text: _('Save changes'), id: 'save' }, { text: _('Discard changes'), id: 'discard' }, { text: _('Cancel'), id: 'cancel' }]);
const buttonId = await this.props.dialogs.showMenu(
_('This note has been modified:'),
[{ text: _('Save changes'), id: 'save' }, { text: _('Discard changes'), id: 'discard' }, { text: _('Cancel'), id: 'cancel' }],
);
if (buttonId === 'cancel') return true;
if (buttonId === 'save') await this.saveNoteButton_press();
@ -269,7 +275,7 @@ class NoteScreenComponent extends BaseScreenComponent<Props, State> implements B
try {
await CommandService.instance().execute('openItem', msg);
} catch (error) {
dialogs.error(this, error.message);
await this.props.dialogs.error(error.message);
}
};
@ -664,14 +670,15 @@ class NoteScreenComponent extends BaseScreenComponent<Props, State> implements B
if (canResize) {
const resizeLargeImages = Setting.value('imageResizing');
if (resizeLargeImages === 'alwaysAsk') {
const userAnswer = await dialogs.pop(this, `${_('You are about to attach a large image (%dx%d pixels). Would you like to resize it down to %d pixels before attaching it?', dimensions.width, dimensions.height, maxSize)}\n\n${_('(You may disable this prompt in the options)')}`, [
{ text: _('Yes'), id: 'yes' },
{ text: _('No'), id: 'no' },
{ text: _('Cancel'), id: 'cancel' },
]);
const userAnswer = await this.props.dialogs.showMenu(
`${_('You are about to attach a large image (%dx%d pixels). Would you like to resize it down to %d pixels before attaching it?', dimensions.width, dimensions.height, maxSize)}\n\n${_('(You may disable this prompt in the options)')}`, [
{ text: _('Yes'), id: 'yes' },
{ text: _('No'), id: 'no' },
{ text: _('Cancel'), id: 'cancel' },
]);
if (userAnswer === 'yes') return await saveResizedImage();
if (userAnswer === 'no') return await saveOriginalImage();
if (userAnswer === 'cancel') return false;
if (userAnswer === 'cancel' || !userAnswer) return false;
} else if (resizeLargeImages === 'alwaysResize') {
return await saveResizedImage();
}
@ -759,7 +766,7 @@ class NoteScreenComponent extends BaseScreenComponent<Props, State> implements B
if (!done) return null;
} else {
if (fileType === 'image' && mimeType !== 'image/svg+xml') {
dialogs.error(this, _('Unsupported image type: %s', mimeType));
await this.props.dialogs.error(_('Unsupported image type: %s', mimeType));
return null;
} else {
await shim.fsDriver().copy(localFilePath, targetPath);
@ -773,7 +780,7 @@ class NoteScreenComponent extends BaseScreenComponent<Props, State> implements B
}
} catch (error) {
reg.logger().warn('Could not attach file:', error);
await dialogs.error(this, error.message);
await this.props.dialogs.error(error.message);
return null;
}
@ -996,7 +1003,7 @@ class NoteScreenComponent extends BaseScreenComponent<Props, State> implements B
await Linking.openURL(url);
} catch (error) {
this.props.dispatch({ type: 'SIDE_MENU_CLOSE' });
await dialogs.error(this, error.message);
await this.props.dialogs.error(error.message);
}
}
@ -1007,7 +1014,7 @@ class NoteScreenComponent extends BaseScreenComponent<Props, State> implements B
try {
await Linking.openURL(note.source_url);
} catch (error) {
await dialogs.error(this, error.message);
await this.props.dialogs.error(error.message);
}
}
@ -1070,7 +1077,7 @@ class NoteScreenComponent extends BaseScreenComponent<Props, State> implements B
if (Platform.OS === 'ios') buttons.push({ text: _('Attach photo'), id: 'attachPhoto' });
buttons.push({ text: _('Take photo'), id: 'takePhoto' });
const buttonId = await dialogs.pop(this, _('Choose an option'), buttons);
const buttonId = await this.props.dialogs.showMenu(_('Choose an option'), buttons);
if (buttonId === 'takePhoto') await this.takePhoto_onPress();
if (buttonId === 'attachFile') await this.attachFile_onPress();
@ -1631,12 +1638,6 @@ class NoteScreenComponent extends BaseScreenComponent<Props, State> implements B
<SelectDateTimeDialog themeId={this.props.themeId} shown={this.state.alarmDialogShown} date={dueDate} onAccept={this.onAlarmDialogAccept} onReject={this.onAlarmDialogReject} />
<DialogBox
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
ref={(dialogbox: any) => {
this.dialogbox = dialogbox;
}}
/>
{noteTagDialog}
</View>
);
@ -1648,8 +1649,9 @@ class NoteScreenComponent extends BaseScreenComponent<Props, State> implements B
// which can cause some bugs where previously set state to another note would interfere
// how the new note should be rendered
const NoteScreenWrapper = (props: Props) => {
const dialogs = useContext(DialogContext);
return (
<NoteScreenComponent key={props.noteId} {...props} />
<NoteScreenComponent key={props.noteId} dialogs={dialogs} {...props} />
);
};

View File

@ -11,15 +11,14 @@ import { themeStyle } from '../global-style';
import { FolderPickerOptions, ScreenHeader } from '../ScreenHeader';
import { _ } from '@joplin/lib/locale';
import ActionButton from '../buttons/FloatingActionButton';
const { dialogs } = require('../../utils/dialogs.js');
const DialogBox = require('react-native-dialogbox').default;
import BackButtonService from '../../services/BackButtonService';
import { BaseScreenComponent } from '../base-screen';
import { AppState } from '../../utils/types';
import { FolderEntity, NoteEntity, TagEntity } from '@joplin/lib/services/database/types';
import { itemIsInTrash } from '@joplin/lib/services/trash';
import AccessibleView from '../accessibility/AccessibleView';
import { Dispatch } from 'redux';
import { DialogContext, DialogControl } from '../DialogManager';
import { useContext } from 'react';
interface Props {
dispatch: Dispatch;
@ -46,17 +45,18 @@ interface State {
}
interface ComponentProps extends Props {
dialogManager: DialogControl;
}
type Styles = Record<string, ViewStyle|TextStyle>;
class NotesScreenComponent extends BaseScreenComponent<Props, State> {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Partial refactor of old code from before rule was applied
private dialogbox: any;
class NotesScreenComponent extends BaseScreenComponent<ComponentProps, State> {
private onAppStateChangeSub_: NativeEventSubscription = null;
private styles_: Record<number, Styles> = {};
private folderPickerOptions_: FolderPickerOptions;
public constructor(props: Props) {
public constructor(props: ComponentProps) {
super(props);
}
@ -99,20 +99,12 @@ class NotesScreenComponent extends BaseScreenComponent<Props, State> {
id: { name: 'showCompletedTodos', value: !Setting.value('showCompletedTodos') },
});
const r = await dialogs.pop(this, Setting.settingMetadata('notes.sortOrder.field').label(), buttons);
const r = await this.props.dialogManager.showMenu(Setting.settingMetadata('notes.sortOrder.field').label(), buttons);
if (!r) return;
Setting.setValue(r.name, r.value);
};
private backHandler = () => {
if (this.dialogbox && this.dialogbox.state && this.dialogbox.state.isVisible) {
this.dialogbox.close();
return true;
}
return false;
};
public styles() {
if (!this.styles_) this.styles_ = {};
const themeId = this.props.themeId;
@ -132,14 +124,12 @@ class NotesScreenComponent extends BaseScreenComponent<Props, State> {
}
public async componentDidMount() {
BackButtonService.addHandler(this.backHandler);
await this.refreshNotes();
this.onAppStateChangeSub_ = RNAppState.addEventListener('change', this.onAppStateChange_);
}
public async componentWillUnmount() {
if (this.onAppStateChangeSub_) this.onAppStateChangeSub_.remove();
BackButtonService.removeHandler(this.backHandler);
}
public async componentDidUpdate(prevProps: Props) {
@ -298,17 +288,16 @@ class NotesScreenComponent extends BaseScreenComponent<Props, State> {
<ScreenHeader title={iconString + title} showBackButton={false} sortButton_press={this.sortButton_press} folderPickerOptions={this.folderPickerOptions()} showSearchButton={true} showSideMenuButton={true} />
<NoteList />
{actionButtonComp}
<DialogBox
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
ref={(dialogbox: any) => {
this.dialogbox = dialogbox;
}}
/>
</AccessibleView>
);
}
}
const NotesScreenWrapper: React.FC<Props> = props => {
const dialogManager = useContext(DialogContext);
return <NotesScreenComponent {...props} dialogManager={dialogManager}/>;
};
const NotesScreen = connect((state: AppState) => {
return {
folders: state.folders,
@ -327,6 +316,6 @@ const NotesScreen = connect((state: AppState) => {
noteSelectionEnabled: state.noteSelectionEnabled,
notesOrder: stateUtils.notesOrder(state.settings),
};
})(NotesScreenComponent);
})(NotesScreenWrapper);
export default NotesScreen;

View File

@ -1,29 +1,33 @@
const React = require('react');
import * as React from 'react';
const { View, Button, Text, TextInput, TouchableOpacity, StyleSheet, ScrollView } = require('react-native');
import { View, Button, Text, TextInput, TouchableOpacity, StyleSheet, ScrollView } from 'react-native';
import { AppState } from '../../utils/types';
const { connect } = require('react-redux');
const { ScreenHeader } = require('../ScreenHeader');
const { _ } = require('@joplin/lib/locale');
import { ScreenHeader } from '../ScreenHeader';
import { _ } from '@joplin/lib/locale';
const { BaseScreenComponent } = require('../base-screen');
const DialogBox = require('react-native-dialogbox').default;
const { dialogs } = require('../../utils/dialogs.js');
const Shared = require('@joplin/lib/components/shared/dropbox-login-shared');
const { themeStyle } = require('../global-style');
import shim, { MessageBoxType } from '@joplin/lib/shim';
import { themeStyle } from '../global-style';
class DropboxLoginScreenComponent extends BaseScreenComponent {
constructor() {
public constructor() {
super();
this.styles_ = {};
this.shared_ = new Shared(this, msg => dialogs.info(this, msg), msg => dialogs.error(this, msg));
this.shared_ = new Shared(
this,
(msg: string) => shim.showMessageBox(msg, { type: MessageBoxType.Info }),
(msg: string) => shim.showErrorDialog(msg),
);
}
UNSAFE_componentWillMount() {
public UNSAFE_componentWillMount() {
this.shared_.refreshUrl();
}
styles() {
private styles() {
const themeId = this.props.themeId;
const theme = themeStyle(themeId);
@ -47,7 +51,7 @@ class DropboxLoginScreenComponent extends BaseScreenComponent {
return this.styles_[themeId];
}
render() {
public render() {
const theme = themeStyle(this.props.themeId);
return (
@ -70,21 +74,15 @@ class DropboxLoginScreenComponent extends BaseScreenComponent {
{/* Add this extra padding to make sure the view is scrollable when the keyboard is visible on small screens (iPhone SE) */}
<View style={{ height: 200 }}></View>
</ScrollView>
<DialogBox
ref={dialogbox => {
this.dialogbox = dialogbox;
}}
/>
</View>
);
}
}
const DropboxLoginScreen = connect(state => {
const DropboxLoginScreen = connect((state: AppState) => {
return {
themeId: state.settings.theme,
};
})(DropboxLoginScreenComponent);
module.exports = { DropboxLoginScreen };
export default DropboxLoginScreen;

View File

@ -3,8 +3,6 @@ const { TextInput, TouchableOpacity, Linking, View, StyleSheet, Text, Button, Sc
const { connect } = require('react-redux');
import ScreenHeader from '../ScreenHeader';
import { themeStyle } from '../global-style';
const DialogBox = require('react-native-dialogbox').default;
const { dialogs } = require('../../utils/dialogs.js');
import EncryptionService from '@joplin/lib/services/e2ee/EncryptionService';
import { _ } from '@joplin/lib/locale';
import time from '@joplin/lib/time';
@ -13,7 +11,8 @@ import { MasterKeyEntity } from '@joplin/lib/services/e2ee/types';
import { State } from '@joplin/lib/reducer';
import { SyncInfo } from '@joplin/lib/services/synchronizer/syncInfoUtils';
import { getDefaultMasterKey, setupAndDisableEncryption, toggleAndSetupEncryption } from '@joplin/lib/services/e2ee/utils';
import { useMemo, useRef, useState } from 'react';
import { useMemo, useState } from 'react';
import shim from '@joplin/lib/shim';
interface Props {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
@ -35,7 +34,6 @@ const EncryptionConfigScreen = (props: Props) => {
const { passwordChecks, masterPasswordKeys } = usePasswordChecker(props.masterKeys, props.activeMasterKeyId, props.masterPassword, props.passwords);
const { inputPasswords, onInputPasswordChange } = useInputPasswords(props.passwords);
const { inputMasterPassword, onMasterPasswordSave, onMasterPasswordChange } = useInputMasterPassword(props.masterKeys, props.activeMasterKeyId);
const dialogBoxRef = useRef(null);
const mkComps = [];
@ -240,7 +238,7 @@ const EncryptionConfigScreen = (props: Props) => {
const onToggleButtonClick = async () => {
if (props.encryptionEnabled) {
const ok = await dialogs.confirmRef(dialogBoxRef.current, _('Disabling encryption means *all* your notes and attachments are going to be re-synchronised and sent unencrypted to the sync target. Do you wish to continue?'));
const ok = await shim.showConfirmationDialog(_('Disabling encryption means *all* your notes and attachments are going to be re-synchronised and sent unencrypted to the sync target. Do you wish to continue?'));
if (!ok) return;
try {
@ -312,7 +310,6 @@ const EncryptionConfigScreen = (props: Props) => {
{nonExistingMasterKeySection}
<View style={{ flex: 1, height: 20 }}></View>
</ScrollView>
<DialogBox ref={dialogBoxRef}/>
</View>
);
};

View File

@ -6,7 +6,7 @@ const Folder = require('@joplin/lib/models/Folder').default;
const BaseModel = require('@joplin/lib/BaseModel').default;
const { ScreenHeader } = require('../ScreenHeader');
const { BaseScreenComponent } = require('../base-screen');
const { dialogs } = require('../../utils/dialogs.js');
const shim = require('@joplin/lib/shim').default;
const { _ } = require('@joplin/lib/locale');
const { default: FolderPicker } = require('../FolderPicker');
const TextInput = require('../TextInput').default;
@ -73,7 +73,7 @@ class FolderScreenComponent extends BaseScreenComponent {
if (folder.id && !(await Folder.canNestUnder(folder.id, folder.parent_id))) throw new Error(_('Cannot move notebook to this location'));
folder = await Folder.save(folder, { userSideValidation: true });
} catch (error) {
dialogs.error(this, _('The notebook could not be saved: %s', error.message));
shim.showErrorDialog(_('The notebook could not be saved: %s', error.message));
return;
}
@ -115,11 +115,6 @@ class FolderScreenComponent extends BaseScreenComponent {
/>
</View>
<View style={{ flex: 1 }} />
<dialogs.DialogBox
ref={dialogbox => {
this.dialogbox = dialogbox;
}}
/>
</View>
);
}

View File

@ -49,7 +49,6 @@
"react": "18.3.1",
"react-native": "0.74.1",
"react-native-device-info": "10.14.0",
"react-native-dialogbox": "0.6.10",
"react-native-document-picker": "9.3.0",
"react-native-dropdownalert": "5.1.0",
"react-native-exit-app": "2.0.0",

View File

@ -64,7 +64,7 @@ import StatusScreen from './components/screens/status';
import SearchScreen from './components/screens/SearchScreen';
const { OneDriveLoginScreen } = require('./components/screens/onedrive-login.js');
import EncryptionConfigScreen from './components/screens/encryption-config';
const { DropboxLoginScreen } = require('./components/screens/dropbox-login.js');
import DropboxLoginScreen from './components/screens/dropbox-login.js';
import { MenuProvider } from 'react-native-popup-menu';
import SideMenu, { SideMenuPosition } from './components/SideMenu';
import SideMenuContent from './components/side-menu-content';
@ -1343,7 +1343,7 @@ class AppComponent extends React.Component {
},
},
}}>
<DialogManager>
<DialogManager themeId={this.props.themeId}>
{mainContent}
</DialogManager>
</PaperProvider>

View File

@ -1,82 +0,0 @@
const DialogBox = require('react-native-dialogbox').default;
const { Keyboard } = require('react-native');
// Add this at the bottom of the component:
//
// <DialogBox ref={dialogbox => { this.dialogbox = dialogbox }}/>
const dialogs = {};
dialogs.confirmRef = (ref, message) => {
if (!ref) throw new Error('ref is required');
return new Promise((resolve) => {
Keyboard.dismiss();
ref.confirm({
content: message,
ok: {
callback: () => {
resolve(true);
},
},
cancel: {
callback: () => {
resolve(false);
},
},
});
});
};
dialogs.confirm = (parentComponent, message) => {
if (!parentComponent) throw new Error('parentComponent is required');
if (!('dialogbox' in parentComponent)) throw new Error('A "dialogbox" component must be defined on the parent component!');
return dialogs.confirmRef(parentComponent.dialogbox, message);
};
dialogs.pop = (parentComponent, message, buttons, options = null) => {
if (!parentComponent) throw new Error('parentComponent is required');
if (!('dialogbox' in parentComponent)) throw new Error('A "dialogbox" component must be defined on the parent component!');
if (!options) options = {};
if (!('buttonFlow' in options)) options.buttonFlow = 'auto';
return new Promise((resolve) => {
Keyboard.dismiss();
const btns = [];
for (let i = 0; i < buttons.length; i++) {
btns.push({
text: buttons[i].text,
callback: () => {
parentComponent.dialogbox.close();
resolve(buttons[i].id);
},
});
}
parentComponent.dialogbox.pop({
content: message,
btns: btns,
buttonFlow: options.buttonFlow,
});
});
};
dialogs.error = (parentComponent, message) => {
Keyboard.dismiss();
return parentComponent.dialogbox.alert(message);
};
dialogs.info = (parentComponent, message) => {
Keyboard.dismiss();
return parentComponent.dialogbox.alert(message);
};
dialogs.DialogBox = DialogBox;
module.exports = { dialogs };

View File

@ -1,28 +1,27 @@
import { _ } from '@joplin/lib/locale';
import { Alert } from 'react-native';
import { DialogControl, PromptButton } from '../components/DialogManager';
import { DialogControl } from '../components/DialogManager';
import { RefObject } from 'react';
import { MessageBoxType, ShowMessageBoxOptions } from '@joplin/lib/shim';
import { PromptButton } from '../components/DialogManager/types';
interface Options {
title: string;
buttons: string[];
}
const makeShowMessageBox = (dialogControl: null|RefObject<DialogControl>) => (message: string, options: Options = null) => {
const makeShowMessageBox = (dialogControl: null|RefObject<DialogControl>) => (message: string, options: ShowMessageBoxOptions = null) => {
return new Promise<number>(resolve => {
const defaultButtons: PromptButton[] = [
{
text: _('OK'),
onPress: () => resolve(0),
},
{
text: _('Cancel'),
onPress: () => resolve(1),
style: 'cancel',
},
];
const okButton: PromptButton = {
text: _('OK'),
onPress: () => resolve(0),
};
const cancelButton: PromptButton = {
text: _('Cancel'),
onPress: () => resolve(1),
style: 'cancel',
};
const defaultConfirmButtons = [okButton, cancelButton];
const defaultAlertButtons = [okButton];
let buttons = defaultButtons;
const dialogType = options.type ?? MessageBoxType.Confirm;
let buttons = dialogType === MessageBoxType.Confirm ? defaultConfirmButtons : defaultAlertButtons;
if (options?.buttons) {
buttons = options.buttons.map((text, index) => {
return {

View File

@ -1,7 +1,7 @@
import { CommandRuntime, CommandDeclaration, CommandContext } from '../services/CommandService';
import { _ } from '../locale';
import Note from '../models/Note';
import shim from '../shim';
import shim, { MessageBoxType } from '../shim';
export const declaration: CommandDeclaration = {
name: 'permanentlyDeleteNote',
@ -21,7 +21,7 @@ export const runtime = (): CommandRuntime => {
buttons: [_('Delete'), _('Cancel')],
defaultId: 1,
cancelId: 1,
type: 'question',
type: MessageBoxType.Confirm,
});
if (result === deleteIndex) {

View File

@ -40,6 +40,20 @@ interface AttachFileToNoteOptions {
markupLanguage?: MarkupLanguage;
}
export enum MessageBoxType {
Confirm = 'question',
Error = 'error',
Info = 'info',
}
export interface ShowMessageBoxOptions {
title?: string;
buttons?: string[];
type?: MessageBoxType;
defaultId?: number;
cancelId?: number;
}
let isTestingEnv_ = false;
// We need to ensure that there's only one instance of React being used by all
@ -397,13 +411,16 @@ const shim = {
// Returns the index of the button that was clicked. By default,
// 0 -> OK
// 1 -> Cancel
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
showMessageBox: (_message: string, _options: any = null): Promise<number> => {
showMessageBox: (_message: string, _options: ShowMessageBoxOptions = null): Promise<number> => {
throw new Error('Not implemented');
},
showErrorDialog: async (message: string): Promise<void> => {
await shim.showMessageBox(message, { type: MessageBoxType.Error });
},
showConfirmationDialog: async (message: string): Promise<boolean> => {
return await shim.showMessageBox(message) === 0;
return await shim.showMessageBox(message, { type: MessageBoxType.Confirm }) === 0;
},
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied

View File

@ -8402,7 +8402,6 @@ __metadata:
react-dom: 18.3.1
react-native: 0.74.1
react-native-device-info: 10.14.0
react-native-dialogbox: 0.6.10
react-native-document-picker: 9.3.0
react-native-dropdownalert: 5.1.0
react-native-exit-app: 2.0.0
@ -39434,18 +39433,6 @@ __metadata:
languageName: node
linkType: hard
"react-native-dialogbox@npm:0.6.10":
version: 0.6.10
resolution: "react-native-dialogbox@npm:0.6.10"
dependencies:
prop-types: ^15.6.2
peerDependencies:
react: "*"
react-native: ">=0.30.0"
checksum: 4163dbf7975551b905053b9df4cdbcb02116acb2aaf16e88546e0f48b82bc18af5f0fffec384dcb1f570cc35fc59804bdb7cba0a153e75cacd72201f159e7e58
languageName: node
linkType: hard
"react-native-document-picker@npm:9.3.0":
version: 9.3.0
resolution: "react-native-document-picker@npm:9.3.0"