1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-11-27 08:21:03 +02:00

Mobile: Accessibility: Add checked/unchecked accessibility information to the "sort notes by" dialog (#11411)

This commit is contained in:
Henry Heino 2024-11-20 03:39:33 -08:00 committed by GitHub
parent c9608cf4a1
commit d648e43cfb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
11 changed files with 135 additions and 39 deletions

View File

@ -588,6 +588,7 @@ 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/PromptButton.js
packages/app-mobile/components/DialogManager/PromptDialog.js
packages/app-mobile/components/DialogManager/hooks/useDialogControl.js
packages/app-mobile/components/DialogManager/index.js

1
.gitignore vendored
View File

@ -564,6 +564,7 @@ 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/PromptButton.js
packages/app-mobile/components/DialogManager/PromptDialog.js
packages/app-mobile/components/DialogManager/hooks/useDialogControl.js
packages/app-mobile/components/DialogManager/index.js

View File

@ -0,0 +1,83 @@
import * as React from 'react';
import { useMemo } from 'react';
import { StyleSheet, TextStyle, View } from 'react-native';
import { TouchableRipple, Text } from 'react-native-paper';
import { PromptButtonSpec } from './types';
import { ThemeStyle, themeStyle } from '../global-style';
import Icon from '../Icon';
interface Props {
themeId: number;
buttonSpec: PromptButtonSpec;
}
const useStyles = (theme: ThemeStyle) => {
return useMemo(() => {
const buttonText: TextStyle = {
color: theme.color4,
textAlign: 'center',
};
return StyleSheet.create({
buttonContainer: {
// This applies the borderRadius to the TouchableRipple's parent, which
// seems necessary on Android.
borderRadius: theme.borderRadius,
overflow: 'hidden',
},
button: {
borderRadius: theme.borderRadius,
padding: 10,
},
buttonContent: {
display: 'flex',
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'baseline',
},
buttonText,
icon: {
...buttonText,
marginRight: 8,
},
});
}, [theme]);
};
const PromptButton: React.FC<Props> = props => {
const theme = themeStyle(props.themeId);
const styles = useStyles(theme);
const { checked, text, iconChecked, onPress } = props.buttonSpec;
const isCheckbox = (checked ?? null) !== null;
const icon = checked ? (
<>
<Icon
accessibilityLabel={null}
style={styles.icon}
name={iconChecked ?? 'fas fa-check'}
/>
</>
) : null;
return (
<View style={styles.buttonContainer}>
<TouchableRipple
onPress={onPress}
style={styles.button}
rippleColor={theme.backgroundColorHover4}
accessibilityRole={isCheckbox ? 'checkbox' : 'button'}
accessibilityState={isCheckbox ? { checked } : null}
aria-checked={isCheckbox ? checked : undefined}
>
<View style={styles.buttonContent}>
{icon}
<Text style={styles.buttonText}>{text}</Text>
</View>
</TouchableRipple>
</View>
);
};
export default PromptButton;

View File

@ -1,9 +1,10 @@
import * as React from 'react';
import { Button, Dialog, Divider, Surface, Text } from 'react-native-paper';
import { 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';
import PromptButton from './PromptButton';
interface Props {
dialog: PromptDialogData;
@ -17,18 +18,12 @@ const useStyles = (themeId: number, isMenu: boolean) => {
return StyleSheet.create({
dialogContainer: {
backgroundColor: theme.backgroundColor,
borderRadius: 24,
paddingTop: 24,
borderRadius: theme.borderRadius,
paddingTop: theme.borderRadius,
marginLeft: 4,
marginRight: 4,
},
buttonScrollerContent: {
flexDirection: 'row',
justifyContent: 'flex-end',
flexWrap: 'wrap',
},
dialogContent: {
paddingBottom: 14,
},
@ -53,12 +48,11 @@ const PromptDialog: React.FC<Props> = ({ dialog, themeId }) => {
const styles = useStyles(themeId, isMenu);
const buttons = dialog.buttons.map((button, index) => {
return (
<Button
key={`${index}-${button.text}`}
onPress={button.onPress}
>{button.text}</Button>
);
return <PromptButton
key={`${index}-${button.text}`}
buttonSpec={button}
themeId={themeId}
/>;
});
const titleComponent = <Text
variant='titleMedium'

View File

@ -1,6 +1,6 @@
import * as React from 'react';
import { Alert, Platform } from 'react-native';
import { DialogControl, DialogType, MenuChoice, PromptButton, PromptDialogData, PromptOptions } from '../types';
import { DialogControl, DialogType, MenuChoice, PromptButtonSpec, PromptDialogData, PromptOptions } from '../types';
import { _ } from '@joplin/lib/locale';
import { useMemo, useRef } from 'react';
@ -32,7 +32,7 @@ const useDialogControl = (setPromptDialogs: SetPromptDialogs) => {
}]);
});
},
prompt: (title: string, message: string, buttons: PromptButton[] = defaultButtons, options?: PromptOptions) => {
prompt: (title: string, message: string, buttons: PromptButtonSpec[] = defaultButtons, options?: PromptOptions) => {
// Alert.alert doesn't work on web.
if (Platform.OS !== 'web') {
// Note: Alert.alert provides a more native style on iOS.
@ -71,11 +71,11 @@ const useDialogControl = (setPromptDialogs: SetPromptDialogs) => {
key: `menu-dialog-${nextDialogIdRef.current++}`,
title: '',
message: title,
buttons: choices.map(choice => ({
text: choice.text,
buttons: choices.map(({ id, ...buttonProps }) => ({
...buttonProps,
onPress: () => {
dismiss();
resolve(choice.id);
resolve(id);
},
})),
onDismiss: dismiss,

View File

@ -1,23 +1,28 @@
export interface PromptButton {
interface BaseButtonSpec {
text: string;
onPress?: ()=> void;
style?: 'cancel'|'default'|'destructive';
checked?: boolean|null;
iconChecked?: string;
}
export interface PromptButtonSpec extends BaseButtonSpec {
onPress?: ()=> void;
}
export interface PromptOptions {
cancelable?: boolean;
}
export interface MenuChoice<IdType> {
text: string;
export interface MenuChoice<IdType> extends BaseButtonSpec {
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;
prompt(title: string, message: string, buttons?: PromptButtonSpec[], options?: PromptOptions): void;
showMenu<IdType>(title: string, choices: MenuChoice<IdType>[]): Promise<IdType>;
}
@ -31,7 +36,7 @@ export interface PromptDialogData {
key: string;
title: string;
message: string;
buttons: PromptButton[];
buttons: PromptButtonSpec[];
onDismiss: (()=> void)|null;
}

View File

@ -3,6 +3,8 @@ import { Platform, TextStyle, ViewStyle } from 'react-native';
import { themeById } from '@joplin/lib/theme';
import { Theme as BaseTheme } from '@joplin/lib/themes/type';
const Color = require('color');
const baseStyle = {
appearance: 'light',
fontSize: 16,
@ -16,12 +18,15 @@ const baseStyle = {
};
export type ThemeStyle = BaseTheme & typeof baseStyle & {
backgroundColorHover4: string;
fontSize: number;
fontSizeSmaller: number;
marginRight: number;
marginLeft: number;
marginTop: number;
marginBottom: number;
borderRadius: number;
icon: TextStyle;
lineInput: ViewStyle;
buttonRow: ViewStyle;
@ -112,6 +117,9 @@ function extraStyles(theme: BaseTheme) {
keyboardAppearance: theme.appearance,
color5: theme.color5 ?? theme.backgroundColor4,
backgroundColor5: theme.backgroundColor5 ?? theme.color4,
backgroundColorHover4: Color(theme.color4).alpha(0.12).rgb().string(),
borderRadius: 24,
};
}

View File

@ -19,6 +19,7 @@ import AccessibleView from '../accessibility/AccessibleView';
import { Dispatch } from 'redux';
import { DialogContext, DialogControl } from '../DialogManager';
import { useContext } from 'react';
import { MenuChoice } from '../DialogManager/types';
interface Props {
dispatch: Dispatch;
@ -68,34 +69,35 @@ class NotesScreenComponent extends BaseScreenComponent<ComponentProps, State> {
};
private sortButton_press = async () => {
const buttons = [];
type IdType = { name: string; value: string|boolean };
const buttons: MenuChoice<IdType>[] = [];
const sortNoteOptions = Setting.enumOptions('notes.sortOrder.field');
const makeCheckboxText = function(selected: boolean, sign: string, label: string) {
const s = sign === 'tick' ? '✓' : '⬤';
return (selected ? `${s} ` : '') + label;
};
for (const field in sortNoteOptions) {
if (!sortNoteOptions.hasOwnProperty(field)) continue;
buttons.push({
text: makeCheckboxText(Setting.value('notes.sortOrder.field') === field, 'bullet', sortNoteOptions[field]),
text: sortNoteOptions[field],
iconChecked: 'fas fa-circle',
checked: Setting.value('notes.sortOrder.field') === field,
id: { name: 'notes.sortOrder.field', value: field },
});
}
buttons.push({
text: makeCheckboxText(Setting.value('notes.sortOrder.reverse'), 'tick', `[ ${Setting.settingMetadata('notes.sortOrder.reverse').label()} ]`),
text: `[ ${Setting.settingMetadata('notes.sortOrder.reverse').label()} ]`,
checked: Setting.value('notes.sortOrder.reverse'),
id: { name: 'notes.sortOrder.reverse', value: !Setting.value('notes.sortOrder.reverse') },
});
buttons.push({
text: makeCheckboxText(Setting.value('uncompletedTodosOnTop'), 'tick', `[ ${Setting.settingMetadata('uncompletedTodosOnTop').label()} ]`),
text: `[ ${Setting.settingMetadata('uncompletedTodosOnTop').label()} ]`,
checked: Setting.value('uncompletedTodosOnTop'),
id: { name: 'uncompletedTodosOnTop', value: !Setting.value('uncompletedTodosOnTop') },
});
buttons.push({
text: makeCheckboxText(Setting.value('showCompletedTodos'), 'tick', `[ ${Setting.settingMetadata('showCompletedTodos').label()} ]`),
text: `[ ${Setting.settingMetadata('showCompletedTodos').label()} ]`,
checked: Setting.value('showCompletedTodos'),
id: { name: 'showCompletedTodos', value: !Setting.value('showCompletedTodos') },
});

View File

@ -35,6 +35,7 @@
"@react-native-community/slider": "4.5.2",
"assert-browserify": "2.0.0",
"buffer": "6.0.3",
"color": "3.2.1",
"constants-browserify": "1.0.0",
"crypto-browserify": "3.12.0",
"deprecated-react-native-prop-types": "5.0.0",

View File

@ -3,16 +3,16 @@ import { Alert } from 'react-native';
import { DialogControl } from '../components/DialogManager';
import { RefObject } from 'react';
import { MessageBoxType, ShowMessageBoxOptions } from '@joplin/lib/shim';
import { PromptButton } from '../components/DialogManager/types';
import { PromptButtonSpec } from '../components/DialogManager/types';
const makeShowMessageBox = (dialogControl: null|RefObject<DialogControl>) => (message: string, options: ShowMessageBoxOptions = null) => {
return new Promise<number>(resolve => {
const okButton: PromptButton = {
const okButton: PromptButtonSpec = {
text: _('OK'),
onPress: () => resolve(0),
};
const cancelButton: PromptButton = {
const cancelButton: PromptButtonSpec = {
text: _('Cancel'),
onPress: () => resolve(1),
style: 'cancel',

View File

@ -8389,6 +8389,7 @@ __metadata:
babel-plugin-module-resolver: 4.1.0
babel-plugin-react-native-web: 0.19.12
buffer: 6.0.3
color: 3.2.1
constants-browserify: 1.0.0
crypto-browserify: 3.12.0
deprecated-react-native-prop-types: 5.0.0