From c9608cf4a19e1fa4b13eb24a580ec916a7fabb8d Mon Sep 17 00:00:00 2001 From: Henry Heino <46334387+personalizedrefrigerator@users.noreply.github.com> Date: Wed, 20 Nov 2024 03:37:09 -0800 Subject: [PATCH] Mobile: Accessibility: Improve screen reader accessibility of the tag list (#11420) --- .eslintignore | 1 + .gitignore | 1 + .../app-mobile/components/screens/tags.js | 114 ------------------ .../app-mobile/components/screens/tags.tsx | 100 +++++++++++++++ packages/app-mobile/root.tsx | 2 +- 5 files changed, 103 insertions(+), 115 deletions(-) delete mode 100644 packages/app-mobile/components/screens/tags.js create mode 100644 packages/app-mobile/components/screens/tags.tsx diff --git a/.eslintignore b/.eslintignore index a03688c9bd..9859bdd0fc 100644 --- a/.eslintignore +++ b/.eslintignore @@ -766,6 +766,7 @@ 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/screens/tags.js packages/app-mobile/components/side-menu-content.js packages/app-mobile/components/testing/TestProviderStack.js packages/app-mobile/components/voiceTyping/VoiceTypingDialog.js diff --git a/.gitignore b/.gitignore index 17a5d0be4c..e7ab6ab717 100644 --- a/.gitignore +++ b/.gitignore @@ -742,6 +742,7 @@ 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/screens/tags.js packages/app-mobile/components/side-menu-content.js packages/app-mobile/components/testing/TestProviderStack.js packages/app-mobile/components/voiceTyping/VoiceTypingDialog.js diff --git a/packages/app-mobile/components/screens/tags.js b/packages/app-mobile/components/screens/tags.js deleted file mode 100644 index d3550daa54..0000000000 --- a/packages/app-mobile/components/screens/tags.js +++ /dev/null @@ -1,114 +0,0 @@ -const React = require('react'); - -const { View, Text, FlatList, StyleSheet, TouchableOpacity } = require('react-native'); -const { connect } = require('react-redux'); -const Tag = require('@joplin/lib/models/Tag').default; -const { themeStyle } = require('../global-style'); -const { ScreenHeader } = require('../ScreenHeader'); -const { _ } = require('@joplin/lib/locale'); -const { BaseScreenComponent } = require('../base-screen'); - -class TagsScreenComponent extends BaseScreenComponent { - static navigationOptions() { - return { header: null }; - } - - constructor() { - super(); - - this.state = { - tags: [], - }; - - this.tagList_renderItem = this.tagList_renderItem.bind(this); - this.tagList_keyExtractor = this.tagList_keyExtractor.bind(this); - this.tagItem_press = this.tagItem_press.bind(this); - } - - styles() { - if (this.styles_) return this.styles_; - - const theme = themeStyle(this.props.themeId); - - this.styles_ = StyleSheet.create({ - listItem: { - flexDirection: 'row', - borderBottomWidth: 1, - borderBottomColor: theme.dividerColor, - alignItems: 'flex-start', - paddingLeft: theme.marginLeft, - paddingRight: theme.marginRight, - paddingTop: theme.itemMarginTop, - paddingBottom: theme.itemMarginBottom, - }, - listItemText: { - flex: 1, - color: theme.color, - fontSize: theme.fontSize, - }, - }); - - return this.styles_; - } - - tagItem_press(event) { - this.props.dispatch({ type: 'SIDE_MENU_CLOSE' }); - - this.props.dispatch({ - type: 'NAV_GO', - routeName: 'Notes', - tagId: event.id, - }); - } - - tagList_renderItem(event) { - const tag = event.item; - return ( - { - this.tagItem_press({ id: tag.id }); - }} - > - - {tag.title} - - - ); - } - - tagList_keyExtractor(item) { - return item.id; - } - - async componentDidMount() { - const tags = await Tag.allWithNotes(); - tags.sort((a, b) => { - return a.title.toLowerCase() < b.title.toLowerCase() ? -1 : +1; - }); - this.setState({ tags: tags }); - } - - render() { - const theme = themeStyle(this.props.themeId); - - const rootStyle = { - flex: 1, - backgroundColor: theme.backgroundColor, - }; - - return ( - - - - - ); - } -} - -const TagsScreen = connect(state => { - return { - themeId: state.settings.theme, - }; -})(TagsScreenComponent); - -module.exports = { TagsScreen }; diff --git a/packages/app-mobile/components/screens/tags.tsx b/packages/app-mobile/components/screens/tags.tsx new file mode 100644 index 0000000000..36eb5a1f66 --- /dev/null +++ b/packages/app-mobile/components/screens/tags.tsx @@ -0,0 +1,100 @@ +import * as React from 'react'; + +import { View, Text, FlatList, StyleSheet, TouchableOpacity } from 'react-native'; +import { connect } from 'react-redux'; +import Tag from '@joplin/lib/models/Tag'; +import { themeStyle } from '../global-style'; +import { ScreenHeader } from '../ScreenHeader'; +import { _ } from '@joplin/lib/locale'; +import { AppState } from '../../utils/types'; +import { TagEntity } from '@joplin/lib/services/database/types'; +import { useCallback, useMemo, useState } from 'react'; +import { Dispatch } from 'redux'; +import useAsyncEffect from '@joplin/lib/hooks/useAsyncEffect'; + +interface Props { + dispatch: Dispatch; + themeId: number; +} + +const useStyles = (themeId: number) => { + return useMemo(() => { + const theme = themeStyle(themeId); + + return StyleSheet.create({ + listItem: { + flexDirection: 'row', + borderBottomWidth: 1, + borderBottomColor: theme.dividerColor, + alignItems: 'flex-start', + paddingLeft: theme.marginLeft, + paddingRight: theme.marginRight, + paddingTop: theme.itemMarginTop, + paddingBottom: theme.itemMarginBottom, + }, + listItemText: { + flex: 1, + color: theme.color, + fontSize: theme.fontSize, + }, + rootStyle: theme.rootStyle, + }); + }, [themeId]); +}; + + +const TagsScreenComponent: React.FC = props => { + const [tags, setTags] = useState([]); + const styles = useStyles(props.themeId); + + type TagItemPressEvent = { id: string }; + + useAsyncEffect(async () => { + const tags = await Tag.allWithNotes(); + tags.sort((a, b) => { + return a.title.toLowerCase() < b.title.toLowerCase() ? -1 : +1; + }); + setTags(tags); + }, []); + + const onTagItemPress = useCallback((event: TagItemPressEvent) => { + props.dispatch({ type: 'SIDE_MENU_CLOSE' }); + + props.dispatch({ + type: 'NAV_GO', + routeName: 'Notes', + tagId: event.id, + }); + }, [props.dispatch]); + + type RenderItemEvent = { item: TagEntity }; + const onRenderItem = useCallback(({ item }: RenderItemEvent) => { + return ( + onTagItemPress({ id: item.id })} + accessibilityRole='button' + accessibilityHint={_('Shows notes for tag')} + > + + {item.title} + + + ); + }, [onTagItemPress, styles]); + + return ( + + + tag.id} /> + + ); +}; + + +const TagsScreen = connect((state: AppState) => { + return { + themeId: state.settings.theme, + }; +})(TagsScreenComponent); + +export default TagsScreen; diff --git a/packages/app-mobile/root.tsx b/packages/app-mobile/root.tsx index ea39627800..09435fa437 100644 --- a/packages/app-mobile/root.tsx +++ b/packages/app-mobile/root.tsx @@ -56,7 +56,7 @@ import RevisionService from '@joplin/lib/services/RevisionService'; import JoplinDatabase from '@joplin/lib/JoplinDatabase'; import Database from '@joplin/lib/database'; import NotesScreen from './components/screens/Notes'; -const { TagsScreen } = require('./components/screens/tags.js'); +import TagsScreen from './components/screens/tags'; import ConfigScreen from './components/screens/ConfigScreen/ConfigScreen'; const { FolderScreen } = require('./components/screens/folder.js'); import LogScreen from './components/screens/LogScreen';