1
0
mirror of https://github.com/laurent22/joplin.git synced 2025-11-26 22:41:17 +02:00

Mobile: Resolves #10374: Add more options when long pressing the icon on mobile (#11517)

This commit is contained in:
Laurent Cozic
2024-12-16 10:49:46 +01:00
committed by GitHub
parent dc96811940
commit 3983a3a52f
9 changed files with 116 additions and 28 deletions

View File

@@ -1,6 +1,6 @@
import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService'; import { CommandRuntime, CommandDeclaration, CommandContext } from '@joplin/lib/services/CommandService';
import Logger from '@joplin/utils/Logger'; import Logger from '@joplin/utils/Logger';
import goToNote from './util/goToNote'; import goToNote, { GotoNoteOptions } from './util/goToNote';
import Note from '@joplin/lib/models/Note'; import Note from '@joplin/lib/models/Note';
import Setting from '@joplin/lib/models/Setting'; import Setting from '@joplin/lib/models/Setting';
@@ -12,7 +12,7 @@ export const declaration: CommandDeclaration = {
export const runtime = (): CommandRuntime => { export const runtime = (): CommandRuntime => {
return { return {
execute: async (_context: CommandContext, body = '', todo = false) => { execute: async (_context: CommandContext, body = '', todo = false, options: GotoNoteOptions = null) => {
const folderId = Setting.value('activeFolderId'); const folderId = Setting.value('activeFolderId');
if (!folderId) { if (!folderId) {
logger.warn('Not creating new note -- no active folder ID.'); logger.warn('Not creating new note -- no active folder ID.');
@@ -26,7 +26,7 @@ export const runtime = (): CommandRuntime => {
}, { provisional: true }); }, { provisional: true });
logger.info(`Navigating to note ${note.id}`); logger.info(`Navigating to note ${note.id}`);
await goToNote(note.id, ''); await goToNote(note.id, '', options);
}, },
}; };
}; };

View File

@@ -1,7 +1,17 @@
import Note from '@joplin/lib/models/Note'; import Note from '@joplin/lib/models/Note';
import NavService from '@joplin/lib/services/NavService'; import NavService from '@joplin/lib/services/NavService';
import { AttachFileAction } from '../../components/screens/Note/commands/attachFile';
export interface GotoNoteOptions {
attachFileAction?: AttachFileAction | null;
}
const goToNote = async (id: string, hash?: string, options: GotoNoteOptions = null) => {
options = {
attachFileAction: null,
...options,
};
const goToNote = async (id: string, hash?: string) => {
if (!(await Note.load(id))) { if (!(await Note.load(id))) {
throw new Error(`No note with id ${id}`); throw new Error(`No note with id ${id}`);
} }
@@ -9,6 +19,7 @@ const goToNote = async (id: string, hash?: string) => {
return NavService.go('Note', { return NavService.go('Note', {
noteId: id, noteId: id,
noteHash: hash, noteHash: hash,
newNoteAttachFileAction: options.attachFileAction,
}); });
}; };

View File

@@ -62,6 +62,7 @@ import { CameraResult } from '../../CameraView/types';
import { DialogContext, DialogControl } from '../../DialogManager'; import { DialogContext, DialogControl } from '../../DialogManager';
import { CommandRuntimeProps, EditorMode, PickerResponse } from './types'; import { CommandRuntimeProps, EditorMode, PickerResponse } from './types';
import commands from './commands'; import commands from './commands';
import { AttachFileAction, AttachFileOptions } from './commands/attachFile';
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
const emptyArray: any[] = []; const emptyArray: any[] = [];
@@ -83,6 +84,7 @@ interface Props extends BaseProps {
highlightedWords: string[]; highlightedWords: string[];
noteHash: string; noteHash: string;
toolbarEnabled: boolean; toolbarEnabled: boolean;
newNoteAttachFileAction: AttachFileAction;
} }
interface ComponentProps extends Props { interface ComponentProps extends Props {
@@ -523,6 +525,19 @@ class NoteScreenComponent extends BaseScreenComponent<ComponentProps, State> imp
// has already been granted, it doesn't slow down opening the note. If it hasn't // has already been granted, it doesn't slow down opening the note. If it hasn't
// been granted, the popup will open anyway. // been granted, the popup will open anyway.
void this.requestGeoLocationPermissions(); void this.requestGeoLocationPermissions();
if (this.props.newNoteAttachFileAction) {
setTimeout(async () => {
if (this.props.newNoteAttachFileAction === AttachFileAction.AttachDrawing) {
await this.drawPicture_onPress();
} else {
const options: AttachFileOptions = {
action: this.props.newNoteAttachFileAction,
};
await CommandService.instance().execute('attachFile', '', options);
}
}, 100);
}
} }
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
@@ -1294,6 +1309,11 @@ class NoteScreenComponent extends BaseScreenComponent<ComponentProps, State> imp
done = true; done = true;
} }
if (!this.noteEditorVisible()) {
logger.info(`Note editor is not visible - not setting focus on ${fieldToFocus}`);
done = true;
}
if (done) { if (done) {
shim.clearInterval(this.focusUpdateIID_); shim.clearInterval(this.focusUpdateIID_);
this.focusUpdateIID_ = null; this.focusUpdateIID_ = null;
@@ -1375,6 +1395,10 @@ class NoteScreenComponent extends BaseScreenComponent<ComponentProps, State> imp
this.setState({ voiceTypingDialogShown: false }); this.setState({ voiceTypingDialogShown: false });
} }
private noteEditorVisible() {
return !this.state.showCamera && !this.state.showImageEditor;
}
public render() { public render() {
// Commands must be registered before child components can render. // Commands must be registered before child components can render.
// Calling this in the constructor won't work in strict mode, where // Calling this in the constructor won't work in strict mode, where
@@ -1611,6 +1635,7 @@ const NoteScreen = connect((state: AppState) => {
return { return {
noteId: state.selectedNoteIds.length ? state.selectedNoteIds[0] : null, noteId: state.selectedNoteIds.length ? state.selectedNoteIds[0] : null,
noteHash: state.selectedNoteHash, noteHash: state.selectedNoteHash,
newNoteAttachFileAction: state.newNoteAttachFileAction,
itemType: state.selectedItemType, itemType: state.selectedItemType,
folders: state.folders, folders: state.folders,
searchQuery: state.searchQuery, searchQuery: state.searchQuery,

View File

@@ -8,6 +8,17 @@ import Logger from '@joplin/utils/Logger';
const logger = Logger.create('attachFile'); const logger = Logger.create('attachFile');
export enum AttachFileAction {
TakePhoto = 'takePhoto',
AttachFile = 'attachFile',
AttachPhoto = 'attachPhoto',
AttachDrawing = 'attachDrawing',
}
export interface AttachFileOptions {
action?: AttachFileAction | null;
}
export const declaration: CommandDeclaration = { export const declaration: CommandDeclaration = {
name: 'attachFile', name: 'attachFile',
label: () => _('Attach file'), label: () => _('Attach file'),
@@ -50,35 +61,46 @@ export const runtime = (props: CommandRuntimeProps): CommandRuntime => {
} }
}; };
const showAttachMenu = async () => { const showAttachMenu = async (action: AttachFileAction = null) => {
props.hideKeyboard(); props.hideKeyboard();
const buttons = []; let buttonId: AttachFileAction = null;
// On iOS, it will show "local files", which means certain files saved from the browser if (action) {
// and the iCloud files, but it doesn't include photos and images from the CameraRoll buttonId = action;
// } else {
// On Android, it will depend on the phone, but usually it will allow browsing all files and photos. const buttons = [];
buttons.push({ text: _('Attach file'), id: 'attachFile' });
// Disabled on Android because it doesn't work due to permission issues, but enabled on iOS // On iOS, it will show "local files", which means certain files saved from the browser
// because that's only way to browse photos from the camera roll. // and the iCloud files, but it doesn't include photos and images from the CameraRoll
if (Platform.OS === 'ios') buttons.push({ text: _('Attach photo'), id: 'attachPhoto' }); //
buttons.push({ text: _('Take photo'), id: 'takePhoto' }); // On Android, it will depend on the phone, but usually it will allow browsing all files and photos.
buttons.push({ text: _('Attach file'), id: AttachFileAction.AttachFile });
const buttonId = await props.dialogs.showMenu(_('Choose an option'), buttons); // Disabled on Android because it doesn't work due to permission issues, but enabled on iOS
// because that's only way to browse photos from the camera roll.
if (Platform.OS === 'ios') buttons.push({ text: _('Attach photo'), id: AttachFileAction.AttachPhoto });
buttons.push({ text: _('Take photo'), id: AttachFileAction.TakePhoto });
if (buttonId === 'takePhoto') await takePhoto(); buttonId = await props.dialogs.showMenu(_('Choose an option'), buttons) as AttachFileAction;
if (buttonId === 'attachFile') await attachFile(); }
if (buttonId === 'attachPhoto') await attachPhoto();
if (buttonId === AttachFileAction.TakePhoto) await takePhoto();
if (buttonId === AttachFileAction.AttachFile) await attachFile();
if (buttonId === AttachFileAction.AttachPhoto) await attachPhoto();
}; };
return { return {
execute: async (_context: CommandContext, filePath?: string) => { execute: async (_context: CommandContext, filePath?: string, options: AttachFileOptions = null) => {
options = {
action: null,
...options,
};
if (filePath) { if (filePath) {
await props.attachFile({ uri: filePath }, 'all'); await props.attachFile({ uri: filePath }, 'all');
} else { } else {
await showAttachMenu(); await showAttachMenu(options.action);
} }
}, },

View File

@@ -354,6 +354,10 @@ const appReducer = (state = appDefaultState, action: any) => {
newState.selectedNoteHash = action.noteHash; newState.selectedNoteHash = action.noteHash;
} }
if ('newNoteAttachFileAction' in action) {
newState.newNoteAttachFileAction = action.newNoteAttachFileAction;
}
if ('sharedData' in action) { if ('sharedData' in action) {
newState.sharedData = action.sharedData; newState.sharedData = action.sharedData;
} else { } else {

View File

@@ -4,6 +4,8 @@ import { Dispatch } from 'redux';
import CommandService from '@joplin/lib/services/CommandService'; import CommandService from '@joplin/lib/services/CommandService';
import Logger from '@joplin/utils/Logger'; import Logger from '@joplin/utils/Logger';
import { DeviceEventEmitter } from 'react-native'; import { DeviceEventEmitter } from 'react-native';
import { GotoNoteOptions } from './commands/util/goToNote';
import { AttachFileAction } from './components/screens/Note/commands/attachFile';
const logger = Logger.create('setupQuickActions'); const logger = Logger.create('setupQuickActions');
@@ -19,9 +21,17 @@ export default async (dispatch: Dispatch) => {
return null; return null;
} }
// List of iOS icons:
// https://github.com/EvanBacon/expo-quick-actions?tab=readme-ov-file#system-icons
//
// Note: on Android, anything beyond the fourth menu item appears to be ignored, at least on
// emulator.
QuickActions.setShortcutItems([ QuickActions.setShortcutItems([
{ type: 'New note', title: _('New note'), icon: 'Compose', userInfo }, { type: 'newNote', title: _('New note'), icon: 'Compose', userInfo },
{ type: 'New to-do', title: _('New to-do'), icon: 'Add', userInfo }, { type: 'newTodo', title: _('New to-do'), icon: 'Add', userInfo },
{ type: 'newPhoto', title: _('New photo'), icon: 'CapturePhoto', userInfo },
{ type: 'newResource', title: _('New attachment'), icon: 'Bookmark', userInfo },
{ type: 'newDrawing', title: _('New drawing'), icon: 'Favorite', userInfo },
]); ]);
try { try {
@@ -50,6 +60,18 @@ const quickActionHandler = (dispatch: Dispatch) => async (data: TData) => {
dispatch({ type: 'NAV_BACK' }); dispatch({ type: 'NAV_BACK' });
dispatch({ type: 'SIDE_MENU_CLOSE' }); dispatch({ type: 'SIDE_MENU_CLOSE' });
const isTodo = data.type === 'New to-do' ? 1 : 0; const isTodo = data.type === 'newTodo' ? 1 : 0;
await CommandService.instance().execute('newNote', '', isTodo); const options: GotoNoteOptions = {
attachFileAction: null,
};
if (data.type === 'newPhoto') {
options.attachFileAction = AttachFileAction.TakePhoto;
} else if (data.type === 'newResource') {
options.attachFileAction = AttachFileAction.AttachFile;
} else if (data.type === 'newDrawing') {
options.attachFileAction = AttachFileAction.AttachDrawing;
}
await CommandService.instance().execute('newNote', '', isTodo, options);
}; };

View File

@@ -1,7 +1,6 @@
import { defaultState } from '@joplin/lib/reducer'; import { defaultState } from '@joplin/lib/reducer';
import { AppState } from './types'; import { AppState } from './types';
export const DEFAULT_ROUTE = { export const DEFAULT_ROUTE = {
type: 'NAV_GO', type: 'NAV_GO',
routeName: 'Notes', routeName: 'Notes',
@@ -10,7 +9,6 @@ export const DEFAULT_ROUTE = {
const appDefaultState: AppState = { const appDefaultState: AppState = {
smartFilterId: undefined, smartFilterId: undefined,
...defaultState,
keyboardVisible: false, keyboardVisible: false,
route: DEFAULT_ROUTE, route: DEFAULT_ROUTE,
noteSelectionEnabled: false, noteSelectionEnabled: false,
@@ -18,5 +16,7 @@ const appDefaultState: AppState = {
isOnMobileData: false, isOnMobileData: false,
disableSideMenuGestures: false, disableSideMenuGestures: false,
showPanelsDialog: false, showPanelsDialog: false,
newNoteAttachFileAction: null,
...defaultState,
}; };
export default appDefaultState; export default appDefaultState;

View File

@@ -1,4 +1,5 @@
import { State } from '@joplin/lib/reducer'; import { State } from '@joplin/lib/reducer';
import { AttachFileAction } from '../components/screens/Note/commands/attachFile';
export interface AppState extends State { export interface AppState extends State {
showPanelsDialog: boolean; showPanelsDialog: boolean;
@@ -10,4 +11,6 @@ export interface AppState extends State {
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied // eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
noteSideMenuOptions: any; noteSideMenuOptions: any;
disableSideMenuGestures: boolean; disableSideMenuGestures: boolean;
newNoteAttachFileAction: AttachFileAction | null;
} }

View File

@@ -147,5 +147,6 @@ setsize
Comprar Comprar
seguidores seguidores
devbox devbox
tablist
Ökonomie Ökonomie
Favorite
tablist