diff --git a/android/app/src/main/assets/js/index.js b/android/app/src/main/assets/js/index.js
index a867f8ae..0247daad 100644
--- a/android/app/src/main/assets/js/index.js
+++ b/android/app/src/main/assets/js/index.js
@@ -9,14 +9,10 @@ const volumeIcon =
'';
const selectAllIcon =
'';
-const translateIcon =
- '';
const resumeIcon =
'';
const pauseIcon =
'';
-const stopIcon =
- '';
const selectVolumeIcon =
'';
/**
@@ -446,7 +442,6 @@ class TextToSpeech {
if (!this.leaf.hasChildNodes()) {
return false;
}
- const a = [];
for (let i = 0; i < this.leaf.childNodes.length; i++) {
if (
!this.readableNodeNames.includes(this.leaf.childNodes.item(i).nodeName)
@@ -598,7 +593,17 @@ class ContextMenu {
});
}
+ closeMenu() {
+ if (this.isOpened) {
+ this.isOpened = false;
+ this.contextMenu.remove();
+ }
+ }
+
init() {
+ this.reader.chapter.addEventListener('click', () => {
+ this.closeMenu(this.contextMenu);
+ });
document.addEventListener('contextmenu', e => {
e.preventDefault();
if (e.target instanceof HTMLImageElement) {
diff --git a/src/screens/reader/components/WebViewReader.tsx b/src/screens/reader/components/WebViewReader.tsx
index a6e985d5..96e3b4f1 100644
--- a/src/screens/reader/components/WebViewReader.tsx
+++ b/src/screens/reader/components/WebViewReader.tsx
@@ -51,6 +51,18 @@ type WebViewReaderProps = {
pageReader: boolean;
};
+const onLogMessage = (payload: { nativeEvent: { data: string } }) => {
+ let dataPayload;
+ try {
+ dataPayload = JSON.parse(payload.nativeEvent.data);
+ } catch (e) {}
+ if (dataPayload) {
+ if (dataPayload.type === 'console') {
+ console.info(`[Console] ${JSON.stringify(dataPayload.msg, null, 2)}`);
+ }
+ }
+};
+
const WebViewReader: React.FC = props => {
const {
data: { chapter, novel },
@@ -139,24 +151,6 @@ const WebViewReader: React.FC = props => {
console.warn = console.log;
console.error = console.log;
`;
- //@ts-ignore
- const onMessage = payload => {
- let dataPayload;
- try {
- dataPayload = JSON.parse(payload.nativeEvent.data);
- } catch (e) {}
-
- if (dataPayload) {
- if (dataPayload.type === 'console') {
- // eslint-disable-next-line no-console
- console.info(`[Console] ${JSON.stringify(dataPayload.msg, null, 2)}`);
- } else if (false) {
- if (dataPayload.type !== 'save') {
- console.log(dataPayload);
- }
- }
- }
- };
return (
= props => {
javaScriptEnabled={true}
onLayout={async () => onLayout()}
onMessage={(ev: { nativeEvent: { data: string } }) => {
- __DEV__ && onMessage(ev);
+ __DEV__ && onLogMessage(ev);
const event: WebViewPostEvent = JSON.parse(ev.nativeEvent.data);
switch (event.type) {
case 'hide':
diff --git a/src/screens/settings/SettingsReaderScreen/Modals/VoicePickerModal.tsx b/src/screens/settings/SettingsReaderScreen/Modals/VoicePickerModal.tsx
index f5851409..6ce31e29 100644
--- a/src/screens/settings/SettingsReaderScreen/Modals/VoicePickerModal.tsx
+++ b/src/screens/settings/SettingsReaderScreen/Modals/VoicePickerModal.tsx
@@ -1,33 +1,35 @@
-import React, { useEffect, useState } from 'react';
+import React, { useState } from 'react';
import { StyleSheet } from 'react-native';
-import { Portal, Modal, overlay, TextInput } from 'react-native-paper';
+import {
+ Portal,
+ Modal,
+ overlay,
+ TextInput,
+ ActivityIndicator,
+} from 'react-native-paper';
import { RadioButton } from '@components/RadioButton/RadioButton';
import { useChapterReaderSettings, useTheme } from '@hooks/persisted';
-import { Voice, getAvailableVoicesAsync } from 'expo-speech';
+import { Voice } from 'expo-speech';
import { FlashList } from '@shopify/flash-list';
interface VoicePickerModalProps {
visible: boolean;
onDismiss: () => void;
+ voices: Voice[];
}
const VoicePickerModal: React.FC = ({
onDismiss,
visible,
+ voices,
}) => {
const theme = useTheme();
- const [voices, setVoices] = useState([]);
const [searchedVoices, setSearchedVoices] = useState([]);
const [searchText, setSearchText] = useState('');
const { setChapterReaderSettings, tts } = useChapterReaderSettings();
- useEffect(() => {
- getAvailableVoicesAsync().then(res => {
- res.sort((a, b) => a.name.localeCompare(b.name));
- setVoices([{ name: 'System', language: 'System' } as Voice, ...res]);
- });
- }, []);
+
return (
= ({
keyExtractor={item => item.identifier || 'system'}
estimatedItemSize={64}
removeClippedSubviews={true}
+ ListEmptyComponent={
+
+ }
/>
diff --git a/src/screens/settings/SettingsReaderScreen/Settings/TextToSpeechSettings.tsx b/src/screens/settings/SettingsReaderScreen/Settings/TextToSpeechSettings.tsx
index 9f92ac12..0c3aa1af 100644
--- a/src/screens/settings/SettingsReaderScreen/Settings/TextToSpeechSettings.tsx
+++ b/src/screens/settings/SettingsReaderScreen/Settings/TextToSpeechSettings.tsx
@@ -1,13 +1,24 @@
-import { List } from '@components';
+import { Button, List } from '@components';
import { useChapterReaderSettings, useTheme } from '@hooks/persisted';
-import React from 'react';
+import React, { useEffect, useState } from 'react';
import VoicePickerModal from '../Modals/VoicePickerModal';
import { useBoolean } from '@hooks';
import { Portal } from 'react-native-paper';
+import { StyleSheet, View, Text } from 'react-native';
+import Slider from '@react-native-community/slider';
+import { getAvailableVoicesAsync, Voice } from 'expo-speech';
export default function TextToSpeechSettings() {
const theme = useTheme();
- const { tts } = useChapterReaderSettings();
+ const [voices, setVoices] = useState([]);
+ useEffect(() => {
+ getAvailableVoicesAsync().then(res => {
+ res.sort((a, b) => a.name.localeCompare(b.name));
+ setVoices([{ name: 'System', language: 'System' } as Voice, ...res]);
+ });
+ }, []);
+
+ const { tts, setChapterReaderSettings } = useChapterReaderSettings();
const {
value: voiceModalVisible,
setTrue: showVoiceModal,
@@ -15,19 +26,87 @@ export default function TextToSpeechSettings() {
} = useBoolean();
return (
<>
- Text to Speech
+
+ Text to Speech
+
+
+
+ Voice rate
+
+
+ setChapterReaderSettings({ tts: { ...tts, rate: value } })
+ }
+ />
+
+ Voice pitch
+
+
+ setChapterReaderSettings({ tts: { ...tts, pitch: value } })
+ }
+ />
+
+
>
);
}
+
+const styles = StyleSheet.create({
+ row: {
+ flexDirection: 'row',
+ justifyContent: 'space-between',
+ alignItems: 'center',
+ },
+ label: {
+ textAlign: 'center',
+ fontSize: 16,
+ },
+ slider: {
+ flex: 1,
+ height: 40,
+ },
+});
diff --git a/src/screens/settings/SettingsReaderScreen/SettingsReaderScreen.tsx b/src/screens/settings/SettingsReaderScreen/SettingsReaderScreen.tsx
index d41c309b..6f5e3ebe 100644
--- a/src/screens/settings/SettingsReaderScreen/SettingsReaderScreen.tsx
+++ b/src/screens/settings/SettingsReaderScreen/SettingsReaderScreen.tsx
@@ -1,5 +1,5 @@
import { View, ScrollView, StatusBar, Dimensions } from 'react-native';
-import React, { useMemo, useRef, useState } from 'react';
+import React, { useEffect, useMemo, useRef, useState } from 'react';
import { useNavigation } from '@react-navigation/native';
import WebView from 'react-native-webview';
@@ -139,7 +139,11 @@ const SettingsReaderScreen = () => {
`;
const readerBackgroundColor = readerSettings.theme;
-
+ useEffect(() => {
+ return () => {
+ Speech.stop();
+ };
+ }, []);
return (
<>