1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-08-10 22:11:50 +02:00

Chore: Mobile: Update mobile components for compatibility with React Native 0.79 (#12154)

This commit is contained in:
Henry Heino
2025-04-24 00:48:58 -07:00
committed by GitHub
parent cf626bee76
commit dfa340a137
22 changed files with 93 additions and 61 deletions

View File

@@ -205,7 +205,7 @@ class Dropdown extends Component<DropdownProps, DropdownState> {
<View
style={{ flexDirection: 'row', flex: 1, alignItems: 'center' }}
onLayout={this.updateHeaderCoordinates}
ref={ref => (this.headerRef = ref)}
ref={ref => { this.headerRef = ref; } }
>
<TouchableOpacity
style={headerWrapperStyle}

View File

@@ -1,5 +1,4 @@
const React = require('react');
import * as React from 'react';
import { FunctionComponent, ReactElement } from 'react';
import { _ } from '@joplin/lib/locale';
import Folder, { FolderEntityWithChildren } from '@joplin/lib/models/Folder';

View File

@@ -198,8 +198,8 @@ const useTooltipStyles = (themeId: number) => {
// On web, by default, pressing buttons defocuses the active edit control, dismissing the
// virtual keyboard. This hook creates listeners that optionally prevent the keyboard from dismissing.
const usePreventKeyboardDismissTouchListeners = (preventKeyboardDismiss: boolean, onPress: ()=> void, disabled: boolean) => {
const touchStartPointRef = useRef<[number, number]>();
const isTapRef = useRef<boolean>();
const touchStartPointRef = useRef<[number, number]>([-1, -1]);
const isTapRef = useRef<boolean>(false);
const onTouchStart = useCallback((event: GestureResponderEvent) => {
if (Platform.OS === 'web' && preventKeyboardDismiss) {
const touch = event.nativeEvent.touches[0];

View File

@@ -1,9 +1,8 @@
// Dialog allowing the user to update/create links
const React = require('react');
const { useState, useEffect, useMemo, useRef } = require('react');
const { StyleSheet } = require('react-native');
const { View, Text, TextInput, Button } = require('react-native');
import * as React from 'react';
import { useState, useEffect, useMemo, useRef } from 'react';
import { View, Text, TextInput, Button, StyleSheet } from 'react-native';
import Modal from '../Modal';
import { themeStyle } from '@joplin/lib/theme';
@@ -26,7 +25,7 @@ const EditLinkDialog = (props: LinkDialogProps) => {
const [linkLabel, setLinkLabel] = useState('');
const [linkURL, setLinkURL] = useState('');
const linkInputRef = useRef();
const linkInputRef = useRef<TextInput|null>(null);
// Reset the label and URL when shown/hidden
useEffect(() => {
@@ -53,10 +52,6 @@ const EditLinkDialog = (props: LinkDialogProps) => {
shadowOpacity: 0.4,
shadowRadius: 1,
},
button: {
color: theme.color2,
backgroundColor: theme.backgroundColor2,
},
text: {
color: theme.color,
},
@@ -146,7 +141,6 @@ const EditLinkDialog = (props: LinkDialogProps) => {
{linkURLInput}
</View>
<Button
style={styles.button}
onPress={onSubmit}
title={_('Done')}
/>

View File

@@ -5,7 +5,7 @@ import Setting from '@joplin/lib/models/Setting';
import shim from '@joplin/lib/shim';
import { themeStyle } from '@joplin/lib/theme';
import { Theme } from '@joplin/lib/themes/type';
import { MutableRefObject, useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
import { useCallback, useContext, useEffect, useMemo, useRef, useState } from 'react';
import { BackHandler, Platform } from 'react-native';
import ExtendedWebView from '../../ExtendedWebView';
import { OnMessageEvent, WebViewControl } from '../../ExtendedWebView/types';
@@ -83,7 +83,7 @@ const useCss = (editorTheme: Theme) => {
const ImageEditor = (props: Props) => {
const editorTheme: Theme = themeStyle(props.themeId);
const webviewRef: MutableRefObject<WebViewControl>|null = useRef(null);
const webviewRef = useRef<WebViewControl|null>(null);
const [imageChanged, setImageChanged] = useState(false);
const dialogs = useContext(DialogContext);
@@ -124,10 +124,10 @@ const ImageEditor = (props: Props) => {
onRequestCloseEditor(true);
return true;
};
BackHandler.addEventListener('hardwareBackPress', hardwareBackPressListener);
const handle = BackHandler.addEventListener('hardwareBackPress', hardwareBackPressListener);
return () => {
BackHandler.removeEventListener('hardwareBackPress', hardwareBackPressListener);
handle.remove();
};
}, [onRequestCloseEditor]);

View File

@@ -1,7 +1,7 @@
// Displays a find/replace dialog
const React = require('react');
const { useMemo, useState, useEffect } = require('react');
import * as React from 'react';
import { useMemo, useState, useEffect } from 'react';
import { EditorSettings } from './types';
import { _ } from '@joplin/lib/locale';

View File

@@ -1,4 +1,4 @@
const React = require('react');
import * as React from 'react';
import { Component } from 'react';
@@ -88,7 +88,7 @@ class NoteListComponent extends Component<NoteListProps> {
if (this.props.items.length) {
return <FlatList
ref={ref => (this.rootRef_ = ref)}
ref={ref => { this.rootRef_ = ref; }}
data={this.props.items}
renderItem={({ item }) => <NoteItem note={item} />}
keyExtractor={item => item.id}

View File

@@ -1,6 +1,6 @@
const React = require('react');
import * as React from 'react';
import { useCallback, useEffect, useMemo, useState } from 'react';
const { View, StyleSheet } = require('react-native');
import { View, StyleSheet } from 'react-native';
import createRootStyle from '../../utils/createRootStyle';
import ScreenHeader from '../ScreenHeader';
import { _ } from '@joplin/lib/locale';

View File

@@ -1,7 +1,7 @@
import * as React from 'react';
import { themeStyle } from './global-style';
import { _ } from '@joplin/lib/locale';
const { View, Button, Text, StyleSheet } = require('react-native');
import { View, Button, Text, StyleSheet } from 'react-native';
import time from '@joplin/lib/time';
import { Platform } from 'react-native';
import Modal from './Modal';
@@ -157,11 +157,11 @@ export default class SelectDateTimeDialog extends React.PureComponent<any, any>
}}
>
<View style={{ ...styles.modalView, backgroundColor: theme.backgroundColor }}>
<View style={{ padding: 15, flexBasis: 'auto', paddingBottom: 0, flexGrow: 0, width: '100%', borderBottomWidth: 1, borderBottomColor: theme.dividerColor, borderBottomStyle: 'solid' }}>
<View style={{ padding: 15, flexBasis: 'auto', paddingBottom: 0, flexGrow: 0, width: '100%', borderBottomWidth: 1, borderBottomColor: theme.dividerColor }}>
<Text style={{ ...styles.modalText, color: theme.color, fontSize: 14, fontWeight: 'bold' }}>{_('Set alarm')}</Text>
</View>
{this.renderContent()}
<View style={{ padding: 20, flexBasis: 'auto', borderTopWidth: 1, borderTopStyle: 'solid', borderTopColor: theme.dividerColor }}>
<View style={{ padding: 20, flexBasis: 'auto', borderTopWidth: 1, borderTopColor: theme.dividerColor }}>
<View style={{ marginBottom: 10 }}>
<Button title={_('Save alarm')} onPress={() => this.onAccept()} key="saveButton" />
</View>

View File

@@ -15,9 +15,11 @@ type Option = {
isDivider: true;
};
export type SideMenuContentOptions = Option[];
interface Props {
themeId: number;
options: Option[];
options: SideMenuContentOptions;
}
const useStyles = (themeId: number) => {

View File

@@ -1,4 +1,4 @@
const React = require('react');
import * as React from 'react';
import { useMemo } from 'react';
import { themeStyle } from './global-style';
import { TextInput, TextInputProps, StyleSheet } from 'react-native';

View File

@@ -1,4 +1,4 @@
const React = require('react');
import * as React from 'react';
import Setting from '@joplin/lib/models/Setting';
import { useEffect, useMemo, useState } from 'react';
import { View, Dimensions, Alert, Button } from 'react-native';

View File

@@ -56,7 +56,7 @@ const FloatingActionButton = (props: ActionButtonProps) => {
if (open) onMenuToggled();
}, [open, onMenuToggled]);
const mainButtonRef = useRef<View>();
const mainButtonRef = useRef<View>(null);
const closedIcon = useIcon(props.mainButton?.icon ?? 'add');
const openIcon = useIcon('close');

View File

@@ -99,7 +99,7 @@ interface Props {
}
const PluginRunnerWebViewComponent: React.FC<Props> = props => {
const webviewRef = useRef<WebViewControl>();
const webviewRef = useRef<WebViewControl>(null);
const [webviewLoaded, setLoaded] = useState(false);
const [webviewReloadCounter, setWebviewReloadCounter] = useState(0);

View File

@@ -9,7 +9,7 @@ import usePlugin from '@joplin/lib/hooks/usePlugin';
const usePluginItem = (id: string, pluginSettings: PluginSettings, initialItem: PluginItem|null): PluginItem => {
const plugin = usePlugin(id);
const lastManifest = useRef<PluginManifest>();
const lastManifest = useRef<PluginManifest|null>(null);
if (plugin) {
lastManifest.current = plugin.manifest;
} else if (!lastManifest.current) {

View File

@@ -20,7 +20,7 @@ import BackButtonService from '../../../services/BackButtonService';
import NavService, { OnNavigateCallback as OnNavigateCallback } from '@joplin/lib/services/NavService';
import { ModelType } from '@joplin/lib/BaseModel';
import FloatingActionButton from '../../buttons/FloatingActionButton';
const { fileExtension, safeFileExtension } = require('@joplin/lib/path-utils');
import { fileExtension, safeFileExtension } from '@joplin/lib/path-utils';
import * as mimeUtils from '@joplin/lib/mime-utils';
import ScreenHeader, { MenuOptionType } from '../../ScreenHeader';
import NoteTagsDialog from '../NoteTagsDialog';
@@ -1561,7 +1561,6 @@ class NoteScreenComponent extends BaseScreenComponent<ComponentProps, State> imp
<TextInput
autoCapitalize="sentences"
style={this.styles().bodyTextInput}
ref="noteBodyTextField"
multiline={true}
value={note.body}
onChangeText={this.onPlainEditorTextChange}

View File

@@ -1,7 +1,7 @@
import * as React from 'react';
import useSyncTargetUpgrade from '@joplin/lib/services/synchronizer/gui/useSyncTargetUpgrade';
import { _ } from '@joplin/lib/locale';
const { View, Text, ScrollView } = require('react-native');
import { View, Text, ScrollView, TextStyle } from 'react-native';
const { connect } = require('react-redux');
import { themeStyle } from '../global-style';
@@ -14,7 +14,7 @@ function UpgradeSyncTargetScreen(props: any) {
const theme = themeStyle(props.themeId);
const lineStyle = { ...theme.normalText, marginBottom: 20 };
const stackTraceStyle = { ...theme.normalText, flexWrap: 'nowrap', fontSize: theme.fontSize * 0.5, color: theme.colorFaded };
const stackTraceStyle: TextStyle = { ...theme.normalText, flexWrap: 'nowrap', fontSize: theme.fontSize * 0.5, color: theme.colorFaded };
const headerStyle = { ...theme.headerStyle, marginBottom: 20 };
function renderUpgradeError() {

View File

@@ -109,7 +109,7 @@ const useAudioRecorder = (onFileSaved: OnFileSavedCallback, onDismiss: ()=> void
const [error, setError] = useState('');
const [duration, setDuration] = useState(0);
const recordingRef = useRef<Audio.Recording|null>();
const recordingRef = useRef<Audio.Recording|null>(null);
const onStartRecording = useCallback(async () => {
try {
setRecordingState(RecorderState.Loading);

View File

@@ -1,4 +1,4 @@
const React = require('react');
import * as React from 'react';
import shim from '@joplin/lib/shim';
shim.setReact(React);
@@ -17,7 +17,7 @@ import UpgradeSyncTargetScreen from './components/screens/UpgradeSyncTargetScree
import Setting, { AppType, Env } from '@joplin/lib/models/Setting';
import PoorManIntervals from '@joplin/lib/PoorManIntervals';
import reducer, { NotesParent, parseNotesParent, serializeNotesParent } from '@joplin/lib/reducer';
import ShareExtension from './utils/ShareExtension';
import ShareExtension, { UnsubscribeShareListener } from './utils/ShareExtension';
import handleShared from './utils/shareHandler';
import uuid from '@joplin/lib/uuid';
import { loadKeychainServiceAndSettings } from '@joplin/lib/services/SettingUtils';
@@ -30,14 +30,14 @@ const VersionInfo = require('react-native-version-info').default;
import { Keyboard, BackHandler, Animated, StatusBar, Platform, Dimensions } from 'react-native';
import { AppState as RNAppState, EmitterSubscription, View, Text, Linking, NativeEventSubscription, Appearance, ActivityIndicator } from 'react-native';
import getResponsiveValue from './components/getResponsiveValue';
import NetInfo from '@react-native-community/netinfo';
import NetInfo, { NetInfoSubscription } from '@react-native-community/netinfo';
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 fastDeepEqual = require('fast-deep-equal');
import { Provider as PaperProvider, MD3DarkTheme, MD3LightTheme } from 'react-native-paper';
import BackButtonService from './services/BackButtonService';
import BackButtonService, { BackButtonHandler } from './services/BackButtonService';
import NavService from '@joplin/lib/services/NavService';
import { createStore, applyMiddleware, Dispatch } from 'redux';
import reduxSharedMiddleware from '@joplin/lib/components/shared/reduxSharedMiddleware';
@@ -68,9 +68,9 @@ import DropboxLoginScreen from './components/screens/dropbox-login.js';
import { MenuProvider } from 'react-native-popup-menu';
import SideMenu, { SideMenuPosition } from './components/SideMenu';
import SideMenuContent from './components/side-menu-content';
import SideMenuContentNote from './components/SideMenuContentNote';
import SideMenuContentNote, { SideMenuContentOptions } from './components/SideMenuContentNote';
import { reg } from '@joplin/lib/registry';
const { defaultState } = require('@joplin/lib/reducer');
import { defaultState } from '@joplin/lib/reducer';
import FileApiDriverLocal from '@joplin/lib/file-api-driver-local';
import ResourceFetcher from '@joplin/lib/services/ResourceFetcher';
import SearchEngine from '@joplin/lib/services/search/SearchEngine';
@@ -853,7 +853,27 @@ async function initialize(dispatch: Dispatch) {
reg.logger().info('Application initialized');
}
class AppComponent extends React.Component {
interface AppComponentProps {
dispatch: Dispatch;
themeId: number;
biometricsDone: boolean;
routeName: string;
selectedFolderId: string;
appState: string;
noteSideMenuOptions: SideMenuContentOptions;
disableSideMenuGestures: boolean;
historyCanGoBack: boolean;
showSideMenu: boolean;
noteSelectionEnabled: boolean;
}
interface AppComponentState {
sideMenuWidth: number;
sensorInfo: SensorInfo;
sideMenuContentOpacity: Animated.Value;
}
class AppComponent extends React.Component<AppComponentProps, AppComponentState> {
private urlOpenListener_: EmitterSubscription|null = null;
private appStateChangeListener_: NativeEventSubscription|null = null;
@@ -864,8 +884,18 @@ class AppComponent extends React.Component {
private dropdownAlert_ = (_data: any) => new Promise<any>(res => res);
private callbackUrl: string|null = null;
public constructor() {
super();
private lastSyncStarted_ = false;
private quickActionShortcutListener_: EmitterSubscription|undefined;
private unsubscribeScreenWidthChangeHandler_: EmitterSubscription|undefined;
private unsubscribeNetInfoHandler_: NetInfoSubscription|undefined;
private unsubscribeNewShareListener_: UnsubscribeShareListener|undefined;
private onAppStateChange_: ()=> void;
private backButtonHandler_: BackButtonHandler;
private handleNewShare_: ()=> void;
private handleOpenURL_: (event: unknown)=> void;
public constructor(props: AppComponentProps) {
super(props);
this.state = {
sideMenuContentOpacity: new Animated.Value(0),
@@ -1390,8 +1420,7 @@ class AppComponent extends React.Component {
}
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const mapStateToProps = (state: any) => {
const mapStateToProps = (state: AppState) => {
return {
historyCanGoBack: state.historyCanGoBack,
showSideMenu: state.showSideMenu,

View File

@@ -1,6 +1,6 @@
import { BackHandler } from 'react-native';
type BackButtonHandler = ()=> boolean|Promise<boolean>;
export type BackButtonHandler = ()=> boolean|Promise<boolean>;
export default class BackButtonService {
private static handlers_: BackButtonHandler[] = [];

View File

@@ -1,6 +1,5 @@
import { NativeEventEmitter } from 'react-native';
const { NativeModules, Platform } = require('react-native');
import { NativeModules, Platform } from 'react-native';
export interface SharedData {
title?: string;
@@ -8,26 +7,36 @@ export interface SharedData {
resources?: string[];
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
type ShareListener = (event: any)=> void;
export type UnsubscribeShareListener = ()=> void;
type ShareExtensionType = {
data: ()=> Promise<SharedData>;
close: ()=> void;
shareURL: ()=> string;
addShareListener: (listener: ShareListener)=> UnsubscribeShareListener|undefined;
};
let eventEmitter: NativeEventEmitter | undefined;
const ShareExtension = (NativeModules.ShareExtension) ?
const ShareExtension: ShareExtensionType = (NativeModules.ShareExtension) ?
{
data: () => NativeModules.ShareExtension.data(),
close: () => NativeModules.ShareExtension.close(),
shareURL: (Platform.OS === 'ios') ? NativeModules.ShareExtension.getConstants().SHARE_EXTENSION_SHARE_URL : '',
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
addShareListener: (Platform.OS === 'android') ? ((handler: (event: any)=> void) => {
addShareListener: (Platform.OS === 'android') ? ((handler) => {
if (!eventEmitter) {
eventEmitter = new NativeEventEmitter(NativeModules.ShareExtension);
}
return eventEmitter.addListener('new_share_intent', handler).remove;
}) : (() => {}),
}) : (() => undefined),
} :
{
data: () => {},
close: () => {},
shareURL: '',
addShareListener: () => {},
addShareListener: () => undefined,
};
export default ShareExtension;

View File

@@ -4,9 +4,9 @@ import shim from '@joplin/lib/shim';
import Note from '@joplin/lib/models/Note';
import checkPermissions from './checkPermissions.js';
import NavService from '@joplin/lib/services/NavService';
const { ToastAndroid } = require('react-native');
const { PermissionsAndroid } = require('react-native');
const { Platform } = require('react-native');
import { ToastAndroid } from 'react-native';
import { PermissionsAndroid } from 'react-native';
import { Platform } from 'react-native';
// eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied
export default async (sharedData: SharedData, folderId: string, dispatch: Function) => {