From e50b6c4a476111d9d9ef6375be3ba58e8f711e80 Mon Sep 17 00:00:00 2001 From: Rider21 <58046032+Rider21@users.noreply.github.com> Date: Tue, 28 May 2024 11:50:24 +0300 Subject: [PATCH] Feat: Plugin storages (#986) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * test * fix * Update pluginManager.ts * test * fix lint * test * test webview * sync * / * Update WebviewScreen.tsx * CookieManager * Update storage.ts * Update сookie.ts * fix title * MarqueeText... * Slow * Even slower * Squashed commit of the following: commit da4d7e878aced6567b98fc802c34715b27dfbe00 Author: Rider21 <58046032+Rider21@users.noreply.github.com> Date: Fri Mar 8 13:21:19 2024 +0300 / commit 592c4dbfca2d04931ffffba19d1eec20db3ac183 Author: Rider21 <58046032+Rider21@users.noreply.github.com> Date: Fri Mar 8 13:20:34 2024 +0300 test commit 63240502d67cda6e851d602728b873e0b6d2aed0 Author: Rider21 <58046032+Rider21@users.noreply.github.com> Date: Fri Mar 8 12:38:13 2024 +0300 / commit 591d2592a7f64e65da4f2c61216b564f3eafa2e6 Author: Rider21 <58046032+Rider21@users.noreply.github.com> Date: Fri Mar 8 11:42:03 2024 +0300 fix progressbar commit 3d34cfb72827fdcf61267eafbb128b9e893ef40f Author: Rider21 <58046032+Rider21@users.noreply.github.com> Date: Thu Mar 7 21:13:49 2024 +0300 forgot commit c36d090a60eadfcc4bee1f0cae04bf9213a694a4 Author: Rider21 <58046032+Rider21@users.noreply.github.com> Date: Thu Mar 7 20:51:31 2024 +0300 / commit 3c4e43b7bb6f845d94737aa8cba96b0dc1714b4b Author: Rider21 <58046032+Rider21@users.noreply.github.com> Date: Thu Mar 7 20:15:38 2024 +0300 / commit 0a20a483968fa42d24996a1d74f4c8e91f86d49a Author: Rider21 <58046032+Rider21@users.noreply.github.com> Date: Thu Mar 7 19:46:31 2024 +0300 add zIndex commit 7d4490110457f1ae189890268731fc1726a87825 Author: Rider21 <58046032+Rider21@users.noreply.github.com> Date: Thu Mar 7 19:27:32 2024 +0300 test commit b585d5ee0a6d36707ddc323547fdd1976451ee5a Author: Rider21 <58046032+Rider21@users.noreply.github.com> Date: Thu Mar 7 18:28:49 2024 +0300 / commit cb7241c33d6cea3c1493f41b46fdd67f70455731 Author: Rider21 <58046032+Rider21@users.noreply.github.com> Date: Thu Mar 7 18:06:30 2024 +0300 Update Appbar.tsx commit 5fab766e17708a1be6a9f53029918cad122110bb Author: Rider21 <58046032+Rider21@users.noreply.github.com> Date: Thu Mar 7 17:55:24 2024 +0300 Update WebviewScreen.tsx commit aec9b92d033156936b97a8455f6198fa20570906 Author: Rider21 <58046032+Rider21@users.noreply.github.com> Date: Thu Mar 7 17:15:16 2024 +0300 xd commit 1fa385b4bd85bea4da34ff9c23021fb92bc622d9 Author: Rider21 <58046032+Rider21@users.noreply.github.com> Date: Thu Mar 7 17:11:23 2024 +0300 2 commit 589870de27cee2fcf29c894bd055a936bb0e7a5d Author: Rider21 <58046032+Rider21@users.noreply.github.com> Date: Thu Mar 7 16:28:24 2024 +0300 1 commit 343d2dd5b64404a3ea54d8e0366d8c856c9ea429 Author: Rider21 <58046032+Rider21@users.noreply.github.com> Date: Thu Mar 7 16:11:08 2024 +0300 test commit f2dbf34a7962b04cee46fdb37a9d0ea83ca541fe Author: Rider21 <58046032+Rider21@users.noreply.github.com> Date: Thu Mar 7 14:36:48 2024 +0300 test commit 04c04d97a9dd87815c36145b3e0bd63e1bb3d118 Author: Rider21 <58046032+Rider21@users.noreply.github.com> Date: Wed Mar 6 22:37:45 2024 +0300 test zoom commit 6b80559e053a0d1a31f4c4762570b894bb5f70e5 Author: Rider21 <58046032+Rider21@users.noreply.github.com> Date: Wed Mar 6 22:33:55 2024 +0300 fix commit 287ce94efc18036ff9cca3054e9ca182137ffe8c Author: Rider21 <58046032+Rider21@users.noreply.github.com> Date: Wed Mar 6 19:00:01 2024 +0300 test commit 67bdddc4d10920b559122f3d7df0225cea65497b Author: Rider21 <58046032+Rider21@users.noreply.github.com> Date: Wed Mar 6 11:10:22 2024 +0300 Test newArch commit 762353788716341474832815f332ad6ff9ebe5be Author: Rider21 <58046032+Rider21@users.noreply.github.com> Date: Tue Mar 5 20:49:29 2024 +0300 Update Appbar.tsx commit 2a876cb942847fb4c67085a6736b2d9e6e9a3012 Author: Rider21 <58046032+Rider21@users.noreply.github.com> Date: Tue Mar 5 20:42:28 2024 +0300 modal commit ba4cadec3f87d565fe045e3c977f604444a06571 Author: Rider21 <58046032+Rider21@users.noreply.github.com> Date: Tue Mar 5 16:50:38 2024 +0300 1 commit 6c14a18b7083fc3292f796408e2defdad2728b10 Author: Rider21 <58046032+Rider21@users.noreply.github.com> Date: Tue Mar 5 14:54:27 2024 +0300 test * fix * fix * Update pluginManager.ts * test * fix * fix lint * -marquee * fix defaultCover * test * Update src/plugins/helpers/storage.ts Co-authored-by: nyagami * test * Typescript fixes * private pluginID --------- Co-authored-by: nyagami --- package-lock.json | 20 +-- package.json | 6 +- src/hooks/persisted/usePlugins.ts | 2 +- src/navigators/types/index.ts | 2 +- src/plugins/helpers/constants.js | 2 - src/plugins/helpers/constants.ts | 2 + src/plugins/helpers/storage.ts | 123 +++++++++++++++ src/plugins/pluginManager.ts | 44 ++++-- src/plugins/types/filterTypes.ts | 14 +- src/plugins/types/index.ts | 1 + .../BrowseSourceScreen/BrowseSourceScreen.tsx | 1 + src/screens/WebviewScreen/WebviewScreen.tsx | 70 ++++++-- .../WebviewScreen/components/Appbar.tsx | 149 +++++++----------- src/screens/WebviewScreen/components/Menu.tsx | 102 ++++++++++++ .../NovelScreenButtonGroup.tsx | 2 +- src/screens/reader/ReaderScreen.tsx | 2 +- .../reader/components/ReaderFooter.tsx | 2 +- .../settings/SettingsAdvancedScreen.tsx | 2 + 18 files changed, 401 insertions(+), 145 deletions(-) delete mode 100644 src/plugins/helpers/constants.js create mode 100644 src/plugins/helpers/constants.ts create mode 100644 src/plugins/helpers/storage.ts create mode 100644 src/screens/WebviewScreen/components/Menu.tsx diff --git a/package-lock.json b/package-lock.json index ecf0f5a27..5cc3c760f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,7 +36,7 @@ "i18n-js": "^3.8.0", "lodash-es": "^4.17.21", "protobufjs": "^7.2.6", - "qs": "^6.11.2", + "qs": "^6.12.0", "react": "18.2.0", "react-native": "0.72.10", "react-native-background-actions": "^3.0.1", @@ -56,7 +56,7 @@ "react-native-tab-view": "^3.5.2", "react-native-vector-icons": "^9.0.0", "react-native-webview": "^13.2.2", - "sanitize-html": "^2.7.0", + "sanitize-html": "^2.13.0", "urlencode": "^2.0.0" }, "devDependencies": { @@ -70,7 +70,7 @@ "@types/lodash-es": "^4.17.6", "@types/react": "^18.0.24", "@types/react-native-vector-icons": "^6.4.10", - "@types/sanitize-html": "^2.6.2", + "@types/sanitize-html": "^2.11.0", "@typescript-eslint/eslint-plugin": "^5.10.2", "@typescript-eslint/parser": "^5.10.2", "babel-plugin-module-resolver": "^4.1.0", @@ -16533,9 +16533,9 @@ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==" }, "node_modules/sanitize-html": { - "version": "2.12.1", - "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.12.1.tgz", - "integrity": "sha512-Plh+JAn0UVDpBRP/xEjsk+xDCoOvMBwQUf/K+/cBAVuTbtX8bj2VB7S1sL1dssVpykqp0/KPSesHrqXtokVBpA==", + "version": "2.13.0", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.13.0.tgz", + "integrity": "sha512-Xff91Z+4Mz5QiNSLdLWwjgBDm5b1RU6xBT0+12rapjiaR7SwfRdjw8f+6Rir2MXKLrDicRFHdb51hGOAxmsUIA==", "dependencies": { "deepmerge": "^4.2.2", "escape-string-regexp": "^4.0.0", @@ -16545,14 +16545,6 @@ "postcss": "^8.3.11" } }, - "node_modules/sanitize-html/node_modules/deepmerge": { - "version": "4.3.1", - "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", - "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/sanitize-html/node_modules/htmlparser2": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", diff --git a/package.json b/package.json index d8779a187..2440215af 100644 --- a/package.json +++ b/package.json @@ -42,7 +42,7 @@ "i18n-js": "^3.8.0", "lodash-es": "^4.17.21", "protobufjs": "^7.2.6", - "qs": "^6.11.2", + "qs": "^6.12.0", "react": "18.2.0", "react-native": "0.72.10", "react-native-background-actions": "^3.0.1", @@ -62,7 +62,7 @@ "react-native-tab-view": "^3.5.2", "react-native-vector-icons": "^9.0.0", "react-native-webview": "^13.2.2", - "sanitize-html": "^2.7.0", + "sanitize-html": "^2.13.0", "urlencode": "^2.0.0" }, "devDependencies": { @@ -76,7 +76,7 @@ "@types/lodash-es": "^4.17.6", "@types/react": "^18.0.24", "@types/react-native-vector-icons": "^6.4.10", - "@types/sanitize-html": "^2.6.2", + "@types/sanitize-html": "^2.11.0", "@typescript-eslint/eslint-plugin": "^5.10.2", "@typescript-eslint/parser": "^5.10.2", "babel-plugin-module-resolver": "^4.1.0", diff --git a/src/hooks/persisted/usePlugins.ts b/src/hooks/persisted/usePlugins.ts index 52e311dd4..b7b30ed1d 100644 --- a/src/hooks/persisted/usePlugins.ts +++ b/src/hooks/persisted/usePlugins.ts @@ -101,7 +101,7 @@ export default function usePlugins() { */ const installPlugin = (plugin: PluginItem) => { - return _install(plugin.url).then(_plg => { + return _install(plugin.id, plugin.url).then(_plg => { if (_plg) { const installedPlugins = getMMKVObject(INSTALLED_PLUGINS) || []; diff --git a/src/navigators/types/index.ts b/src/navigators/types/index.ts index b7284e0e4..f24353a6c 100644 --- a/src/navigators/types/index.ts +++ b/src/navigators/types/index.ts @@ -30,7 +30,7 @@ export type RootStackParamList = { WebviewScreen: { name: string; url: string; - pluginId?: string; + pluginId: string; isNovel?: boolean; }; }; diff --git a/src/plugins/helpers/constants.js b/src/plugins/helpers/constants.js deleted file mode 100644 index bbc5754e4..000000000 --- a/src/plugins/helpers/constants.js +++ /dev/null @@ -1,2 +0,0 @@ -export const defaultCover = - 'https://github.com/LNReader/lnreader-sources/blob/main/icons/src/coverNotAvailable.jpg?raw=true'; diff --git a/src/plugins/helpers/constants.ts b/src/plugins/helpers/constants.ts new file mode 100644 index 000000000..ec07e8235 --- /dev/null +++ b/src/plugins/helpers/constants.ts @@ -0,0 +1,2 @@ +export const defaultCover = + 'https://github.com/LNReader/lnreader-plugins/blob/master/icons/coverNotAvailable.webp?raw=true'; diff --git a/src/plugins/helpers/storage.ts b/src/plugins/helpers/storage.ts new file mode 100644 index 000000000..e99be46b3 --- /dev/null +++ b/src/plugins/helpers/storage.ts @@ -0,0 +1,123 @@ +import { MMKV } from 'react-native-mmkv'; + +const store = new MMKV({ id: 'plugin_db' }); + +const PLUGIN_STORAGE = '_DB_'; +const WEBVIEW_LOCAL_STORAGE = '_LocalStorage'; +const WEBVIEW_SESSION_STORAGE = '_SessionStorage'; + +interface StoredItem { + created: Date; + value: any; + expires?: number; // timestamp (miliseconds) +} + +class Storage { + #pluginID: string; + + constructor(pluginID: string) { + this.#pluginID = pluginID; + } + + /** + * Sets a key-value pair in storage. + * + * @param {string} key - The key to set. + * @param {any} value - The value to set. + * @param {Date | number} [expires] - Optional expiry date or time in milliseconds. + */ + set(key: string, value: any, expires?: Date | number): void { + const item: StoredItem = { + created: new Date(), + value, + expires: expires instanceof Date ? expires.getTime() : expires, + }; + store.set(this.#pluginID + PLUGIN_STORAGE + key, JSON.stringify(item)); + } + + /** + * Retrieves the value for a given key from storage. + * + * @param {string} key - The key to retrieve the value for. + * @param {boolean} [raw] - Optional flag to return the raw stored item. + * @returns {any} The stored value or undefined if key is not found. + */ + get(key: string, raw?: boolean): any { + const storedItem = store.getString(this.#pluginID + PLUGIN_STORAGE + key); + if (storedItem) { + const item: StoredItem = JSON.parse(storedItem); + if (item.expires) { + if (Date.now() > item.expires) { + this.delete(key); + return undefined; + } + if (raw) { + item.expires = new Date(item.expires).getTime(); + } + } + return raw ? item : item.value; + } + return undefined; + } + + /** + * Deletes a key from storage. + * + * @param {string} key - The key to delete. + */ + delete(key: string): void { + store.delete(this.#pluginID + PLUGIN_STORAGE + key); + } + + /** + * Clears all stored items from storage. + */ + clearAll(): void { + const keysToRemove = this.getAllKeys(); + keysToRemove.forEach(key => this.delete(key)); + } + + /** + * Retrieves all keys set by the `set` method. + * + * @returns {string[]} An array of keys. + */ + getAllKeys(): string[] { + const keys = store + .getAllKeys() + .filter(key => key.startsWith(this.#pluginID + PLUGIN_STORAGE)) + .map(key => key.replace(this.#pluginID + PLUGIN_STORAGE, '')); + return keys; + } +} + +class LocalStorage { + #pluginID: string; + + constructor(pluginID: string) { + this.#pluginID = pluginID; + } + + get(): StoredItem['value'] | undefined { + const data = store.getString(this.#pluginID + WEBVIEW_LOCAL_STORAGE); + return data ? JSON.parse(data) : undefined; + } +} + +class SessionStorage { + #pluginID: string; + + constructor(pluginID: string) { + this.#pluginID = pluginID; + } + + get(): StoredItem['value'] | undefined { + const data = store.getString(this.#pluginID + WEBVIEW_SESSION_STORAGE); + return data ? JSON.parse(data) : undefined; + } +} + +export { Storage, LocalStorage, SessionStorage }; + +//to record data from the web view +export { WEBVIEW_LOCAL_STORAGE, WEBVIEW_SESSION_STORAGE, store }; diff --git a/src/plugins/pluginManager.ts b/src/plugins/pluginManager.ts index e36bdef7c..1c0b879dc 100644 --- a/src/plugins/pluginManager.ts +++ b/src/plugins/pluginManager.ts @@ -2,6 +2,7 @@ import RNFS from 'react-native-fs'; import { reverse, uniqBy } from 'lodash-es'; import { PluginDownloadFolder } from '@utils/constants/download'; import { newer } from '@utils/compareVersion'; +import { store } from './helpers/storage'; // packages for plugins import { load } from 'cheerio'; @@ -12,6 +13,7 @@ import { FilterTypes } from './types/filterTypes'; import { isUrlAbsolute } from './helpers/isAbsoluteUrl'; import { fetchApi, fetchFile, fetchProto, fetchText } from './helpers/fetch'; import { defaultCover } from './helpers/constants'; +import { Storage, LocalStorage, SessionStorage } from './helpers/storage'; import { encode, decode } from 'urlencode'; import { Parser } from 'htmlparser2'; import TextFile from '@native/TextFile'; @@ -33,12 +35,18 @@ const packages: Record = { '@libs/defaultCover': { defaultCover }, }; -const _require = (packageName: string) => { - return packages[packageName]; -}; - -const initPlugin = (rawCode: string) => { +const initPlugin = (pluginId: string, rawCode: string) => { try { + const _require = (packageName: string) => { + if (packageName === '@libs/storage') { + return { + storage: new Storage(pluginId), + localStorage: new LocalStorage(pluginId), + sessionStorage: new SessionStorage(pluginId), + }; + } + return packages[packageName]; + }; /* eslint no-new-func: "off", curly: "error" */ const plugin: Plugin = Function( 'require', @@ -84,26 +92,31 @@ const deserializePlugins = () => { return TextFile.readFile(pluginsFilePath) .then(content => { serializedPlugins = JSON.parse(content); - for (const script of Object.values(serializedPlugins)) { - const plugin = initPlugin(script as string); - if (plugin) { - plugins[plugin.id] = plugin; + Object.entries(serializedPlugins).forEach(([pluginId, script]) => { + if (script) { + const plugin = initPlugin(pluginId, script); + if (plugin) { + plugins[plugin.id] = plugin; + } } - } + }); }) .catch(() => { // nothing to read }); }; -const installPlugin = async (url: string): Promise => { +const installPlugin = async ( + pluginId: string, + url: string, +): Promise => { try { return await fetch(url, { headers: { 'pragma': 'no-cache', 'cache-control': 'no-cache' }, }) .then(res => res.text()) .then(async rawCode => { - const plugin = initPlugin(rawCode); + const plugin = initPlugin(pluginId, rawCode); if (!plugin) { return undefined; } @@ -122,11 +135,16 @@ const installPlugin = async (url: string): Promise => { const uninstallPlugin = async (_plugin: PluginItem) => { plugins[_plugin.id] = undefined; + store.getAllKeys().forEach(key => { + if (key.startsWith(_plugin.id)) { + store.delete(key); + } + }); return serializePlugin(_plugin.id, '', false); }; const updatePlugin = async (plugin: PluginItem) => { - return installPlugin(plugin.url); + return installPlugin(plugin.id, plugin.url); }; const fetchPlugins = async (): Promise => { diff --git a/src/plugins/types/filterTypes.ts b/src/plugins/types/filterTypes.ts index 42e13d5c0..a53c09cf0 100644 --- a/src/plugins/types/filterTypes.ts +++ b/src/plugins/types/filterTypes.ts @@ -43,13 +43,13 @@ type ExcludableCheckboxFilter = { export type Filters = Record< string, - | { label: string } & ( - | PickerFilter - | CheckboxFilter - | TextFilter - | SwitchFilter - | ExcludableCheckboxFilter - ) + { label: string } & ( + | PickerFilter + | CheckboxFilter + | TextFilter + | SwitchFilter + | ExcludableCheckboxFilter + ) >; export type FilterToValues< diff --git a/src/plugins/types/index.ts b/src/plugins/types/index.ts index 01d4616f5..64777b63c 100644 --- a/src/plugins/types/index.ts +++ b/src/plugins/types/index.ts @@ -67,4 +67,5 @@ export interface Plugin extends PluginItem { searchNovels: (searchTerm: string, pageNo: number) => Promise; fetchImage: (url: string) => Promise; resolveUrl?: (path: string, isNovel?: boolean) => string; + webStorageUtilized?: boolean; } diff --git a/src/screens/BrowseSourceScreen/BrowseSourceScreen.tsx b/src/screens/BrowseSourceScreen/BrowseSourceScreen.tsx index d2cd8e4ed..1c631afad 100644 --- a/src/screens/BrowseSourceScreen/BrowseSourceScreen.tsx +++ b/src/screens/BrowseSourceScreen/BrowseSourceScreen.tsx @@ -64,6 +64,7 @@ const BrowseSourceScreen = ({ route, navigation }: BrowseSourceScreenProps) => { navigation.navigate('WebviewScreen', { name: pluginName, url: site, + pluginId, }); }; diff --git a/src/screens/WebviewScreen/WebviewScreen.tsx b/src/screens/WebviewScreen/WebviewScreen.tsx index bd9013af4..b3b8c3035 100644 --- a/src/screens/WebviewScreen/WebviewScreen.tsx +++ b/src/screens/WebviewScreen/WebviewScreen.tsx @@ -2,16 +2,29 @@ import React, { useRef, useState } from 'react'; import WebView, { WebViewNavigation } from 'react-native-webview'; import { ProgressBar } from 'react-native-paper'; +import { getPlugin } from '@plugins/pluginManager'; import { useBackHandler } from '@hooks'; import { useTheme } from '@hooks/persisted'; import { WebviewScreenProps } from '@navigators/types'; import { getUserAgent } from '@hooks/persisted/useUserAgent'; import { resolveUrl } from '@services/plugin/fetch'; +import { + WEBVIEW_LOCAL_STORAGE, + WEBVIEW_SESSION_STORAGE, + store, +} from '@plugins/helpers/storage'; import Appbar from './components/Appbar'; +import Menu from './components/Menu'; + +type StorageData = { + localStorage?: Record; + sessionStorage?: Record; +}; const WebviewScreen = ({ route, navigation }: WebviewScreenProps) => { const { name, url, pluginId, isNovel } = route.params; - const uri = pluginId ? resolveUrl(pluginId, url, isNovel) : url; + const isSave = getPlugin(pluginId)?.webStorageUtilized; + const uri = resolveUrl(pluginId, url, isNovel); const theme = useTheme(); const webViewRef = useRef(null); @@ -21,31 +34,60 @@ const WebviewScreen = ({ route, navigation }: WebviewScreenProps) => { const [currentUrl, setCurrentUrl] = useState(uri); const [canGoBack, setCanGoBack] = useState(false); const [canGoForward, setCanGoForward] = useState(false); + const [tempData, setTempData] = useState(); + const [menuVisible, setMenuVisible] = useState(false); const handleNavigation = (e: WebViewNavigation) => { + if (!e.loading) { + setTitle(e.title); + } setCurrentUrl(e.url); setCanGoBack(e.canGoBack); setCanGoForward(e.canGoForward); }; + const saveData = () => { + if (pluginId && tempData && isSave) { + store.set( + pluginId + WEBVIEW_LOCAL_STORAGE, + JSON.stringify(tempData?.localStorage || {}), + ); + store.set( + pluginId + WEBVIEW_SESSION_STORAGE, + JSON.stringify(tempData?.sessionStorage || {}), + ); + } + }; + useBackHandler(() => { + if (menuVisible) { + setMenuVisible(false); + return true; + } if (canGoBack) { webViewRef.current?.goBack(); return true; } + saveData(); return false; }); + const injectJavaScriptCode = + 'window.ReactNativeWebView.postMessage(JSON.stringify({localStorage, sessionStorage}))'; + return ( <> { + saveData(); + navigation.goBack(); + }} /> { userAgent={getUserAgent()} ref={webViewRef} source={{ uri }} - onLoadProgress={({ nativeEvent }) => { - setProgress(nativeEvent.progress); - }} - onLoadEnd={({ nativeEvent }) => { - setTitle(nativeEvent.title); - }} + setDisplayZoomControls={true} + setBuiltInZoomControls={false} + setSupportMultipleWindows={false} + injectedJavaScript={injectJavaScriptCode} onNavigationStateChange={handleNavigation} + onLoadProgress={({ nativeEvent }) => setProgress(nativeEvent.progress)} + onMessage={({ nativeEvent }) => + setTempData(JSON.parse(nativeEvent.data)) + } /> + {menuVisible ? ( + + ) : null} ); }; diff --git a/src/screens/WebviewScreen/components/Appbar.tsx b/src/screens/WebviewScreen/components/Appbar.tsx index 033ae986b..9243520ad 100644 --- a/src/screens/WebviewScreen/components/Appbar.tsx +++ b/src/screens/WebviewScreen/components/Appbar.tsx @@ -1,137 +1,102 @@ -import React, { useState } from 'react'; -import { Share, View, Text } from 'react-native'; +import React, { RefObject } from 'react'; +import { View, Text, StyleSheet } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; -import { IconButton, Menu } from 'react-native-paper'; -import WebView from 'react-native-webview'; -import * as Linking from 'expo-linking'; -import { WebviewScreenProps } from '@navigators/types'; -import { getString } from '@strings/translations'; +import { IconButtonV2 } from '@components'; import { ThemeColors } from '@theme/types'; -import { showToast } from '@utils/showToast'; +import WebView from 'react-native-webview'; interface AppbarProps { title: string; theme: ThemeColors; - currentUrl: string; canGoBack: boolean; canGoForward: boolean; webView: RefObject; - navigation: WebviewScreenProps['navigation']; + setMenuVisible: (value: boolean) => void; + goBack: () => void; } const Appbar: React.FC = ({ title, theme, - currentUrl, canGoBack, canGoForward, webView, - navigation, + setMenuVisible, + goBack, }) => { const { top } = useSafeAreaInsets(); - const [menuVisible, setMenuVisible] = useState(false); return ( - navigation.goBack()} - theme={{ colors: { ...theme } }} + - - + {title} - - + webView.current?.goBack()} - theme={{ colors: { ...theme } }} + theme={theme} /> - webView.current?.goForward()} - theme={{ colors: { ...theme } }} + theme={theme} /> - setMenuVisible(false)} - anchor={ - setMenuVisible(true)} - theme={{ colors: { ...theme } }} - /> - } - style={{ backgroundColor: theme.surface2 }} - contentStyle={{ backgroundColor: theme.surface2 }} - > - { - setMenuVisible(false); - webView.current?.reload(); - }} - /> - { - setMenuVisible(false); - Share.share({ message: currentUrl }); - }} - /> - { - setMenuVisible(false); - Linking.openURL(currentUrl); - }} - /> - { - setMenuVisible(false); - webView.current?.clearCache?.(true); - webView.current?.reload(); - showToast(getString('webview.dataDeleted')); - }} - /> - + setMenuVisible(true)} + theme={theme} + /> ); }; export default Appbar; + +const styles = StyleSheet.create({ + container: { + flexDirection: 'row', + justifyContent: 'center', + }, + titleContainer: { + flex: 1, + justifyContent: 'center', + }, + title: { + paddingBottom: 2, + paddingLeft: 2, + fontSize: 18, + }, + url: { + fontSize: 16, + }, + iconContainer: { + flexDirection: 'row', + justifyContent: 'flex-end', + }, +}); diff --git a/src/screens/WebviewScreen/components/Menu.tsx b/src/screens/WebviewScreen/components/Menu.tsx new file mode 100644 index 000000000..155ba4973 --- /dev/null +++ b/src/screens/WebviewScreen/components/Menu.tsx @@ -0,0 +1,102 @@ +import React, { RefObject } from 'react'; +import { Pressable, Share, View, Text, StyleSheet } from 'react-native'; +import { useSafeAreaInsets } from 'react-native-safe-area-context'; +import { WebView } from 'react-native-webview'; +import * as Linking from 'expo-linking'; + +import { getString } from '@strings/translations'; +import { ThemeColors } from '@theme/types'; +import { showToast } from '@utils/showToast'; + +interface MenuProps { + theme: ThemeColors; + currentUrl: string; + webView: RefObject; + setMenuVisible: (value: boolean) => void; +} + +const Menu: React.FC = ({ + theme, + currentUrl, + webView, + setMenuVisible, +}) => { + const { top } = useSafeAreaInsets(); + + return ( + setMenuVisible(false)} style={styles.container}> + + { + setMenuVisible(false); + webView.current?.reload(); + }} + > + + {getString('webview.refresh')} + + + + { + setMenuVisible(false); + Share.share({ message: currentUrl }); + }} + > + + {getString('webview.share')} + + + + { + setMenuVisible(false); + Linking.openURL(currentUrl); + }} + > + + {getString('webview.openInBrowser')} + + + + { + setMenuVisible(false); + webView.current?.clearCache?.(true); + webView.current?.reload(); + showToast(getString('webview.dataDeleted')); + }} + > + + {getString('webview.clearData')} + + + + + ); +}; + +export default Menu; + +const styles = StyleSheet.create({ + container: { + position: 'absolute', + top: 0, + bottom: 0, + left: 0, + right: 0, + justifyContent: 'center', + alignItems: 'center', + }, + menuContainer: { + position: 'absolute', + right: 0, + }, + menuButton: { + padding: 15, + }, +}); diff --git a/src/screens/novel/components/NovelScreenButtonGroup/NovelScreenButtonGroup.tsx b/src/screens/novel/components/NovelScreenButtonGroup/NovelScreenButtonGroup.tsx index b3b214ef9..f645f2225 100644 --- a/src/screens/novel/components/NovelScreenButtonGroup/NovelScreenButtonGroup.tsx +++ b/src/screens/novel/components/NovelScreenButtonGroup/NovelScreenButtonGroup.tsx @@ -34,7 +34,7 @@ const NovelScreenButtonGroup: React.FC = ({ const handleOpenWebView = async () => { navigate('WebviewScreen', { - name: novel.pluginId, + name: novel.name, url: novel.path, pluginId: novel.pluginId, isNovel: true, diff --git a/src/screens/reader/ReaderScreen.tsx b/src/screens/reader/ReaderScreen.tsx index c0cd88003..45988db65 100644 --- a/src/screens/reader/ReaderScreen.tsx +++ b/src/screens/reader/ReaderScreen.tsx @@ -331,7 +331,7 @@ export const ChapterContent = ({ title: 'WebView', onPress: () => navigation.navigate('WebviewScreen', { - name: `${chapter.name} | ${novel.name}`, + name: novel.name, url: chapter.path, pluginId: novel.pluginId, }), diff --git a/src/screens/reader/components/ReaderFooter.tsx b/src/screens/reader/components/ReaderFooter.tsx index 1e1a3a14f..9ddcc8848 100644 --- a/src/screens/reader/components/ReaderFooter.tsx +++ b/src/screens/reader/components/ReaderFooter.tsx @@ -71,7 +71,7 @@ const ChapterFooter = ({ style={styles.buttonStyles} onPress={() => navigation.navigate('WebviewScreen', { - name: `${chapter.name} | ${novel.name}`, + name: novel.name, url: chapter.path, pluginId: novel.pluginId, }) diff --git a/src/screens/settings/SettingsAdvancedScreen.tsx b/src/screens/settings/SettingsAdvancedScreen.tsx index 700d95dcc..d21098b8f 100644 --- a/src/screens/settings/SettingsAdvancedScreen.tsx +++ b/src/screens/settings/SettingsAdvancedScreen.tsx @@ -26,11 +26,13 @@ import { AdvancedSettingsScreenProps } from '@navigators/types'; import { StyleSheet, View } from 'react-native'; import { getUserAgentSync } from 'react-native-device-info'; import CookieManager from '@react-native-cookies/cookies'; +import { store } from '@plugins/helpers/storage'; const AdvancedSettings = ({ navigation }: AdvancedSettingsScreenProps) => { const theme = useTheme(); const clearCookies = () => { CookieManager.clearAll(); + store.clearAll(); showToast(getString('webview.cookiesCleared')); };