1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-01-02 12:47:41 +02:00

Mobile: Resolves #10594: Move mobile plugin setting tabs under a separate section (#10600)

This commit is contained in:
Henry Heino 2024-06-18 02:02:11 -07:00 committed by GitHub
parent 1fb392ff4e
commit d9dadf28cb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 200 additions and 137 deletions

View File

@ -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

3
.gitignore vendored
View File

@ -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

View File

@ -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> = 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 ? (
<Icon
name='fas fa-puzzle-piece'
accessibilityLabel={_('From a plugin')}
style={titleStyle}
/>
) : null;
const isBeta = item.name === 'plugins';
const betaChip = isBeta ? <BetaChip size={10}/> : null;
return (
<TouchableRipple
key={section.name}
role='tab'
aria-selected={selected}
onPress={() => props.openSection(section.name)}
>
<View
style={selected ? styles.selectedSidebarButton : styles.sidebarButton}
>
<Icon
name={icon}
accessibilityLabel={null}
style={styles.sidebarIcon}
/>
<View style={{ display: 'flex', flexDirection: 'column', flex: 1 }}>
<View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
<Text
numberOfLines={1}
style={titleStyle}
>
{label}
</Text>
{betaChip}
</View>
<Text
style={styles.sidebarButtonDescriptionText}
numberOfLines={1}
ellipsizeMode='tail'
>
{shortDescription ?? ''}
</Text>
</View>
{sourceIcon}
</View>
</TouchableRipple>
);
};
const [flatListRef, setFlatListRef] = useState<FlatList|null>(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 (
<View style={containerStyle}>
<FlatList
role='tablist'
ref={setFlatListRef}
data={sections}
renderItem={onRenderButton}
keyExtractor={item => item.name}
getItemLayout={(_data, index) => ({
length: itemHeight, offset: itemHeight * index, index,
})}
/>
</View>
);
};
export default SectionSelector;

View File

@ -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<Props> = ({ 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 ? <BetaChip size={10}/> : null;
return (
<TouchableRipple
key={section.name}
role='tab'
aria-selected={selected}
onPress={onPress}
>
<View
style={selected ? styleSheet.selectedSidebarButton : styleSheet.sidebarButton}
>
<Icon
name={icon}
accessibilityLabel={null}
style={styleSheet.sidebarIcon}
/>
<View style={{ display: 'flex', flexDirection: 'column', flex: 1 }}>
<View style={{ flexDirection: 'row', justifyContent: 'space-between' }}>
<Text
numberOfLines={1}
style={titleStyle}
>
{label}
</Text>
{betaChip}
</View>
<Text
style={styleSheet.sidebarButtonDescriptionText}
numberOfLines={1}
ellipsizeMode='tail'
>
{shortDescription ?? ''}
</Text>
</View>
</View>
</TouchableRipple>
);
};
export default SectionTab;

View File

@ -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<string, any>;
selectedSectionName: string|null;
openSection: (sectionName: string)=> void;
}
type SectionDivider = { divider: boolean; label: string; name: string; source?: string };
const SectionSelector: FunctionComponent<Props> = 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 (
<View style={styles.sidebarHeader}>
<Text variant='labelLarge' style={styles.sidebarHeaderText}>{item.label}</Text>
</View>
);
} else {
return (
<SectionTab
selected={selected}
section={section as SettingMetadataSection}
styles={props.styles}
onPress={() => props.openSection(section.name)}
/>
);
}
};
const [flatListRef, setFlatListRef] = useState<FlatList|null>(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 (
<View style={containerStyle}>
<FlatList
role='tablist'
ref={setFlatListRef}
data={sections}
renderItem={onRenderButton}
keyExtractor={item => 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 });
}}
/>
</View>
);
};
export default SectionSelector;

View File

@ -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 {