mirror of
https://github.com/laurent22/joplin.git
synced 2024-12-24 10:27:10 +02:00
parent
f9a1ab4d40
commit
18e86a7ba3
@ -70,6 +70,7 @@ interface ScreenHeaderProps {
|
|||||||
onRedoButtonPress: OnPressCallback;
|
onRedoButtonPress: OnPressCallback;
|
||||||
onSaveButtonPress: OnPressCallback;
|
onSaveButtonPress: OnPressCallback;
|
||||||
sortButton_press?: OnPressCallback;
|
sortButton_press?: OnPressCallback;
|
||||||
|
onSearchButtonPress?: OnPressCallback;
|
||||||
|
|
||||||
showSideMenuButton?: boolean;
|
showSideMenuButton?: boolean;
|
||||||
showSearchButton?: boolean;
|
showSearchButton?: boolean;
|
||||||
@ -242,8 +243,12 @@ class ScreenHeaderComponent extends PureComponent<ScreenHeaderProps, ScreenHeade
|
|||||||
}
|
}
|
||||||
|
|
||||||
private searchButton_press() {
|
private searchButton_press() {
|
||||||
|
if (this.props.onSearchButtonPress) {
|
||||||
|
this.props.onSearchButtonPress();
|
||||||
|
} else {
|
||||||
void NavService.go('Search');
|
void NavService.go('Search');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private async duplicateButton_press() {
|
private async duplicateButton_press() {
|
||||||
const noteIds = this.props.selectedNoteIds;
|
const noteIds = this.props.selectedNoteIds;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
import { Platform, Linking, View, Switch, ScrollView, Text, TouchableOpacity, Alert, PermissionsAndroid, Dimensions, AccessibilityInfo } from 'react-native';
|
import { Platform, Linking, View, Switch, ScrollView, Text, TouchableOpacity, Alert, PermissionsAndroid, Dimensions, AccessibilityInfo } from 'react-native';
|
||||||
import Setting, { AppType } from '@joplin/lib/models/Setting';
|
import Setting, { AppType, SettingMetadataSection } from '@joplin/lib/models/Setting';
|
||||||
import NavService from '@joplin/lib/services/NavService';
|
import NavService from '@joplin/lib/services/NavService';
|
||||||
import SearchEngine from '@joplin/lib/services/searchengine/SearchEngine';
|
import SearchEngine from '@joplin/lib/services/searchengine/SearchEngine';
|
||||||
import checkPermissions from '../../../utils/checkPermissions';
|
import checkPermissions from '../../../utils/checkPermissions';
|
||||||
@ -18,21 +18,25 @@ import * as shared from '@joplin/lib/components/shared/config/config-shared';
|
|||||||
import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry';
|
import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry';
|
||||||
import biometricAuthenticate from '../../biometrics/biometricAuthenticate';
|
import biometricAuthenticate from '../../biometrics/biometricAuthenticate';
|
||||||
import configScreenStyles, { ConfigScreenStyles } from './configScreenStyles';
|
import configScreenStyles, { ConfigScreenStyles } from './configScreenStyles';
|
||||||
import NoteExportButton from './NoteExportSection/NoteExportButton';
|
import NoteExportButton, { exportButtonDescription, exportButtonTitle } from './NoteExportSection/NoteExportButton';
|
||||||
import SettingsButton from './SettingsButton';
|
import SettingsButton from './SettingsButton';
|
||||||
import Clipboard from '@react-native-community/clipboard';
|
import Clipboard from '@react-native-community/clipboard';
|
||||||
import { ReactNode } from 'react';
|
import { ReactElement, ReactNode } from 'react';
|
||||||
import { Dispatch } from 'redux';
|
import { Dispatch } from 'redux';
|
||||||
import SectionHeader from './SectionHeader';
|
import SectionHeader from './SectionHeader';
|
||||||
import ExportProfileButton from './NoteExportSection/ExportProfileButton';
|
import ExportProfileButton, { exportProfileButtonTitle } from './NoteExportSection/ExportProfileButton';
|
||||||
import SettingComponent from './SettingComponent';
|
import SettingComponent from './SettingComponent';
|
||||||
import ExportDebugReportButton from './NoteExportSection/ExportDebugReportButton';
|
import ExportDebugReportButton, { exportDebugReportTitle } from './NoteExportSection/ExportDebugReportButton';
|
||||||
import SectionSelector from './SectionSelector';
|
import SectionSelector from './SectionSelector';
|
||||||
|
import { TextInput } from 'react-native-paper';
|
||||||
|
|
||||||
interface ConfigScreenState {
|
interface ConfigScreenState {
|
||||||
settings: any;
|
settings: any;
|
||||||
changedSettingKeys: string[];
|
changedSettingKeys: string[];
|
||||||
|
|
||||||
|
searchQuery: string;
|
||||||
|
searching: boolean;
|
||||||
|
|
||||||
fixingSearchIndex: boolean;
|
fixingSearchIndex: boolean;
|
||||||
checkSyncConfigResult: { ok: boolean; errorMessage: string }|'checking'|null;
|
checkSyncConfigResult: { ok: boolean; errorMessage: string }|'checking'|null;
|
||||||
showAdvancedSettings: boolean;
|
showAdvancedSettings: boolean;
|
||||||
@ -66,6 +70,8 @@ class ConfigScreenComponent extends BaseScreenComponent<ConfigScreenProps, Confi
|
|||||||
selectedSectionName: null,
|
selectedSectionName: null,
|
||||||
fixingSearchIndex: false,
|
fixingSearchIndex: false,
|
||||||
sidebarWidth: 100,
|
sidebarWidth: 100,
|
||||||
|
searchQuery: '',
|
||||||
|
searching: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
this.scrollViewRef_ = React.createRef<ScrollView>();
|
this.scrollViewRef_ = React.createRef<ScrollView>();
|
||||||
@ -128,6 +134,21 @@ class ConfigScreenComponent extends BaseScreenComponent<ConfigScreenProps, Confi
|
|||||||
void NavService.go('Log');
|
void NavService.go('Log');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
private setShowSearch_(searching: boolean) {
|
||||||
|
if (searching !== this.state.searching) {
|
||||||
|
this.setState({ searching });
|
||||||
|
AccessibilityInfo.announceForAccessibility(searching ? _('Search shown') : _('Search hidden'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private onSearchButtonPress_ = () => {
|
||||||
|
this.setShowSearch_(!this.state.searching);
|
||||||
|
};
|
||||||
|
|
||||||
|
private onSearchUpdate_ = (newQuery: string) => {
|
||||||
|
this.setState({ searchQuery: newQuery });
|
||||||
|
};
|
||||||
|
|
||||||
private updateSidebarWidth = () => {
|
private updateSidebarWidth = () => {
|
||||||
const windowWidth = Dimensions.get('window').width;
|
const windowWidth = Dimensions.get('window').width;
|
||||||
|
|
||||||
@ -150,10 +171,13 @@ class ConfigScreenComponent extends BaseScreenComponent<ConfigScreenProps, Confi
|
|||||||
return this.state.sidebarWidth > windowWidth / 2;
|
return this.state.sidebarWidth > windowWidth / 2;
|
||||||
}
|
}
|
||||||
|
|
||||||
private switchSectionPress_ = (section: string) => {
|
private onJumpToSection_ = (section: string) => {
|
||||||
const label = Setting.sectionNameToLabel(section);
|
const label = Setting.sectionNameToLabel(section);
|
||||||
AccessibilityInfo.announceForAccessibility(_('Opening section %s', label));
|
AccessibilityInfo.announceForAccessibility(_('Opening section %s', label));
|
||||||
this.setState({ selectedSectionName: section });
|
this.setState({
|
||||||
|
selectedSectionName: section,
|
||||||
|
searching: false,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
private showSectionNavigation_ = () => {
|
private showSectionNavigation_ = () => {
|
||||||
@ -245,6 +269,12 @@ class ConfigScreenComponent extends BaseScreenComponent<ConfigScreenProps, Confi
|
|||||||
await BackButtonService.back();
|
await BackButtonService.back();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Cancel search on back
|
||||||
|
if (this.state.searching) {
|
||||||
|
this.setShowSearch_(false);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Show navigation when pressing "back" (unless always visible).
|
// Show navigation when pressing "back" (unless always visible).
|
||||||
if (this.state.selectedSectionName && this.navigationFillsScreen()) {
|
if (this.state.selectedSectionName && this.navigationFillsScreen()) {
|
||||||
this.showSectionNavigation_();
|
this.showSectionNavigation_();
|
||||||
@ -298,10 +328,73 @@ class ConfigScreenComponent extends BaseScreenComponent<ConfigScreenProps, Confi
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public sectionToComponent(key: string, section: any, settings: any, isSelected: boolean) {
|
public sectionToComponent(key: string, section: SettingMetadataSection, settings: any, isSelected: boolean) {
|
||||||
const settingComps = [];
|
const settingComps: ReactElement[] = [];
|
||||||
|
|
||||||
|
const headerTitle = Setting.sectionNameToLabel(section.name);
|
||||||
|
|
||||||
|
const matchesSearchQuery = (relatedText: string|string[]) => {
|
||||||
|
let searchThrough;
|
||||||
|
if (Array.isArray(relatedText)) {
|
||||||
|
searchThrough = relatedText.join('\n');
|
||||||
|
} else {
|
||||||
|
searchThrough = relatedText;
|
||||||
|
}
|
||||||
|
searchThrough = searchThrough.toLocaleLowerCase();
|
||||||
|
|
||||||
|
const searchQuery = this.state.searchQuery.toLocaleLowerCase().trim();
|
||||||
|
|
||||||
|
const hasSearchMatches =
|
||||||
|
headerTitle.toLocaleLowerCase() === searchQuery
|
||||||
|
|| searchThrough.includes(searchQuery);
|
||||||
|
|
||||||
|
// Don't show results when the search input is empty
|
||||||
|
return this.state.searchQuery.length > 0 && hasSearchMatches;
|
||||||
|
};
|
||||||
|
|
||||||
|
const addSettingComponent = (component: ReactElement, relatedText: string|string[]) => {
|
||||||
|
const hiddenBySearch = this.state.searching && !matchesSearchQuery(relatedText);
|
||||||
|
if (component && !hiddenBySearch) {
|
||||||
|
settingComps.push(component);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const addSettingButton = (key: string, title: string, clickHandler: ()=> void, options: any = null) => {
|
||||||
|
const relatedText = [title];
|
||||||
|
if (typeof options === 'object' && options?.description) {
|
||||||
|
relatedText.push(options.description);
|
||||||
|
}
|
||||||
|
addSettingComponent(this.renderButton(key, title, clickHandler, options), relatedText);
|
||||||
|
};
|
||||||
|
|
||||||
const styleSheet = this.styles().styleSheet;
|
const styleSheet = this.styles().styleSheet;
|
||||||
|
const addSettingLink = (key: string, title: string, target: string) => {
|
||||||
|
const component = (
|
||||||
|
<View key={key} style={styleSheet.settingContainer}>
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() => {
|
||||||
|
void Linking.openURL(target);
|
||||||
|
}}
|
||||||
|
accessibilityRole='link'
|
||||||
|
>
|
||||||
|
<Text key="label" style={styleSheet.linkText}>
|
||||||
|
{title}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
|
||||||
|
addSettingComponent(component, title);
|
||||||
|
};
|
||||||
|
|
||||||
|
const addSettingText = (key: string, text: string) => {
|
||||||
|
addSettingComponent(
|
||||||
|
<View key={key} style={styleSheet.settingContainer}>
|
||||||
|
<Text style={styleSheet.settingText}>{text}</Text>
|
||||||
|
</View>,
|
||||||
|
text,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
for (let i = 0; i < section.metadatas.length; i++) {
|
for (let i = 0; i < section.metadatas.length; i++) {
|
||||||
const md = section.metadatas[i];
|
const md = section.metadatas[i];
|
||||||
@ -322,24 +415,29 @@ class ConfigScreenComponent extends BaseScreenComponent<ConfigScreenProps, Confi
|
|||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
||||||
settingComps.push(this.renderButton('check_sync_config_button', _('Check synchronisation configuration'), this.checkSyncConfig_, { statusComp: statusComp }));
|
addSettingButton('check_sync_config_button', _('Check synchronisation configuration'), this.checkSyncConfig_, { statusComp: statusComp });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const settingComp = this.settingToComponent(md.key, settings[md.key]);
|
const settingComp = this.settingToComponent(md.key, settings[md.key]);
|
||||||
settingComps.push(settingComp);
|
const relatedText = [md.label?.() ?? '', md.description?.() ?? ''];
|
||||||
|
addSettingComponent(
|
||||||
|
settingComp,
|
||||||
|
relatedText,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (section.name === 'sync') {
|
if (section.name === 'sync') {
|
||||||
settingComps.push(this.renderButton('e2ee_config_button', _('Encryption Config'), this.e2eeConfig_));
|
addSettingButton('e2ee_config_button', _('Encryption Config'), this.e2eeConfig_);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (section.name === 'joplinCloud') {
|
if (section.name === 'joplinCloud') {
|
||||||
|
const label = _('Email to note');
|
||||||
const description = _('Any email sent to this address will be converted into a note and added to your collection. The note will be saved into the Inbox notebook');
|
const description = _('Any email sent to this address will be converted into a note and added to your collection. The note will be saved into the Inbox notebook');
|
||||||
settingComps.push(
|
addSettingComponent(
|
||||||
<View key="joplinCloud">
|
<View key="joplinCloud">
|
||||||
<View style={this.styles().styleSheet.settingContainerNoBottomBorder}>
|
<View style={this.styles().styleSheet.settingContainerNoBottomBorder}>
|
||||||
<Text style={this.styles().styleSheet.settingText}>{_('Email to note')}</Text>
|
<Text style={this.styles().styleSheet.settingText}>{label}</Text>
|
||||||
<Text style={{ fontWeight: 'bold' }}>{this.props.settings['sync.10.inboxEmail']}</Text>
|
<Text style={{ fontWeight: 'bold' }}>{this.props.settings['sync.10.inboxEmail']}</Text>
|
||||||
</View>
|
</View>
|
||||||
{
|
{
|
||||||
@ -351,20 +449,30 @@ class ConfigScreenComponent extends BaseScreenComponent<ConfigScreenProps, Confi
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
</View>,
|
</View>,
|
||||||
|
[label, description],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (section.name === 'tools') {
|
if (section.name === 'tools') {
|
||||||
settingComps.push(this.renderButton('profiles_buttons', _('Manage profiles'), this.manageProfilesButtonPress_));
|
addSettingButton('profiles_buttons', _('Manage profiles'), this.manageProfilesButtonPress_);
|
||||||
settingComps.push(this.renderButton('status_button', _('Sync Status'), this.syncStatusButtonPress_));
|
addSettingButton('status_button', _('Sync Status'), this.syncStatusButtonPress_);
|
||||||
settingComps.push(this.renderButton('log_button', _('Log'), this.logButtonPress_));
|
addSettingButton('log_button', _('Log'), this.logButtonPress_);
|
||||||
settingComps.push(this.renderButton('fix_search_engine_index', this.state.fixingSearchIndex ? _('Fixing search index...') : _('Fix search index'), this.fixSearchEngineIndexButtonPress_, { disabled: this.state.fixingSearchIndex, description: _('Use this to rebuild the search index if there is a problem with search. It may take a long time depending on the number of notes.') }));
|
addSettingButton('fix_search_engine_index', this.state.fixingSearchIndex ? _('Fixing search index...') : _('Fix search index'), this.fixSearchEngineIndexButtonPress_, { disabled: this.state.fixingSearchIndex, description: _('Use this to rebuild the search index if there is a problem with search. It may take a long time depending on the number of notes.') });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (section.name === 'export') {
|
if (section.name === 'export') {
|
||||||
settingComps.push(<NoteExportButton key='export_as_jex_button' styles={this.styles()} />);
|
addSettingComponent(
|
||||||
settingComps.push(<ExportDebugReportButton key='export_report_button' styles={this.styles()}/>);
|
<NoteExportButton key='export_as_jex_button' styles={this.styles()} />,
|
||||||
settingComps.push(<ExportProfileButton key='export_data' styles={this.styles()}/>);
|
[exportButtonTitle(), exportButtonDescription()],
|
||||||
|
);
|
||||||
|
addSettingComponent(
|
||||||
|
<ExportDebugReportButton key='export_report_button' styles={this.styles()}/>,
|
||||||
|
exportDebugReportTitle(),
|
||||||
|
);
|
||||||
|
addSettingComponent(
|
||||||
|
<ExportProfileButton key='export_data' styles={this.styles()}/>,
|
||||||
|
exportProfileButtonTitle(),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (section.name === 'moreInfo') {
|
if (section.name === 'moreInfo') {
|
||||||
@ -372,7 +480,7 @@ class ConfigScreenComponent extends BaseScreenComponent<ConfigScreenProps, Confi
|
|||||||
// Note: `PermissionsAndroid` doesn't work so we have to ask the user to manually
|
// Note: `PermissionsAndroid` doesn't work so we have to ask the user to manually
|
||||||
// set these permissions. https://stackoverflow.com/questions/49771084/permission-always-returns-never-ask-again
|
// set these permissions. https://stackoverflow.com/questions/49771084/permission-always-returns-never-ask-again
|
||||||
|
|
||||||
settingComps.push(
|
addSettingComponent(
|
||||||
<View key="permission_info" style={styleSheet.settingContainer}>
|
<View key="permission_info" style={styleSheet.settingContainer}>
|
||||||
<View key="permission_info_wrapper">
|
<View key="permission_info_wrapper">
|
||||||
<Text key="perm1a" style={styleSheet.settingText}>
|
<Text key="perm1a" style={styleSheet.settingText}>
|
||||||
@ -389,95 +497,60 @@ class ConfigScreenComponent extends BaseScreenComponent<ConfigScreenProps, Confi
|
|||||||
</Text>
|
</Text>
|
||||||
</View>
|
</View>
|
||||||
</View>,
|
</View>,
|
||||||
|
'',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
settingComps.push(
|
addSettingLink('donate_link', _('Make a donation'), 'https://joplinapp.org/donate/');
|
||||||
<View key="donate_link" style={styleSheet.settingContainer}>
|
addSettingLink('website_link', _('Joplin website'), 'https://joplinapp.org/');
|
||||||
<TouchableOpacity
|
addSettingLink('privacy_link', _('Privacy Policy'), 'https://joplinapp.org/privacy/');
|
||||||
onPress={() => {
|
|
||||||
void Linking.openURL('https://joplinapp.org/donate/');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Text key="label" style={styleSheet.linkText}>
|
|
||||||
{_('Make a donation')}
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>,
|
|
||||||
);
|
|
||||||
|
|
||||||
settingComps.push(
|
addSettingText('version_info_app', `Joplin ${VersionInfo.appVersion}`);
|
||||||
<View key="website_link" style={styleSheet.settingContainer}>
|
addSettingText('version_info_db', _('Database v%s', reg.db().version()));
|
||||||
<TouchableOpacity
|
addSettingText('version_info_fts', _('FTS enabled: %d', this.props.settings['db.ftsEnabled']));
|
||||||
onPress={() => {
|
addSettingText('version_info_hermes', _('Hermes enabled: %d', (global as any).HermesInternal ? 1 : 0));
|
||||||
void Linking.openURL('https://joplinapp.org/');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Text key="label" style={styleSheet.linkText}>
|
|
||||||
{_('Joplin website')}
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>,
|
|
||||||
);
|
|
||||||
|
|
||||||
settingComps.push(
|
|
||||||
<View key="privacy_link" style={styleSheet.settingContainer}>
|
|
||||||
<TouchableOpacity
|
|
||||||
onPress={() => {
|
|
||||||
void Linking.openURL('https://joplinapp.org/privacy/');
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Text key="label" style={styleSheet.linkText}>
|
|
||||||
{_('Privacy Policy')}
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>,
|
|
||||||
);
|
|
||||||
|
|
||||||
settingComps.push(
|
|
||||||
<View key="version_info_app" style={styleSheet.settingContainer}>
|
|
||||||
<Text style={styleSheet.settingText}>{`Joplin ${VersionInfo.appVersion}`}</Text>
|
|
||||||
</View>,
|
|
||||||
);
|
|
||||||
|
|
||||||
settingComps.push(
|
|
||||||
<View key="version_info_db" style={styleSheet.settingContainer}>
|
|
||||||
<Text style={styleSheet.settingText}>{_('Database v%s', reg.db().version())}</Text>
|
|
||||||
</View>,
|
|
||||||
);
|
|
||||||
|
|
||||||
settingComps.push(
|
|
||||||
<View key="version_info_fts" style={styleSheet.settingContainer}>
|
|
||||||
<Text style={styleSheet.settingText}>{_('FTS enabled: %d', this.props.settings['db.ftsEnabled'])}</Text>
|
|
||||||
</View>,
|
|
||||||
);
|
|
||||||
|
|
||||||
settingComps.push(
|
|
||||||
<View key="version_info_hermes" style={styleSheet.settingContainer}>
|
|
||||||
<Text style={styleSheet.settingText}>{_('Hermes enabled: %d', (global as any).HermesInternal ? 1 : 0)}</Text>
|
|
||||||
</View>,
|
|
||||||
);
|
|
||||||
|
|
||||||
const featureFlagKeys = Setting.featureFlagKeys(AppType.Mobile);
|
const featureFlagKeys = Setting.featureFlagKeys(AppType.Mobile);
|
||||||
if (featureFlagKeys.length) {
|
if (featureFlagKeys.length) {
|
||||||
const headerKey = 'featureFlags';
|
const headerKey = 'featureFlags';
|
||||||
settingComps.push(<SectionHeader
|
const featureFlagsTitle = _('Feature flags');
|
||||||
|
addSettingComponent(
|
||||||
|
<SectionHeader
|
||||||
key={headerKey}
|
key={headerKey}
|
||||||
styles={this.styles().styleSheet}
|
styles={this.styles().styleSheet}
|
||||||
title={_('Feature flags')}
|
title={featureFlagsTitle}
|
||||||
onLayout={event => this.onHeaderLayout(headerKey, event)}
|
onLayout={event => this.onHeaderLayout(headerKey, event)}
|
||||||
/>);
|
/>,
|
||||||
|
_('Feature flags'),
|
||||||
|
);
|
||||||
|
|
||||||
settingComps.push(<View key="featureFlagsContainer">{this.renderFeatureFlags(settings, featureFlagKeys)}</View>);
|
addSettingComponent(
|
||||||
|
<View key="featureFlagsContainer">{this.renderFeatureFlags(settings, featureFlagKeys)}</View>,
|
||||||
|
featureFlagsTitle,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!settingComps.length) return null;
|
if (!settingComps.length) return null;
|
||||||
if (!isSelected) return null;
|
if (!isSelected && !this.state.searching) return null;
|
||||||
|
|
||||||
|
const headerComponent = (
|
||||||
|
<TouchableOpacity onPress={() => {
|
||||||
|
this.onJumpToSection_(section.name);
|
||||||
|
}}>
|
||||||
|
<SectionHeader
|
||||||
|
styles={styleSheet}
|
||||||
|
title={headerTitle}
|
||||||
|
/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View key={key} onLayout={(event: any) => this.onSectionLayout(key, event)}>
|
<View key={key} onLayout={(event: any) => this.onSectionLayout(key, event)}>
|
||||||
<View>{settingComps}</View>
|
<View>
|
||||||
|
{this.state.searching ? headerComponent : null}
|
||||||
|
{settingComps}
|
||||||
|
</View>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -563,18 +636,22 @@ class ConfigScreenComponent extends BaseScreenComponent<ConfigScreenProps, Confi
|
|||||||
currentSectionName = 'general';
|
currentSectionName = 'general';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.state.searching) {
|
||||||
|
currentSectionName = null;
|
||||||
|
}
|
||||||
|
|
||||||
const sectionSelector = (
|
const sectionSelector = (
|
||||||
<SectionSelector
|
<SectionSelector
|
||||||
selectedSectionName={currentSectionName}
|
selectedSectionName={currentSectionName}
|
||||||
styles={this.styles()}
|
styles={this.styles()}
|
||||||
settings={settings}
|
settings={settings}
|
||||||
openSection={this.switchSectionPress_}
|
openSection={this.onJumpToSection_}
|
||||||
width={this.state.sidebarWidth}
|
width={this.state.sidebarWidth}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|
||||||
let currentSection: ReactNode;
|
let currentSection: ReactNode;
|
||||||
if (currentSectionName) {
|
if (currentSectionName || this.state.searching) {
|
||||||
const settingComps = shared.settingsToComponents2(
|
const settingComps = shared.settingsToComponents2(
|
||||||
this, AppType.Mobile, settings, currentSectionName,
|
this, AppType.Mobile, settings, currentSectionName,
|
||||||
|
|
||||||
@ -582,11 +659,20 @@ class ConfigScreenComponent extends BaseScreenComponent<ConfigScreenProps, Confi
|
|||||||
// of React in lib/ and app-mobile/
|
// of React in lib/ and app-mobile/
|
||||||
) as ReactNode[];
|
) as ReactNode[];
|
||||||
|
|
||||||
|
const searchInput = <TextInput
|
||||||
|
value={this.state.searchQuery}
|
||||||
|
label={_('Search')}
|
||||||
|
placeholder={_('Search...')}
|
||||||
|
onChangeText={this.onSearchUpdate_}
|
||||||
|
autoFocus={true}
|
||||||
|
/>;
|
||||||
|
|
||||||
currentSection = (
|
currentSection = (
|
||||||
<ScrollView
|
<ScrollView
|
||||||
ref={this.scrollViewRef_}
|
ref={this.scrollViewRef_}
|
||||||
style={{ flexGrow: 1 }}
|
style={{ flexGrow: 1 }}
|
||||||
>
|
>
|
||||||
|
{this.state.searching ? searchInput : null}
|
||||||
{settingComps}
|
{settingComps}
|
||||||
</ScrollView>
|
</ScrollView>
|
||||||
);
|
);
|
||||||
@ -620,10 +706,11 @@ class ConfigScreenComponent extends BaseScreenComponent<ConfigScreenProps, Confi
|
|||||||
<ScreenHeader
|
<ScreenHeader
|
||||||
title={screenHeadingText}
|
title={screenHeadingText}
|
||||||
showSaveButton={true}
|
showSaveButton={true}
|
||||||
showSearchButton={false}
|
showSearchButton={true}
|
||||||
showSideMenuButton={false}
|
showSideMenuButton={false}
|
||||||
saveButtonDisabled={!this.hasUnsavedChanges()}
|
saveButtonDisabled={!this.hasUnsavedChanges()}
|
||||||
onSaveButtonPress={this.saveButton_press}
|
onSaveButtonPress={this.saveButton_press}
|
||||||
|
onSearchButtonPress={this.onSearchButtonPress_}
|
||||||
/>
|
/>
|
||||||
{mainComponent}
|
{mainComponent}
|
||||||
</View>
|
</View>
|
||||||
|
@ -11,6 +11,8 @@ interface Props {
|
|||||||
styles: ConfigScreenStyles;
|
styles: ConfigScreenStyles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const exportDebugReportTitle = () => _('Export Debug Report');
|
||||||
|
|
||||||
const ExportDebugReportButton = (props: Props) => {
|
const ExportDebugReportButton = (props: Props) => {
|
||||||
const [creatingReport, setCreatingReport] = useState(false);
|
const [creatingReport, setCreatingReport] = useState(false);
|
||||||
|
|
||||||
@ -24,7 +26,7 @@ const ExportDebugReportButton = (props: Props) => {
|
|||||||
|
|
||||||
const exportDebugReportButton = (
|
const exportDebugReportButton = (
|
||||||
<SettingsButton
|
<SettingsButton
|
||||||
title={creatingReport ? _('Creating report...') : _('Export Debug Report')}
|
title={creatingReport ? _('Creating report...') : exportDebugReportTitle()}
|
||||||
clickHandler={exportDebugButtonPress}
|
clickHandler={exportDebugButtonPress}
|
||||||
styles={props.styles}
|
styles={props.styles}
|
||||||
disabled={creatingReport}
|
disabled={creatingReport}
|
||||||
|
@ -13,6 +13,8 @@ interface Props {
|
|||||||
styles: ConfigScreenStyles;
|
styles: ConfigScreenStyles;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const exportProfileButtonTitle = () => _('Export profile');
|
||||||
|
|
||||||
const ExportProfileButton = (props: Props) => {
|
const ExportProfileButton = (props: Props) => {
|
||||||
const [profileExportStatus, setProfileExportStatus] = useState<'idle'|'prompt'|'exporting'>('idle');
|
const [profileExportStatus, setProfileExportStatus] = useState<'idle'|'prompt'|'exporting'>('idle');
|
||||||
const [profileExportPath, setProfileExportPath] = useState<string>('');
|
const [profileExportPath, setProfileExportPath] = useState<string>('');
|
||||||
@ -31,7 +33,7 @@ const ExportProfileButton = (props: Props) => {
|
|||||||
const exportProfileButton = (
|
const exportProfileButton = (
|
||||||
<SettingsButton
|
<SettingsButton
|
||||||
styles={props.styles}
|
styles={props.styles}
|
||||||
title={profileExportStatus === 'exporting' ? _('Exporting profile...') : _('Export profile')}
|
title={profileExportStatus === 'exporting' ? _('Exporting profile...') : exportProfileButtonTitle()}
|
||||||
clickHandler={exportProfileButtonPress}
|
clickHandler={exportProfileButtonPress}
|
||||||
description={_('For debugging purpose only: export your profile to an external SD card.')}
|
description={_('For debugging purpose only: export your profile to an external SD card.')}
|
||||||
disabled={profileExportStatus === 'exporting'}
|
disabled={profileExportStatus === 'exporting'}
|
||||||
|
@ -24,6 +24,9 @@ enum ExportStatus {
|
|||||||
Exported,
|
Exported,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const exportButtonTitle = () => _('Export all notes as JEX');
|
||||||
|
export const exportButtonDescription = () => _('Share a copy of all notes in a file format that can be imported by Joplin on a computer.');
|
||||||
|
|
||||||
const NoteExportButton: FunctionComponent<Props> = props => {
|
const NoteExportButton: FunctionComponent<Props> = props => {
|
||||||
const [exportStatus, setExportStatus] = useState<ExportStatus>(ExportStatus.NotStarted);
|
const [exportStatus, setExportStatus] = useState<ExportStatus>(ExportStatus.NotStarted);
|
||||||
const [exportProgress, setExportProgress] = useState<number|undefined>(0);
|
const [exportProgress, setExportProgress] = useState<number|undefined>(0);
|
||||||
@ -80,13 +83,12 @@ const NoteExportButton: FunctionComponent<Props> = props => {
|
|||||||
indeterminate={exportProgress === undefined}
|
indeterminate={exportProgress === undefined}
|
||||||
progress={exportProgress}/>
|
progress={exportProgress}/>
|
||||||
);
|
);
|
||||||
const descriptionText = _('Share a copy of all notes in a file format that can be imported by Joplin on a computer.');
|
|
||||||
|
|
||||||
const startOrCancelExportButton = (
|
const startOrCancelExportButton = (
|
||||||
<SettingsButton
|
<SettingsButton
|
||||||
title={exportStatus === ExportStatus.Exporting ? _('Exporting...') : _('Export all notes as JEX')}
|
title={exportStatus === ExportStatus.Exporting ? _('Exporting...') : exportButtonTitle()}
|
||||||
disabled={exportStatus === ExportStatus.Exporting}
|
disabled={exportStatus === ExportStatus.Exporting}
|
||||||
description={exportStatus === ExportStatus.NotStarted ? descriptionText : null}
|
description={exportStatus === ExportStatus.NotStarted ? exportButtonDescription() : null}
|
||||||
statusComponent={progressComponent}
|
statusComponent={progressComponent}
|
||||||
clickHandler={startExport}
|
clickHandler={startExport}
|
||||||
styles={props.styles}
|
styles={props.styles}
|
||||||
|
Loading…
Reference in New Issue
Block a user