From e1522986441672d0a1b06bd49836dc9d55ae790a Mon Sep 17 00:00:00 2001 From: unknown Date: Fri, 29 Mar 2024 15:07:15 +0800 Subject: [PATCH 1/7] =?UTF-8?q?=E5=9B=9E=E5=AE=B6=E5=9C=A8=E5=86=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/app/build.gradle | 2 +- src/utils/cache.ts | 48 ++++++++++++++++++++++++++++++++++++++++ src/utils/common.ts | 12 ++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 src/utils/cache.ts diff --git a/android/app/build.gradle b/android/app/build.gradle index 415eda8..282ace9 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -75,7 +75,7 @@ android { namespace "com.mangareader" defaultConfig { - applicationId "com.youniaogu.mangareader" + applicationId "com.mangareader" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 45 diff --git a/src/utils/cache.ts b/src/utils/cache.ts new file mode 100644 index 0000000..3e3b81d --- /dev/null +++ b/src/utils/cache.ts @@ -0,0 +1,48 @@ +import AsyncStorage from '@react-native-async-storage/async-storage'; +import { ImageState } from '~/components/ComicImage'; +import { trycatch, limitProperties } from './common'; + +type CacheMap = Record; + +class Cache { + private comicCacheKey = 'comic_cache_key'; + private _cacheMap: CacheMap = {}; + private _identification: string; + private _allCacheMap: Record = {}; + + constructor(identification: string) { + this._identification = identification; + this._initCacheMap(); + } + + private _initCacheMap() { + const fn = async () => { + const value = await AsyncStorage.getItem(this.comicCacheKey); + if (value != null) { + this._allCacheMap = JSON.parse(value); + this._cacheMap = this._allCacheMap[this._identification] || {}; + } + }; + trycatch(fn, 'Cache read error: The error message is '); + } + + getImageState(uri: string) { + return this._cacheMap[uri]; + } + + setImageState(uri: string, state: ImageState) { + this._cacheMap[uri] = state; + } + + storeCacheMap() { + const fn = async () => { + // 只缓存50条数据 + const storeData = limitProperties(this._allCacheMap, 50); + storeData[this._identification] = this._cacheMap; + AsyncStorage.setItem(this.comicCacheKey, JSON.stringify(storeData)); + }; + trycatch(fn, 'Cache store error: The error message is '); + } +} + +export default Cache; diff --git a/src/utils/common.ts b/src/utils/common.ts index 9f9832d..2beda51 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -440,3 +440,15 @@ export function getDefaultFillMedianHeight( landscape: list[mid]?.landscapeHeight || defaultHeight.landscape, }; } + +export function limitProperties>( + obj: T, + maxPropertiesLength: number = 50 +): T { + let keys = Object.keys(obj); + if (keys.length > maxPropertiesLength) { + let oldestKey = keys[0]; + delete obj[oldestKey]; + } + return obj; +} From 3094a7689cfc83876070c43db38a3d7890f361a6 Mon Sep 17 00:00:00 2001 From: unknown Date: Sat, 30 Mar 2024 17:52:17 +0800 Subject: [PATCH 2/7] =?UTF-8?q?=E5=9B=9E=E5=AE=B6=E5=86=8D=E5=86=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/Reader.tsx | 20 +++++++++++-- src/utils/cache.ts | 44 +++++++++++++-------------- src/views/Chapter.tsx | 62 ++++++++++++++++++++++++++------------- 3 files changed, 81 insertions(+), 45 deletions(-) diff --git a/src/components/Reader.tsx b/src/components/Reader.tsx index 14a373b..0448e0e 100644 --- a/src/components/Reader.tsx +++ b/src/components/Reader.tsx @@ -23,6 +23,7 @@ import { useFocusEffect } from '@react-navigation/native'; import { Box, Flex } from 'native-base'; import Controller, { LongPressController } from '~/components/Controller'; import ComicImage, { ImageState } from '~/components/ComicImage'; +import Cache from '~/utils/cache'; export interface ReaderProps { initPage?: number; @@ -48,6 +49,7 @@ export interface ReaderProps { onScroll?: (event: NativeSyntheticEvent) => void; onScrollBeginDrag?: (event: NativeSyntheticEvent) => void; onScrollEndDrag?: (event: NativeSyntheticEvent) => void; + cache: Cache; } export interface ReaderRef { @@ -96,6 +98,7 @@ const Reader: ForwardRefRenderFunction = ( onScroll, onScrollBeginDrag, onScrollEndDrag, + cache, }, ref ) => { @@ -207,14 +210,19 @@ const Reader: ForwardRefRenderFunction = ( const renderVerticalItem = ({ item, index }: ListRenderItemInfo<(typeof data)[0]>) => { const { uri, scrambleType, needUnscramble } = item; const verticalState = verticalStateRef.current[index] || undefined; + const cacheState = cache.getImageState(uri); return ( = ( defaultLandscapeHeight={defaultLandscapeHeightRef.current} layoutMode={LayoutMode.Vertical} onChange={(state, idx = index) => { + cache.setImageState(uri, state); verticalStateRef.current[idx] = state; onImageLoad && onImageLoad(uri, item.chapterHash, item.current); @@ -370,6 +379,13 @@ const Reader: ForwardRefRenderFunction = ( keyExtractor={(item) => item.uri} ListHeaderComponent={} ListFooterComponent={} + overrideItemLayout={(layout, item) => { + const state = cache.getImageState(item.uri); + if (state) { + layout.size = + orientation === Orientation.Portrait ? state.portraitHeight : state.landscapeHeight; + } + }} /> ); }; diff --git a/src/utils/cache.ts b/src/utils/cache.ts index 3e3b81d..5025b08 100644 --- a/src/utils/cache.ts +++ b/src/utils/cache.ts @@ -1,29 +1,22 @@ import AsyncStorage from '@react-native-async-storage/async-storage'; import { ImageState } from '~/components/ComicImage'; -import { trycatch, limitProperties } from './common'; type CacheMap = Record; class Cache { - private comicCacheKey = 'comic_cache_key'; + private _comicCacheKey: string; private _cacheMap: CacheMap = {}; - private _identification: string; - private _allCacheMap: Record = {}; constructor(identification: string) { - this._identification = identification; - this._initCacheMap(); + this._comicCacheKey = `CACHE_${identification}`; } - private _initCacheMap() { - const fn = async () => { - const value = await AsyncStorage.getItem(this.comicCacheKey); - if (value != null) { - this._allCacheMap = JSON.parse(value); - this._cacheMap = this._allCacheMap[this._identification] || {}; - } - }; - trycatch(fn, 'Cache read error: The error message is '); + async initCacheMap() { + const value = await AsyncStorage.getItem(this._comicCacheKey); + console.log('readData: ', JSON.stringify(value)); + if (value != null) { + this._cacheMap = JSON.parse(value); + } } getImageState(uri: string) { @@ -34,14 +27,19 @@ class Cache { this._cacheMap[uri] = state; } - storeCacheMap() { - const fn = async () => { - // 只缓存50条数据 - const storeData = limitProperties(this._allCacheMap, 50); - storeData[this._identification] = this._cacheMap; - AsyncStorage.setItem(this.comicCacheKey, JSON.stringify(storeData)); - }; - trycatch(fn, 'Cache store error: The error message is '); + async storeCacheMap() { + try { + const keys = await AsyncStorage.getAllKeys(); + const cacheKeys = keys.filter((key) => key.includes('CACHE_')); + // 限制只存50条 + if (cacheKeys.length >= 50) { + await AsyncStorage.removeItem(cacheKeys[0]); + } + console.log('_cacheMap: ', JSON.stringify(this._cacheMap)); + await AsyncStorage.setItem(this._comicCacheKey, JSON.stringify(this._cacheMap)); + } catch (error) { + console.log('setCache error', error); + } } } diff --git a/src/views/Chapter.tsx b/src/views/Chapter.tsx index 7bea10a..42f70be 100644 --- a/src/views/Chapter.tsx +++ b/src/views/Chapter.tsx @@ -12,6 +12,7 @@ import { Hearing, Timer, } from '~/utils'; +import Cache from '~/utils/cache'; import { Box, Text, @@ -147,6 +148,8 @@ const Chapter = ({ route, navigation }: StackChapterProps) => { const pageSliderRef = useRef(null); const callbackRef = useRef<(type: Volume) => void>(); const sourceRef = useRef(''); + const [render, setRender] = useState(false); + const cache = useMemo(() => new Cache(mangaHash), [mangaHash]); callbackRef.current = (type) => { if (type === Volume.Down) { @@ -174,6 +177,23 @@ const Chapter = ({ route, navigation }: StackChapterProps) => { dispatch(viewPage({ mangaHash, page: current })); }, [current, dispatch, mangaHash]) ); + const init = useCallback(async () => { + setRender(false); + try { + await cache.initCacheMap(); + } catch (error) { + } finally { + setRender(true); + } + }, [cache]); + useFocusEffect( + useCallback(() => { + init(); + return () => { + cache.storeCacheMap(); + }; + }, [init, cache]) + ); useVolumeUpDown( useCallback((type) => callbackRef.current && callbackRef.current(type), []), hearing === Hearing.Enable @@ -432,26 +452,28 @@ const Chapter = ({ route, navigation }: StackChapterProps) => { : 'dark-content' } /> - - setTimerSwitch(false)} - onScrollEndDrag={() => setTimerSwitch(true)} - onZoomStart={(scale) => setTimerSwitch(scale <= 1)} - onZoomEnd={(scale) => setTimerSwitch(scale <= 1)} - /> + {render && ( + setTimerSwitch(false)} + onScrollEndDrag={() => setTimerSwitch(true)} + onZoomStart={(scale) => setTimerSwitch(scale <= 1)} + onZoomEnd={(scale) => setTimerSwitch(scale <= 1)} + cache={cache} + /> + )} Date: Sun, 31 Mar 2024 14:05:53 +0800 Subject: [PATCH 3/7] =?UTF-8?q?fix:=20=E6=9D=A1=E6=BC=AB=E6=A8=A1=E5=BC=8F?= =?UTF-8?q?=E4=B8=8B=E5=8F=8D=E5=90=91=E6=BB=9A=E5=8A=A8=E7=94=BB=E9=9D=A2?= =?UTF-8?q?=E6=8A=96=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils/cache.ts | 47 +++++++++++++++++++++++++++++++--------------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/src/utils/cache.ts b/src/utils/cache.ts index 5025b08..176e40e 100644 --- a/src/utils/cache.ts +++ b/src/utils/cache.ts @@ -1,21 +1,32 @@ -import AsyncStorage from '@react-native-async-storage/async-storage'; +import { FileSystem, Dirs } from 'react-native-file-access'; import { ImageState } from '~/components/ComicImage'; type CacheMap = Record; class Cache { - private _comicCacheKey: string; + private _basePath = `${Dirs.DocumentDir}/@cache`; // 或者Dirs.CacheDir? + private _path: string; private _cacheMap: CacheMap = {}; constructor(identification: string) { - this._comicCacheKey = `CACHE_${identification}`; + this._path = `${this._basePath}/${identification}.json`; } async initCacheMap() { - const value = await AsyncStorage.getItem(this._comicCacheKey); - console.log('readData: ', JSON.stringify(value)); - if (value != null) { - this._cacheMap = JSON.parse(value); + try { + const dirExists = await FileSystem.exists(this._basePath); + if (!dirExists) { + await FileSystem.mkdir(this._basePath); + } + const exists = await FileSystem.exists(this._path); + if (exists) { + const content = await FileSystem.readFile(this._path); + this._cacheMap = JSON.parse(content); + } else { + await FileSystem.writeFile(this._path, JSON.stringify({})); + } + } catch (error) { + console.error('An error in initCacheMap:', error); } } @@ -29,16 +40,22 @@ class Cache { async storeCacheMap() { try { - const keys = await AsyncStorage.getAllKeys(); - const cacheKeys = keys.filter((key) => key.includes('CACHE_')); - // 限制只存50条 - if (cacheKeys.length >= 50) { - await AsyncStorage.removeItem(cacheKeys[0]); + await FileSystem.writeFile(this._path, JSON.stringify(this._cacheMap)); + } catch (error) { + console.error('An error in storeCacheMap:', error); + } + } + + // 清除缓存 + static async clearCache() { + const basePath = `${Dirs.DocumentDir}/@cache`; + try { + const dirExists = await FileSystem.exists(basePath); + if (dirExists) { + await FileSystem.unlink(basePath); } - console.log('_cacheMap: ', JSON.stringify(this._cacheMap)); - await AsyncStorage.setItem(this._comicCacheKey, JSON.stringify(this._cacheMap)); } catch (error) { - console.log('setCache error', error); + console.error('An error in clearCache:', error); } } } From 6d2eb375531cfa3b4758cc6a0a91afd6321b7234 Mon Sep 17 00:00:00 2001 From: rxw22 <2587897409@qq.com> Date: Sun, 31 Mar 2024 16:31:26 +0800 Subject: [PATCH 4/7] update applicationId --- android/app/build.gradle | 2 +- src/utils/common.ts | 12 ------------ 2 files changed, 1 insertion(+), 13 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 282ace9..415eda8 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -75,7 +75,7 @@ android { namespace "com.mangareader" defaultConfig { - applicationId "com.mangareader" + applicationId "com.youniaogu.mangareader" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 45 diff --git a/src/utils/common.ts b/src/utils/common.ts index 2beda51..9f9832d 100644 --- a/src/utils/common.ts +++ b/src/utils/common.ts @@ -440,15 +440,3 @@ export function getDefaultFillMedianHeight( landscape: list[mid]?.landscapeHeight || defaultHeight.landscape, }; } - -export function limitProperties>( - obj: T, - maxPropertiesLength: number = 50 -): T { - let keys = Object.keys(obj); - if (keys.length > maxPropertiesLength) { - let oldestKey = keys[0]; - delete obj[oldestKey]; - } - return obj; -} From 07040324f7c844c41b1ef60f677f37ab39649d54 Mon Sep 17 00:00:00 2001 From: raoxiongwen Date: Wed, 10 Apr 2024 17:27:24 +0800 Subject: [PATCH 5/7] =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=9D=A1=E6=BC=AB?= =?UTF-8?q?=E6=A8=A1=E5=BC=8F=E4=B8=8B=E6=BC=AB=E7=94=BB=E6=A8=A1=E7=B3=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/ComicImage.tsx | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/components/ComicImage.tsx b/src/components/ComicImage.tsx index 87b8429..c61dcd8 100644 --- a/src/components/ComicImage.tsx +++ b/src/components/ComicImage.tsx @@ -2,7 +2,7 @@ import React, { useCallback, useState, useMemo, useRef, memo } from 'react'; import { aspectFit, AsyncStatus, LayoutMode, Orientation, ScrambleType, unscramble } from '~/utils'; import { Image as ReactNativeImage, StyleSheet, Dimensions, DimensionValue } from 'react-native'; import { useDebouncedSafeAreaFrame, useDebouncedSafeAreaInsets } from '~/hooks'; -import { CachedImage, CacheManager } from '@georstat/react-native-image-cache'; +import { CacheManager } from '@georstat/react-native-image-cache'; import { useFocusEffect } from '@react-navigation/native'; import { Center, Image } from 'native-base'; import ErrorWithRetry from '~/components/ErrorWithRetry'; @@ -84,6 +84,7 @@ const DefaultImage = ({ }; }, [layoutMode, imageState, orientation, defaultPortraitHeight, defaultLandscapeHeight]); const uriRef = useRef(uri); + const base64Ref = useRef(uri); const updateData = useCallback( (data: ImageState) => { @@ -112,7 +113,6 @@ const DefaultImage = ({ updateData({ ...imageState, dataUrl: uri, loadStatus: AsyncStatus.Fulfilled }); return; } - ReactNativeImage.getSize( 'data:image/png;base64,' + base64, (width, height) => { @@ -123,6 +123,7 @@ const DefaultImage = ({ height: windowHeight - top - bottom, } ); + base64Ref.current = 'data:image/png;base64,' + base64; updateData({ ...imageState, dataUrl: uri, @@ -199,11 +200,16 @@ const DefaultImage = ({ ); } + // https://github.com/candlefinance/faster-image 这个是不是能够替代FastImage return ( - From 37a0da71a63528c453a80f5e2ec8754438bd2260 Mon Sep 17 00:00:00 2001 From: rxw22 <2587897409@qq.com> Date: Wed, 10 Apr 2024 21:11:48 +0800 Subject: [PATCH 6/7] Use FastImage entirely instead of CachedImage --- android/app/build.gradle | 3 +- src/components/ComicImage.tsx | 124 ++++++++++++++++------------------ 2 files changed, 59 insertions(+), 68 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index a8e599d..9e6ba27 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -75,7 +75,8 @@ android { namespace "com.mangareader" defaultConfig { - applicationId "com.youniaogu.mangareader" + // applicationId "com.youniaogu.mangareader" + applicationId "com.mangareader" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 46 diff --git a/src/components/ComicImage.tsx b/src/components/ComicImage.tsx index c61dcd8..80833fd 100644 --- a/src/components/ComicImage.tsx +++ b/src/components/ComicImage.tsx @@ -1,12 +1,12 @@ import React, { useCallback, useState, useMemo, useRef, memo } from 'react'; import { aspectFit, AsyncStatus, LayoutMode, Orientation, ScrambleType, unscramble } from '~/utils'; -import { Image as ReactNativeImage, StyleSheet, Dimensions, DimensionValue } from 'react-native'; +import { StyleSheet, Dimensions, DimensionValue } from 'react-native'; import { useDebouncedSafeAreaFrame, useDebouncedSafeAreaInsets } from '~/hooks'; import { CacheManager } from '@georstat/react-native-image-cache'; import { useFocusEffect } from '@react-navigation/native'; import { Center, Image } from 'native-base'; import ErrorWithRetry from '~/components/ErrorWithRetry'; -import FastImage, { ResizeMode } from 'react-native-fast-image'; +import FastImage, { OnLoadEvent, ResizeMode } from 'react-native-fast-image'; import Canvas, { Image as CanvasImage } from 'react-native-canvas'; const groundPoundGif = require('~/assets/ground_pound.gif'); @@ -84,7 +84,6 @@ const DefaultImage = ({ }; }, [layoutMode, imageState, orientation, defaultPortraitHeight, defaultLandscapeHeight]); const uriRef = useRef(uri); - const base64Ref = useRef(uri); const updateData = useCallback( (data: ImageState) => { @@ -101,64 +100,38 @@ const DefaultImage = ({ const handleError = useCallback(() => { updateData({ ...imageState, loadStatus: AsyncStatus.Rejected }); }, [imageState, updateData]); - const loadImage = useCallback(() => { + + const handleImageLoadStart = useCallback(() => { setImageState({ ...imageState, loadStatus: AsyncStatus.Pending }); - CacheManager.prefetchBlob(uri, { headers }) - .then((base64) => { - if (!base64) { - handleError(); - return; - } - if (layoutMode === LayoutMode.Horizontal) { - updateData({ ...imageState, dataUrl: uri, loadStatus: AsyncStatus.Fulfilled }); - return; - } - ReactNativeImage.getSize( - 'data:image/png;base64,' + base64, - (width, height) => { - const { dWidth, dHeight } = aspectFit( - { width, height }, - { - width: (windowWidth - left - right) / 2, - height: windowHeight - top - bottom, - } - ); - base64Ref.current = 'data:image/png;base64,' + base64; - updateData({ - ...imageState, - dataUrl: uri, - multipleFitWidth: dWidth, - multipleFitHeight: dHeight, - landscapeHeight: (height / width) * Math.max(windowWidth, windowHeight), - portraitHeight: (height / width) * Math.min(windowWidth, windowHeight), - loadStatus: AsyncStatus.Fulfilled, - }); - }, - handleError - ); - }) - .catch(handleError); - }, [ - uri, - headers, - imageState, - layoutMode, - updateData, - handleError, - windowWidth, - windowHeight, - top, - left, - right, - bottom, - ]); - useFocusEffect( - useCallback(() => { - if (imageState.loadStatus === AsyncStatus.Default) { - loadImage(); + }, [imageState]); + + const handleImageLoad = useCallback( + (e: OnLoadEvent) => { + if (layoutMode === LayoutMode.Horizontal) { + updateData({ ...imageState, dataUrl: uri, loadStatus: AsyncStatus.Fulfilled }); + return; } - }, [imageState, loadImage]) + const { width, height } = e.nativeEvent; + const { dWidth, dHeight } = aspectFit( + { width, height }, + { + width: (windowWidth - left - right) / 2, + height: windowHeight - top - bottom, + } + ); + updateData({ + ...imageState, + dataUrl: uri, + multipleFitWidth: dWidth, + multipleFitHeight: dHeight, + landscapeHeight: (height / width) * Math.max(windowWidth, windowHeight), + portraitHeight: (height / width) * Math.min(windowWidth, windowHeight), + loadStatus: AsyncStatus.Fulfilled, + }); + }, + [updateData, layoutMode, imageState, uri, windowWidth, windowHeight, left, right, top, bottom] ); + useFocusEffect( useCallback(() => { if (uriRef.current !== uri) { @@ -169,18 +142,17 @@ const DefaultImage = ({ ); const handleRetry = () => { - CacheManager.removeCacheEntry(uri) - .then(() => {}) - .catch(() => {}) - .finally(() => updateData({ ...imageState, loadStatus: AsyncStatus.Default })); + updateData({ ...imageState, loadStatus: AsyncStatus.Default }); }; if ( imageState.loadStatus === AsyncStatus.Pending || imageState.loadStatus === AsyncStatus.Default ) { + // onLoad() prop returns wrong values in 'height' and 'width' on Android + // https://github.com/DylanVann/react-native-fast-image/issues/944 return ( -
+
groundpound +
); } @@ -200,15 +183,12 @@ const DefaultImage = ({ ); } - // https://github.com/candlefinance/faster-image 这个是不是能够替代FastImage return ( { From 84a9550b59bfc9938aedb474f0055a48d3dac12f Mon Sep 17 00:00:00 2001 From: rxw22 <2587897409@qq.com> Date: Wed, 10 Apr 2024 21:13:24 +0800 Subject: [PATCH 7/7] update applicationId --- android/app/build.gradle | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/android/app/build.gradle b/android/app/build.gradle index 9e6ba27..a8e599d 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -75,8 +75,7 @@ android { namespace "com.mangareader" defaultConfig { - // applicationId "com.youniaogu.mangareader" - applicationId "com.mangareader" + applicationId "com.youniaogu.mangareader" minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode 46