diff --git a/.eslintignore b/.eslintignore index bda5afa94..db944f4e5 100644 --- a/.eslintignore +++ b/.eslintignore @@ -611,7 +611,8 @@ packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/utils/expo packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/utils/makeImportExportCacheDirectory.js packages/app-mobile/components/screens/ConfigScreen/SectionDescription.js packages/app-mobile/components/screens/ConfigScreen/SectionHeader.js -packages/app-mobile/components/screens/ConfigScreen/SectionSelector.js +packages/app-mobile/components/screens/ConfigScreen/SectionSelector/SectionTab.js +packages/app-mobile/components/screens/ConfigScreen/SectionSelector/index.js packages/app-mobile/components/screens/ConfigScreen/SettingComponent.js packages/app-mobile/components/screens/ConfigScreen/SettingItem.js packages/app-mobile/components/screens/ConfigScreen/SettingsButton.js diff --git a/.gitignore b/.gitignore index dbc20c568..9336628cb 100644 --- a/.gitignore +++ b/.gitignore @@ -590,7 +590,8 @@ packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/utils/expo packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/utils/makeImportExportCacheDirectory.js packages/app-mobile/components/screens/ConfigScreen/SectionDescription.js packages/app-mobile/components/screens/ConfigScreen/SectionHeader.js -packages/app-mobile/components/screens/ConfigScreen/SectionSelector.js +packages/app-mobile/components/screens/ConfigScreen/SectionSelector/SectionTab.js +packages/app-mobile/components/screens/ConfigScreen/SectionSelector/index.js packages/app-mobile/components/screens/ConfigScreen/SettingComponent.js packages/app-mobile/components/screens/ConfigScreen/SettingItem.js packages/app-mobile/components/screens/ConfigScreen/SettingsButton.js diff --git a/packages/app-mobile/components/screens/ConfigScreen/SectionSelector.tsx b/packages/app-mobile/components/screens/ConfigScreen/SectionSelector.tsx deleted file mode 100644 index 124f19e8a..000000000 --- a/packages/app-mobile/components/screens/ConfigScreen/SectionSelector.tsx +++ /dev/null @@ -1,135 +0,0 @@ -import * as React from 'react'; - -import Setting, { AppType, SettingMetadataSection, SettingSectionSource } from '@joplin/lib/models/Setting'; -import { FunctionComponent, useEffect, useMemo, useState } from 'react'; -import { ConfigScreenStyles } from './configScreenStyles'; -import { FlatList, Text, View, ViewStyle } from 'react-native'; -import { settingsSections } from '@joplin/lib/components/shared/config/config-shared'; -import Icon from '../../Icon'; -import { _ } from '@joplin/lib/locale'; -import { TouchableRipple } from 'react-native-paper'; -import BetaChip from '../../BetaChip'; - -interface Props { - styles: ConfigScreenStyles; - - width: number|undefined; - - // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied - settings: any; - selectedSectionName: string|null; - openSection: (sectionName: string)=> void; -} - -const SectionSelector: FunctionComponent = props => { - const sections = useMemo(() => { - return settingsSections({ device: AppType.Mobile, settings: props.settings }); - }, [props.settings]); - const styles = props.styles.styleSheet; - - const itemHeight = styles.sidebarButton.height; - - const onRenderButton = ({ item }: { item: SettingMetadataSection }) => { - const section = item; - const selected = props.selectedSectionName === section.name; - const icon = Setting.sectionNameToIcon(section.name, AppType.Mobile); - const label = Setting.sectionNameToLabel(section.name); - const shortDescription = Setting.sectionMetadataToSummary(section); - const isPlugin = item.source === SettingSectionSource.Plugin; - - const titleStyle = selected ? styles.sidebarSelectedButtonText : styles.sidebarButtonMainText; - - const sourceIcon = isPlugin ? ( - - ) : null; - - const isBeta = item.name === 'plugins'; - const betaChip = isBeta ? : null; - - return ( - props.openSection(section.name)} - > - - - - - - {label} - - - {betaChip} - - - {shortDescription ?? ''} - - - {sourceIcon} - - - ); - }; - - const [flatListRef, setFlatListRef] = useState(null); - - useEffect(() => { - if (flatListRef && props.selectedSectionName) { - let selectedIndex = 0; - for (const section of sections) { - if (section.name === props.selectedSectionName) { - break; - } - selectedIndex ++; - } - - flatListRef.scrollToIndex({ - index: selectedIndex, - viewPosition: 0.5, - }); - } - }, [props.selectedSectionName, flatListRef, sections]); - - const containerStyle: ViewStyle = useMemo(() => ({ - width: props.width, - maxWidth: props.width, - minWidth: props.width, - flex: 1, - }), [props.width]); - - return ( - - item.name} - getItemLayout={(_data, index) => ({ - length: itemHeight, offset: itemHeight * index, index, - })} - /> - - ); -}; - -export default SectionSelector; diff --git a/packages/app-mobile/components/screens/ConfigScreen/SectionSelector/SectionTab.tsx b/packages/app-mobile/components/screens/ConfigScreen/SectionSelector/SectionTab.tsx new file mode 100644 index 000000000..57f9af2a8 --- /dev/null +++ b/packages/app-mobile/components/screens/ConfigScreen/SectionSelector/SectionTab.tsx @@ -0,0 +1,66 @@ +import * as React from 'react'; +import { ConfigScreenStyles } from '../configScreenStyles'; +import Icon from '../../../Icon'; +import BetaChip from '../../../BetaChip'; +import { TouchableRipple, Text } from 'react-native-paper'; +import { View } from 'react-native'; +import Setting, { AppType, SettingMetadataSection } from '@joplin/lib/models/Setting'; + +interface Props { + selected: boolean; + section: SettingMetadataSection; + styles: ConfigScreenStyles; + onPress: ()=> void; +} + +const SectionTab: React.FC = ({ styles, onPress, selected, section }) => { + const icon = Setting.sectionNameToIcon(section.name, AppType.Mobile); + const label = Setting.sectionNameToLabel(section.name); + const shortDescription = Setting.sectionMetadataToSummary(section); + + const styleSheet = styles.styleSheet; + const titleStyle = selected ? styleSheet.sidebarSelectedButtonText : styleSheet.sidebarButtonMainText; + + const isBeta = section.name === 'plugins'; + const betaChip = isBeta ? : null; + + return ( + + + + + + + {label} + + + {betaChip} + + + {shortDescription ?? ''} + + + + + ); +}; + +export default SectionTab; diff --git a/packages/app-mobile/components/screens/ConfigScreen/SectionSelector/index.tsx b/packages/app-mobile/components/screens/ConfigScreen/SectionSelector/index.tsx new file mode 100644 index 000000000..bb1c3cf5b --- /dev/null +++ b/packages/app-mobile/components/screens/ConfigScreen/SectionSelector/index.tsx @@ -0,0 +1,116 @@ +import * as React from 'react'; + +import { AppType, SettingMetadataSection, SettingSectionSource } from '@joplin/lib/models/Setting'; +import { FunctionComponent, useEffect, useMemo, useState } from 'react'; +import { ConfigScreenStyles } from '../configScreenStyles'; +import { FlatList, View, ViewStyle } from 'react-native'; +import { Text } from 'react-native-paper'; +import { settingsSections } from '@joplin/lib/components/shared/config/config-shared'; +import { _ } from '@joplin/lib/locale'; +import SectionTab from './SectionTab'; + +interface Props { + styles: ConfigScreenStyles; + + width: number|undefined; + + // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied + settings: Record; + selectedSectionName: string|null; + openSection: (sectionName: string)=> void; +} +type SectionDivider = { divider: boolean; label: string; name: string; source?: string }; + +const SectionSelector: FunctionComponent = props => { + type SectionListType = (SectionDivider|SettingMetadataSection)[]; + const sections = useMemo((): SectionListType => { + const allSections = settingsSections({ device: AppType.Mobile, settings: props.settings }); + + const builtInSections = []; + const pluginSections = []; + for (const section of allSections) { + if (section.source === SettingSectionSource.Plugin) { + pluginSections.push(section); + } else { + builtInSections.push(section); + } + } + + let result: SectionListType = builtInSections; + if (pluginSections.length > 0) { + result = result.concat([ + { label: _('Plugins'), name: 'plugins-divider', divider: true }, + ...pluginSections, + ]); + } + return result; + }, [props.settings]); + + const styles = props.styles.styleSheet; + const onRenderButton = ({ item }: { item: SettingMetadataSection|SectionDivider }) => { + const section = item; + const selected = props.selectedSectionName === section.name; + + if ('divider' in item && item.divider) { + return ( + + {item.label} + + ); + } else { + return ( + props.openSection(section.name)} + /> + ); + } + }; + + const [flatListRef, setFlatListRef] = useState(null); + + useEffect(() => { + if (flatListRef && props.selectedSectionName) { + let selectedIndex = 0; + for (const section of sections) { + if (section.name === props.selectedSectionName) { + break; + } + selectedIndex ++; + } + + flatListRef.scrollToIndex({ + index: selectedIndex, + viewPosition: 0.5, + }); + } + }, [props.selectedSectionName, flatListRef, sections]); + + const containerStyle: ViewStyle = useMemo(() => ({ + width: props.width, + maxWidth: props.width, + minWidth: props.width, + flex: 1, + }), [props.width]); + + return ( + + item.name} + onScrollToIndexFailed={({ index, averageItemLength }) => { + // Scrolling to a particular index can fail if the item at that index hasn't been rendered yet. + // This shouldn't happen often, so a guess should be sufficient. + flatListRef.scrollToOffset({ offset: (index + 0.5) * averageItemLength }); + }} + /> + + ); +}; + +export default SectionSelector; diff --git a/packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.ts b/packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.ts index 2e9e6ccd9..8ebf4dfba 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.ts +++ b/packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.ts @@ -33,6 +33,8 @@ export interface ConfigScreenStyleSheet { sidebarButtonMainText: TextStyle; sidebarSelectedButtonText: TextStyle; sidebarButtonDescriptionText: TextStyle; + sidebarHeader: ViewStyle; + sidebarHeaderText: TextStyle; settingControl: TextStyle; } @@ -204,6 +206,18 @@ const configScreenStyles = (themeId: number): ConfigScreenStyles => { fontWeight: 'bold', }, sidebarButtonDescriptionText, + sidebarHeader: { + paddingLeft: 12, + height: sidebarButtonHeight / 2, + flexDirection: 'column', + justifyContent: 'center', + backgroundColor: theme.oddBackgroundColor, + }, + sidebarHeaderText: { + color: theme.color, + fontWeight: 'bold', + fontSize: theme.fontSize, + }, }); return {