You've already forked joplin
mirror of
https://github.com/laurent22/joplin.git
synced 2025-12-11 23:17:19 +02:00
Compare commits
15 Commits
note-list-
...
personaliz
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
44a7bf3b2b | ||
|
|
b60a94bb6e | ||
|
|
c0d0c16d7a | ||
|
|
d3ca725395 | ||
|
|
d14ec824b4 | ||
|
|
f7a003fbf1 | ||
|
|
887e03e338 | ||
|
|
b169283ea8 | ||
|
|
b0c898839d | ||
|
|
54aa68baad | ||
|
|
3b7fdd5019 | ||
|
|
63a9c571bb | ||
|
|
22a10b0abb | ||
|
|
ad5a21193e | ||
|
|
7af794d8c6 |
@@ -864,6 +864,9 @@ packages/app-desktop/utils/markupLanguageUtils.js.map
|
|||||||
packages/app-mobile/PluginAssetsLoader.d.ts
|
packages/app-mobile/PluginAssetsLoader.d.ts
|
||||||
packages/app-mobile/PluginAssetsLoader.js
|
packages/app-mobile/PluginAssetsLoader.js
|
||||||
packages/app-mobile/PluginAssetsLoader.js.map
|
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.d.ts
|
||||||
packages/app-mobile/components/BackButtonDialogBox.js
|
packages/app-mobile/components/BackButtonDialogBox.js
|
||||||
packages/app-mobile/components/BackButtonDialogBox.js.map
|
packages/app-mobile/components/BackButtonDialogBox.js.map
|
||||||
@@ -981,6 +984,15 @@ packages/app-mobile/components/NoteEditor/SelectionFormatting.js.map
|
|||||||
packages/app-mobile/components/NoteEditor/types.d.ts
|
packages/app-mobile/components/NoteEditor/types.d.ts
|
||||||
packages/app-mobile/components/NoteEditor/types.js
|
packages/app-mobile/components/NoteEditor/types.js
|
||||||
packages/app-mobile/components/NoteEditor/types.js.map
|
packages/app-mobile/components/NoteEditor/types.js.map
|
||||||
|
packages/app-mobile/components/ProfileSwitcher/ProfileEditor.d.ts
|
||||||
|
packages/app-mobile/components/ProfileSwitcher/ProfileEditor.js
|
||||||
|
packages/app-mobile/components/ProfileSwitcher/ProfileEditor.js.map
|
||||||
|
packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.d.ts
|
||||||
|
packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.js
|
||||||
|
packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.js.map
|
||||||
|
packages/app-mobile/components/ProfileSwitcher/useProfileConfig.d.ts
|
||||||
|
packages/app-mobile/components/ProfileSwitcher/useProfileConfig.js
|
||||||
|
packages/app-mobile/components/ProfileSwitcher/useProfileConfig.js.map
|
||||||
packages/app-mobile/components/ScreenHeader.d.ts
|
packages/app-mobile/components/ScreenHeader.d.ts
|
||||||
packages/app-mobile/components/ScreenHeader.js
|
packages/app-mobile/components/ScreenHeader.js
|
||||||
packages/app-mobile/components/ScreenHeader.js.map
|
packages/app-mobile/components/ScreenHeader.js.map
|
||||||
@@ -990,6 +1002,9 @@ packages/app-mobile/components/SelectDateTimeDialog.js.map
|
|||||||
packages/app-mobile/components/SideMenu.d.ts
|
packages/app-mobile/components/SideMenu.d.ts
|
||||||
packages/app-mobile/components/SideMenu.js
|
packages/app-mobile/components/SideMenu.js
|
||||||
packages/app-mobile/components/SideMenu.js.map
|
packages/app-mobile/components/SideMenu.js.map
|
||||||
|
packages/app-mobile/components/TextInput.d.ts
|
||||||
|
packages/app-mobile/components/TextInput.js
|
||||||
|
packages/app-mobile/components/TextInput.js.map
|
||||||
packages/app-mobile/components/biometrics/BiometricPopup.d.ts
|
packages/app-mobile/components/biometrics/BiometricPopup.d.ts
|
||||||
packages/app-mobile/components/biometrics/BiometricPopup.js
|
packages/app-mobile/components/biometrics/BiometricPopup.js
|
||||||
packages/app-mobile/components/biometrics/BiometricPopup.js.map
|
packages/app-mobile/components/biometrics/BiometricPopup.js.map
|
||||||
@@ -1035,6 +1050,9 @@ packages/app-mobile/services/AlarmServiceDriver.ios.js.map
|
|||||||
packages/app-mobile/services/e2ee/RSA.react-native.d.ts
|
packages/app-mobile/services/e2ee/RSA.react-native.d.ts
|
||||||
packages/app-mobile/services/e2ee/RSA.react-native.js
|
packages/app-mobile/services/e2ee/RSA.react-native.js
|
||||||
packages/app-mobile/services/e2ee/RSA.react-native.js.map
|
packages/app-mobile/services/e2ee/RSA.react-native.js.map
|
||||||
|
packages/app-mobile/services/profiles/index.d.ts
|
||||||
|
packages/app-mobile/services/profiles/index.js
|
||||||
|
packages/app-mobile/services/profiles/index.js.map
|
||||||
packages/app-mobile/setupQuickActions.d.ts
|
packages/app-mobile/setupQuickActions.d.ts
|
||||||
packages/app-mobile/setupQuickActions.js
|
packages/app-mobile/setupQuickActions.js
|
||||||
packages/app-mobile/setupQuickActions.js.map
|
packages/app-mobile/setupQuickActions.js.map
|
||||||
@@ -1053,6 +1071,9 @@ packages/app-mobile/utils/TlsUtils.js.map
|
|||||||
packages/app-mobile/utils/checkPermissions.d.ts
|
packages/app-mobile/utils/checkPermissions.d.ts
|
||||||
packages/app-mobile/utils/checkPermissions.js
|
packages/app-mobile/utils/checkPermissions.js
|
||||||
packages/app-mobile/utils/checkPermissions.js.map
|
packages/app-mobile/utils/checkPermissions.js.map
|
||||||
|
packages/app-mobile/utils/createRootStyle.d.ts
|
||||||
|
packages/app-mobile/utils/createRootStyle.js
|
||||||
|
packages/app-mobile/utils/createRootStyle.js.map
|
||||||
packages/app-mobile/utils/debounce.d.ts
|
packages/app-mobile/utils/debounce.d.ts
|
||||||
packages/app-mobile/utils/debounce.js
|
packages/app-mobile/utils/debounce.js
|
||||||
packages/app-mobile/utils/debounce.js.map
|
packages/app-mobile/utils/debounce.js.map
|
||||||
|
|||||||
25
.gitignore
vendored
25
.gitignore
vendored
@@ -852,6 +852,9 @@ packages/app-desktop/utils/markupLanguageUtils.js.map
|
|||||||
packages/app-mobile/PluginAssetsLoader.d.ts
|
packages/app-mobile/PluginAssetsLoader.d.ts
|
||||||
packages/app-mobile/PluginAssetsLoader.js
|
packages/app-mobile/PluginAssetsLoader.js
|
||||||
packages/app-mobile/PluginAssetsLoader.js.map
|
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.d.ts
|
||||||
packages/app-mobile/components/BackButtonDialogBox.js
|
packages/app-mobile/components/BackButtonDialogBox.js
|
||||||
packages/app-mobile/components/BackButtonDialogBox.js.map
|
packages/app-mobile/components/BackButtonDialogBox.js.map
|
||||||
@@ -969,6 +972,15 @@ packages/app-mobile/components/NoteEditor/SelectionFormatting.js.map
|
|||||||
packages/app-mobile/components/NoteEditor/types.d.ts
|
packages/app-mobile/components/NoteEditor/types.d.ts
|
||||||
packages/app-mobile/components/NoteEditor/types.js
|
packages/app-mobile/components/NoteEditor/types.js
|
||||||
packages/app-mobile/components/NoteEditor/types.js.map
|
packages/app-mobile/components/NoteEditor/types.js.map
|
||||||
|
packages/app-mobile/components/ProfileSwitcher/ProfileEditor.d.ts
|
||||||
|
packages/app-mobile/components/ProfileSwitcher/ProfileEditor.js
|
||||||
|
packages/app-mobile/components/ProfileSwitcher/ProfileEditor.js.map
|
||||||
|
packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.d.ts
|
||||||
|
packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.js
|
||||||
|
packages/app-mobile/components/ProfileSwitcher/ProfileSwitcher.js.map
|
||||||
|
packages/app-mobile/components/ProfileSwitcher/useProfileConfig.d.ts
|
||||||
|
packages/app-mobile/components/ProfileSwitcher/useProfileConfig.js
|
||||||
|
packages/app-mobile/components/ProfileSwitcher/useProfileConfig.js.map
|
||||||
packages/app-mobile/components/ScreenHeader.d.ts
|
packages/app-mobile/components/ScreenHeader.d.ts
|
||||||
packages/app-mobile/components/ScreenHeader.js
|
packages/app-mobile/components/ScreenHeader.js
|
||||||
packages/app-mobile/components/ScreenHeader.js.map
|
packages/app-mobile/components/ScreenHeader.js.map
|
||||||
@@ -978,6 +990,9 @@ packages/app-mobile/components/SelectDateTimeDialog.js.map
|
|||||||
packages/app-mobile/components/SideMenu.d.ts
|
packages/app-mobile/components/SideMenu.d.ts
|
||||||
packages/app-mobile/components/SideMenu.js
|
packages/app-mobile/components/SideMenu.js
|
||||||
packages/app-mobile/components/SideMenu.js.map
|
packages/app-mobile/components/SideMenu.js.map
|
||||||
|
packages/app-mobile/components/TextInput.d.ts
|
||||||
|
packages/app-mobile/components/TextInput.js
|
||||||
|
packages/app-mobile/components/TextInput.js.map
|
||||||
packages/app-mobile/components/biometrics/BiometricPopup.d.ts
|
packages/app-mobile/components/biometrics/BiometricPopup.d.ts
|
||||||
packages/app-mobile/components/biometrics/BiometricPopup.js
|
packages/app-mobile/components/biometrics/BiometricPopup.js
|
||||||
packages/app-mobile/components/biometrics/BiometricPopup.js.map
|
packages/app-mobile/components/biometrics/BiometricPopup.js.map
|
||||||
@@ -1023,6 +1038,9 @@ packages/app-mobile/services/AlarmServiceDriver.ios.js.map
|
|||||||
packages/app-mobile/services/e2ee/RSA.react-native.d.ts
|
packages/app-mobile/services/e2ee/RSA.react-native.d.ts
|
||||||
packages/app-mobile/services/e2ee/RSA.react-native.js
|
packages/app-mobile/services/e2ee/RSA.react-native.js
|
||||||
packages/app-mobile/services/e2ee/RSA.react-native.js.map
|
packages/app-mobile/services/e2ee/RSA.react-native.js.map
|
||||||
|
packages/app-mobile/services/profiles/index.d.ts
|
||||||
|
packages/app-mobile/services/profiles/index.js
|
||||||
|
packages/app-mobile/services/profiles/index.js.map
|
||||||
packages/app-mobile/setupQuickActions.d.ts
|
packages/app-mobile/setupQuickActions.d.ts
|
||||||
packages/app-mobile/setupQuickActions.js
|
packages/app-mobile/setupQuickActions.js
|
||||||
packages/app-mobile/setupQuickActions.js.map
|
packages/app-mobile/setupQuickActions.js.map
|
||||||
@@ -1041,6 +1059,9 @@ packages/app-mobile/utils/TlsUtils.js.map
|
|||||||
packages/app-mobile/utils/checkPermissions.d.ts
|
packages/app-mobile/utils/checkPermissions.d.ts
|
||||||
packages/app-mobile/utils/checkPermissions.js
|
packages/app-mobile/utils/checkPermissions.js
|
||||||
packages/app-mobile/utils/checkPermissions.js.map
|
packages/app-mobile/utils/checkPermissions.js.map
|
||||||
|
packages/app-mobile/utils/createRootStyle.d.ts
|
||||||
|
packages/app-mobile/utils/createRootStyle.js
|
||||||
|
packages/app-mobile/utils/createRootStyle.js.map
|
||||||
packages/app-mobile/utils/debounce.d.ts
|
packages/app-mobile/utils/debounce.d.ts
|
||||||
packages/app-mobile/utils/debounce.js
|
packages/app-mobile/utils/debounce.js
|
||||||
packages/app-mobile/utils/debounce.js.map
|
packages/app-mobile/utils/debounce.js.map
|
||||||
@@ -2368,6 +2389,4 @@ packages/tools/website/utils/types.d.ts
|
|||||||
packages/tools/website/utils/types.js
|
packages/tools/website/utils/types.js
|
||||||
packages/tools/website/utils/types.js.map
|
packages/tools/website/utils/types.js.map
|
||||||
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
|
# AUTO-GENERATED - EXCLUDED TYPESCRIPT BUILD
|
||||||
packages/app-mobile/components/get-responsive-value.test.js
|
|
||||||
packages/app-mobile/components/get-responsive-value.test.js
|
|
||||||
packages/app-mobile/components/get-responsive-value.test.js
|
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import androidx.multidex.MultiDex;
|
|||||||
|
|
||||||
import com.facebook.react.PackageList;
|
import com.facebook.react.PackageList;
|
||||||
import com.facebook.react.ReactApplication;
|
import com.facebook.react.ReactApplication;
|
||||||
|
import com.oblador.vectoricons.VectorIconsPackage;
|
||||||
import com.facebook.react.ReactInstanceManager;
|
import com.facebook.react.ReactInstanceManager;
|
||||||
import com.facebook.react.ReactNativeHost;
|
import com.facebook.react.ReactNativeHost;
|
||||||
import com.facebook.react.ReactPackage;
|
import com.facebook.react.ReactPackage;
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
rootProject.name = 'Joplin'
|
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)
|
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesSettingsGradle(settings)
|
||||||
include ':app'
|
include ':app'
|
||||||
includeBuild('../node_modules/react-native-gradle-plugin')
|
includeBuild('../node_modules/react-native-gradle-plugin')
|
||||||
|
|||||||
73
packages/app-mobile/components/ActionButton.tsx
Normal file
73
packages/app-mobile/components/ActionButton.tsx
Normal 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;
|
||||||
102
packages/app-mobile/components/ProfileSwitcher/ProfileEditor.tsx
Normal file
102
packages/app-mobile/components/ProfileSwitcher/ProfileEditor.tsx
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
const React = require('react');
|
||||||
|
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||||
|
const { View, StyleSheet } = require('react-native');
|
||||||
|
import createRootStyle from '../../utils/createRootStyle';
|
||||||
|
import ScreenHeader from '../ScreenHeader';
|
||||||
|
import { _ } from '@joplin/lib/locale';
|
||||||
|
import { loadProfileConfig, saveProfileConfig } from '../../services/profiles';
|
||||||
|
import { createNewProfile } from '@joplin/lib/services/profileConfig';
|
||||||
|
import useProfileConfig from './useProfileConfig';
|
||||||
|
const { TextInput } = require('react-native-paper');
|
||||||
|
|
||||||
|
interface NavigationState {
|
||||||
|
profileId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Navigation {
|
||||||
|
state: NavigationState;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
themeId: number;
|
||||||
|
dispatch: Function;
|
||||||
|
navigation: Navigation;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyle = (themeId: number) => {
|
||||||
|
return useMemo(() => {
|
||||||
|
return StyleSheet.create({
|
||||||
|
...createRootStyle(themeId),
|
||||||
|
});
|
||||||
|
}, [themeId]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (props: Props) => {
|
||||||
|
const profileId = props.navigation.state?.profileId;
|
||||||
|
const isNew = !profileId;
|
||||||
|
const profileConfig = useProfileConfig();
|
||||||
|
|
||||||
|
const style = useStyle(props.themeId);
|
||||||
|
const [name, setName] = useState('');
|
||||||
|
|
||||||
|
const profile = !isNew && profileConfig ? profileConfig.profiles.find(p => p.id === profileId) : null;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!profile) return;
|
||||||
|
setName(profile.name);
|
||||||
|
}, [profile]);
|
||||||
|
|
||||||
|
const onSaveButtonPress = useCallback(async () => {
|
||||||
|
if (isNew) {
|
||||||
|
const profileConfig = await loadProfileConfig();
|
||||||
|
const result = createNewProfile(profileConfig, name);
|
||||||
|
await saveProfileConfig(result.newConfig);
|
||||||
|
} else {
|
||||||
|
const newProfiles = profileConfig.profiles.map(p => {
|
||||||
|
if (p.id === profile.id) {
|
||||||
|
return {
|
||||||
|
...profile,
|
||||||
|
name,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return p;
|
||||||
|
});
|
||||||
|
|
||||||
|
const newProfileConfig = {
|
||||||
|
...profileConfig,
|
||||||
|
profiles: newProfiles,
|
||||||
|
};
|
||||||
|
|
||||||
|
await saveProfileConfig(newProfileConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
props.dispatch({
|
||||||
|
type: 'NAV_BACK',
|
||||||
|
});
|
||||||
|
}, [name, isNew, profileConfig, profile, props.dispatch]);
|
||||||
|
|
||||||
|
const isModified = useMemo(() => {
|
||||||
|
if (isNew) return true;
|
||||||
|
if (!profile) return false;
|
||||||
|
return profile.name !== name;
|
||||||
|
}, [isNew, profile, name]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={style.root}>
|
||||||
|
<ScreenHeader
|
||||||
|
title={isNew ? _('Create new profile...') : _('Edit profile')}
|
||||||
|
onSaveButtonPress={onSaveButtonPress}
|
||||||
|
saveButtonDisabled={!isModified}
|
||||||
|
showSaveButton={true}
|
||||||
|
showSideMenuButton={false}
|
||||||
|
showSearchButton={false}
|
||||||
|
/>
|
||||||
|
<View style={{}}>
|
||||||
|
<TextInput label={_('Profile name')}
|
||||||
|
value={name}
|
||||||
|
onChangeText={(text: string) => setName(text)}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,176 @@
|
|||||||
|
const React = require('react');
|
||||||
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
|
const { View, FlatList, StyleSheet } = require('react-native');
|
||||||
|
import createRootStyle from '../../utils/createRootStyle';
|
||||||
|
import ScreenHeader from '../ScreenHeader';
|
||||||
|
const { FAB, List } = require('react-native-paper');
|
||||||
|
import { Profile } from '@joplin/lib/services/profileConfig/types';
|
||||||
|
import useProfileConfig from './useProfileConfig';
|
||||||
|
import { Alert } from 'react-native';
|
||||||
|
import { _ } from '@joplin/lib/locale';
|
||||||
|
import { deleteProfileById } from '@joplin/lib/services/profileConfig';
|
||||||
|
import { saveProfileConfig, switchProfile } from '../../services/profiles';
|
||||||
|
const { themeStyle } = require('../global-style');
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
themeId: number;
|
||||||
|
dispatch: Function;
|
||||||
|
}
|
||||||
|
|
||||||
|
const useStyle = (themeId: number) => {
|
||||||
|
return useMemo(() => {
|
||||||
|
const theme = themeStyle(themeId);
|
||||||
|
|
||||||
|
return StyleSheet.create({
|
||||||
|
...createRootStyle(themeId),
|
||||||
|
fab: {
|
||||||
|
position: 'absolute',
|
||||||
|
margin: 16,
|
||||||
|
right: 0,
|
||||||
|
bottom: 0,
|
||||||
|
},
|
||||||
|
profileListItem: {
|
||||||
|
paddingLeft: theme.margin,
|
||||||
|
paddingRight: theme.margin,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, [themeId]);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default (props: Props) => {
|
||||||
|
const style = useStyle(props.themeId);
|
||||||
|
const [profileConfigTime, setProfileConfigTime] = useState(Date.now());
|
||||||
|
|
||||||
|
const profileConfig = useProfileConfig(profileConfigTime);
|
||||||
|
|
||||||
|
const profiles = useMemo(() => {
|
||||||
|
return profileConfig ? profileConfig.profiles : [];
|
||||||
|
}, [profileConfig]);
|
||||||
|
|
||||||
|
const onProfileItemPress = useCallback(async (profile: Profile) => {
|
||||||
|
const doIt = async () => {
|
||||||
|
try {
|
||||||
|
await switchProfile(profile.id);
|
||||||
|
} catch (error) {
|
||||||
|
Alert.alert(_('Could not switch profile: %s', error.message));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Alert.alert(
|
||||||
|
_('Confirmation'),
|
||||||
|
_('To switch the profile, the app is going to close and you will need to restart it.'),
|
||||||
|
[
|
||||||
|
{
|
||||||
|
text: _('Continue'),
|
||||||
|
onPress: () => doIt(),
|
||||||
|
style: 'default',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: _('Cancel'),
|
||||||
|
onPress: () => {},
|
||||||
|
style: 'cancel',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const onEditProfile = useCallback(async (profileId: string) => {
|
||||||
|
props.dispatch({
|
||||||
|
type: 'NAV_GO',
|
||||||
|
routeName: 'ProfileEditor',
|
||||||
|
profileId: profileId,
|
||||||
|
});
|
||||||
|
}, [props.dispatch]);
|
||||||
|
|
||||||
|
const onDeleteProfile = useCallback(async (profile: Profile) => {
|
||||||
|
const doIt = async () => {
|
||||||
|
try {
|
||||||
|
const newConfig = deleteProfileById(profileConfig, profile.id);
|
||||||
|
await saveProfileConfig(newConfig);
|
||||||
|
setProfileConfigTime(Date.now());
|
||||||
|
} catch (error) {
|
||||||
|
Alert.alert(error.message);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
Alert.alert(
|
||||||
|
_('Delete this profile?'),
|
||||||
|
_('All data, including notes, notebooks and tags will be permanently deleted.'),
|
||||||
|
[
|
||||||
|
{
|
||||||
|
text: _('Delete profile "%s"', profile.name),
|
||||||
|
onPress: () => doIt(),
|
||||||
|
style: 'destructive',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: _('Cancel'),
|
||||||
|
onPress: () => {},
|
||||||
|
style: 'cancel',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}, [profileConfig]);
|
||||||
|
|
||||||
|
const renderProfileItem = (event: any) => {
|
||||||
|
const profile = event.item as Profile;
|
||||||
|
const titleStyle = { fontWeight: profile.id === profileConfig.currentProfileId ? 'bold' : 'normal' };
|
||||||
|
return (
|
||||||
|
<List.Item
|
||||||
|
title={profile.name}
|
||||||
|
style={style.profileListItem}
|
||||||
|
titleStyle={titleStyle}
|
||||||
|
left={() => <List.Icon icon="file-account-outline" />}
|
||||||
|
key={profile.id}
|
||||||
|
profileId={profile.id}
|
||||||
|
onPress={() => { void onProfileItemPress(profile); }}
|
||||||
|
onLongPress={() => {
|
||||||
|
Alert.alert(
|
||||||
|
_('Configuration'),
|
||||||
|
'',
|
||||||
|
[
|
||||||
|
{
|
||||||
|
text: _('Edit'),
|
||||||
|
onPress: () => onEditProfile(profile.id),
|
||||||
|
style: 'default',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: _('Delete'),
|
||||||
|
onPress: () => onDeleteProfile(profile),
|
||||||
|
style: 'default',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: _('Close'),
|
||||||
|
onPress: () => {},
|
||||||
|
style: 'cancel',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={style.root}>
|
||||||
|
<ScreenHeader title={_('Profiles')} showSaveButton={false} showSideMenuButton={false} showSearchButton={false} />
|
||||||
|
<View>
|
||||||
|
<FlatList
|
||||||
|
data={profiles}
|
||||||
|
renderItem={renderProfileItem}
|
||||||
|
keyExtractor={(profile: Profile) => profile.id}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<FAB
|
||||||
|
icon="plus"
|
||||||
|
style={style.fab}
|
||||||
|
onPress={() => {
|
||||||
|
props.dispatch({
|
||||||
|
type: 'NAV_GO',
|
||||||
|
routeName: 'ProfileEditor',
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import useAsyncEffect, { AsyncEffectEvent } from '@joplin/lib/hooks/useAsyncEffect';
|
||||||
|
import { ProfileConfig } from '@joplin/lib/services/profileConfig/types';
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { loadProfileConfig } from '../../services/profiles';
|
||||||
|
|
||||||
|
export default (timestamp: number = 0) => {
|
||||||
|
const [profileConfig, setProfileConfig] = useState<ProfileConfig>(null);
|
||||||
|
|
||||||
|
useAsyncEffect(async (event: AsyncEffectEvent) => {
|
||||||
|
const load = async () => {
|
||||||
|
const r = await loadProfileConfig();
|
||||||
|
if (event.cancelled) return;
|
||||||
|
setProfileConfig(r);
|
||||||
|
};
|
||||||
|
|
||||||
|
void load();
|
||||||
|
}, [timestamp]);
|
||||||
|
|
||||||
|
return profileConfig;
|
||||||
|
};
|
||||||
37
packages/app-mobile/components/TextInput.tsx
Normal file
37
packages/app-mobile/components/TextInput.tsx
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
const React = require('react');
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { themeStyle } from '@joplin/lib/theme';
|
||||||
|
import { TextInput, TextInputProps, StyleSheet } from 'react-native';
|
||||||
|
|
||||||
|
interface Props extends TextInputProps {
|
||||||
|
themeId: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (props: Props) => {
|
||||||
|
const theme = themeStyle(props.themeId);
|
||||||
|
const finalProps = { ...props };
|
||||||
|
|
||||||
|
if (!('placeholderTextColor' in finalProps)) finalProps.placeholderTextColor = theme.colorFaded;
|
||||||
|
if (!('underlineColorAndroid' in finalProps)) finalProps.underlineColorAndroid = theme.dividerColor;
|
||||||
|
if (!('selectionColor' in finalProps)) finalProps.selectionColor = theme.textSelectionColor;
|
||||||
|
if (!('keyboardAppearance' in finalProps)) finalProps.keyboardAppearance = theme.keyboardAppearance;
|
||||||
|
if (!('style' in finalProps)) finalProps.style = {};
|
||||||
|
|
||||||
|
const defaultStyle = useMemo(() => {
|
||||||
|
const theme = themeStyle(finalProps.themeId);
|
||||||
|
|
||||||
|
return StyleSheet.create({
|
||||||
|
textInput: {
|
||||||
|
color: theme.color,
|
||||||
|
paddingLeft: 14,
|
||||||
|
paddingRight: 14,
|
||||||
|
paddingTop: 12,
|
||||||
|
paddingBottom: 12,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}, [finalProps.themeId]);
|
||||||
|
|
||||||
|
finalProps.style = [defaultStyle.textInput, finalProps.style];
|
||||||
|
|
||||||
|
return <TextInput {...finalProps} />;
|
||||||
|
};
|
||||||
@@ -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 };
|
|
||||||
@@ -71,7 +71,7 @@ class AppNavComponent extends Component {
|
|||||||
<KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'padding' : null} style={style}>
|
<KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'padding' : null} style={style}>
|
||||||
<NotesScreen visible={notesScreenVisible} navigation={{ state: route }} />
|
<NotesScreen visible={notesScreenVisible} navigation={{ state: route }} />
|
||||||
{searchScreenLoaded && <SearchScreen visible={searchScreenVisible} navigation={{ state: route }} />}
|
{searchScreenLoaded && <SearchScreen visible={searchScreenVisible} navigation={{ state: route }} />}
|
||||||
{!notesScreenVisible && !searchScreenVisible && <Screen navigation={{ state: route }} />}
|
{!notesScreenVisible && !searchScreenVisible && <Screen navigation={{ state: route }} themeId={this.props.themeId} dispatch={this.props.dispatch} />}
|
||||||
<View style={{ height: this.state.autoCompletionBarExtraHeight }} />
|
<View style={{ height: this.state.autoCompletionBarExtraHeight }} />
|
||||||
</KeyboardAvoidingView>
|
</KeyboardAvoidingView>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -69,6 +69,9 @@ function addExtraStyles(style) {
|
|||||||
|
|
||||||
style.keyboardAppearance = style.appearance;
|
style.keyboardAppearance = style.appearance;
|
||||||
|
|
||||||
|
style.color5 = style.backgroundColor4;
|
||||||
|
style.backgroundColor5 = style.color4;
|
||||||
|
|
||||||
return style;
|
return style;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -100,6 +100,13 @@ class ConfigScreenComponent extends BaseScreenComponent {
|
|||||||
void NavService.go('Status');
|
void NavService.go('Status');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
this.manageProfilesButtonPress_ = () => {
|
||||||
|
this.props.dispatch({
|
||||||
|
type: 'NAV_GO',
|
||||||
|
routeName: 'ProfileSwitcher',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
this.exportDebugButtonPress_ = async () => {
|
this.exportDebugButtonPress_ = async () => {
|
||||||
this.setState({ creatingReport: true });
|
this.setState({ creatingReport: true });
|
||||||
const service = new ReportService();
|
const service = new ReportService();
|
||||||
@@ -564,6 +571,7 @@ class ConfigScreenComponent extends BaseScreenComponent {
|
|||||||
|
|
||||||
settingComps.push(this.renderHeader('tools', _('Tools')));
|
settingComps.push(this.renderHeader('tools', _('Tools')));
|
||||||
|
|
||||||
|
settingComps.push(this.renderButton('profiles_buttons', _('Manage profiles'), this.manageProfilesButtonPress_));
|
||||||
settingComps.push(this.renderButton('status_button', _('Sync Status'), this.syncStatusButtonPress_));
|
settingComps.push(this.renderButton('status_button', _('Sync Status'), this.syncStatusButtonPress_));
|
||||||
settingComps.push(this.renderButton('log_button', _('Log'), this.logButtonPress_));
|
settingComps.push(this.renderButton('log_button', _('Log'), this.logButtonPress_));
|
||||||
if (Platform.OS === 'android') {
|
if (Platform.OS === 'android') {
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const md5 = require('md5');
|
|||||||
const { BackButtonService } = require('../../services/back-button.js');
|
const { BackButtonService } = require('../../services/back-button.js');
|
||||||
import NavService from '@joplin/lib/services/NavService';
|
import NavService from '@joplin/lib/services/NavService';
|
||||||
import BaseModel from '@joplin/lib/BaseModel';
|
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 { fileExtension, safeFileExtension } = require('@joplin/lib/path-utils');
|
||||||
const mimeUtils = require('@joplin/lib/mime-utils.js').mime;
|
const mimeUtils = require('@joplin/lib/mime-utils.js').mime;
|
||||||
import ScreenHeader from '../ScreenHeader';
|
import ScreenHeader from '../ScreenHeader';
|
||||||
@@ -1145,21 +1145,19 @@ class NoteScreenComponent extends BaseScreenComponent {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const renderActionButton = () => {
|
const renderActionButton = () => {
|
||||||
const buttons = [];
|
const editButton = {
|
||||||
|
label: _('Edit'),
|
||||||
buttons.push({
|
|
||||||
title: _('Edit'),
|
|
||||||
icon: 'md-create',
|
icon: 'md-create',
|
||||||
onPress: () => {
|
onPress: () => {
|
||||||
this.setState({ mode: 'edit' });
|
this.setState({ mode: 'edit' });
|
||||||
|
|
||||||
this.doFocusUpdate_ = true;
|
this.doFocusUpdate_ = true;
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
|
|
||||||
if (this.state.mode === 'edit') return null;
|
if (this.state.mode === 'edit') return null;
|
||||||
|
|
||||||
return <ActionButton multiStates={true} buttons={buttons} buttonIndex={0} />;
|
return <ActionButton mainButton={editButton} />;
|
||||||
};
|
};
|
||||||
|
|
||||||
const actionButtonComp = renderActionButton();
|
const actionButtonComp = renderActionButton();
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
const React = require('react');
|
const React = require('react');
|
||||||
|
|
||||||
const { View, TextInput, StyleSheet } = require('react-native');
|
const { View } = require('react-native');
|
||||||
const { connect } = require('react-redux');
|
const { connect } = require('react-redux');
|
||||||
const Folder = require('@joplin/lib/models/Folder').default;
|
const Folder = require('@joplin/lib/models/Folder').default;
|
||||||
const BaseModel = require('@joplin/lib/BaseModel').default;
|
const BaseModel = require('@joplin/lib/BaseModel').default;
|
||||||
const { ScreenHeader } = require('../ScreenHeader');
|
const { ScreenHeader } = require('../ScreenHeader');
|
||||||
const { BaseScreenComponent } = require('../base-screen.js');
|
const { BaseScreenComponent } = require('../base-screen.js');
|
||||||
const { dialogs } = require('../../utils/dialogs.js');
|
const { dialogs } = require('../../utils/dialogs.js');
|
||||||
const { themeStyle } = require('../global-style.js');
|
|
||||||
const { _ } = require('@joplin/lib/locale');
|
const { _ } = require('@joplin/lib/locale');
|
||||||
|
const TextInput = require('../TextInput').default;
|
||||||
|
|
||||||
class FolderScreenComponent extends BaseScreenComponent {
|
class FolderScreenComponent extends BaseScreenComponent {
|
||||||
static navigationOptions() {
|
static navigationOptions() {
|
||||||
@@ -21,25 +21,6 @@ class FolderScreenComponent extends BaseScreenComponent {
|
|||||||
folder: Folder.new(),
|
folder: Folder.new(),
|
||||||
lastSavedFolder: null,
|
lastSavedFolder: null,
|
||||||
};
|
};
|
||||||
this.styles_ = {};
|
|
||||||
}
|
|
||||||
|
|
||||||
styles() {
|
|
||||||
const theme = themeStyle(this.props.themeId);
|
|
||||||
|
|
||||||
if (this.styles_[this.props.themeId]) return this.styles_[this.props.themeId];
|
|
||||||
this.styles_ = {};
|
|
||||||
|
|
||||||
const styles = {
|
|
||||||
textInput: {
|
|
||||||
color: theme.color,
|
|
||||||
paddingLeft: theme.marginLeft,
|
|
||||||
marginTop: theme.marginTop,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
this.styles_[this.props.themeId] = StyleSheet.create(styles);
|
|
||||||
return this.styles_[this.props.themeId];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
UNSAFE_componentWillMount() {
|
UNSAFE_componentWillMount() {
|
||||||
@@ -103,12 +84,17 @@ class FolderScreenComponent extends BaseScreenComponent {
|
|||||||
|
|
||||||
render() {
|
render() {
|
||||||
const saveButtonDisabled = !this.isModified();
|
const saveButtonDisabled = !this.isModified();
|
||||||
const theme = themeStyle(this.props.themeId);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={this.rootStyle(this.props.themeId).root}>
|
<View style={this.rootStyle(this.props.themeId).root}>
|
||||||
<ScreenHeader title={_('Edit notebook')} showSaveButton={true} saveButtonDisabled={saveButtonDisabled} onSaveButtonPress={() => this.saveFolderButton_press()} showSideMenuButton={false} showSearchButton={false} />
|
<ScreenHeader title={_('Edit notebook')} showSaveButton={true} saveButtonDisabled={saveButtonDisabled} onSaveButtonPress={() => this.saveFolderButton_press()} showSideMenuButton={false} showSearchButton={false} />
|
||||||
<TextInput placeholder={_('Enter notebook title')} placeholderTextColor={theme.colorFaded} underlineColorAndroid={theme.dividerColor} selectionColor={theme.textSelectionColor} keyboardAppearance={theme.keyboardAppearance} style={this.styles().textInput} autoFocus={true} value={this.state.folder.title} onChangeText={text => this.title_changeText(text)} />
|
<TextInput
|
||||||
|
themeId={this.props.themeId}
|
||||||
|
placeholder={_('Enter notebook title')}
|
||||||
|
autoFocus={true}
|
||||||
|
value={this.state.folder.title}
|
||||||
|
onChangeText={text => this.title_changeText(text)}
|
||||||
|
/>
|
||||||
<dialogs.DialogBox
|
<dialogs.DialogBox
|
||||||
ref={dialogbox => {
|
ref={dialogbox => {
|
||||||
this.dialogbox = dialogbox;
|
this.dialogbox = dialogbox;
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ const Setting = require('@joplin/lib/models/Setting').default;
|
|||||||
const { themeStyle } = require('../global-style.js');
|
const { themeStyle } = require('../global-style.js');
|
||||||
const { ScreenHeader } = require('../ScreenHeader');
|
const { ScreenHeader } = require('../ScreenHeader');
|
||||||
const { _ } = require('@joplin/lib/locale');
|
const { _ } = require('@joplin/lib/locale');
|
||||||
const { ActionButton } = require('../action-button.js');
|
const ActionButton = require('../ActionButton').default;
|
||||||
const { dialogs } = require('../../utils/dialogs.js');
|
const { dialogs } = require('../../utils/dialogs.js');
|
||||||
const DialogBox = require('react-native-dialogbox').default;
|
const DialogBox = require('react-native-dialogbox').default;
|
||||||
const { BaseScreenComponent } = require('../base-screen.js');
|
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) {
|
parentItem(props = null) {
|
||||||
if (!props) props = this.props;
|
if (!props) props = this.props;
|
||||||
|
|
||||||
@@ -238,7 +251,35 @@ class NotesScreenComponent extends BaseScreenComponent {
|
|||||||
|
|
||||||
const addFolderNoteButtons = !!buttonFolderId;
|
const addFolderNoteButtons = !!buttonFolderId;
|
||||||
const thisComp = this;
|
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 (
|
return (
|
||||||
<View style={rootStyle}>
|
<View style={rootStyle}>
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import { FolderEntity, FolderIcon } from '@joplin/lib/services/database/types';
|
|||||||
import { AppState } from '../utils/types';
|
import { AppState } from '../utils/types';
|
||||||
import Setting from '@joplin/lib/models/Setting';
|
import Setting from '@joplin/lib/models/Setting';
|
||||||
import { reg } from '@joplin/lib/registry';
|
import { reg } from '@joplin/lib/registry';
|
||||||
|
import { ProfileConfig } from '@joplin/lib/services/profileConfig/types';
|
||||||
|
|
||||||
// We need this to suppress the useless warning
|
// We need this to suppress the useless warning
|
||||||
// https://github.com/oblador/react-native-vector-icons/issues/1465
|
// https://github.com/oblador/react-native-vector-icons/issues/1465
|
||||||
@@ -31,6 +32,7 @@ interface Props {
|
|||||||
notesParentType: string;
|
notesParentType: string;
|
||||||
folders: FolderEntity[];
|
folders: FolderEntity[];
|
||||||
opacity: number;
|
opacity: number;
|
||||||
|
profileConfig: ProfileConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
const syncIconRotationValue = new Animated.Value(0);
|
const syncIconRotationValue = new Animated.Value(0);
|
||||||
@@ -200,6 +202,15 @@ const SideMenuContentComponent = (props: Props) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const switchProfileButton_press = () => {
|
||||||
|
props.dispatch({ type: 'SIDE_MENU_CLOSE' });
|
||||||
|
|
||||||
|
props.dispatch({
|
||||||
|
type: 'NAV_GO',
|
||||||
|
routeName: 'ProfileSwitcher',
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
const configButton_press = () => {
|
const configButton_press = () => {
|
||||||
props.dispatch({ type: 'SIDE_MENU_CLOSE' });
|
props.dispatch({ type: 'SIDE_MENU_CLOSE' });
|
||||||
void NavService.go('Config');
|
void NavService.go('Config');
|
||||||
@@ -403,6 +414,10 @@ const SideMenuContentComponent = (props: Props) => {
|
|||||||
|
|
||||||
items.push(renderSidebarButton('tag_button', _('Tags'), 'md-pricetag', tagButton_press));
|
items.push(renderSidebarButton('tag_button', _('Tags'), 'md-pricetag', tagButton_press));
|
||||||
|
|
||||||
|
if (props.profileConfig && props.profileConfig.profiles.length > 1) {
|
||||||
|
items.push(renderSidebarButton('switchProfile_button', _('Switch profile'), 'md-people-circle-outline', switchProfileButton_press));
|
||||||
|
}
|
||||||
|
|
||||||
items.push(renderSidebarButton('config_button', _('Configuration'), 'md-settings', configButton_press));
|
items.push(renderSidebarButton('config_button', _('Configuration'), 'md-settings', configButton_press));
|
||||||
|
|
||||||
items.push(makeDivider('divider_2'));
|
items.push(makeDivider('divider_2'));
|
||||||
@@ -502,5 +517,6 @@ export default connect((state: AppState) => {
|
|||||||
resourceFetcher: state.resourceFetcher,
|
resourceFetcher: state.resourceFetcher,
|
||||||
isOnMobileData: state.isOnMobileData,
|
isOnMobileData: state.isOnMobileData,
|
||||||
syncOnlyOverWifi: state.settings['sync.mobileWifiOnly'],
|
syncOnlyOverWifi: state.settings['sync.mobileWifiOnly'],
|
||||||
|
profileConfig: state.profileConfig,
|
||||||
};
|
};
|
||||||
})(SideMenuContentComponent);
|
})(SideMenuContentComponent);
|
||||||
|
|||||||
@@ -248,6 +248,12 @@ PODS:
|
|||||||
- React-Core
|
- React-Core
|
||||||
- react-native-rsa-native (2.0.5):
|
- react-native-rsa-native (2.0.5):
|
||||||
- React
|
- 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-native-slider (4.4.0):
|
||||||
- React-Core
|
- React-Core
|
||||||
- react-native-sqlite-storage (6.0.1):
|
- react-native-sqlite-storage (6.0.1):
|
||||||
@@ -329,6 +335,8 @@ PODS:
|
|||||||
- React-Core
|
- React-Core
|
||||||
- RNDateTimePicker (6.7.1):
|
- RNDateTimePicker (6.7.1):
|
||||||
- React-Core
|
- React-Core
|
||||||
|
- RNExitApp (1.1.0):
|
||||||
|
- React
|
||||||
- RNFileViewer (2.1.5):
|
- RNFileViewer (2.1.5):
|
||||||
- React-Core
|
- React-Core
|
||||||
- RNFS (2.20.0):
|
- RNFS (2.20.0):
|
||||||
@@ -376,6 +384,7 @@ DEPENDENCIES:
|
|||||||
- react-native-image-resizer (from `../node_modules/react-native-image-resizer`)
|
- react-native-image-resizer (from `../node_modules/react-native-image-resizer`)
|
||||||
- "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)"
|
- "react-native-netinfo (from `../node_modules/@react-native-community/netinfo`)"
|
||||||
- react-native-rsa-native (from `../node_modules/react-native-rsa-native`)
|
- 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-slider (from `../node_modules/@react-native-community/slider`)"
|
||||||
- react-native-sqlite-storage (from `../node_modules/react-native-sqlite-storage`)
|
- react-native-sqlite-storage (from `../node_modules/react-native-sqlite-storage`)
|
||||||
- react-native-version-info (from `../node_modules/react-native-version-info`)
|
- react-native-version-info (from `../node_modules/react-native-version-info`)
|
||||||
@@ -396,6 +405,7 @@ DEPENDENCIES:
|
|||||||
- "RNCClipboard (from `../node_modules/@react-native-community/clipboard`)"
|
- "RNCClipboard (from `../node_modules/@react-native-community/clipboard`)"
|
||||||
- "RNCPushNotificationIOS (from `../node_modules/@react-native-community/push-notification-ios`)"
|
- "RNCPushNotificationIOS (from `../node_modules/@react-native-community/push-notification-ios`)"
|
||||||
- "RNDateTimePicker (from `../node_modules/@react-native-community/datetimepicker`)"
|
- "RNDateTimePicker (from `../node_modules/@react-native-community/datetimepicker`)"
|
||||||
|
- RNExitApp (from `../node_modules/react-native-exit-app`)
|
||||||
- RNFileViewer (from `../node_modules/react-native-file-viewer`)
|
- RNFileViewer (from `../node_modules/react-native-file-viewer`)
|
||||||
- RNFS (from `../node_modules/react-native-fs`)
|
- RNFS (from `../node_modules/react-native-fs`)
|
||||||
- RNQuickAction (from `../node_modules/react-native-quick-actions`)
|
- RNQuickAction (from `../node_modules/react-native-quick-actions`)
|
||||||
@@ -469,6 +479,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: "../node_modules/@react-native-community/netinfo"
|
:path: "../node_modules/@react-native-community/netinfo"
|
||||||
react-native-rsa-native:
|
react-native-rsa-native:
|
||||||
:path: "../node_modules/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:
|
react-native-slider:
|
||||||
:path: "../node_modules/@react-native-community/slider"
|
:path: "../node_modules/@react-native-community/slider"
|
||||||
react-native-sqlite-storage:
|
react-native-sqlite-storage:
|
||||||
@@ -509,6 +521,8 @@ EXTERNAL SOURCES:
|
|||||||
:path: "../node_modules/@react-native-community/push-notification-ios"
|
:path: "../node_modules/@react-native-community/push-notification-ios"
|
||||||
RNDateTimePicker:
|
RNDateTimePicker:
|
||||||
:path: "../node_modules/@react-native-community/datetimepicker"
|
:path: "../node_modules/@react-native-community/datetimepicker"
|
||||||
|
RNExitApp:
|
||||||
|
:path: "../node_modules/react-native-exit-app"
|
||||||
RNFileViewer:
|
RNFileViewer:
|
||||||
:path: "../node_modules/react-native-file-viewer"
|
:path: "../node_modules/react-native-file-viewer"
|
||||||
RNFS:
|
RNFS:
|
||||||
@@ -556,6 +570,7 @@ SPEC CHECKSUMS:
|
|||||||
react-native-image-resizer: d9fb629a867335bdc13230ac2a58702bb8c8828f
|
react-native-image-resizer: d9fb629a867335bdc13230ac2a58702bb8c8828f
|
||||||
react-native-netinfo: 2517ad504b3d303e90d7a431b0fcaef76d207983
|
react-native-netinfo: 2517ad504b3d303e90d7a431b0fcaef76d207983
|
||||||
react-native-rsa-native: 12132eb627797529fdb1f0d22fd0f8f9678df64a
|
react-native-rsa-native: 12132eb627797529fdb1f0d22fd0f8f9678df64a
|
||||||
|
react-native-safe-area-context: 99b24a0c5acd0d5dcac2b1a7f18c49ea317be99a
|
||||||
react-native-slider: d2938a12c4e439a227c70eec65d119136eb4aeb5
|
react-native-slider: d2938a12c4e439a227c70eec65d119136eb4aeb5
|
||||||
react-native-sqlite-storage: f6d515e1c446d1e6d026aa5352908a25d4de3261
|
react-native-sqlite-storage: f6d515e1c446d1e6d026aa5352908a25d4de3261
|
||||||
react-native-version-info: a106f23009ac0db4ee00de39574eb546682579b9
|
react-native-version-info: a106f23009ac0db4ee00de39574eb546682579b9
|
||||||
@@ -576,6 +591,7 @@ SPEC CHECKSUMS:
|
|||||||
RNCClipboard: 41d8d918092ae8e676f18adada19104fa3e68495
|
RNCClipboard: 41d8d918092ae8e676f18adada19104fa3e68495
|
||||||
RNCPushNotificationIOS: 87b8d16d3ede4532745e05b03c42cff33a36cc45
|
RNCPushNotificationIOS: 87b8d16d3ede4532745e05b03c42cff33a36cc45
|
||||||
RNDateTimePicker: 0530a73a6f3a1a85814cbde0802736993b9e675e
|
RNDateTimePicker: 0530a73a6f3a1a85814cbde0802736993b9e675e
|
||||||
|
RNExitApp: c4e052df2568b43bec8a37c7cd61194d4cfee2c3
|
||||||
RNFileViewer: ce7ca3ac370e18554d35d6355cffd7c30437c592
|
RNFileViewer: ce7ca3ac370e18554d35d6355cffd7c30437c592
|
||||||
RNFS: 4ac0f0ea233904cb798630b3c077808c06931688
|
RNFS: 4ac0f0ea233904cb798630b3c077808c06931688
|
||||||
RNQuickAction: 6d404a869dc872cde841ad3147416a670d13fa93
|
RNQuickAction: 6d404a869dc872cde841ad3147416a670d13fa93
|
||||||
|
|||||||
@@ -44,6 +44,7 @@
|
|||||||
"react-native-dialogbox": "0.6.10",
|
"react-native-dialogbox": "0.6.10",
|
||||||
"react-native-document-picker": "8.1.3",
|
"react-native-document-picker": "8.1.3",
|
||||||
"react-native-dropdownalert": "4.5.1",
|
"react-native-dropdownalert": "4.5.1",
|
||||||
|
"react-native-exit-app": "1.1.0",
|
||||||
"react-native-file-viewer": "2.1.5",
|
"react-native-file-viewer": "2.1.5",
|
||||||
"react-native-fingerprint-scanner": "6.0.0",
|
"react-native-fingerprint-scanner": "6.0.0",
|
||||||
"react-native-fs": "2.20.0",
|
"react-native-fs": "2.20.0",
|
||||||
@@ -51,9 +52,11 @@
|
|||||||
"react-native-image-picker": "4.10.3",
|
"react-native-image-picker": "4.10.3",
|
||||||
"react-native-image-resizer": "1.4.5",
|
"react-native-image-resizer": "1.4.5",
|
||||||
"react-native-modal-datetime-picker": "14.0.1",
|
"react-native-modal-datetime-picker": "14.0.1",
|
||||||
|
"react-native-paper": "5.0.2",
|
||||||
"react-native-popup-menu": "0.16.1",
|
"react-native-popup-menu": "0.16.1",
|
||||||
"react-native-quick-actions": "0.3.13",
|
"react-native-quick-actions": "0.3.13",
|
||||||
"react-native-rsa-native": "2.0.5",
|
"react-native-rsa-native": "2.0.5",
|
||||||
|
"react-native-safe-area-context": "4.4.1",
|
||||||
"react-native-securerandom": "1.0.1",
|
"react-native-securerandom": "1.0.1",
|
||||||
"react-native-share": "8.1.0",
|
"react-native-share": "8.1.0",
|
||||||
"react-native-side-menu-updated": "1.3.2",
|
"react-native-side-menu-updated": "1.3.2",
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ import KvStore from '@joplin/lib/services/KvStore';
|
|||||||
import NoteScreen from './components/screens/Note';
|
import NoteScreen from './components/screens/Note';
|
||||||
import UpgradeSyncTargetScreen from './components/screens/UpgradeSyncTargetScreen';
|
import UpgradeSyncTargetScreen from './components/screens/UpgradeSyncTargetScreen';
|
||||||
import Setting, { Env } from '@joplin/lib/models/Setting';
|
import Setting, { Env } from '@joplin/lib/models/Setting';
|
||||||
import RNFetchBlob from 'rn-fetch-blob';
|
|
||||||
import PoorManIntervals from '@joplin/lib/PoorManIntervals';
|
import PoorManIntervals from '@joplin/lib/PoorManIntervals';
|
||||||
import reducer from '@joplin/lib/reducer';
|
import reducer from '@joplin/lib/reducer';
|
||||||
import ShareExtension from './utils/ShareExtension';
|
import ShareExtension from './utils/ShareExtension';
|
||||||
@@ -27,6 +26,7 @@ import { setLocale, closestSupportedLocale, defaultLocale } from '@joplin/lib/lo
|
|||||||
import SyncTargetJoplinServer from '@joplin/lib/SyncTargetJoplinServer';
|
import SyncTargetJoplinServer from '@joplin/lib/SyncTargetJoplinServer';
|
||||||
import SyncTargetJoplinCloud from '@joplin/lib/SyncTargetJoplinCloud';
|
import SyncTargetJoplinCloud from '@joplin/lib/SyncTargetJoplinCloud';
|
||||||
import SyncTargetOneDrive from '@joplin/lib/SyncTargetOneDrive';
|
import SyncTargetOneDrive from '@joplin/lib/SyncTargetOneDrive';
|
||||||
|
import initProfile from '@joplin/lib/services/profileConfig/initProfile';
|
||||||
const VersionInfo = require('react-native-version-info').default;
|
const VersionInfo = require('react-native-version-info').default;
|
||||||
const { Keyboard, NativeModules, BackHandler, Animated, View, StatusBar, Linking, Platform, Dimensions } = require('react-native');
|
const { Keyboard, NativeModules, BackHandler, Animated, View, StatusBar, Linking, Platform, Dimensions } = require('react-native');
|
||||||
const RNAppState = require('react-native').AppState;
|
const RNAppState = require('react-native').AppState;
|
||||||
@@ -36,6 +36,7 @@ const DropdownAlert = require('react-native-dropdownalert').default;
|
|||||||
const AlarmServiceDriver = require('./services/AlarmServiceDriver').default;
|
const AlarmServiceDriver = require('./services/AlarmServiceDriver').default;
|
||||||
const SafeAreaView = require('./components/SafeAreaView');
|
const SafeAreaView = require('./components/SafeAreaView');
|
||||||
const { connect, Provider } = require('react-redux');
|
const { connect, Provider } = require('react-redux');
|
||||||
|
import { Provider as PaperProvider, MD3DarkTheme, MD3LightTheme } from 'react-native-paper';
|
||||||
const { BackButtonService } = require('./services/back-button.js');
|
const { BackButtonService } = require('./services/back-button.js');
|
||||||
import NavService from '@joplin/lib/services/NavService';
|
import NavService from '@joplin/lib/services/NavService';
|
||||||
import { createStore, applyMiddleware } from 'redux';
|
import { createStore, applyMiddleware } from 'redux';
|
||||||
@@ -108,8 +109,13 @@ import SyncTargetNone from '@joplin/lib/SyncTargetNone';
|
|||||||
import { setRSA } from '@joplin/lib/services/e2ee/ppk';
|
import { setRSA } from '@joplin/lib/services/e2ee/ppk';
|
||||||
import RSA from './services/e2ee/RSA.react-native';
|
import RSA from './services/e2ee/RSA.react-native';
|
||||||
import { runIntegrationTests } from '@joplin/lib/services/e2ee/ppkTestUtils';
|
import { runIntegrationTests } from '@joplin/lib/services/e2ee/ppkTestUtils';
|
||||||
|
import { Theme, ThemeAppearance } from '@joplin/lib/themes/type';
|
||||||
import { AppState } from './utils/types';
|
import { AppState } from './utils/types';
|
||||||
|
import ProfileSwitcher from './components/ProfileSwitcher/ProfileSwitcher';
|
||||||
|
import ProfileEditor from './components/ProfileSwitcher/ProfileEditor';
|
||||||
import sensorInfo from './components/biometrics/sensorInfo';
|
import sensorInfo from './components/biometrics/sensorInfo';
|
||||||
|
import { getCurrentProfile } from '@joplin/lib/services/profileConfig';
|
||||||
|
import { getDatabaseName, getProfilesRootDir, getResourceDir, setDispatch } from './services/profiles';
|
||||||
|
|
||||||
let storeDispatch = function(_action: any) {};
|
let storeDispatch = function(_action: any) {};
|
||||||
|
|
||||||
@@ -407,11 +413,23 @@ function decryptionWorker_resourceMetadataButNotBlobDecrypted() {
|
|||||||
async function initialize(dispatch: Function) {
|
async function initialize(dispatch: Function) {
|
||||||
shimInit();
|
shimInit();
|
||||||
|
|
||||||
|
setDispatch(dispatch);
|
||||||
|
const { profileConfig, isSubProfile } = await initProfile(getProfilesRootDir());
|
||||||
|
const currentProfile = getCurrentProfile(profileConfig);
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: 'PROFILE_CONFIG_SET',
|
||||||
|
value: profileConfig,
|
||||||
|
});
|
||||||
|
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
Setting.setConstant('env', __DEV__ ? 'dev' : 'prod');
|
Setting.setConstant('env', __DEV__ ? 'dev' : 'prod');
|
||||||
Setting.setConstant('appId', 'net.cozic.joplin-mobile');
|
Setting.setConstant('appId', 'net.cozic.joplin-mobile');
|
||||||
Setting.setConstant('appType', 'mobile');
|
Setting.setConstant('appType', 'mobile');
|
||||||
Setting.setConstant('resourceDir', RNFetchBlob.fs.dirs.DocumentDir);
|
const resourceDir = getResourceDir(currentProfile, isSubProfile);
|
||||||
|
Setting.setConstant('resourceDir', resourceDir);
|
||||||
|
|
||||||
|
await shim.fsDriver().mkdir(resourceDir);
|
||||||
|
|
||||||
const logDatabase = new Database(new DatabaseDriverReactNative());
|
const logDatabase = new Database(new DatabaseDriverReactNative());
|
||||||
await logDatabase.open({ name: 'log.sqlite' });
|
await logDatabase.open({ name: 'log.sqlite' });
|
||||||
@@ -479,9 +497,9 @@ async function initialize(dispatch: Function) {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
if (Setting.value('env') === 'prod') {
|
if (Setting.value('env') === 'prod') {
|
||||||
await db.open({ name: 'joplin.sqlite' });
|
await db.open({ name: getDatabaseName(currentProfile, isSubProfile) });
|
||||||
} else {
|
} else {
|
||||||
await db.open({ name: 'joplin-101.sqlite' });
|
await db.open({ name: getDatabaseName(currentProfile, isSubProfile) });
|
||||||
|
|
||||||
// await db.clearForTesting();
|
// await db.clearForTesting();
|
||||||
}
|
}
|
||||||
@@ -769,6 +787,13 @@ class AppComponent extends React.Component {
|
|||||||
type: 'APP_STATE_SET',
|
type: 'APP_STATE_SET',
|
||||||
state: 'ready',
|
state: 'ready',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// setTimeout(() => {
|
||||||
|
// this.props.dispatch({
|
||||||
|
// type: 'NAV_GO',
|
||||||
|
// routeName: 'ProfileSwitcher',
|
||||||
|
// });
|
||||||
|
// }, 1000);
|
||||||
}
|
}
|
||||||
|
|
||||||
Linking.addEventListener('url', this.handleOpenURL_);
|
Linking.addEventListener('url', this.handleOpenURL_);
|
||||||
@@ -881,7 +906,7 @@ class AppComponent extends React.Component {
|
|||||||
|
|
||||||
public render() {
|
public render() {
|
||||||
if (this.props.appState !== 'ready') return null;
|
if (this.props.appState !== 'ready') return null;
|
||||||
const theme = themeStyle(this.props.themeId);
|
const theme: Theme = themeStyle(this.props.themeId);
|
||||||
|
|
||||||
let sideMenuContent = null;
|
let sideMenuContent = null;
|
||||||
let menuPosition = 'left';
|
let menuPosition = 'left';
|
||||||
@@ -902,6 +927,8 @@ class AppComponent extends React.Component {
|
|||||||
DropboxLogin: { screen: DropboxLoginScreen },
|
DropboxLogin: { screen: DropboxLoginScreen },
|
||||||
EncryptionConfig: { screen: EncryptionConfigScreen },
|
EncryptionConfig: { screen: EncryptionConfigScreen },
|
||||||
UpgradeSyncTarget: { screen: UpgradeSyncTargetScreen },
|
UpgradeSyncTarget: { screen: UpgradeSyncTargetScreen },
|
||||||
|
ProfileSwitcher: { screen: ProfileSwitcher },
|
||||||
|
ProfileEditor: { screen: ProfileEditor },
|
||||||
Log: { screen: LogScreen },
|
Log: { screen: LogScreen },
|
||||||
Status: { screen: StatusScreen },
|
Status: { screen: StatusScreen },
|
||||||
Search: { screen: SearchScreen },
|
Search: { screen: SearchScreen },
|
||||||
@@ -912,7 +939,7 @@ class AppComponent extends React.Component {
|
|||||||
// const statusBarStyle = theme.appearance === 'light-content';
|
// const statusBarStyle = theme.appearance === 'light-content';
|
||||||
const statusBarStyle = 'light-content';
|
const statusBarStyle = 'light-content';
|
||||||
|
|
||||||
return (
|
const mainContent = (
|
||||||
<View style={{ flex: 1, backgroundColor: theme.backgroundColor }}>
|
<View style={{ flex: 1, backgroundColor: theme.backgroundColor }}>
|
||||||
<SideMenu
|
<SideMenu
|
||||||
menu={sideMenuContent}
|
menu={sideMenuContent}
|
||||||
@@ -932,7 +959,7 @@ class AppComponent extends React.Component {
|
|||||||
<SafeAreaView style={{ flex: 0, backgroundColor: theme.backgroundColor2 }}/>
|
<SafeAreaView style={{ flex: 0, backgroundColor: theme.backgroundColor2 }}/>
|
||||||
<SafeAreaView style={{ flex: 1 }}>
|
<SafeAreaView style={{ flex: 1 }}>
|
||||||
<View style={{ flex: 1, backgroundColor: theme.backgroundColor }}>
|
<View style={{ flex: 1, backgroundColor: theme.backgroundColor }}>
|
||||||
<AppNav screens={appNavInit} />
|
<AppNav screens={appNavInit} dispatch={this.props.dispatch} />
|
||||||
</View>
|
</View>
|
||||||
<DropdownAlert ref={(ref: any) => this.dropdownAlert_ = ref} tapToCloseEnabled={true} />
|
<DropdownAlert ref={(ref: any) => this.dropdownAlert_ = ref} tapToCloseEnabled={true} />
|
||||||
<Animated.View pointerEvents='none' style={{ position: 'absolute', backgroundColor: 'black', opacity: this.state.sideMenuContentOpacity, width: '100%', height: '120%' }}/>
|
<Animated.View pointerEvents='none' style={{ position: 'absolute', backgroundColor: 'black', opacity: this.state.sideMenuContentOpacity, width: '100%', height: '120%' }}/>
|
||||||
@@ -945,6 +972,27 @@ class AppComponent extends React.Component {
|
|||||||
</SideMenu>
|
</SideMenu>
|
||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
||||||
|
const paperTheme = theme.appearance === ThemeAppearance.Dark ? MD3DarkTheme : MD3LightTheme;
|
||||||
|
|
||||||
|
// Wrap everything in a PaperProvider -- this allows using components from react-native-paper
|
||||||
|
return (
|
||||||
|
<PaperProvider theme={{
|
||||||
|
...paperTheme,
|
||||||
|
version: 3,
|
||||||
|
colors: {
|
||||||
|
...paperTheme.colors,
|
||||||
|
onPrimaryContainer: theme.color5,
|
||||||
|
primaryContainer: theme.backgroundColor5,
|
||||||
|
surfaceVariant: theme.backgroundColor,
|
||||||
|
onSurfaceVariant: theme.color,
|
||||||
|
primary: theme.color,
|
||||||
|
},
|
||||||
|
}}>
|
||||||
|
{mainContent}
|
||||||
|
</PaperProvider>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
51
packages/app-mobile/services/profiles/index.ts
Normal file
51
packages/app-mobile/services/profiles/index.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
// Helper functions to reduce the boiler plate of loading and saving profiles on
|
||||||
|
// mobile
|
||||||
|
|
||||||
|
const RNExitApp = require('react-native-exit-app').default;
|
||||||
|
import { Profile, ProfileConfig } from '@joplin/lib/services/profileConfig/types';
|
||||||
|
import { loadProfileConfig as libLoadProfileConfig, saveProfileConfig as libSaveProfileConfig } from '@joplin/lib/services/profileConfig/index';
|
||||||
|
import RNFetchBlob from 'rn-fetch-blob';
|
||||||
|
|
||||||
|
let dispatch_: Function = null;
|
||||||
|
export const setDispatch = (dispatch: Function) => {
|
||||||
|
dispatch_ = dispatch;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getProfilesRootDir = () => {
|
||||||
|
return RNFetchBlob.fs.dirs.DocumentDir;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getProfilesConfigPath = () => {
|
||||||
|
return `${getProfilesRootDir()}/profiles.json`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getResourceDir = (profile: Profile, isSubProfile: boolean) => {
|
||||||
|
if (!isSubProfile) return getProfilesRootDir();
|
||||||
|
return `${getProfilesRootDir()}/resources-${profile.id}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getDatabaseName = (profile: Profile, isSubProfile: boolean) => {
|
||||||
|
if (!isSubProfile) return 'joplin.sqlite';
|
||||||
|
return `joplin-${profile.id}.sqlite`;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const loadProfileConfig = async () => {
|
||||||
|
return libLoadProfileConfig(getProfilesConfigPath());
|
||||||
|
};
|
||||||
|
|
||||||
|
export const saveProfileConfig = async (profileConfig: ProfileConfig) => {
|
||||||
|
await libSaveProfileConfig(getProfilesConfigPath(), profileConfig);
|
||||||
|
dispatch_({
|
||||||
|
type: 'PROFILE_CONFIG_SET',
|
||||||
|
value: profileConfig,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
export const switchProfile = async (profileId: string) => {
|
||||||
|
const config = await loadProfileConfig();
|
||||||
|
if (config.currentProfileId === profileId) throw new Error('This profile is already active');
|
||||||
|
|
||||||
|
config.currentProfileId = profileId;
|
||||||
|
await saveProfileConfig(config);
|
||||||
|
RNExitApp.exitApp();
|
||||||
|
};
|
||||||
11
packages/app-mobile/utils/createRootStyle.ts
Normal file
11
packages/app-mobile/utils/createRootStyle.ts
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
const { themeStyle } = require('../components/global-style');
|
||||||
|
|
||||||
|
export default (themeId: number) => {
|
||||||
|
const theme = themeStyle(themeId);
|
||||||
|
return {
|
||||||
|
root: {
|
||||||
|
flex: 1,
|
||||||
|
backgroundColor: theme.backgroundColor,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -7,6 +7,7 @@ import BaseModel from './BaseModel';
|
|||||||
import { Store } from 'redux';
|
import { Store } from 'redux';
|
||||||
import { ProfileConfig } from './services/profileConfig/types';
|
import { ProfileConfig } from './services/profileConfig/types';
|
||||||
import * as ArrayUtils from './ArrayUtils';
|
import * as ArrayUtils from './ArrayUtils';
|
||||||
|
import { FolderEntity } from './services/database/types';
|
||||||
const fastDeepEqual = require('fast-deep-equal');
|
const fastDeepEqual = require('fast-deep-equal');
|
||||||
const { ALL_NOTES_FILTER_ID } = require('./reserved-ids');
|
const { ALL_NOTES_FILTER_ID } = require('./reserved-ids');
|
||||||
const { createSelectorCreator, defaultMemoize } = require('reselect');
|
const { createSelectorCreator, defaultMemoize } = require('reselect');
|
||||||
@@ -55,7 +56,7 @@ export interface State {
|
|||||||
noteSelectionEnabled?: boolean;
|
noteSelectionEnabled?: boolean;
|
||||||
notesSource: string;
|
notesSource: string;
|
||||||
notesParentType: string;
|
notesParentType: string;
|
||||||
folders: any[];
|
folders: FolderEntity[];
|
||||||
tags: any[];
|
tags: any[];
|
||||||
masterKeys: any[];
|
masterKeys: any[];
|
||||||
notLoadedMasterKeys: string[];
|
notLoadedMasterKeys: string[];
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { rtrimSlashes } from '../../path-utils';
|
|||||||
import shim from '../../shim';
|
import shim from '../../shim';
|
||||||
import { CurrentProfileVersion, defaultProfile, defaultProfileConfig, DefaultProfileId, Profile, ProfileConfig } from './types';
|
import { CurrentProfileVersion, defaultProfile, defaultProfileConfig, DefaultProfileId, Profile, ProfileConfig } from './types';
|
||||||
import { customAlphabet } from 'nanoid/non-secure';
|
import { customAlphabet } from 'nanoid/non-secure';
|
||||||
|
import { _ } from '../../locale';
|
||||||
|
|
||||||
export const migrateProfileConfig = (profileConfig: any, toVersion: number): ProfileConfig => {
|
export const migrateProfileConfig = (profileConfig: any, toVersion: number): ProfileConfig => {
|
||||||
let version = 2;
|
let version = 2;
|
||||||
@@ -99,6 +100,17 @@ export const createNewProfile = (config: ProfileConfig, profileName: string) =>
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const deleteProfileById = (config: ProfileConfig, profileId: string): ProfileConfig => {
|
||||||
|
if (profileId === DefaultProfileId) throw new Error(_('The default profile cannot be deleted'));
|
||||||
|
if (profileId === config.currentProfileId) throw new Error(_('The active profile cannot be deleted. Switch to a different profile and try again.'));
|
||||||
|
|
||||||
|
const newProfiles = config.profiles.filter(p => p.id !== profileId);
|
||||||
|
return {
|
||||||
|
...config,
|
||||||
|
profiles: newProfiles,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const profileIdByIndex = (config: ProfileConfig, index: number): string => {
|
export const profileIdByIndex = (config: ProfileConfig, index: number): string => {
|
||||||
return config.profiles[index].id;
|
return config.profiles[index].id;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -41,6 +41,9 @@ export interface Theme {
|
|||||||
backgroundColor4: string;
|
backgroundColor4: string;
|
||||||
color4: string;
|
color4: string;
|
||||||
|
|
||||||
|
backgroundColor5?: string;
|
||||||
|
color5?: string;
|
||||||
|
|
||||||
raisedBackgroundColor: string;
|
raisedBackgroundColor: string;
|
||||||
raisedColor: string;
|
raisedColor: string;
|
||||||
searchMarkerBackgroundColor: string;
|
searchMarkerBackgroundColor: string;
|
||||||
|
|||||||
@@ -112,7 +112,7 @@
|
|||||||
"matchUpdateTypes": ["minor", "patch"],
|
"matchUpdateTypes": ["minor", "patch"],
|
||||||
"automerge": true,
|
"automerge": true,
|
||||||
"labels": ["automerge"],
|
"labels": ["automerge"],
|
||||||
"schedule": "on the first day of the week",
|
"extends": ["schedule:monthly"],
|
||||||
"matchPackageNames": [
|
"matchPackageNames": [
|
||||||
// AWS packages are updated too frequently and we can assume minor
|
// AWS packages are updated too frequently and we can assume minor
|
||||||
// updates are stable.
|
// updates are stable.
|
||||||
|
|||||||
59
yarn.lock
59
yarn.lock
@@ -3150,6 +3150,18 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"@cloudcmd/create-element@npm:^2.0.0":
|
||||||
version: 2.0.2
|
version: 2.0.2
|
||||||
resolution: "@cloudcmd/create-element@npm:2.0.2"
|
resolution: "@cloudcmd/create-element@npm:2.0.2"
|
||||||
@@ -4725,6 +4737,7 @@ __metadata:
|
|||||||
react-native-dialogbox: 0.6.10
|
react-native-dialogbox: 0.6.10
|
||||||
react-native-document-picker: 8.1.3
|
react-native-document-picker: 8.1.3
|
||||||
react-native-dropdownalert: 4.5.1
|
react-native-dropdownalert: 4.5.1
|
||||||
|
react-native-exit-app: 1.1.0
|
||||||
react-native-file-viewer: 2.1.5
|
react-native-file-viewer: 2.1.5
|
||||||
react-native-fingerprint-scanner: 6.0.0
|
react-native-fingerprint-scanner: 6.0.0
|
||||||
react-native-fs: 2.20.0
|
react-native-fs: 2.20.0
|
||||||
@@ -4732,9 +4745,11 @@ __metadata:
|
|||||||
react-native-image-picker: 4.10.3
|
react-native-image-picker: 4.10.3
|
||||||
react-native-image-resizer: 1.4.5
|
react-native-image-resizer: 1.4.5
|
||||||
react-native-modal-datetime-picker: 14.0.1
|
react-native-modal-datetime-picker: 14.0.1
|
||||||
|
react-native-paper: 5.0.2
|
||||||
react-native-popup-menu: 0.16.1
|
react-native-popup-menu: 0.16.1
|
||||||
react-native-quick-actions: 0.3.13
|
react-native-quick-actions: 0.3.13
|
||||||
react-native-rsa-native: 2.0.5
|
react-native-rsa-native: 2.0.5
|
||||||
|
react-native-safe-area-context: 4.4.1
|
||||||
react-native-securerandom: 1.0.1
|
react-native-securerandom: 1.0.1
|
||||||
react-native-share: 8.1.0
|
react-native-share: 8.1.0
|
||||||
react-native-side-menu-updated: 1.3.2
|
react-native-side-menu-updated: 1.3.2
|
||||||
@@ -11994,7 +12009,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"color@npm:3.2.1":
|
"color@npm:3.2.1, color@npm:^3.1.2":
|
||||||
version: 3.2.1
|
version: 3.2.1
|
||||||
resolution: "color@npm:3.2.1"
|
resolution: "color@npm:3.2.1"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -27604,6 +27619,13 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"react-native-exit-app@npm:1.1.0":
|
||||||
|
version: 1.1.0
|
||||||
|
resolution: "react-native-exit-app@npm:1.1.0"
|
||||||
|
checksum: 383e28e03759ebf21ae54cb914e06462fb3ef43ed78895e83395386d615c9a56faacba599edf17677e44a4ebf641102b295545964b787cc09b01b16c9384e8d9
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"react-native-file-viewer@npm:2.1.5":
|
"react-native-file-viewer@npm:2.1.5":
|
||||||
version: 2.1.5
|
version: 2.1.5
|
||||||
resolution: "react-native-file-viewer@npm:2.1.5"
|
resolution: "react-native-file-viewer@npm:2.1.5"
|
||||||
@@ -27687,6 +27709,22 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"react-native-popup-menu@npm:0.16.1":
|
||||||
version: 0.16.1
|
version: 0.16.1
|
||||||
resolution: "react-native-popup-menu@npm:0.16.1"
|
resolution: "react-native-popup-menu@npm:0.16.1"
|
||||||
@@ -27708,6 +27746,16 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"react-native-securerandom@npm:1.0.1":
|
||||||
version: 1.0.1
|
version: 1.0.1
|
||||||
resolution: "react-native-securerandom@npm:1.0.1"
|
resolution: "react-native-securerandom@npm:1.0.1"
|
||||||
@@ -33141,6 +33189,15 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
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":
|
"use-isomorphic-layout-effect@npm:^1.1.2":
|
||||||
version: 1.1.2
|
version: 1.1.2
|
||||||
resolution: "use-isomorphic-layout-effect@npm:1.1.2"
|
resolution: "use-isomorphic-layout-effect@npm:1.1.2"
|
||||||
|
|||||||
Reference in New Issue
Block a user