1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00

Chore: Mobile: Migrate action button to react-native-paper (#7477)

This commit is contained in:
Henry Heino 2023-01-08 04:22:41 -08:00 committed by GitHub
parent 8b3c9e81a7
commit 257a24166e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
13 changed files with 218 additions and 184 deletions

View File

@ -864,6 +864,9 @@ packages/app-desktop/utils/markupLanguageUtils.js.map
packages/app-mobile/PluginAssetsLoader.d.ts
packages/app-mobile/PluginAssetsLoader.js
packages/app-mobile/PluginAssetsLoader.js.map
packages/app-mobile/components/ActionButton.d.ts
packages/app-mobile/components/ActionButton.js
packages/app-mobile/components/ActionButton.js.map
packages/app-mobile/components/BackButtonDialogBox.d.ts
packages/app-mobile/components/BackButtonDialogBox.js
packages/app-mobile/components/BackButtonDialogBox.js.map

3
.gitignore vendored
View File

@ -852,6 +852,9 @@ packages/app-desktop/utils/markupLanguageUtils.js.map
packages/app-mobile/PluginAssetsLoader.d.ts
packages/app-mobile/PluginAssetsLoader.js
packages/app-mobile/PluginAssetsLoader.js.map
packages/app-mobile/components/ActionButton.d.ts
packages/app-mobile/components/ActionButton.js
packages/app-mobile/components/ActionButton.js.map
packages/app-mobile/components/BackButtonDialogBox.d.ts
packages/app-mobile/components/BackButtonDialogBox.js
packages/app-mobile/components/BackButtonDialogBox.js.map

View File

@ -9,6 +9,7 @@ import androidx.multidex.MultiDex;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.oblador.vectoricons.VectorIconsPackage;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;

View File

@ -1,4 +1,6 @@
rootProject.name = 'Joplin'
include ':react-native-vector-icons'
project(':react-native-vector-icons').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-vector-icons/android')
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
include ':app'
includeBuild('../node_modules/react-native-gradle-plugin')

View File

@ -0,0 +1,73 @@
const React = require('react');
import { useState, useCallback, useMemo } from 'react';
const Icon = require('react-native-vector-icons/Ionicons').default;
import { FAB, Portal } from 'react-native-paper';
import { _ } from '@joplin/lib/locale';
type OnButtonPress = ()=> void;
interface ButtonSpec {
icon: string;
label: string;
color?: string;
onPress?: OnButtonPress;
}
interface ActionButtonProps {
buttons?: ButtonSpec[];
// If not given, an "add" button will be used.
mainButton?: ButtonSpec;
}
const defaultOnPress = () => {};
// Returns a render function compatible with React Native Paper.
const getIconRenderFunction = (iconName: string) => {
return (props: any) => <Icon name={iconName} {...props} />;
};
const useIcon = (iconName: string) => {
return useMemo(() => {
return getIconRenderFunction(iconName);
}, [iconName]);
};
const ActionButton = (props: ActionButtonProps) => {
const [open, setOpen] = useState(false);
const onMenuToggled = useCallback(
(state: { open: boolean }) => setOpen(state.open)
, [setOpen]);
const actions = useMemo(() => (props.buttons ?? []).map(button => {
return {
...button,
icon: getIconRenderFunction(button.icon),
onPress: button.onPress ?? defaultOnPress,
};
}), [props.buttons]);
const closedIcon = useIcon(props.mainButton?.icon ?? 'md-add');
const openIcon = useIcon('close');
return (
<Portal>
<FAB.Group
open={open}
accessibilityLabel={props.mainButton?.label ?? _('Add new')}
icon={ open ? openIcon : closedIcon }
fabStyle={{
backgroundColor: props.mainButton?.color ?? 'rgba(231,76,60,1)',
}}
onStateChange={onMenuToggled}
actions={actions}
onPress={props.mainButton?.onPress ?? defaultOnPress}
visible={true}
/>
</Portal>
);
};
export default ActionButton;

View File

@ -1,169 +0,0 @@
const React = require('react');
const { StyleSheet } = require('react-native');
const Note = require('@joplin/lib/models/Note').default;
const Icon = require('react-native-vector-icons/Ionicons').default;
const ReactNativeActionButton = require('react-native-action-button').default;
const { connect } = require('react-redux');
const { _ } = require('@joplin/lib/locale');
// We need this to suppress the useless warning
// https://github.com/oblador/react-native-vector-icons/issues/1465
Icon.loadFont().catch((error) => { console.info(error); });
const styles = StyleSheet.create({
actionButtonIcon: {
fontSize: 20,
height: 22,
color: 'white',
},
itemText: {
// fontSize: 14, // Cannot currently set fontsize since the bow surrounding the label has a fixed size
},
});
class ActionButtonComponent extends React.Component {
constructor() {
super();
this.state = {
buttonIndex: 0,
};
this.renderIconMultiStates = this.renderIconMultiStates.bind(this);
this.renderIcon = this.renderIcon.bind(this);
}
UNSAFE_componentWillReceiveProps(newProps) {
if ('buttonIndex' in newProps) {
this.setState({ buttonIndex: newProps.buttonIndex });
}
}
async newNoteNavigate(folderId, isTodo) {
const newNote = await Note.save({
parent_id: folderId,
is_todo: isTodo ? 1 : 0,
}, { provisional: true });
this.props.dispatch({
type: 'NAV_GO',
routeName: 'Note',
noteId: newNote.id,
});
}
newTodo_press() {
this.newNoteNavigate(this.props.parentFolderId, true);
}
newNote_press() {
this.newNoteNavigate(this.props.parentFolderId, false);
}
renderIconMultiStates() {
const button = this.props.buttons[this.state.buttonIndex];
return <Icon
name={button.icon}
style={styles.actionButtonIcon}
accessibilityLabel={button.title}
/>;
}
renderIcon() {
const mainButton = this.props.mainButton ? this.props.mainButton : {};
const iconName = mainButton.icon ?? 'md-add';
// Icons don't have alt text by default. We need to add it:
const iconTitle = mainButton.title ?? _('Add new');
// TODO: If the button toggles a sub-menu, state whether the submenu is open
// or closed.
return (
<Icon
name={iconName}
style={styles.actionButtonIcon}
accessibilityLabel={iconTitle}
/>
);
}
render() {
const buttons = this.props.buttons ? this.props.buttons : [];
if (this.props.addFolderNoteButtons) {
if (this.props.folders.length) {
buttons.push({
title: _('New to-do'),
onPress: () => {
this.newTodo_press();
},
color: '#9b59b6',
icon: 'md-checkbox-outline',
});
buttons.push({
title: _('New note'),
onPress: () => {
this.newNote_press();
},
color: '#9b59b6',
icon: 'md-document',
});
}
}
const buttonComps = [];
for (let i = 0; i < buttons.length; i++) {
const button = buttons[i];
const buttonTitle = button.title ? button.title : '';
const key = `${buttonTitle.replace(/\s/g, '_')}_${button.icon}`;
buttonComps.push(
// TODO: By default, ReactNativeActionButton also adds a title, which is focusable
// by the screen reader. As such, each item currently is double-focusable
<ReactNativeActionButton.Item key={key} buttonColor={button.color} title={buttonTitle} onPress={button.onPress}>
<Icon
name={button.icon}
style={styles.actionButtonIcon}
accessibilityLabel={buttonTitle}
/>
</ReactNativeActionButton.Item>
);
}
if (!buttonComps.length && !this.props.mainButton) {
return null;
}
if (this.props.multiStates) {
if (!this.props.buttons || !this.props.buttons.length) throw new Error('Multi-state button requires at least one state');
if (this.state.buttonIndex < 0 || this.state.buttonIndex >= this.props.buttons.length) throw new Error(`Button index out of bounds: ${this.state.buttonIndex}/${this.props.buttons.length}`);
const button = this.props.buttons[this.state.buttonIndex];
return (
<ReactNativeActionButton
renderIcon={this.renderIconMultiStates}
buttonColor="rgba(231,76,60,1)"
onPress={() => {
button.onPress();
}}
/>
);
} else {
return (
<ReactNativeActionButton textStyle={styles.itemText} renderIcon={this.renderIcon} buttonColor="rgba(231,76,60,1)" onPress={function() {}}>
{buttonComps}
</ReactNativeActionButton>
);
}
}
}
const ActionButton = connect(state => {
return {
folders: state.folders,
locale: state.settings.locale,
};
})(ActionButtonComponent);
module.exports = { ActionButton };

View File

@ -22,7 +22,7 @@ const md5 = require('md5');
const { BackButtonService } = require('../../services/back-button.js');
import NavService from '@joplin/lib/services/NavService';
import BaseModel from '@joplin/lib/BaseModel';
const { ActionButton } = require('../action-button.js');
import ActionButton from '../ActionButton';
const { fileExtension, safeFileExtension } = require('@joplin/lib/path-utils');
const mimeUtils = require('@joplin/lib/mime-utils.js').mime;
import ScreenHeader from '../ScreenHeader';
@ -1145,21 +1145,19 @@ class NoteScreenComponent extends BaseScreenComponent {
}
const renderActionButton = () => {
const buttons = [];
buttons.push({
title: _('Edit'),
const editButton = {
label: _('Edit'),
icon: 'md-create',
onPress: () => {
this.setState({ mode: 'edit' });
this.doFocusUpdate_ = true;
},
});
};
if (this.state.mode === 'edit') return null;
return <ActionButton multiStates={true} buttons={buttons} buttonIndex={0} />;
return <ActionButton mainButton={editButton} />;
};
const actionButtonComp = renderActionButton();

View File

@ -11,7 +11,7 @@ const Setting = require('@joplin/lib/models/Setting').default;
const { themeStyle } = require('../global-style.js');
const { ScreenHeader } = require('../ScreenHeader');
const { _ } = require('@joplin/lib/locale');
const { ActionButton } = require('../action-button.js');
const ActionButton = require('../ActionButton').default;
const { dialogs } = require('../../utils/dialogs.js');
const DialogBox = require('react-native-dialogbox').default;
const { BaseScreenComponent } = require('../base-screen.js');
@ -179,6 +179,19 @@ class NotesScreenComponent extends BaseScreenComponent {
});
}
newNoteNavigate = async (folderId, isTodo) => {
const newNote = await Note.save({
parent_id: folderId,
is_todo: isTodo ? 1 : 0,
}, { provisional: true });
this.props.dispatch({
type: 'NAV_GO',
routeName: 'Note',
noteId: newNote.id,
});
};
parentItem(props = null) {
if (!props) props = this.props;
@ -238,7 +251,35 @@ class NotesScreenComponent extends BaseScreenComponent {
const addFolderNoteButtons = !!buttonFolderId;
const thisComp = this;
const actionButtonComp = this.props.noteSelectionEnabled || !this.props.visible ? null : <ActionButton addFolderNoteButtons={addFolderNoteButtons} parentFolderId={buttonFolderId}></ActionButton>;
const makeActionButtonComp = () => {
if (addFolderNoteButtons && this.props.folders.length > 0) {
const buttons = [];
buttons.push({
label: _('New to-do'),
onPress: () => {
const isTodo = true;
this.newNoteNavigate(buttonFolderId, isTodo);
},
color: '#9b59b6',
icon: 'md-checkbox-outline',
});
buttons.push({
label: _('New note'),
onPress: () => {
const isTodo = false;
this.newNoteNavigate(buttonFolderId, isTodo);
},
color: '#9b59b6',
icon: 'md-document',
});
return <ActionButton buttons={buttons}/>;
}
return null;
};
const actionButtonComp = this.props.noteSelectionEnabled || !this.props.visible ? null : makeActionButtonComp();
return (
<View style={rootStyle}>

View File

@ -248,6 +248,12 @@ PODS:
- React-Core
- react-native-rsa-native (2.0.5):
- React
- react-native-safe-area-context (4.4.1):
- RCT-Folly
- RCTRequired
- RCTTypeSafety
- React-Core
- ReactCommon/turbomodule/core
- react-native-slider (4.4.0):
- React-Core
- react-native-sqlite-storage (6.0.1):
@ -376,6 +382,7 @@ DEPENDENCIES:
- react-native-image-resizer (from `../node_modules/react-native-image-resizer`)
- "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)"
- react-native-rsa-native (from `../node_modules/react-native-rsa-native`)
- react-native-safe-area-context (from `../node_modules/react-native-safe-area-context`)
- "react-native-slider (from `../node_modules/@react-native-community/slider`)"
- react-native-sqlite-storage (from `../node_modules/react-native-sqlite-storage`)
- react-native-version-info (from `../node_modules/react-native-version-info`)
@ -469,6 +476,8 @@ EXTERNAL SOURCES:
:path: "../node_modules/@react-native-community/netinfo"
react-native-rsa-native:
:path: "../node_modules/react-native-rsa-native"
react-native-safe-area-context:
:path: "../node_modules/react-native-safe-area-context"
react-native-slider:
:path: "../node_modules/@react-native-community/slider"
react-native-sqlite-storage:
@ -530,10 +539,10 @@ SPEC CHECKSUMS:
FBLazyVector: 2b47ff52037bd9ae07cc9b051c9975797814b736
FBReactNativeSpec: 0e0d384ef17a33b385f13f0c7f97702c7cd17858
fmt: ff9d55029c625d3757ed641535fd4a75fedc7ce9
glog: 476ee3e89abb49e07f822b48323c51c57124b572
glog: 5337263514dd6f09803962437687240c5dc39aa4
JoplinCommonShareExtension: a8b60b02704d85a7305627912c0240e94af78db7
JoplinRNShareExtension: 485f3e6dad83b7b77f1572eabc249f869ee55c02
RCT-Folly: 4d8508a426467c48885f1151029bc15fa5d7b3b8
RCT-Folly: a21c126816d8025b547704b777a2ba552f3d9fa9
RCTRequired: 0f06b6068f530932d10e1a01a5352fad4eaacb74
RCTTypeSafety: b0ee81f10ef1b7d977605a2b266823dabd565e65
React: 3becd12bd51ea8a43bdde7e09d0f40fba7820e03
@ -556,6 +565,7 @@ SPEC CHECKSUMS:
react-native-image-resizer: d9fb629a867335bdc13230ac2a58702bb8c8828f
react-native-netinfo: 2517ad504b3d303e90d7a431b0fcaef76d207983
react-native-rsa-native: 12132eb627797529fdb1f0d22fd0f8f9678df64a
react-native-safe-area-context: 99b24a0c5acd0d5dcac2b1a7f18c49ea317be99a
react-native-slider: d2938a12c4e439a227c70eec65d119136eb4aeb5
react-native-sqlite-storage: f6d515e1c446d1e6d026aa5352908a25d4de3261
react-native-version-info: a106f23009ac0db4ee00de39574eb546682579b9

View File

@ -51,9 +51,11 @@
"react-native-image-picker": "4.10.3",
"react-native-image-resizer": "1.4.5",
"react-native-modal-datetime-picker": "14.0.1",
"react-native-paper": "5.0.2",
"react-native-popup-menu": "0.16.1",
"react-native-quick-actions": "0.3.13",
"react-native-rsa-native": "2.0.5",
"react-native-safe-area-context": "4.4.1",
"react-native-securerandom": "1.0.1",
"react-native-share": "8.1.0",
"react-native-side-menu-updated": "1.3.2",

View File

@ -36,6 +36,7 @@ const DropdownAlert = require('react-native-dropdownalert').default;
const AlarmServiceDriver = require('./services/AlarmServiceDriver').default;
const SafeAreaView = require('./components/SafeAreaView');
const { connect, Provider } = require('react-redux');
import { Provider as PaperProvider, MD2DarkTheme as PaperDarkTheme, MD2LightTheme as PaperLightTheme } from 'react-native-paper';
const { BackButtonService } = require('./services/back-button.js');
import NavService from '@joplin/lib/services/NavService';
import { createStore, applyMiddleware } from 'redux';
@ -108,6 +109,7 @@ import SyncTargetNone from '@joplin/lib/SyncTargetNone';
import { setRSA } from '@joplin/lib/services/e2ee/ppk';
import RSA from './services/e2ee/RSA.react-native';
import { runIntegrationTests } from '@joplin/lib/services/e2ee/ppkTestUtils';
import { Theme, ThemeAppearance } from '@joplin/lib/themes/type';
import { AppState } from './utils/types';
import sensorInfo from './components/biometrics/sensorInfo';
@ -881,7 +883,7 @@ class AppComponent extends React.Component {
public render() {
if (this.props.appState !== 'ready') return null;
const theme = themeStyle(this.props.themeId);
const theme: Theme = themeStyle(this.props.themeId);
let sideMenuContent = null;
let menuPosition = 'left';
@ -912,7 +914,7 @@ class AppComponent extends React.Component {
// const statusBarStyle = theme.appearance === 'light-content';
const statusBarStyle = 'light-content';
return (
const mainContent = (
<View style={{ flex: 1, backgroundColor: theme.backgroundColor }}>
<SideMenu
menu={sideMenuContent}
@ -945,6 +947,24 @@ class AppComponent extends React.Component {
</SideMenu>
</View>
);
const paperTheme = theme.appearance === ThemeAppearance.Dark ? PaperDarkTheme : PaperLightTheme;
// Wrap everything in a PaperProvider -- this allows using components from react-native-paper
return (
<PaperProvider theme={{
...paperTheme,
version: 2,
colors: {
...paperTheme.colors,
primary: theme.backgroundColor,
accent: theme.backgroundColor2,
},
}}>
{mainContent}
</PaperProvider>
);
}
}

View File

@ -7,6 +7,7 @@ import BaseModel from './BaseModel';
import { Store } from 'redux';
import { ProfileConfig } from './services/profileConfig/types';
import * as ArrayUtils from './ArrayUtils';
import { FolderEntity } from './services/database/types';
const fastDeepEqual = require('fast-deep-equal');
const { ALL_NOTES_FILTER_ID } = require('./reserved-ids');
const { createSelectorCreator, defaultMemoize } = require('reselect');
@ -55,7 +56,7 @@ export interface State {
noteSelectionEnabled?: boolean;
notesSource: string;
notesParentType: string;
folders: any[];
folders: FolderEntity[];
tags: any[];
masterKeys: any[];
notLoadedMasterKeys: string[];

View File

@ -3150,6 +3150,18 @@ __metadata:
languageName: node
linkType: hard
"@callstack/react-theme-provider@npm:^3.0.8":
version: 3.0.8
resolution: "@callstack/react-theme-provider@npm:3.0.8"
dependencies:
deepmerge: ^3.2.0
hoist-non-react-statics: ^3.3.0
peerDependencies:
react: ">=16.3.0"
checksum: 6077a4795aea4eb06a2a2ffe5cf299c3fdcba56530aa68eba5c3ac0728ce02be2f6e9e71278be303065cb7fbe252c35639538477d5d05ee14f6325d550c8c696
languageName: node
linkType: hard
"@cloudcmd/create-element@npm:^2.0.0":
version: 2.0.2
resolution: "@cloudcmd/create-element@npm:2.0.2"
@ -4732,9 +4744,11 @@ __metadata:
react-native-image-picker: 4.10.3
react-native-image-resizer: 1.4.5
react-native-modal-datetime-picker: 14.0.1
react-native-paper: 5.0.2
react-native-popup-menu: 0.16.1
react-native-quick-actions: 0.3.13
react-native-rsa-native: 2.0.5
react-native-safe-area-context: 4.4.1
react-native-securerandom: 1.0.1
react-native-share: 8.1.0
react-native-side-menu-updated: 1.3.2
@ -11994,7 +12008,7 @@ __metadata:
languageName: node
linkType: hard
"color@npm:3.2.1":
"color@npm:3.2.1, color@npm:^3.1.2":
version: 3.2.1
resolution: "color@npm:3.2.1"
dependencies:
@ -27687,6 +27701,22 @@ __metadata:
languageName: node
linkType: hard
"react-native-paper@npm:5.0.2":
version: 5.0.2
resolution: "react-native-paper@npm:5.0.2"
dependencies:
"@callstack/react-theme-provider": ^3.0.8
color: ^3.1.2
use-event-callback: ^0.1.0
peerDependencies:
react: "*"
react-native: "*"
react-native-safe-area-context: "*"
react-native-vector-icons: "*"
checksum: 46481e3db2b297f2f02d1d05710d7ab329f901acc5a663e4c81fb7db83534e5d63067e9287bb396703e50d955ddfb8c41d7546cbd1ea2825a6888fd2e0fa80de
languageName: node
linkType: hard
"react-native-popup-menu@npm:0.16.1":
version: 0.16.1
resolution: "react-native-popup-menu@npm:0.16.1"
@ -27708,6 +27738,16 @@ __metadata:
languageName: node
linkType: hard
"react-native-safe-area-context@npm:4.4.1":
version: 4.4.1
resolution: "react-native-safe-area-context@npm:4.4.1"
peerDependencies:
react: "*"
react-native: "*"
checksum: ef7c41ea59a34b114c6481fb130e66ef85e8d5b88acb46279131367761ca9fbf22cd310fe613f49b6c9b56dbd83e044be640f0532eda1d3856bf708e96335a35
languageName: node
linkType: hard
"react-native-securerandom@npm:1.0.1":
version: 1.0.1
resolution: "react-native-securerandom@npm:1.0.1"
@ -33141,6 +33181,15 @@ __metadata:
languageName: node
linkType: hard
"use-event-callback@npm:^0.1.0":
version: 0.1.0
resolution: "use-event-callback@npm:0.1.0"
peerDependencies:
react: ">=16.8"
checksum: 1e15fb21306c74f877e9d57686546c363165429412dcb9260254d2dd8f56692cb01ba2162f9169e6fc15b01cf3921b9cd8a9c60cf777d0143afcee92c1a7976a
languageName: node
linkType: hard
"use-isomorphic-layout-effect@npm:^1.1.2":
version: 1.1.2
resolution: "use-isomorphic-layout-effect@npm:1.1.2"