mirror of
https://github.com/laurent22/joplin.git
synced 2025-01-11 18:24:43 +02:00
Mobile: Write to note in realtime using voice typing
This commit is contained in:
parent
c6a9837f1f
commit
7779879c6d
@ -1244,6 +1244,8 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
}
|
||||
|
||||
const renderActionButton = () => {
|
||||
if (this.state.voiceTypingDialogShown) return null;
|
||||
|
||||
const editButton = {
|
||||
label: _('Edit'),
|
||||
icon: 'md-create',
|
||||
@ -1259,8 +1261,6 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
return <ActionButton mainButton={editButton} />;
|
||||
};
|
||||
|
||||
const actionButtonComp = renderActionButton();
|
||||
|
||||
// Save button is not really needed anymore with the improved save logic
|
||||
const showSaveButton = false; // this.state.mode === 'edit' || this.isModified() || this.saveButtonHasBeenShown_;
|
||||
const saveButtonDisabled = true;// !this.isModified();
|
||||
@ -1314,7 +1314,8 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
/>
|
||||
{titleComp}
|
||||
{bodyComponent}
|
||||
{actionButtonComp}
|
||||
{renderActionButton()}
|
||||
{renderVoiceTypingDialog()}
|
||||
|
||||
<SelectDateTimeDialog themeId={this.props.themeId} shown={this.state.alarmDialogShown} date={dueDate} onAccept={this.onAlarmDialogAccept} onReject={this.onAlarmDialogReject} />
|
||||
|
||||
@ -1324,7 +1325,6 @@ class NoteScreenComponent extends BaseScreenComponent {
|
||||
}}
|
||||
/>
|
||||
{noteTagDialog}
|
||||
{renderVoiceTypingDialog()}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
@ -1,10 +1,10 @@
|
||||
import * as React from 'react';
|
||||
import { useState, useEffect, useCallback } from 'react';
|
||||
import { Button, Dialog, Text } from 'react-native-paper';
|
||||
import { Banner, ActivityIndicator } from 'react-native-paper';
|
||||
import { _ } from '@joplin/lib/locale';
|
||||
import useAsyncEffect, { AsyncEffectEvent } from '@joplin/lib/hooks/useAsyncEffect';
|
||||
import { getVosk, Recorder, startRecording, Vosk } from '../../services/voiceTyping/vosk';
|
||||
import { Alert } from 'react-native';
|
||||
import { IconSource } from 'react-native-paper/lib/typescript/src/components/Icon';
|
||||
|
||||
interface Props {
|
||||
onDismiss: ()=> void;
|
||||
@ -42,58 +42,50 @@ export default (props: Props) => {
|
||||
|
||||
useEffect(() => {
|
||||
if (recorderState === RecorderState.Recording) {
|
||||
setRecorder(startRecording(vosk));
|
||||
setRecorder(startRecording(vosk, {
|
||||
onResult: (text: string) => {
|
||||
props.onText(text);
|
||||
},
|
||||
}));
|
||||
}
|
||||
}, [recorderState, vosk]);
|
||||
}, [recorderState, vosk, props.onText]);
|
||||
|
||||
const onDismiss = useCallback(() => {
|
||||
recorder.cleanup();
|
||||
props.onDismiss();
|
||||
}, [recorder, props.onDismiss]);
|
||||
|
||||
const onStop = useCallback(async () => {
|
||||
try {
|
||||
setRecorderState(RecorderState.Processing);
|
||||
const result = await recorder.stop();
|
||||
props.onText(result);
|
||||
} catch (error) {
|
||||
Alert.alert(error.message);
|
||||
}
|
||||
onDismiss();
|
||||
}, [recorder, onDismiss, props.onText]);
|
||||
|
||||
const renderContent = () => {
|
||||
const components: Record<RecorderState, any> = {
|
||||
[RecorderState.Loading]: <Text variant="bodyMedium">{_('Loading...')}</Text>,
|
||||
[RecorderState.Recording]: <Text variant="bodyMedium">{_('Please record your voice...')}</Text>,
|
||||
[RecorderState.Processing]: <Text variant="bodyMedium">{_('Converting speech to text...')}</Text>,
|
||||
const components: Record<RecorderState, string> = {
|
||||
[RecorderState.Loading]: _('Loading...'),
|
||||
[RecorderState.Recording]: _('Please record your voice...'),
|
||||
[RecorderState.Processing]: _('Converting speech to text...'),
|
||||
};
|
||||
|
||||
return components[recorderState];
|
||||
};
|
||||
|
||||
const renderActions = () => {
|
||||
const components: Record<RecorderState, any> = {
|
||||
[RecorderState.Loading]: null,
|
||||
[RecorderState.Recording]: (
|
||||
<Dialog.Actions>
|
||||
<Button onPress={onDismiss}>{_('Cancel')}</Button>
|
||||
<Button onPress={onStop}>{_('Done')}</Button>
|
||||
</Dialog.Actions>
|
||||
),
|
||||
[RecorderState.Processing]: null,
|
||||
const renderIcon = () => {
|
||||
const components: Record<RecorderState, IconSource> = {
|
||||
[RecorderState.Loading]: ({ size }: { size: number }) => <ActivityIndicator animating={true} style={{ width: size, height: size }} />,
|
||||
[RecorderState.Recording]: 'microphone',
|
||||
[RecorderState.Processing]: 'microphone',
|
||||
};
|
||||
|
||||
return components[recorderState];
|
||||
};
|
||||
|
||||
return (
|
||||
<Dialog visible={true} onDismiss={props.onDismiss}>
|
||||
<Dialog.Title>{_('Voice typing')}</Dialog.Title>
|
||||
<Dialog.Content>
|
||||
{renderContent()}
|
||||
</Dialog.Content>
|
||||
{renderActions()}
|
||||
</Dialog>
|
||||
<Banner
|
||||
visible={true}
|
||||
icon={renderIcon()}
|
||||
actions={[
|
||||
{
|
||||
label: _('Done'),
|
||||
onPress: onDismiss,
|
||||
},
|
||||
]}>
|
||||
{`${_('Voice typing...')}\n${renderContent()}`}
|
||||
</Banner>
|
||||
);
|
||||
};
|
||||
|
@ -7,6 +7,10 @@ enum State {
|
||||
Recording,
|
||||
}
|
||||
|
||||
interface StartOptions {
|
||||
onResult: (text: string)=> void;
|
||||
}
|
||||
|
||||
let vosk_: Vosk|null = null;
|
||||
let state_: State = State.Idle;
|
||||
|
||||
@ -26,7 +30,7 @@ export const getVosk = async () => {
|
||||
return vosk_;
|
||||
};
|
||||
|
||||
export const startRecording = (vosk: Vosk): Recorder => {
|
||||
export const startRecording = (vosk: Vosk, options: StartOptions): Recorder => {
|
||||
if (state_ !== State.Idle) throw new Error('Vosk is already recording');
|
||||
|
||||
state_ = State.Recording;
|
||||
@ -56,8 +60,10 @@ export const startRecording = (vosk: Vosk): Recorder => {
|
||||
};
|
||||
|
||||
eventHandlers.push(vosk.onResult(e => {
|
||||
logger.info('Result', e.data);
|
||||
result.push(e.data);
|
||||
const text = e.data;
|
||||
logger.info('Result', text);
|
||||
result.push(text);
|
||||
options.onResult(text);
|
||||
}));
|
||||
|
||||
eventHandlers.push(vosk.onError(e => {
|
||||
|
@ -4,6 +4,10 @@ type Vosk = any;
|
||||
|
||||
export { Vosk };
|
||||
|
||||
interface StartOptions {
|
||||
onResult: (text: string)=> void;
|
||||
}
|
||||
|
||||
export interface Recorder {
|
||||
stop: ()=> Promise<string>;
|
||||
cleanup: ()=> void;
|
||||
@ -13,7 +17,7 @@ export const getVosk = async () => {
|
||||
return {} as any;
|
||||
};
|
||||
|
||||
export const startRecording = (_vosk: Vosk): Recorder => {
|
||||
export const startRecording = (_vosk: Vosk, _options: StartOptions): Recorder => {
|
||||
return {
|
||||
stop: async () => { return ''; },
|
||||
cleanup: () => {},
|
||||
|
Loading…
Reference in New Issue
Block a user