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 +