From 2e11d5bd1888940dac2fa246e2ef915323b85a18 Mon Sep 17 00:00:00 2001 From: Henry Heino Date: Wed, 26 Jul 2023 19:56:26 -0700 Subject: [PATCH 01/28] Mobile: Refactor config screen into multiple files --- .eslintignore | 15 +- .gitignore | 15 +- .../gui/ConfigScreen/ConfigScreen.tsx | 8 +- .../{base-screen.js => base-screen.ts} | 13 +- .../screens/ConfigScreen/ConfigScreen.tsx | 468 ++++++------------ .../ExportDebugReportButton.tsx | 42 ++ .../NoteExportSection/ExportProfileButton.tsx | 79 +++ .../NoteExportSection/NoteExportButton.tsx | 12 +- .../{ => utils}/exportAllFolders.ts | 0 .../utils/exportDebugReport.ts | 32 ++ .../NoteExportSection/utils/exportProfile.ts | 35 ++ .../screens/ConfigScreen/SectionHeader.tsx | 25 + .../screens/ConfigScreen/SettingComponent.tsx | 144 ++++++ .../screens/ConfigScreen/SettingItem.tsx | 0 ...figScreenButton.tsx => SettingsButton.tsx} | 12 +- .../screens/ConfigScreen/SettingsToggle.tsx | 45 ++ .../ConfigScreen/configScreenStyles.ts | 25 +- .../components/screens/ConfigScreen/types.ts | 10 + .../{config-shared.js => config-shared.ts} | 93 ++-- packages/lib/models/Setting.ts | 6 +- packages/lib/reducer.ts | 2 +- packages/lib/registry.ts | 2 + 22 files changed, 689 insertions(+), 394 deletions(-) rename packages/app-mobile/components/{base-screen.js => base-screen.ts} (50%) create mode 100644 packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/ExportDebugReportButton.tsx create mode 100644 packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/ExportProfileButton.tsx rename packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/{ => utils}/exportAllFolders.ts (100%) create mode 100644 packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/utils/exportDebugReport.ts create mode 100644 packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/utils/exportProfile.ts create mode 100644 packages/app-mobile/components/screens/ConfigScreen/SectionHeader.tsx create mode 100644 packages/app-mobile/components/screens/ConfigScreen/SettingComponent.tsx create mode 100644 packages/app-mobile/components/screens/ConfigScreen/SettingItem.tsx rename packages/app-mobile/components/screens/ConfigScreen/{ConfigScreenButton.tsx => SettingsButton.tsx} (74%) create mode 100644 packages/app-mobile/components/screens/ConfigScreen/SettingsToggle.tsx create mode 100644 packages/app-mobile/components/screens/ConfigScreen/types.ts rename packages/lib/components/shared/{config-shared.js => config-shared.ts} (65%) diff --git a/.eslintignore b/.eslintignore index d6c33847f22..0af2881bb6f 100644 --- a/.eslintignore +++ b/.eslintignore @@ -419,17 +419,27 @@ packages/app-mobile/components/ScreenHeader.js packages/app-mobile/components/SelectDateTimeDialog.js packages/app-mobile/components/TextInput.js packages/app-mobile/components/app-nav.js +packages/app-mobile/components/base-screen.js packages/app-mobile/components/biometrics/BiometricPopup.js packages/app-mobile/components/biometrics/biometricAuthenticate.js packages/app-mobile/components/biometrics/sensorInfo.js packages/app-mobile/components/getResponsiveValue.test.js packages/app-mobile/components/getResponsiveValue.js packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.js -packages/app-mobile/components/screens/ConfigScreen/ConfigScreenButton.js +packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/ExportDebugReportButton.js +packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/ExportProfileButton.js packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/NoteExportButton.test.js packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/NoteExportButton.js -packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/exportAllFolders.js +packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/utils/exportAllFolders.js +packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/utils/exportDebugReport.js +packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/utils/exportProfile.js +packages/app-mobile/components/screens/ConfigScreen/SectionHeader.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 +packages/app-mobile/components/screens/ConfigScreen/SettingsToggle.js packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.js +packages/app-mobile/components/screens/ConfigScreen/types.js packages/app-mobile/components/screens/Note.js packages/app-mobile/components/screens/Notes.js packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js @@ -514,6 +524,7 @@ packages/lib/commands/index.js packages/lib/commands/openMasterPasswordDialog.js packages/lib/commands/synchronize.js packages/lib/components/EncryptionConfigScreen/utils.js +packages/lib/components/shared/config-shared.js packages/lib/components/shared/note-screen-shared.js packages/lib/components/shared/reduxSharedMiddleware.js packages/lib/database-driver-better-sqlite.js diff --git a/.gitignore b/.gitignore index 61ffca10366..81c0d9ca38d 100644 --- a/.gitignore +++ b/.gitignore @@ -404,17 +404,27 @@ packages/app-mobile/components/ScreenHeader.js packages/app-mobile/components/SelectDateTimeDialog.js packages/app-mobile/components/TextInput.js packages/app-mobile/components/app-nav.js +packages/app-mobile/components/base-screen.js packages/app-mobile/components/biometrics/BiometricPopup.js packages/app-mobile/components/biometrics/biometricAuthenticate.js packages/app-mobile/components/biometrics/sensorInfo.js packages/app-mobile/components/getResponsiveValue.test.js packages/app-mobile/components/getResponsiveValue.js packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.js -packages/app-mobile/components/screens/ConfigScreen/ConfigScreenButton.js +packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/ExportDebugReportButton.js +packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/ExportProfileButton.js packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/NoteExportButton.test.js packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/NoteExportButton.js -packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/exportAllFolders.js +packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/utils/exportAllFolders.js +packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/utils/exportDebugReport.js +packages/app-mobile/components/screens/ConfigScreen/NoteExportSection/utils/exportProfile.js +packages/app-mobile/components/screens/ConfigScreen/SectionHeader.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 +packages/app-mobile/components/screens/ConfigScreen/SettingsToggle.js packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.js +packages/app-mobile/components/screens/ConfigScreen/types.js packages/app-mobile/components/screens/Note.js packages/app-mobile/components/screens/Notes.js packages/app-mobile/components/screens/UpgradeSyncTargetScreen.js @@ -499,6 +509,7 @@ packages/lib/commands/index.js packages/lib/commands/openMasterPasswordDialog.js packages/lib/commands/synchronize.js packages/lib/components/EncryptionConfigScreen/utils.js +packages/lib/components/shared/config-shared.js packages/lib/components/shared/note-screen-shared.js packages/lib/components/shared/reduxSharedMiddleware.js packages/lib/database-driver-better-sqlite.js diff --git a/packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx b/packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx index 8b8edea2123..5e8b97d9da3 100644 --- a/packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx +++ b/packages/app-desktop/gui/ConfigScreen/ConfigScreen.tsx @@ -12,7 +12,7 @@ const { connect } = require('react-redux'); const { themeStyle } = require('@joplin/lib/theme'); const pathUtils = require('@joplin/lib/path-utils'); import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry'; -const shared = require('@joplin/lib/components/shared/config-shared.js'); +import * as shared from '@joplin/lib/components/shared/config-shared.js'; import ClipperConfigScreen from '../ClipperConfigScreen'; import restart from '../../services/restart'; import PluginService from '@joplin/lib/services/plugins/PluginService'; @@ -95,7 +95,7 @@ class ConfigScreenComponent extends React.Component { } public sectionByName(name: string) { - const sections = shared.settingsSections({ device: 'desktop', settings: this.state.settings }); + const sections = shared.settingsSections({ device: AppType.Desktop, settings: this.state.settings }); for (const section of sections) { if (section.name === name) return section; } @@ -685,7 +685,7 @@ class ConfigScreenComponent extends React.Component { const hasChanges = this.hasChanges(); - const settingComps = shared.settingsToComponents2(this, 'desktop', settings, this.state.selectedSectionName); + const settingComps = shared.settingsToComponents2(this, AppType.Desktop, settings, this.state.selectedSectionName); // screenComp is a custom config screen, such as the encryption config screen or keymap config screen. // These screens handle their own loading/saving of settings and have bespoke rendering. @@ -694,7 +694,7 @@ class ConfigScreenComponent extends React.Component { if (screenComp) containerStyle.display = 'none'; - const sections = shared.settingsSections({ device: 'desktop', settings }); + const sections = shared.settingsSections({ device: AppType.Desktop, settings }); const needRestartComp: any = this.state.needRestart ? (
diff --git a/packages/app-mobile/components/base-screen.js b/packages/app-mobile/components/base-screen.ts similarity index 50% rename from packages/app-mobile/components/base-screen.js rename to packages/app-mobile/components/base-screen.ts index b94c6f70145..c2b2e6e1e1a 100644 --- a/packages/app-mobile/components/base-screen.js +++ b/packages/app-mobile/components/base-screen.ts @@ -1,12 +1,12 @@ -const React = require('react'); -const { StyleSheet } = require('react-native'); +import * as React from 'react'; +import { StyleSheet } from 'react-native'; const { themeStyle } = require('./global-style.js'); -const rootStyles_ = {}; +const rootStyles_: Record = {}; -class BaseScreenComponent extends React.Component { +class BaseScreenComponent extends React.Component { - rootStyle(themeId) { + protected rootStyle(themeId: number) { const theme = themeStyle(themeId); if (rootStyles_[themeId]) return rootStyles_[themeId]; rootStyles_[themeId] = StyleSheet.create({ @@ -19,4 +19,5 @@ class BaseScreenComponent extends React.Component { } } -module.exports = { BaseScreenComponent }; +export { BaseScreenComponent }; +export default BaseScreenComponent; diff --git a/packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx b/packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx index aabff19f353..701cf02fe84 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx +++ b/packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx @@ -1,14 +1,10 @@ /* eslint-disable @typescript-eslint/explicit-member-accessibility */ -import Slider from '@react-native-community/slider'; -const React = require('react'); -import { Platform, Linking, View, Switch, ScrollView, Text, Button, TouchableOpacity, TextInput, Alert, PermissionsAndroid, TouchableNativeFeedback } from 'react-native'; +import * as React from 'react'; +import { Platform, Linking, View, Switch, ScrollView, Text, TouchableOpacity, Alert, PermissionsAndroid } from 'react-native'; import Setting, { AppType } from '@joplin/lib/models/Setting'; import NavService from '@joplin/lib/services/NavService'; -import ReportService from '@joplin/lib/services/ReportService'; import SearchEngine from '@joplin/lib/services/searchengine/SearchEngine'; import checkPermissions from '../../../utils/checkPermissions'; -import time from '@joplin/lib/time'; -import shim from '@joplin/lib/shim'; import setIgnoreTlsErrors from '../../../utils/TlsUtils'; import { reg } from '@joplin/lib/registry'; import { State } from '@joplin/lib/reducer'; @@ -17,197 +13,126 @@ const VersionInfo = require('react-native-version-info').default; const { connect } = require('react-redux'); import ScreenHeader from '../../ScreenHeader'; const { _ } = require('@joplin/lib/locale'); -const { BaseScreenComponent } = require('../../base-screen.js'); -const { Dropdown } = require('../../Dropdown'); +import BaseScreenComponent from '../../base-screen'; const { themeStyle } = require('../../global-style.js'); -const shared = require('@joplin/lib/components/shared/config-shared.js'); +import * as shared from '@joplin/lib/components/shared/config-shared.js'; import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry'; -import { openDocumentTree } from '@joplin/react-native-saf-x'; import biometricAuthenticate from '../../biometrics/biometricAuthenticate'; -import configScreenStyles from './configScreenStyles'; +import configScreenStyles, { ConfigScreenStyles } from './configScreenStyles'; import NoteExportButton from './NoteExportSection/NoteExportButton'; -import ConfigScreenButton from './ConfigScreenButton'; +import SettingsButton from './SettingsButton'; import Clipboard from '@react-native-community/clipboard'; +import { ReactNode } from 'react'; +import { Dispatch } from 'redux'; +import SectionHeader from './SectionHeader'; +import ExportProfileButton from './NoteExportSection/ExportProfileButton'; +import SettingComponent from './SettingComponent'; +import ExportDebugReportButton from './NoteExportSection/ExportDebugReportButton'; + +interface ConfigScreenState { + settings: any; + changedSettingKeys: string[]; + + fixingSearchIndex: boolean; + checkSyncConfigResult: { ok: boolean; errorMessage: string }|'checking'|null; + showAdvancedSettings: boolean; + + creatingReport?: boolean; + profileExportPath: string; + profileExportStatus: string; + fileSystemSyncPath: string; +} + +interface ConfigScreenProps { + settings: any; + themeId: number; + navigation: any; + + dispatch: Dispatch; +} -class ConfigScreenComponent extends BaseScreenComponent { +class ConfigScreenComponent extends BaseScreenComponent { public static navigationOptions(): any { return { header: null }; } private componentsY_: Record = {}; + private styles_: Record = {}; + private scrollViewRef_: React.RefObject; - public constructor() { - super(); - this.styles_ = {}; + public constructor(props: ConfigScreenProps) { + super(props); this.state = { creatingReport: false, profileExportStatus: 'idle', profileExportPath: '', fileSystemSyncPath: Setting.value('sync.2.path'), - }; + } as any; - this.scrollViewRef_ = React.createRef(); + this.scrollViewRef_ = React.createRef(); shared.init(this, reg); + } - this.selectDirectoryButtonPress = async () => { - try { - const doc = await openDocumentTree(true); - if (doc?.uri) { - this.setState({ fileSystemSyncPath: doc.uri }); - shared.updateSettingValue(this, 'sync.2.path', doc.uri); - } else { - throw new Error('User cancelled operation'); - } - } catch (e) { - reg.logger().info('Didn\'t pick sync dir: ', e); - } - }; - - this.checkSyncConfig_ = async () => { - // to ignore TLS erros we need to chage the global state of the app, if the check fails we need to restore the original state - // this call sets the new value and returns the previous one which we can use later to revert the change - const prevIgnoreTlsErrors = await setIgnoreTlsErrors(this.state.settings['net.ignoreTlsErrors']); - const result = await shared.checkSyncConfig(this, this.state.settings); - if (!result || !result.ok) { - await setIgnoreTlsErrors(prevIgnoreTlsErrors); - } - }; + private checkSyncConfig_ = async () => { + // to ignore TLS erros we need to chage the global state of the app, if the check fails we need to restore the original state + // this call sets the new value and returns the previous one which we can use later to revert the change + const prevIgnoreTlsErrors = await setIgnoreTlsErrors(this.state.settings['net.ignoreTlsErrors']); + const result = await shared.checkSyncConfig(this, this.state.settings); + if (!result || !result.ok) { + await setIgnoreTlsErrors(prevIgnoreTlsErrors); + } + }; - this.e2eeConfig_ = () => { - void NavService.go('EncryptionConfig'); - }; + private e2eeConfig_ = () => { + void NavService.go('EncryptionConfig'); + }; - this.saveButton_press = async () => { - if (this.state.changedSettingKeys.includes('sync.target') && this.state.settings['sync.target'] === SyncTargetRegistry.nameToId('filesystem')) { - if (Platform.OS === 'android') { - if (Platform.Version < 29) { - if (!(await this.checkFilesystemPermission())) { - Alert.alert(_('Warning'), _('In order to use file system synchronisation your permission to write to external storage is required.')); - } + private saveButton_press = async () => { + if (this.state.changedSettingKeys.includes('sync.target') && this.state.settings['sync.target'] === SyncTargetRegistry.nameToId('filesystem')) { + if (Platform.OS === 'android') { + if (Platform.Version < 29) { + if (!(await this.checkFilesystemPermission())) { + Alert.alert(_('Warning'), _('In order to use file system synchronisation your permission to write to external storage is required.')); } } - - // Save settings anyway, even if permission has not been granted - } - - // changedSettingKeys is cleared in shared.saveSettings so reading it now - const setIgnoreTlsErrors = this.state.changedSettingKeys.includes('net.ignoreTlsErrors'); - - await shared.saveSettings(this); - - if (setIgnoreTlsErrors) { - await setIgnoreTlsErrors(Setting.value('net.ignoreTlsErrors')); - } - }; - - this.saveButton_press = this.saveButton_press.bind(this); - - this.syncStatusButtonPress_ = () => { - void NavService.go('Status'); - }; - - this.manageProfilesButtonPress_ = () => { - this.props.dispatch({ - type: 'NAV_GO', - routeName: 'ProfileSwitcher', - }); - }; - - this.exportDebugButtonPress_ = async () => { - this.setState({ creatingReport: true }); - const service = new ReportService(); - - const logItems = await reg.logger().lastEntries(null); - const logItemRows = [['Date', 'Level', 'Message']]; - for (let i = 0; i < logItems.length; i++) { - const item = logItems[i]; - logItemRows.push([time.formatMsToLocal(item.timestamp, 'MM-DDTHH:mm:ss'), item.level, item.message]); - } - const logItemCsv = service.csvCreate(logItemRows); - - const itemListCsv = await service.basicItemList({ format: 'csv' }); - - const externalDir = await shim.fsDriver().getExternalDirectoryPath(); - - if (!externalDir) { - this.setState({ creatingReport: false }); - return; - } - - const filePath = `${externalDir}/syncReport-${new Date().getTime()}.txt`; - - const finalText = [logItemCsv, itemListCsv].join('\n================================================================================\n'); - await shim.fsDriver().writeFile(filePath, finalText, 'utf8'); - alert(`Debug report exported to ${filePath}`); - this.setState({ creatingReport: false }); - }; - - this.fixSearchEngineIndexButtonPress_ = async () => { - this.setState({ fixingSearchIndex: true }); - await SearchEngine.instance().rebuildIndex(); - this.setState({ fixingSearchIndex: false }); - }; - - this.exportProfileButtonPress_ = async () => { - const externalDir = await shim.fsDriver().getExternalDirectoryPath(); - if (!externalDir) { - return; } - const p = this.state.profileExportPath ? this.state.profileExportPath : `${externalDir}/JoplinProfileExport`; - this.setState({ - profileExportStatus: 'prompt', - profileExportPath: p, - }); - }; + // Save settings anyway, even if permission has not been granted + } - this.exportProfileButtonPress2_ = async () => { - this.setState({ profileExportStatus: 'exporting' }); + // changedSettingKeys is cleared in shared.saveSettings so reading it now + const shouldSetIgnoreTlsErrors = this.state.changedSettingKeys.includes('net.ignoreTlsErrors'); - const dbPath = '/data/data/net.cozic.joplin/databases'; - const exportPath = this.state.profileExportPath; - const resourcePath = `${exportPath}/resources`; - try { - const response = await checkPermissions(PermissionsAndroid.PERMISSIONS.WRITE_EXTERNAL_STORAGE); - if (response !== PermissionsAndroid.RESULTS.GRANTED) { - throw new Error('Permission denied'); - } + await shared.saveSettings(this); - const copyFiles = async (source: string, dest: string) => { - await shim.fsDriver().mkdir(dest); + if (shouldSetIgnoreTlsErrors) { + await setIgnoreTlsErrors(Setting.value('net.ignoreTlsErrors')); + } + }; - const files = await shim.fsDriver().readDirStats(source); + private syncStatusButtonPress_ = () => { + void NavService.go('Status'); + }; - for (const file of files) { - const source_ = `${source}/${file.path}`; - const dest_ = `${dest}/${file.path}`; - if (!file.isDirectory()) { - reg.logger().info(`Copying profile: ${source_} => ${dest_}`); - await shim.fsDriver().copy(source_, dest_); - } else { - await copyFiles(source_, dest_); - } - } - }; - await copyFiles(dbPath, exportPath); - await copyFiles(Setting.value('resourceDir'), resourcePath); + private manageProfilesButtonPress_ = () => { + this.props.dispatch({ + type: 'NAV_GO', + routeName: 'ProfileSwitcher', + }); + }; - alert('Profile has been exported!'); - } catch (error) { - alert(`Could not export files: ${error.message}`); - } finally { - this.setState({ profileExportStatus: 'idle' }); - } - }; + private fixSearchEngineIndexButtonPress_ = async () => { + this.setState({ fixingSearchIndex: true }); + await SearchEngine.instance().rebuildIndex(); + this.setState({ fixingSearchIndex: false }); + }; - this.logButtonPress_ = () => { - void NavService.go('Log'); - }; + private logButtonPress_ = () => { + void NavService.go('Log'); + }; - this.handleSetting = this.handleSetting.bind(this); - } public async checkFilesystemPermission() { if (Platform.OS !== 'android') { @@ -225,7 +150,7 @@ class ConfigScreenComponent extends BaseScreenComponent { this.setState({ settings: this.props.settings }); } - public styles() { + public styles(): ConfigScreenStyles { const themeId = this.props.themeId; if (this.styles_[themeId]) return this.styles_[themeId]; @@ -301,17 +226,19 @@ class ConfigScreenComponent extends BaseScreenComponent { } public renderHeader(key: string, title: string) { - const theme = themeStyle(this.props.themeId); return ( - this.onHeaderLayout(key, event)}> - {title} - + this.onHeaderLayout(key, event)} + /> ); } private renderButton(key: string, title: string, clickHandler: ()=> void, options: any = null) { return ( - - {messages[0]} + {messages[0]} {messages.length >= 1 ? ( - {messages[1]} + {messages[1]} ) : null} @@ -360,8 +287,8 @@ class ConfigScreenComponent extends BaseScreenComponent { 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( - - {_('Email to note')} + + {_('Email to note')} {this.props.settings['sync.10.inboxEmail']} { @@ -392,22 +319,18 @@ class ConfigScreenComponent extends BaseScreenComponent { return ( - - + + {label} - void updateSettingValue(key, value)} /> + void updateSettingValue(key, value)} /> {descriptionComp} ); } - private containerStyle(hasDescription: boolean): any { - return !hasDescription ? this.styles().settingContainer : this.styles().settingContainerNoBottomBorder; - } - - private async handleSetting(key: string, value: any): Promise { + private handleSetting = async (key: string, value: any): Promise => { // When the user tries to enable biometrics unlock, we ask for the // fingerprint or Face ID, and if it's correct we save immediately. If // it's not, we don't turn on the setting. @@ -428,123 +351,24 @@ class ConfigScreenComponent extends BaseScreenComponent { } return false; - } + }; public settingToComponent(key: string, value: any) { - const themeId = this.props.themeId; - const theme = themeStyle(themeId); - const output: any = null; - const updateSettingValue = async (key: string, value: any) => { const handled = await this.handleSetting(key, value); if (!handled) shared.updateSettingValue(this, key, value); }; - const md = Setting.settingMetadata(key); - const settingDescription = md.description ? md.description() : ''; - - const descriptionComp = !settingDescription ? null : {settingDescription}; - const containerStyle = this.containerStyle(!!settingDescription); - - if (md.isEnum) { - value = value.toString(); - - const items = Setting.enumOptionsToValueLabels(md.options(), md.optionsOrder ? md.optionsOrder() : []); - - return ( - - - - {md.label()} - - { - void updateSettingValue(key, itemValue); - }} - /> - - {descriptionComp} - - ); - } else if (md.type === Setting.TYPE_BOOL) { - return this.renderToggle(key, md.label(), value, updateSettingValue, descriptionComp); - // return ( - // - // - // - // {md.label()} - // - // updateSettingValue(key, value)} /> - // - // {descriptionComp} - // - // ); - } else if (md.type === Setting.TYPE_INT) { - const unitLabel = md.unitLabel ? md.unitLabel(value) : value; - const minimum = 'minimum' in md ? md.minimum : 0; - const maximum = 'maximum' in md ? md.maximum : 10; - - // Note: Do NOT add the minimumTrackTintColor and maximumTrackTintColor props - // on the Slider as they are buggy and can crash the app on certain devices. - // https://github.com/laurent22/joplin/issues/2733 - // https://github.com/react-native-community/react-native-slider/issues/161 - return ( - - - {md.label()} - - - {unitLabel} - void updateSettingValue(key, value)} /> - - - ); - } else if (md.type === Setting.TYPE_STRING) { - if (md.key === 'sync.2.path' && shim.fsDriver().isUsingAndroidSAF()) { - return ( - - - - {md.label()} - - - {this.state.fileSystemSyncPath} - - - - ); - } - return ( - - - - {md.label()} - - void updateSettingValue(key, value)} secureTextEntry={!!md.secure} /> - - {descriptionComp} - - ); - } else { - // throw new Error('Unsupported setting type: ' + md.type); - } - - return output; + return ( + + ); } private renderFeatureFlags(settings: any, featureFlagKeys: string[]): any[] { @@ -562,9 +386,9 @@ class ConfigScreenComponent extends BaseScreenComponent { public render() { const settings = this.state.settings; - const theme = themeStyle(this.props.themeId); + const styleSheet = this.styles().styleSheet; - const settingComps = shared.settingsToComponents2(this, 'mobile', settings); + const settingComps = shared.settingsToComponents2(this, AppType.Mobile, settings); settingComps.push(this.renderHeader('tools', _('Tools'))); @@ -574,24 +398,9 @@ class ConfigScreenComponent extends BaseScreenComponent { 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.') })); settingComps.push(this.renderHeader('export', _('Export'))); - settingComps.push(); - - if (shim.mobilePlatform() === 'android') { - settingComps.push(this.renderButton('export_report_button', this.state.creatingReport ? _('Creating report...') : _('Export Debug Report'), this.exportDebugButtonPress_, { disabled: this.state.creatingReport })); - settingComps.push(this.renderButton('export_data', this.state.profileExportStatus === 'exporting' ? _('Exporting profile...') : _('Export profile'), this.exportProfileButtonPress_, { disabled: this.state.profileExportStatus === 'exporting', description: _('For debugging purpose only: export your profile to an external SD card.') })); - - if (this.state.profileExportStatus === 'prompt') { - const profileExportPrompt = ( - - Path: - this.setState({ profileExportPath: event.nativeEvent.text })} value={this.state.profileExportPath} placeholder="/path/to/sdcard" keyboardAppearance={theme.keyboardAppearance} /> - + + ); + } else { + settingComps = [ + sectionSelector, + ]; + } return ( - - {settingComps as ReactNode} + + {settingComps} ); } diff --git a/packages/app-mobile/components/screens/ConfigScreen/SectionSelector.tsx b/packages/app-mobile/components/screens/ConfigScreen/SectionSelector.tsx new file mode 100644 index 00000000000..0fe7dbd84bb --- /dev/null +++ b/packages/app-mobile/components/screens/ConfigScreen/SectionSelector.tsx @@ -0,0 +1,44 @@ +import * as React from 'react'; + +import Setting, { AppType } from '@joplin/lib/models/Setting'; +import { FunctionComponent, ReactNode, useMemo } from 'react'; +import { ConfigScreenStyles } from './configScreenStyles'; +import { Button } from 'react-native-paper'; +import { ScrollView, Text } from 'react-native'; +import { settingsSections } from '@joplin/lib/components/shared/config-shared'; + +interface Props { + styles: ConfigScreenStyles; + + settings: any; + openSection: (sectionName: string)=> void; +} + +const SectionSelector: FunctionComponent = props => { + const sections = useMemo(() => { + return settingsSections({ device: AppType.Mobile, settings: props.settings }); + }, [props.settings]); + + const sectionButtons: ReactNode[] = []; + + for (const section of sections) { + sectionButtons.push( + + ); + } + + return ( + + {sectionButtons} + + ); +}; + +export default SectionSelector; diff --git a/packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.ts b/packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.ts index 24fc650ac2f..db660e3d3cc 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.ts +++ b/packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.ts @@ -19,6 +19,9 @@ export interface ConfigScreenStyleSheet { permissionText: TextStyle; textInput: TextStyle; + titlebarText: TextStyle; + titlebarHeaderPart: ViewStyle; + switchSettingText: TextStyle; switchSettingContainer: ViewStyle; switchSettingControl: TextStyle; @@ -92,6 +95,16 @@ const configScreenStyles = (themeId: number): ConfigScreenStyles => { backgroundColor: theme.warningBackgroundColor, fontSize: theme.fontSizeSmaller, }, + titlebarText: theme.titlebarText, + + titlebarHeaderPart: { + ...theme.titlebarText, + flex: 0, + + // TODO(personalizedrefrigerator): Why is division by two needed for consistency? + paddingTop: (theme.titlebarText.paddingTop ?? 0) / 2, + paddingBottom: (theme.titlebarText.paddingBottom ?? 0) / 2, + }, sliderUnits: { color: theme.color, diff --git a/packages/lib/components/shared/config-shared.ts b/packages/lib/components/shared/config-shared.ts index 2efc425afb4..231931e95c3 100644 --- a/packages/lib/components/shared/config-shared.ts +++ b/packages/lib/components/shared/config-shared.ts @@ -214,7 +214,7 @@ export const settingsSections = createSelector( }); } else { output.push(...([ - 'tools', 'export', 'featureFlags', 'moreInfo', + 'tools', 'export', 'moreInfo', ].map(name => { return { name, diff --git a/packages/lib/models/Setting.ts b/packages/lib/models/Setting.ts index cc7caceb176..2fee5bbe59b 100644 --- a/packages/lib/models/Setting.ts +++ b/packages/lib/models/Setting.ts @@ -2539,7 +2539,6 @@ class Setting extends BaseModel { if (name === 'joplinCloud') return _('Joplin Cloud'); if (name === 'tools') return _('Tools'); if (name === 'export') return _('Export'); - if (name === 'featureFlags') return _('Feature flags'); if (name === 'moreInfo') return _('More information'); if (this.customSections_[name] && this.customSections_[name].label) return this.customSections_[name].label; From a2f074b516384cadc0eacfc321d4ca3fc1500227 Mon Sep 17 00:00:00 2001 From: Henry Heino Date: Thu, 27 Jul 2023 05:04:27 -0700 Subject: [PATCH 06/28] Show section headings to the left of settings in tablet mode --- .../screens/ConfigScreen/ConfigScreen.tsx | 44 +++++++++++++++---- .../screens/ConfigScreen/SectionSelector.tsx | 23 +++++++--- .../ConfigScreen/configScreenStyles.ts | 2 + 3 files changed, 55 insertions(+), 14 deletions(-) diff --git a/packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx b/packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx index 206f5f119f2..2e0d99db2e2 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx +++ b/packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx @@ -1,6 +1,6 @@ /* eslint-disable @typescript-eslint/explicit-member-accessibility */ import * as React from 'react'; -import { Platform, Linking, View, Switch, ScrollView, Text, TouchableOpacity, Alert, PermissionsAndroid } from 'react-native'; +import { Platform, Linking, View, Switch, ScrollView, Text, TouchableOpacity, Alert, PermissionsAndroid, Dimensions } from 'react-native'; import Setting, { AppType } from '@joplin/lib/models/Setting'; import NavService from '@joplin/lib/services/NavService'; import SearchEngine from '@joplin/lib/services/searchengine/SearchEngine'; @@ -411,10 +411,12 @@ class ConfigScreenComponent extends BaseScreenComponent this.onHeaderLayout(headerKey, event)} />); settingComps.push({this.renderFeatureFlags(settings, featureFlagKeys)}); @@ -426,12 +428,6 @@ class ConfigScreenComponent extends BaseScreenComponent this.onSectionLayout(key, event)}> - this.onHeaderLayout(key, event)} - /> {settingComps} ); @@ -510,11 +506,16 @@ class ConfigScreenComponent extends BaseScreenComponent ); @@ -532,6 +533,7 @@ class ConfigScreenComponent extends BaseScreenComponent ')} ); + let settingComps: ReactNode[]; if (this.state.selectedSectionName) { settingComps = shared.settingsToComponents2( @@ -557,6 +559,30 @@ class ConfigScreenComponent extends BaseScreenComponent + {settingComps} + + ); + + let mainComponent; + if (windowWidth > sectionSelectorDesiredWidth * 2 && this.state.selectedSectionName) { + mainComponent = ( + + {sectionSelector} + + {currentSection} + + ); + } else { + mainComponent = currentSection; + } + return ( - {settingComps} + {mainComponent} ); } diff --git a/packages/app-mobile/components/screens/ConfigScreen/SectionSelector.tsx b/packages/app-mobile/components/screens/ConfigScreen/SectionSelector.tsx index 0fe7dbd84bb..a93da2ce8a5 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/SectionSelector.tsx +++ b/packages/app-mobile/components/screens/ConfigScreen/SectionSelector.tsx @@ -4,13 +4,16 @@ import Setting, { AppType } from '@joplin/lib/models/Setting'; import { FunctionComponent, ReactNode, useMemo } from 'react'; import { ConfigScreenStyles } from './configScreenStyles'; import { Button } from 'react-native-paper'; -import { ScrollView, Text } from 'react-native'; +import { ScrollView, Text, View } from 'react-native'; import { settingsSections } from '@joplin/lib/components/shared/config-shared'; interface Props { styles: ConfigScreenStyles; + minWidth: number|undefined; + settings: any; + selectedSectionName: string|null; openSection: (sectionName: string)=> void; } @@ -22,22 +25,32 @@ const SectionSelector: FunctionComponent = props => { const sectionButtons: ReactNode[] = []; for (const section of sections) { + const selected = props.selectedSectionName === section.name; + + // TODO(personalizedrefrigerator): Accessibility: Mark which button is selected sectionButtons.push( ); } + // Add an additional spacer at the end to ensure that the last item is visible + sectionButtons.push(); + return ( - - {sectionButtons} - + + + {sectionButtons} + + ); }; diff --git a/packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.ts b/packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.ts index db660e3d3cc..30ec5a44816 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.ts +++ b/packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.ts @@ -32,6 +32,7 @@ export interface ConfigScreenStyleSheet { export interface ConfigScreenStyles { styleSheet: ConfigScreenStyleSheet; + selectedSectionButtonColor: string; keyboardAppearance: 'default'|'light'|'dark'; getContainerStyle(hasDescription: boolean): ViewStyle; } @@ -159,6 +160,7 @@ const configScreenStyles = (themeId: number): ConfigScreenStyles => { return { styleSheet, + selectedSectionButtonColor: theme.backgroundColorActive3, keyboardAppearance: theme.keyboardAppearance, getContainerStyle: (hasDescription) => { return !hasDescription ? styleSheet.settingContainer : styleSheet.settingContainerNoBottomBorder; From 830a974556633d69114ac00ac43b2e9c738b62ac Mon Sep 17 00:00:00 2001 From: Henry Heino Date: Thu, 27 Jul 2023 08:20:12 -0700 Subject: [PATCH 07/28] Restyle section headings --- .../components/screens/ConfigScreen/ConfigScreen.tsx | 1 + .../components/screens/ConfigScreen/SectionSelector.tsx | 8 +++++--- .../components/screens/ConfigScreen/configScreenStyles.ts | 6 ++++++ 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx b/packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx index 2e0d99db2e2..50d9dc37c56 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx +++ b/packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx @@ -572,6 +572,7 @@ class ConfigScreenComponent extends BaseScreenComponent sectionSelectorDesiredWidth * 2 && this.state.selectedSectionName) { mainComponent = ( {sectionSelector} diff --git a/packages/app-mobile/components/screens/ConfigScreen/SectionSelector.tsx b/packages/app-mobile/components/screens/ConfigScreen/SectionSelector.tsx index a93da2ce8a5..6a99e393add 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/SectionSelector.tsx +++ b/packages/app-mobile/components/screens/ConfigScreen/SectionSelector.tsx @@ -32,12 +32,14 @@ const SectionSelector: FunctionComponent = props => { ); } diff --git a/packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.ts b/packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.ts index 30ec5a44816..e11e1a43736 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.ts +++ b/packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.ts @@ -7,6 +7,7 @@ export interface ConfigScreenStyleSheet { settingContainer: ViewStyle; settingContainerNoBottomBorder: ViewStyle; headerWrapperStyle: ViewStyle; + selectedHeaderWrapperStyle: ViewStyle; headerTextStyle: TextStyle; settingText: TextStyle; @@ -147,6 +148,11 @@ const configScreenStyles = (themeId: number): ConfigScreenStyles => { ...settingContainerStyle, ...theme.headerWrapperStyle, }, + selectedHeaderWrapperStyle: { + ...settingContainerStyle, + ...theme.headerWrapperStyle, + backgroundColor: theme.backgroundColorActive3, + }, switchSettingControl: { ...settingControlStyle, From 954db22b9b1db800c74a4ee21262d0c0eeb9f650 Mon Sep 17 00:00:00 2001 From: Henry Heino Date: Fri, 28 Jul 2023 10:21:21 -0700 Subject: [PATCH 08/28] Fix sidebar scroll not scrolling to currently selected item --- .../screens/ConfigScreen/ConfigScreen.tsx | 27 ++++++-------- .../screens/ConfigScreen/SectionSelector.tsx | 37 +++++++++++++++++-- packages/lib/models/Setting.ts | 3 +- 3 files changed, 47 insertions(+), 20 deletions(-) diff --git a/packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx b/packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx index 50d9dc37c56..cd9f0dabe06 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx +++ b/packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx @@ -1,4 +1,3 @@ -/* eslint-disable @typescript-eslint/explicit-member-accessibility */ import * as React from 'react'; import { Platform, Linking, View, Switch, ScrollView, Text, TouchableOpacity, Alert, PermissionsAndroid, Dimensions } from 'react-native'; import Setting, { AppType } from '@joplin/lib/models/Setting'; @@ -534,9 +533,9 @@ class ConfigScreenComponent extends BaseScreenComponent ); - let settingComps: ReactNode[]; + let currentSection: ReactNode; if (this.state.selectedSectionName) { - settingComps = shared.settingsToComponents2( + const settingComps = shared.settingsToComponents2( this, AppType.Mobile, settings, this.state.selectedSectionName // TODO: Remove this cast. Currently necessary because of different versions @@ -553,21 +552,19 @@ class ConfigScreenComponent extends BaseScreenComponent ); + + currentSection = ( + + {settingComps} + + ); } else { - settingComps = [ - sectionSelector, - ]; + currentSection = sectionSelector; } - const currentSection = ( - - {settingComps} - - ); - let mainComponent; if (windowWidth > sectionSelectorDesiredWidth * 2 && this.state.selectedSectionName) { mainComponent = ( diff --git a/packages/app-mobile/components/screens/ConfigScreen/SectionSelector.tsx b/packages/app-mobile/components/screens/ConfigScreen/SectionSelector.tsx index 6a99e393add..5e89e37aeb3 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/SectionSelector.tsx +++ b/packages/app-mobile/components/screens/ConfigScreen/SectionSelector.tsx @@ -1,10 +1,10 @@ import * as React from 'react'; import Setting, { AppType } from '@joplin/lib/models/Setting'; -import { FunctionComponent, ReactNode, useMemo } from 'react'; +import { FunctionComponent, ReactNode, useCallback, useMemo, useRef, useState } from 'react'; import { ConfigScreenStyles } from './configScreenStyles'; import { Button } from 'react-native-paper'; -import { ScrollView, Text, View } from 'react-native'; +import { LayoutChangeEvent, NativeScrollEvent, NativeSyntheticEvent, ScrollView, Text, View } from 'react-native'; import { settingsSections } from '@joplin/lib/components/shared/config-shared'; interface Props { @@ -22,6 +22,23 @@ const SectionSelector: FunctionComponent = props => { return settingsSections({ device: AppType.Mobile, settings: props.settings }); }, [props.settings]); + const [visibleRegion, setVisibleRegion] = useState<{ y: number; height: number }|null>(null); + + const scrollViewRef = useRef(); + const onSelectedSectionLayout = React.useCallback((event: LayoutChangeEvent) => { + const y = event.nativeEvent.layout.y; + const buttonHeight = event.nativeEvent.layout.height; + + const scrollView = scrollViewRef.current; + if (scrollView && visibleRegion) { + const withinViewport = y >= visibleRegion.y && y + buttonHeight < visibleRegion.y + visibleRegion.height; + + if (!withinViewport) { + scrollView.scrollTo({ y }); + } + } + }, [visibleRegion]); + const sectionButtons: ReactNode[] = []; for (const section of sections) { @@ -32,10 +49,11 @@ const SectionSelector: FunctionComponent = props => { - - ); - currentSection = ( = props => { return settingsSections({ device: AppType.Mobile, settings: props.settings }); }, [props.settings]); - const [visibleRegion, setVisibleRegion] = useState<{ y: number; height: number }|null>(null); + const itemHeight = 50; - const scrollViewRef = useRef(); - const onSelectedSectionLayout = React.useCallback((event: LayoutChangeEvent) => { - const y = event.nativeEvent.layout.y; - const buttonHeight = event.nativeEvent.layout.height; - - const scrollView = scrollViewRef.current; - if (scrollView && visibleRegion) { - const withinViewport = y >= visibleRegion.y && y + buttonHeight < visibleRegion.y + visibleRegion.height; - - if (!withinViewport) { - scrollView.scrollTo({ y }); - } - } - }, [visibleRegion]); - - const sectionButtons: ReactNode[] = []; - - for (const section of sections) { + 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); - // TODO(personalizedrefrigerator): Accessibility: Mark which button is selected - sectionButtons.push( + return ( ); - } - - // Add an additional spacer at the end to ensure that the last item is visible - sectionButtons.push(); + }; + + 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 ++; + } - type ScrollEvent = NativeSyntheticEvent; - const updateScrollViewVisibileRegion = useCallback((event: ScrollEvent) => { - setVisibleRegion({ - y: event.nativeEvent.contentOffset.y, - height: event.nativeEvent.layoutMeasurement.height, - }); - }, []); + flatListRef.scrollToIndex({ + index: selectedIndex, + viewPosition: 0.5, + }); + } + }, [props.selectedSectionName, flatListRef, sections]); return ( - - - {sectionButtons} - + + item.name} + getItemLayout={(_data, index) => ({ + length: itemHeight, offset: itemHeight * index, index, + })} + /> ); }; From 9247076d4d6dcdd766c24cf834abf761f8a321af Mon Sep 17 00:00:00 2001 From: Henry Heino Date: Thu, 17 Aug 2023 06:47:37 -0700 Subject: [PATCH 12/28] Sidebar: Use selection color for selected buttons --- .../screens/ConfigScreen/SectionSelector.tsx | 10 ++++------ .../screens/ConfigScreen/configScreenStyles.ts | 18 +++++++++++++++++- 2 files changed, 21 insertions(+), 7 deletions(-) diff --git a/packages/app-mobile/components/screens/ConfigScreen/SectionSelector.tsx b/packages/app-mobile/components/screens/ConfigScreen/SectionSelector.tsx index f9c90eb1942..5a834cbd887 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/SectionSelector.tsx +++ b/packages/app-mobile/components/screens/ConfigScreen/SectionSelector.tsx @@ -22,8 +22,9 @@ const SectionSelector: FunctionComponent = props => { const sections = useMemo(() => { return settingsSections({ device: AppType.Mobile, settings: props.settings }); }, [props.settings]); + const styles = props.styles.styleSheet; - const itemHeight = 50; + const itemHeight = styles.sidebarButtonContent.height; const onRenderButton = ({ item }: { item: SettingMetadataSection }) => { const section = item; @@ -36,11 +37,8 @@ const SectionSelector: FunctionComponent = props => { key={section.name} accessibilityLabel={_('Selected: %s', label)} onPress={() => props.openSection(section.name)} - contentStyle={{ - justifyContent: 'flex-start', - alignItems: 'center', - height: 50, - }} + contentStyle={props.styles.styleSheet.sidebarButtonContent} + style={selected ? props.styles.styleSheet.selectedSidebarButtonContainer : undefined} buttonColor={selected ? props.styles.selectedSectionButtonColor : undefined} mode={selected ? 'contained-tonal' : 'text'} icon={icon} diff --git a/packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.ts b/packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.ts index e11e1a43736..3aa58d91b8d 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.ts +++ b/packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.ts @@ -27,6 +27,11 @@ export interface ConfigScreenStyleSheet { switchSettingContainer: ViewStyle; switchSettingControl: TextStyle; + // height is required to allow pre-computing the positions + // of the buttons. + sidebarButtonContent: TextStyle & { height: number }; + selectedSidebarButtonContainer: ViewStyle; + settingControl: TextStyle; } @@ -159,6 +164,17 @@ const configScreenStyles = (themeId: number): ConfigScreenStyles => { color: undefined, flex: 0, }, + + sidebarButtonContent: { + justifyContent: 'flex-start', + alignItems: 'center', + height: 48, + color: theme.color, + }, + + selectedSidebarButtonContainer: { + backgroundColor: theme.selectedColor, + }, }; const styleSheet = StyleSheet.create(styles); @@ -166,7 +182,7 @@ const configScreenStyles = (themeId: number): ConfigScreenStyles => { return { styleSheet, - selectedSectionButtonColor: theme.backgroundColorActive3, + selectedSectionButtonColor: theme.selectedColor, keyboardAppearance: theme.keyboardAppearance, getContainerStyle: (hasDescription) => { return !hasDescription ? styleSheet.settingContainer : styleSheet.settingContainerNoBottomBorder; From 89643f5d879d4ed3c78fb6b01584226506af9a71 Mon Sep 17 00:00:00 2001 From: Henry Heino Date: Fri, 18 Aug 2023 19:47:11 -0700 Subject: [PATCH 13/28] Default to the general section --- .../components/screens/ConfigScreen/ConfigScreen.tsx | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx b/packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx index 0f9c3fb0acc..544d13ab1cc 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx +++ b/packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx @@ -517,8 +517,14 @@ class ConfigScreenComponent extends BaseScreenComponent ); + const showAsSidebar = windowWidth > sectionSelectorDesiredWidth * 2.5; + let currentSectionName = this.state.selectedSectionName; + if (showAsSidebar && !currentSectionName) { + currentSectionName = 'general'; + } + let currentSection: ReactNode; - if (this.state.selectedSectionName) { + if (currentSectionName) { const settingComps = shared.settingsToComponents2( this, AppType.Mobile, settings, this.state.selectedSectionName @@ -539,7 +545,7 @@ class ConfigScreenComponent extends BaseScreenComponent sectionSelectorDesiredWidth * 2 && this.state.selectedSectionName) { + if (showAsSidebar && currentSectionName) { mainComponent = ( Date: Mon, 23 Oct 2023 14:27:45 -0700 Subject: [PATCH 14/28] Add subheadings, fix width of section selector --- .../screens/ConfigScreen/ConfigScreen.tsx | 22 ++++---- .../screens/ConfigScreen/SectionSelector.tsx | 45 ++++++++++------ .../ConfigScreen/configScreenStyles.ts | 54 +++++++++++++++---- packages/app-mobile/root.tsx | 3 +- packages/lib/models/Setting.ts | 27 +++++++++- 5 files changed, 111 insertions(+), 40 deletions(-) diff --git a/packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx b/packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx index f43e714f25f..c2f553b95a8 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx +++ b/packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx @@ -9,12 +9,12 @@ import { reg } from '@joplin/lib/registry'; import { State } from '@joplin/lib/reducer'; const { BackButtonService } = require('../../../services/back-button.js'); const VersionInfo = require('react-native-version-info').default; -const { connect } = require('react-redux'); +import { connect } from 'react-redux'; import ScreenHeader from '../../ScreenHeader'; -const { _ } = require('@joplin/lib/locale'); +import { _ } from '@joplin/lib/locale'; import BaseScreenComponent from '../../base-screen'; const { themeStyle } = require('../../global-style.js'); -import * as shared from '@joplin/lib/components/shared/config/config-shared.js'; +import * as shared from '@joplin/lib/components/shared/config/config-shared'; import SyncTargetRegistry from '@joplin/lib/SyncTargetRegistry'; import biometricAuthenticate from '../../biometrics/biometricAuthenticate'; import configScreenStyles, { ConfigScreenStyles } from './configScreenStyles'; @@ -505,7 +505,13 @@ class ConfigScreenComponent extends BaseScreenComponent sectionSelectorDesiredWidth * 2.3; + let currentSectionName = this.state.selectedSectionName; + if (showAsSidebar && !currentSectionName) { + currentSectionName = 'general'; + } const sectionSelector = ( ); - const showAsSidebar = windowWidth > sectionSelectorDesiredWidth * 2.5; - let currentSectionName = this.state.selectedSectionName; - if (showAsSidebar && !currentSectionName) { - currentSectionName = 'general'; - } - let currentSection: ReactNode; if (currentSectionName) { const settingComps = shared.settingsToComponents2( diff --git a/packages/app-mobile/components/screens/ConfigScreen/SectionSelector.tsx b/packages/app-mobile/components/screens/ConfigScreen/SectionSelector.tsx index 5a834cbd887..e7a69f91af5 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/SectionSelector.tsx +++ b/packages/app-mobile/components/screens/ConfigScreen/SectionSelector.tsx @@ -3,15 +3,15 @@ import * as React from 'react'; import Setting, { AppType, SettingMetadataSection } from '@joplin/lib/models/Setting'; import { FunctionComponent, useEffect, useMemo, useState } from 'react'; import { ConfigScreenStyles } from './configScreenStyles'; -import { Button } from 'react-native-paper'; -import { FlatList, Text, View } from 'react-native'; +import { FlatList, Text, Pressable, View } from 'react-native'; import { settingsSections } from '@joplin/lib/components/shared/config/config-shared'; +const Icon = require('react-native-vector-icons/MaterialCommunityIcons').default; import { _ } from '@joplin/lib/locale'; interface Props { styles: ConfigScreenStyles; - minWidth: number|undefined; + width: number|undefined; settings: any; selectedSectionName: string|null; @@ -24,29 +24,42 @@ const SectionSelector: FunctionComponent = props => { }, [props.settings]); const styles = props.styles.styleSheet; - const itemHeight = styles.sidebarButtonContent.height; + 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); return ( - + + + + {label} + + + {shortDescription ?? ''} + + + ); }; @@ -70,7 +83,7 @@ const SectionSelector: FunctionComponent = props => { }, [props.selectedSectionName, flatListRef, sections]); return ( - + { borderBottomColor: theme.dividerColor, }; + const sidebarButton: SidebarButtonStyle = { + height: theme.fontSize * 4, + flex: 1, + flexDirection: 'row', + alignItems: 'center', + paddingEnd: theme.marginRight, + }; + + const sidebarButtonMainText: TextStyle = { + color: theme.color, + fontSize: theme.fontSize, + }; + + const sidebarButtonDescriptionText: TextStyle = { + ...sidebarButtonMainText, + fontSize: theme.fontSizeSmaller, + color: theme.colorFaded, + }; + + const styles: ConfigScreenStyleSheet = { body: { flex: 1, @@ -165,16 +189,24 @@ const configScreenStyles = (themeId: number): ConfigScreenStyles => { flex: 0, }, - sidebarButtonContent: { - justifyContent: 'flex-start', - alignItems: 'center', - height: 48, - color: theme.color, - }, - selectedSidebarButtonContainer: { + sidebarButton, + selectedSidebarButton: { + ...sidebarButton, backgroundColor: theme.selectedColor, }, + + sidebarButtonMainText: sidebarButtonMainText, + sidebarIcon: { + ...sidebarButtonMainText, + paddingRight: theme.marginLeft, + paddingLeft: theme.marginRight, + }, + sidebarSelectedButtonText: { + ...sidebarButtonMainText, + fontWeight: 'bold', + }, + sidebarButtonDescriptionText, }; const styleSheet = StyleSheet.create(styles); diff --git a/packages/app-mobile/root.tsx b/packages/app-mobile/root.tsx index b4e8b055297..925779ca47b 100644 --- a/packages/app-mobile/root.tsx +++ b/packages/app-mobile/root.tsx @@ -1091,7 +1091,8 @@ class AppComponent extends React.Component { { shouldShowMainContent && } - (this.dropdownAlert_ = func)} /> { !shouldShowMainContent && (this.dropdownAlert_ = func)} /> + { !shouldShowMainContent && = { + 'general': _('Language, date format'), + 'appearance': _('App theme, editor font'), + 'sync': _('Sync, encryption, proxy'), + 'joplinCloud': _('Joplin Cloud settings'), + 'markdownPlugins': _('Markdown plugins: Media player, math, footnotes, soft breaks, ...'), + 'note': _('Geolocation, spellcheck, markdown toolbar, image resize'), + 'revisionService': _('Enable/disable note history, keep notes for...'), + 'tools': _('Application log, profiles, sync status'), + 'export': _('Export all notes'), + 'moreInfo': _('Privacy policy, donate, website'), + }; + + return sectionNameToSummary[metadata.name] ?? ''; + } + public static sectionNameToIcon(name: string, appType: AppType) { const desktopNameToIconMap: Record = { 'general': 'icon-general', From 5bc2ef80e415bf07241b35744aa2329b9f7ec20b Mon Sep 17 00:00:00 2001 From: Henry Heino Date: Mon, 23 Oct 2023 15:05:28 -0700 Subject: [PATCH 15/28] Show general tab by default when in tablet mode --- .../screens/ConfigScreen/ConfigScreen.tsx | 51 ++++++++++++++----- 1 file changed, 39 insertions(+), 12 deletions(-) diff --git a/packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx b/packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx index c2f553b95a8..4bebc592bb6 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx +++ b/packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { Platform, Linking, View, Switch, ScrollView, Text, TouchableOpacity, Alert, PermissionsAndroid, Dimensions } 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 NavService from '@joplin/lib/services/NavService'; import SearchEngine from '@joplin/lib/services/searchengine/SearchEngine'; @@ -38,6 +38,7 @@ interface ConfigScreenState { showAdvancedSettings: boolean; selectedSectionName: string|null; + sidebarWidth: number; } interface ConfigScreenProps { @@ -64,6 +65,7 @@ class ConfigScreenComponent extends BaseScreenComponent(); @@ -129,11 +131,34 @@ class ConfigScreenComponent extends BaseScreenComponent { + const windowWidth = Dimensions.get('window').width; + + let sidebarNewWidth = windowWidth; + + const sidebarValidWidths = [280, 230]; + const maxFractionOfWindowSize = 1 / 3; + for (const width of sidebarValidWidths) { + if (width < windowWidth * maxFractionOfWindowSize) { + sidebarNewWidth = width; + break; + } + } + + this.setState({ sidebarWidth: sidebarNewWidth }); + }; + + private navigationFillsScreen() { + const windowWidth = Dimensions.get('window').width; + return this.state.sidebarWidth > windowWidth / 2; + } + private switchSectionPress_ = (section: string) => { + AccessibilityInfo.announceForAccessibility(_('Opening section %s', section)); this.setState({ selectedSectionName: section }); }; - private showOverviewSection_ = () => { + private showSectionNavigation_ = () => { this.setState({ selectedSectionName: null }); }; @@ -186,8 +211,9 @@ class ConfigScreenComponent extends BaseScreenComponent sectionSelectorDesiredWidth * 2.3; + // If the navigation is a sidebar, always show a section. let currentSectionName = this.state.selectedSectionName; if (showAsSidebar && !currentSectionName) { currentSectionName = 'general'; @@ -515,18 +542,18 @@ class ConfigScreenComponent extends BaseScreenComponent ); let currentSection: ReactNode; if (currentSectionName) { const settingComps = shared.settingsToComponents2( - this, AppType.Mobile, settings, this.state.selectedSectionName, + this, AppType.Mobile, settings, currentSectionName, // TODO: Remove this cast. Currently necessary because of different versions // of React in lib/ and app-mobile/ @@ -561,8 +588,8 @@ class ConfigScreenComponent extends BaseScreenComponent Date: Mon, 23 Oct 2023 15:28:11 -0700 Subject: [PATCH 16/28] Revert several unnecessary changes --- .../app-desktop/gui/ConfigScreen/Sidebar.tsx | 7 +------ .../app-mobile/components/ScreenHeader.tsx | 16 +++++++++++----- .../app-mobile/components/global-style.js | 11 ----------- .../ConfigScreen/configScreenStyles.ts | 19 ------------------- 4 files changed, 12 insertions(+), 41 deletions(-) diff --git a/packages/app-desktop/gui/ConfigScreen/Sidebar.tsx b/packages/app-desktop/gui/ConfigScreen/Sidebar.tsx index 2b20b979fa5..ab89b9aa152 100644 --- a/packages/app-desktop/gui/ConfigScreen/Sidebar.tsx +++ b/packages/app-desktop/gui/ConfigScreen/Sidebar.tsx @@ -92,12 +92,7 @@ export default function Sidebar(props: Props) { function renderButton(section: any) { const selected = props.selection === section.name; return ( - { props.onSelectionChange({ section: section }); }} - > + { props.onSelectionChange({ section: section }); }}> diff --git a/packages/app-mobile/components/ScreenHeader.tsx b/packages/app-mobile/components/ScreenHeader.tsx index 991e65b3d97..a983e3aa2d4 100644 --- a/packages/app-mobile/components/ScreenHeader.tsx +++ b/packages/app-mobile/components/ScreenHeader.tsx @@ -1,7 +1,7 @@ const React = require('react'); import { connect } from 'react-redux'; -import { PureComponent, ReactNode } from 'react'; +import { PureComponent } from 'react'; import { View, Text, StyleSheet, TouchableOpacity, Image, ScrollView, Dimensions, ViewStyle } from 'react-native'; const Icon = require('react-native-vector-icons/Ionicons').default; const { BackButtonService } = require('../services/back-button.js'); @@ -56,7 +56,6 @@ interface ScreenHeaderProps { undoButtonDisabled?: boolean; showRedoButton: boolean; menuOptions: MenuOptionType[]; - titleComponent?: ReactNode; title?: string|null; folders: FolderEntity[]; folderPickerOptions?: { @@ -188,7 +187,16 @@ class ScreenHeaderComponent extends PureComponent ); - } else if (this.props.titleComponent) { - return this.props.titleComponent; } else { const title = 'title' in this.props && this.props.title !== null ? this.props.title : ''; return {title}; diff --git a/packages/app-mobile/components/global-style.js b/packages/app-mobile/components/global-style.js index 1755739c1a6..c97cabaeb66 100644 --- a/packages/app-mobile/components/global-style.js +++ b/packages/app-mobile/components/global-style.js @@ -67,17 +67,6 @@ function addExtraStyles(style) { backgroundColor: style.headerBackgroundColor, }; - style.titlebarText = { - flex: 1, - textAlignVertical: 'center', - marginLeft: 10, - color: style.colorBright2, - fontWeight: 'bold', - fontSize: style.fontSize, - paddingTop: 15, - paddingBottom: 15, - }; - style.keyboardAppearance = style.appearance; style.color5 = style.backgroundColor4; diff --git a/packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.ts b/packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.ts index 2bb0db4a01d..713c73463ca 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.ts +++ b/packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.ts @@ -9,7 +9,6 @@ export interface ConfigScreenStyleSheet { settingContainer: ViewStyle; settingContainerNoBottomBorder: ViewStyle; headerWrapperStyle: ViewStyle; - selectedHeaderWrapperStyle: ViewStyle; headerTextStyle: TextStyle; settingText: TextStyle; @@ -22,9 +21,6 @@ export interface ConfigScreenStyleSheet { permissionText: TextStyle; textInput: TextStyle; - titlebarText: TextStyle; - titlebarHeaderPart: ViewStyle; - switchSettingText: TextStyle; switchSettingContainer: ViewStyle; switchSettingControl: TextStyle; @@ -126,16 +122,6 @@ const configScreenStyles = (themeId: number): ConfigScreenStyles => { backgroundColor: theme.warningBackgroundColor, fontSize: theme.fontSizeSmaller, }, - titlebarText: theme.titlebarText, - - titlebarHeaderPart: { - ...theme.titlebarText, - flex: 0, - - // TODO(personalizedrefrigerator): Why is division by two needed for consistency? - paddingTop: (theme.titlebarText.paddingTop ?? 0) / 2, - paddingBottom: (theme.titlebarText.paddingBottom ?? 0) / 2, - }, sliderUnits: { color: theme.color, @@ -177,11 +163,6 @@ const configScreenStyles = (themeId: number): ConfigScreenStyles => { ...settingContainerStyle, ...theme.headerWrapperStyle, }, - selectedHeaderWrapperStyle: { - ...settingContainerStyle, - ...theme.headerWrapperStyle, - backgroundColor: theme.backgroundColorActive3, - }, switchSettingControl: { ...settingControlStyle, From ea8d65c1a4e7675594b1631ed09b9e98e5ff9cfd Mon Sep 17 00:00:00 2001 From: Henry Heino Date: Mon, 23 Oct 2023 15:31:32 -0700 Subject: [PATCH 17/28] Re-enable throw on unsupported setting type --- .../components/screens/ConfigScreen/SettingComponent.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/app-mobile/components/screens/ConfigScreen/SettingComponent.tsx b/packages/app-mobile/components/screens/ConfigScreen/SettingComponent.tsx index 89d3569f4d5..87b381d7195 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/SettingComponent.tsx +++ b/packages/app-mobile/components/screens/ConfigScreen/SettingComponent.tsx @@ -144,8 +144,10 @@ const SettingComponent: React.FunctionComponent = props => { {descriptionComp} ); - } else { - // throw new Error('Unsupported setting type: ' + md.type); + } else if (md.type === Setting.TYPE_BUTTON) { + // TODO: Not yet supported + } else if (Setting.value('env') === 'dev') { + throw new Error(`Unsupported setting type: ${md.type}`); } return output; From cb9c8947acb2cbaa84382c47f2f8a930d7a0873e Mon Sep 17 00:00:00 2001 From: Henry Heino Date: Tue, 24 Oct 2023 08:49:39 -0700 Subject: [PATCH 18/28] markdown toolbar -> editor toolbar --- packages/lib/models/Setting.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/lib/models/Setting.ts b/packages/lib/models/Setting.ts index 2ccd1ca1c67..33c235f30fb 100644 --- a/packages/lib/models/Setting.ts +++ b/packages/lib/models/Setting.ts @@ -2641,7 +2641,7 @@ class Setting extends BaseModel { 'sync': _('Sync, encryption, proxy'), 'joplinCloud': _('Joplin Cloud settings'), 'markdownPlugins': _('Markdown plugins: Media player, math, footnotes, soft breaks, ...'), - 'note': _('Geolocation, spellcheck, markdown toolbar, image resize'), + 'note': _('Geolocation, spellcheck, editor toolbar, image resize'), 'revisionService': _('Enable/disable note history, keep notes for...'), 'tools': _('Application log, profiles, sync status'), 'export': _('Export all notes'), From a835503dc4ca8630553592888154c6e3e259a150 Mon Sep 17 00:00:00 2001 From: Henry Heino Date: Tue, 24 Oct 2023 08:50:08 -0700 Subject: [PATCH 19/28] Update Joplin cloud description --- packages/lib/models/Setting.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/lib/models/Setting.ts b/packages/lib/models/Setting.ts index 33c235f30fb..cd0c5524c96 100644 --- a/packages/lib/models/Setting.ts +++ b/packages/lib/models/Setting.ts @@ -2639,7 +2639,7 @@ class Setting extends BaseModel { 'general': _('Language, date format'), 'appearance': _('App theme, editor font'), 'sync': _('Sync, encryption, proxy'), - 'joplinCloud': _('Joplin Cloud settings'), + 'joplinCloud': _('Email To Note, login information'), 'markdownPlugins': _('Markdown plugins: Media player, math, footnotes, soft breaks, ...'), 'note': _('Geolocation, spellcheck, editor toolbar, image resize'), 'revisionService': _('Enable/disable note history, keep notes for...'), From be2d388f8c3f0cc9da3503e621099e4ba6ea72fb Mon Sep 17 00:00:00 2001 From: Henry Heino Date: Tue, 24 Oct 2023 08:50:35 -0700 Subject: [PATCH 20/28] Enable/disable -> Toggle --- packages/lib/models/Setting.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/lib/models/Setting.ts b/packages/lib/models/Setting.ts index cd0c5524c96..5f826905ad1 100644 --- a/packages/lib/models/Setting.ts +++ b/packages/lib/models/Setting.ts @@ -2642,7 +2642,7 @@ class Setting extends BaseModel { 'joplinCloud': _('Email To Note, login information'), 'markdownPlugins': _('Markdown plugins: Media player, math, footnotes, soft breaks, ...'), 'note': _('Geolocation, spellcheck, editor toolbar, image resize'), - 'revisionService': _('Enable/disable note history, keep notes for...'), + 'revisionService': _('Toggle note history, keep notes for...'), 'tools': _('Application log, profiles, sync status'), 'export': _('Export all notes'), 'moreInfo': _('Privacy policy, donate, website'), From 40a0a632d0fd6bd0f436c6d8433c12052611aea1 Mon Sep 17 00:00:00 2001 From: Henry Heino Date: Tue, 24 Oct 2023 08:50:57 -0700 Subject: [PATCH 21/28] Export all notes -> Export your data --- packages/lib/models/Setting.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/lib/models/Setting.ts b/packages/lib/models/Setting.ts index 5f826905ad1..01e7fa35b7b 100644 --- a/packages/lib/models/Setting.ts +++ b/packages/lib/models/Setting.ts @@ -2644,7 +2644,7 @@ class Setting extends BaseModel { 'note': _('Geolocation, spellcheck, editor toolbar, image resize'), 'revisionService': _('Toggle note history, keep notes for...'), 'tools': _('Application log, profiles, sync status'), - 'export': _('Export all notes'), + 'export': _('Export your data'), 'moreInfo': _('Privacy policy, donate, website'), }; From ed14c42c487da9173191df3ac62a46bd060d1d63 Mon Sep 17 00:00:00 2001 From: Henry Heino Date: Tue, 24 Oct 2023 08:51:23 -0700 Subject: [PATCH 22/28] Remove "Markdown plugins: " prefix --- packages/lib/models/Setting.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/lib/models/Setting.ts b/packages/lib/models/Setting.ts index 01e7fa35b7b..8976d49ec12 100644 --- a/packages/lib/models/Setting.ts +++ b/packages/lib/models/Setting.ts @@ -2640,7 +2640,7 @@ class Setting extends BaseModel { 'appearance': _('App theme, editor font'), 'sync': _('Sync, encryption, proxy'), 'joplinCloud': _('Email To Note, login information'), - 'markdownPlugins': _('Markdown plugins: Media player, math, footnotes, soft breaks, ...'), + 'markdownPlugins': _('Media player, math, footnotes, soft breaks, ...'), 'note': _('Geolocation, spellcheck, editor toolbar, image resize'), 'revisionService': _('Toggle note history, keep notes for...'), 'tools': _('Application log, profiles, sync status'), From 3dbe2fcbe1229cf64ab7851b851e3efd9686c220 Mon Sep 17 00:00:00 2001 From: Henry Heino Date: Tue, 24 Oct 2023 09:43:11 -0700 Subject: [PATCH 23/28] Style adjustments --- .../components/screens/ConfigScreen/SectionSelector.tsx | 1 - .../components/screens/ConfigScreen/configScreenStyles.ts | 4 +++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/app-mobile/components/screens/ConfigScreen/SectionSelector.tsx b/packages/app-mobile/components/screens/ConfigScreen/SectionSelector.tsx index e7a69f91af5..306ee5e650f 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/SectionSelector.tsx +++ b/packages/app-mobile/components/screens/ConfigScreen/SectionSelector.tsx @@ -42,7 +42,6 @@ const SectionSelector: FunctionComponent = props => { > diff --git a/packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.ts b/packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.ts index 713c73463ca..f0f803eb477 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.ts +++ b/packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.ts @@ -88,7 +88,8 @@ const configScreenStyles = (themeId: number): ConfigScreenStyles => { const sidebarButtonDescriptionText: TextStyle = { ...sidebarButtonMainText, fontSize: theme.fontSizeSmaller, - color: theme.colorFaded, + color: theme.color, + opacity: 0.75, }; @@ -182,6 +183,7 @@ const configScreenStyles = (themeId: number): ConfigScreenStyles => { ...sidebarButtonMainText, paddingRight: theme.marginLeft, paddingLeft: theme.marginRight, + fontSize: 20, }, sidebarSelectedButtonText: { ...sidebarButtonMainText, From 7b4dab8f8a47943883a14392f394ff1b3a63e4ca Mon Sep 17 00:00:00 2001 From: Henry Heino Date: Tue, 24 Oct 2023 11:28:51 -0700 Subject: [PATCH 24/28] Update icons --- .eslintignore | 1 + .gitignore | 1 + packages/app-mobile/components/Icon.tsx | 31 ++++++++++++ .../screens/ConfigScreen/SectionSelector.tsx | 2 +- .../ConfigScreen/configScreenStyles.ts | 9 ++-- packages/lib/models/Setting.ts | 50 +++++++++---------- 6 files changed, 62 insertions(+), 32 deletions(-) create mode 100644 packages/app-mobile/components/Icon.tsx diff --git a/.eslintignore b/.eslintignore index 33821cfb2ac..9eaebbb14a3 100644 --- a/.eslintignore +++ b/.eslintignore @@ -423,6 +423,7 @@ packages/app-mobile/components/Dropdown.test.js packages/app-mobile/components/Dropdown.js packages/app-mobile/components/ExtendedWebView.js packages/app-mobile/components/FolderPicker.js +packages/app-mobile/components/Icon.js packages/app-mobile/components/Modal.js packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.js packages/app-mobile/components/NoteBodyViewer/hooks/useEditPopup.test.js diff --git a/.gitignore b/.gitignore index 40229378a41..153f797e9da 100644 --- a/.gitignore +++ b/.gitignore @@ -405,6 +405,7 @@ packages/app-mobile/components/Dropdown.test.js packages/app-mobile/components/Dropdown.js packages/app-mobile/components/ExtendedWebView.js packages/app-mobile/components/FolderPicker.js +packages/app-mobile/components/Icon.js packages/app-mobile/components/Modal.js packages/app-mobile/components/NoteBodyViewer/NoteBodyViewer.js packages/app-mobile/components/NoteBodyViewer/hooks/useEditPopup.test.js diff --git a/packages/app-mobile/components/Icon.tsx b/packages/app-mobile/components/Icon.tsx new file mode 100644 index 00000000000..c6dfb4d8428 --- /dev/null +++ b/packages/app-mobile/components/Icon.tsx @@ -0,0 +1,31 @@ + +import * as React from 'react'; +import { TextStyle } from 'react-native'; +const FontAwesomeIcon = require('react-native-vector-icons/FontAwesome5').default; + +interface Props { + name: string; + style: TextStyle; +} + +const Icon: React.FC = props => { + // Matches: + // 1. A prefix of word characters (\w+) + // 2. A suffix of non-spaces (\S+) + // An "fa-" at the beginning of the suffix is ignored. + const nameMatch = props.name.match(/^(\w+)\s+(?:fa-)?(\S+)$/); + + const namePrefix = nameMatch ? nameMatch[1] : ''; + const nameSuffix = nameMatch ? nameMatch[2] : props.name; + + return ( + + ); +}; + +export default Icon; diff --git a/packages/app-mobile/components/screens/ConfigScreen/SectionSelector.tsx b/packages/app-mobile/components/screens/ConfigScreen/SectionSelector.tsx index 306ee5e650f..cfe272c3ede 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/SectionSelector.tsx +++ b/packages/app-mobile/components/screens/ConfigScreen/SectionSelector.tsx @@ -5,8 +5,8 @@ import { FunctionComponent, useEffect, useMemo, useState } from 'react'; import { ConfigScreenStyles } from './configScreenStyles'; import { FlatList, Text, Pressable, View } from 'react-native'; import { settingsSections } from '@joplin/lib/components/shared/config/config-shared'; -const Icon = require('react-native-vector-icons/MaterialCommunityIcons').default; import { _ } from '@joplin/lib/locale'; +import Icon from '../../Icon'; interface Props { styles: ConfigScreenStyles; diff --git a/packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.ts b/packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.ts index f0f803eb477..c427a1999f1 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.ts +++ b/packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.ts @@ -72,8 +72,9 @@ const configScreenStyles = (themeId: number): ConfigScreenStyles => { borderBottomColor: theme.dividerColor, }; + const sidebarButtonHeight = theme.fontSize * 4; const sidebarButton: SidebarButtonStyle = { - height: theme.fontSize * 4, + height: sidebarButtonHeight, flex: 1, flexDirection: 'row', alignItems: 'center', @@ -181,9 +182,9 @@ const configScreenStyles = (themeId: number): ConfigScreenStyles => { sidebarButtonMainText: sidebarButtonMainText, sidebarIcon: { ...sidebarButtonMainText, - paddingRight: theme.marginLeft, - paddingLeft: theme.marginRight, - fontSize: 20, + textAlign: 'center', + fontSize: 18, + width: sidebarButtonHeight * 0.8, }, sidebarSelectedButtonText: { ...sidebarButtonMainText, diff --git a/packages/lib/models/Setting.ts b/packages/lib/models/Setting.ts index 8976d49ec12..39ab57f684f 100644 --- a/packages/lib/models/Setting.ts +++ b/packages/lib/models/Setting.ts @@ -2652,7 +2652,7 @@ class Setting extends BaseModel { } public static sectionNameToIcon(name: string, appType: AppType) { - const desktopNameToIconMap: Record = { + const nameToIconMap: Record = { 'general': 'icon-general', 'sync': 'icon-sync', 'appearance': 'icon-appearance', @@ -2666,43 +2666,39 @@ class Setting extends BaseModel { 'server': 'far fa-hand-scissors', 'keymap': 'fa fa-keyboard', 'joplinCloud': 'fa fa-cloud', + 'tools': 'fa fa-toolbox', + 'export': 'fa fa-file-export', + 'moreInfo': 'fa fa-info-circle', }; - // Mobile icons are selected from the FontAwesome5 section of - // react-native-vector-icons: https://oblador.github.io/react-native-vector-icons/ + // Icomoon icons are currently not present in the mobile app -- we override these + // below. + // + // These icons come from react-native-vector-icons. + // See https://oblador.github.io/react-native-vector-icons/ const mobileNameToIconMap: Record = { - 'general': 'tune', - 'sync': 'sync', - 'appearance': 'ruler', - 'note': 'note', - 'plugins': 'puzzle', - 'markdownPlugins': 'marker', - 'application': 'cog', - 'revisionService': 'history', - 'encryption': 'key', - 'server': 'hand-scissors', - 'keymap': 'keyboard', - 'tools': 'toolbox', - 'export': 'export', - 'moreInfo': 'information-outline', - 'joplinCloud': 'cloud', + 'general': 'fa fa-sliders-h', + 'sync': 'fa fa-sync', + 'appearance': 'fa fa-ruler', + 'note': 'fa fa-sticky-note', + 'revisionService': 'far fa-history', + 'plugins': 'fa fa-puzzle-piece', + 'application': 'fa fa-cog', + 'encryption': 'fa fa-key', }; - if (appType === AppType.Desktop && name in desktopNameToIconMap) { - return desktopNameToIconMap[name]; - } - + // Overridden? if (appType === AppType.Mobile && name in mobileNameToIconMap) { return mobileNameToIconMap[name]; } + if (name in nameToIconMap) { + return nameToIconMap[name]; + } + if (this.customSections_[name] && this.customSections_[name].iconName) return this.customSections_[name].iconName; - if (appType === AppType.Desktop) { - return 'fas fa-cog'; - } else { - return 'cog'; - } + return 'fas fa-cog'; } public static appTypeToLabel(name: string) { From 68552c1374c2ee3650f596031e08515b6ced76e0 Mon Sep 17 00:00:00 2001 From: Henry Heino Date: Tue, 24 Oct 2023 14:11:05 -0700 Subject: [PATCH 25/28] Re-word markdownPlugins setting description --- packages/lib/models/Setting.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/lib/models/Setting.ts b/packages/lib/models/Setting.ts index 39ab57f684f..cc637eaf903 100644 --- a/packages/lib/models/Setting.ts +++ b/packages/lib/models/Setting.ts @@ -2640,9 +2640,9 @@ class Setting extends BaseModel { 'appearance': _('App theme, editor font'), 'sync': _('Sync, encryption, proxy'), 'joplinCloud': _('Email To Note, login information'), - 'markdownPlugins': _('Media player, math, footnotes, soft breaks, ...'), + 'markdownPlugins': _('Media player, math, diagrams, table of contents'), 'note': _('Geolocation, spellcheck, editor toolbar, image resize'), - 'revisionService': _('Toggle note history, keep notes for...'), + 'revisionService': _('Toggle note history, keep notes for'), 'tools': _('Application log, profiles, sync status'), 'export': _('Export your data'), 'moreInfo': _('Privacy policy, donate, website'), From 4733969469023ef76da94af4b01dfc9ca0192877 Mon Sep 17 00:00:00 2001 From: Henry Heino Date: Wed, 25 Oct 2023 16:01:54 -0700 Subject: [PATCH 26/28] Slight increase in tab height --- .../components/screens/ConfigScreen/configScreenStyles.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.ts b/packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.ts index c427a1999f1..b77e656af26 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.ts +++ b/packages/app-mobile/components/screens/ConfigScreen/configScreenStyles.ts @@ -72,7 +72,7 @@ const configScreenStyles = (themeId: number): ConfigScreenStyles => { borderBottomColor: theme.dividerColor, }; - const sidebarButtonHeight = theme.fontSize * 4; + const sidebarButtonHeight = theme.fontSize * 4 + 5; const sidebarButton: SidebarButtonStyle = { height: sidebarButtonHeight, flex: 1, From adee7c61c5a3c02477eecb0889dafd59f204ee51 Mon Sep 17 00:00:00 2001 From: Henry Heino Date: Wed, 25 Oct 2023 16:55:44 -0700 Subject: [PATCH 27/28] Partial accessibility fixes --- packages/app-mobile/components/Icon.tsx | 13 +++++++++++++ .../screens/ConfigScreen/SectionSelector.tsx | 6 ++++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/app-mobile/components/Icon.tsx b/packages/app-mobile/components/Icon.tsx index c6dfb4d8428..741f0ab6d25 100644 --- a/packages/app-mobile/components/Icon.tsx +++ b/packages/app-mobile/components/Icon.tsx @@ -6,6 +6,9 @@ const FontAwesomeIcon = require('react-native-vector-icons/FontAwesome5').defaul interface Props { name: string; style: TextStyle; + + // If `null` is given, the content must be labeled elsewhere. + accessibilityLabel: string|null; } const Icon: React.FC = props => { @@ -18,10 +21,20 @@ const Icon: React.FC = props => { const namePrefix = nameMatch ? nameMatch[1] : ''; const nameSuffix = nameMatch ? nameMatch[2] : props.name; + // If there's no label, make sure that the screen reader doesn't try + // to read the characters from the icon font (they don't make sense + // without the icon font applied). + const accessibilityHidden = props.accessibilityLabel === null; + return ( diff --git a/packages/app-mobile/components/screens/ConfigScreen/SectionSelector.tsx b/packages/app-mobile/components/screens/ConfigScreen/SectionSelector.tsx index cfe272c3ede..24e87012ab8 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/SectionSelector.tsx +++ b/packages/app-mobile/components/screens/ConfigScreen/SectionSelector.tsx @@ -5,7 +5,6 @@ import { FunctionComponent, useEffect, useMemo, useState } from 'react'; import { ConfigScreenStyles } from './configScreenStyles'; import { FlatList, Text, Pressable, View } from 'react-native'; import { settingsSections } from '@joplin/lib/components/shared/config/config-shared'; -import { _ } from '@joplin/lib/locale'; import Icon from '../../Icon'; interface Props { @@ -36,12 +35,14 @@ const SectionSelector: FunctionComponent = props => { return ( props.openSection(section.name)} style={selected ? styles.selectedSidebarButton : styles.sidebarButton} > @@ -84,6 +85,7 @@ const SectionSelector: FunctionComponent = props => { return ( Date: Wed, 25 Oct 2023 17:43:49 -0700 Subject: [PATCH 28/28] Announce section label instead of ID --- .../components/screens/ConfigScreen/ConfigScreen.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx b/packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx index 4bebc592bb6..659fe9b5677 100644 --- a/packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx +++ b/packages/app-mobile/components/screens/ConfigScreen/ConfigScreen.tsx @@ -154,7 +154,8 @@ class ConfigScreenComponent extends BaseScreenComponent { - AccessibilityInfo.announceForAccessibility(_('Opening section %s', section)); + const label = Setting.sectionNameToLabel(section); + AccessibilityInfo.announceForAccessibility(_('Opening section %s', label)); this.setState({ selectedSectionName: section }); };