Spaces:
Runtime error
Runtime error
Commit ·
0ab8c90
1
Parent(s): 4af0f6c
12-11-24
Browse files- backend/__pycache__/urls.cpython-312.pyc +0 -0
- backend/api/__pycache__/cloudflare_turnstile.cpython-312.pyc +0 -0
- backend/api/__pycache__/queue.cpython-312.pyc +0 -0
- backend/api/__pycache__/stream_file.cpython-312.pyc +0 -0
- backend/api/__pycache__/web_scrap.cpython-312.pyc +0 -0
- backend/api/cloudflare_turnstile.py +2 -2
- backend/api/queue.py +1 -1
- backend/api/stream_file.py +1 -1
- backend/api/web_scrap.py +4 -33
- backend/migrations/0002_remove_requestcache_room.py +17 -0
- backend/migrations/__pycache__/0002_remove_requestcache_room.cpython-312.pyc +0 -0
- backend/models/__pycache__/model_cache.cpython-312.pyc +0 -0
- backend/models/model_cache.py +0 -3
- backend/urls.py +0 -1
- core/__pycache__/middleware.cpython-312.pyc +0 -0
- core/middleware.py +1 -1
- frontend/app/_layout.tsx +7 -7
- frontend/app/explore/components/widgets.tsx +1 -0
- frontend/app/explore/index.tsx +9 -29
- frontend/app/explore/stylesheet/show_list_styles.tsx +2 -2
- frontend/app/index.tsx +1 -1
- frontend/app/read/[source]/[comic_id]/{index.tsx → [chapter_idx].tsx} +32 -41
- frontend/app/read/components/chapter_image.tsx +129 -3
- frontend/app/read/modules/get_chapter.tsx +2 -1
- frontend/app/view/[source]/[comic_id].tsx +15 -11
- frontend/app/view/componenets/chapter.tsx +2 -2
- frontend/app/view/componenets/widgets/bookmark.tsx +1116 -0
- frontend/app/view/componenets/widgets/page_navigation.tsx +159 -0
- frontend/app/view/componenets/{widgets.tsx → widgets/request_chapter.tsx} +7 -551
- frontend/components/Image.tsx +5 -1
- frontend/components/dropdown.tsx +2 -0
- frontend/components/menu/components/menu_button.tsx +27 -34
- frontend/components/menu/menu.tsx +95 -18
- frontend/components/menu/stylesheet/styles.tsx +16 -9
- frontend/components/navigation/TabBarIcon.tsx +0 -9
- frontend/constants/module/storages/chapter_data_storage.tsx +11 -8
- frontend/constants/module/storages/chapter_storage.tsx +8 -6
- frontend/constants/module/storages/comic_storage.tsx +13 -1
- frontend/constants/module/storages/image_cache_storage.tsx +4 -4
- frontend/constants/module/storages/storage.tsx +2 -1
backend/__pycache__/urls.cpython-312.pyc
CHANGED
|
Binary files a/backend/__pycache__/urls.cpython-312.pyc and b/backend/__pycache__/urls.cpython-312.pyc differ
|
|
|
backend/api/__pycache__/cloudflare_turnstile.cpython-312.pyc
CHANGED
|
Binary files a/backend/api/__pycache__/cloudflare_turnstile.cpython-312.pyc and b/backend/api/__pycache__/cloudflare_turnstile.cpython-312.pyc differ
|
|
|
backend/api/__pycache__/queue.cpython-312.pyc
CHANGED
|
Binary files a/backend/api/__pycache__/queue.cpython-312.pyc and b/backend/api/__pycache__/queue.cpython-312.pyc differ
|
|
|
backend/api/__pycache__/stream_file.cpython-312.pyc
CHANGED
|
Binary files a/backend/api/__pycache__/stream_file.cpython-312.pyc and b/backend/api/__pycache__/stream_file.cpython-312.pyc differ
|
|
|
backend/api/__pycache__/web_scrap.cpython-312.pyc
CHANGED
|
Binary files a/backend/api/__pycache__/web_scrap.cpython-312.pyc and b/backend/api/__pycache__/web_scrap.cpython-312.pyc differ
|
|
|
backend/api/cloudflare_turnstile.py
CHANGED
|
@@ -11,7 +11,7 @@ from ipware import get_client_ip
|
|
| 11 |
env = environ.Env()
|
| 12 |
|
| 13 |
@csrf_exempt
|
| 14 |
-
@ratelimit(key='ip', rate='
|
| 15 |
def verify(request):
|
| 16 |
if request.method != "POST": return HttpResponseBadRequest('Allowed POST request only!', status=400)
|
| 17 |
client_ip, is_routable = get_client_ip(request)
|
|
@@ -29,7 +29,7 @@ def verify(request):
|
|
| 29 |
result = req.json()
|
| 30 |
status = result.get("success")
|
| 31 |
if (status):
|
| 32 |
-
|
| 33 |
queryset = CloudflareTurnStileCache.objects.create(token=token)
|
| 34 |
queryset.refresh_from_db()
|
| 35 |
return JsonResponse(result)
|
|
|
|
| 11 |
env = environ.Env()
|
| 12 |
|
| 13 |
@csrf_exempt
|
| 14 |
+
@ratelimit(key='ip', rate='10/m')
|
| 15 |
def verify(request):
|
| 16 |
if request.method != "POST": return HttpResponseBadRequest('Allowed POST request only!', status=400)
|
| 17 |
client_ip, is_routable = get_client_ip(request)
|
|
|
|
| 29 |
result = req.json()
|
| 30 |
status = result.get("success")
|
| 31 |
if (status):
|
| 32 |
+
|
| 33 |
queryset = CloudflareTurnStileCache.objects.create(token=token)
|
| 34 |
queryset.refresh_from_db()
|
| 35 |
return JsonResponse(result)
|
backend/api/queue.py
CHANGED
|
@@ -18,7 +18,7 @@ env = environ.Env()
|
|
| 18 |
|
| 19 |
|
| 20 |
@csrf_exempt
|
| 21 |
-
@ratelimit(key='ip', rate='
|
| 22 |
def request_chapter(request):
|
| 23 |
try:
|
| 24 |
if request.method != "POST": return HttpResponseBadRequest('Allowed POST request only!', status=400)
|
|
|
|
| 18 |
|
| 19 |
|
| 20 |
@csrf_exempt
|
| 21 |
+
@ratelimit(key='ip', rate='10/m')
|
| 22 |
def request_chapter(request):
|
| 23 |
try:
|
| 24 |
if request.method != "POST": return HttpResponseBadRequest('Allowed POST request only!', status=400)
|
backend/api/stream_file.py
CHANGED
|
@@ -8,7 +8,7 @@ from backend.models.model_cache import SocketRequestChapterQueueCache, ComicStor
|
|
| 8 |
import os, json, sys
|
| 9 |
|
| 10 |
@csrf_exempt
|
| 11 |
-
@ratelimit(key='ip', rate='
|
| 12 |
def download_chapter(request):
|
| 13 |
if request.method != "POST": return HttpResponseBadRequest('Allowed POST request only!', status=400)
|
| 14 |
token = request.META.get('HTTP_X_CLOUDFLARE_TURNSTILE_TOKEN')
|
|
|
|
| 8 |
import os, json, sys
|
| 9 |
|
| 10 |
@csrf_exempt
|
| 11 |
+
@ratelimit(key='ip', rate='30/m')
|
| 12 |
def download_chapter(request):
|
| 13 |
if request.method != "POST": return HttpResponseBadRequest('Allowed POST request only!', status=400)
|
| 14 |
token = request.META.get('HTTP_X_CLOUDFLARE_TURNSTILE_TOKEN')
|
backend/api/web_scrap.py
CHANGED
|
@@ -18,7 +18,7 @@ env = environ.Env()
|
|
| 18 |
|
| 19 |
|
| 20 |
@csrf_exempt
|
| 21 |
-
@ratelimit(key='ip', rate='
|
| 22 |
def get_list(request):
|
| 23 |
if request.method != "POST": return HttpResponseBadRequest('Allowed POST request only!', status=400)
|
| 24 |
token = request.META.get('HTTP_X_CLOUDFLARE_TURNSTILE_TOKEN')
|
|
@@ -35,7 +35,7 @@ def get_list(request):
|
|
| 35 |
return JsonResponse({"data":DATA})
|
| 36 |
|
| 37 |
|
| 38 |
-
@ratelimit(key='ip', rate='
|
| 39 |
def search(request):
|
| 40 |
# if request.method != "POST": return HttpResponseBadRequest('Allowed POST request only!', status=400)
|
| 41 |
try:
|
|
@@ -47,7 +47,7 @@ def search(request):
|
|
| 47 |
|
| 48 |
|
| 49 |
@csrf_exempt
|
| 50 |
-
@ratelimit(key='ip', rate='
|
| 51 |
def get(request):
|
| 52 |
if request.method != "POST": return HttpResponseBadRequest('Allowed POST request only!', status=400)
|
| 53 |
token = request.META.get('HTTP_X_CLOUDFLARE_TURNSTILE_TOKEN')
|
|
@@ -79,33 +79,4 @@ def get_cover(request,source,id,cover_id):
|
|
| 79 |
except Exception as e:
|
| 80 |
return HttpResponseBadRequest(str(e), status=500)
|
| 81 |
|
| 82 |
-
|
| 83 |
-
def get_chapter(request):
|
| 84 |
-
try:
|
| 85 |
-
id = "manga-lo816008/1/410.html"
|
| 86 |
-
job = web_scrap.source_control["colamanga"].get_chapter.scrap(id=id,output_dir=os.path.join(BASE_DIR,"media"))
|
| 87 |
-
if job.get("status") == "success":
|
| 88 |
-
chapter_id = id.split("/")[-1].split(".")[0]
|
| 89 |
-
input_dir = os.path.join(BASE_DIR,"media",id.split("/")[0],chapter_id,"original")
|
| 90 |
-
merged_output_dir = os.path.join(BASE_DIR,"media",id.split("/")[0],chapter_id,"merged")
|
| 91 |
-
os.makedirs(merged_output_dir,exist_ok=True)
|
| 92 |
-
|
| 93 |
-
manage_image.merge_images_vertically(input_dir=input_dir,output_dir=merged_output_dir,max_size=10*1024*1024)
|
| 94 |
-
|
| 95 |
-
translated_merged_output_dir = os.path.join(BASE_DIR,"media",id.split("/")[0],chapter_id,"merged_translated")
|
| 96 |
-
os.makedirs(translated_merged_output_dir,exist_ok=True)
|
| 97 |
-
|
| 98 |
-
|
| 99 |
-
subprocess.run(
|
| 100 |
-
["python", "-m", "manga_translator", "-v", "--manga2eng", "--translator=m2m100_big", "-l", "ENG", "-i", f"{merged_output_dir}", "-o", f"{translated_merged_output_dir}"],
|
| 101 |
-
cwd=os.path.join(BASE_DIR,"backend","module","utils","image_translator"), shell=True, check=True
|
| 102 |
-
)
|
| 103 |
-
|
| 104 |
-
translated_splited_output_dir = os.path.join(BASE_DIR,"media",id.split("/")[0],chapter_id,"translated")
|
| 105 |
-
|
| 106 |
-
os.makedirs(translated_splited_output_dir,exist_ok=True)
|
| 107 |
-
manage_image.split_image_vertically(input_dir=translated_merged_output_dir,output_dir=translated_splited_output_dir)
|
| 108 |
-
except Exception as e:
|
| 109 |
-
print(e)
|
| 110 |
-
|
| 111 |
-
return JsonResponse({})
|
|
|
|
| 18 |
|
| 19 |
|
| 20 |
@csrf_exempt
|
| 21 |
+
@ratelimit(key='ip', rate='20/m')
|
| 22 |
def get_list(request):
|
| 23 |
if request.method != "POST": return HttpResponseBadRequest('Allowed POST request only!', status=400)
|
| 24 |
token = request.META.get('HTTP_X_CLOUDFLARE_TURNSTILE_TOKEN')
|
|
|
|
| 35 |
return JsonResponse({"data":DATA})
|
| 36 |
|
| 37 |
|
| 38 |
+
@ratelimit(key='ip', rate='20/m')
|
| 39 |
def search(request):
|
| 40 |
# if request.method != "POST": return HttpResponseBadRequest('Allowed POST request only!', status=400)
|
| 41 |
try:
|
|
|
|
| 47 |
|
| 48 |
|
| 49 |
@csrf_exempt
|
| 50 |
+
@ratelimit(key='ip', rate='20/m')
|
| 51 |
def get(request):
|
| 52 |
if request.method != "POST": return HttpResponseBadRequest('Allowed POST request only!', status=400)
|
| 53 |
token = request.META.get('HTTP_X_CLOUDFLARE_TURNSTILE_TOKEN')
|
|
|
|
| 79 |
except Exception as e:
|
| 80 |
return HttpResponseBadRequest(str(e), status=500)
|
| 81 |
|
| 82 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
backend/migrations/0002_remove_requestcache_room.py
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Generated by Django 5.1.1 on 2024-11-08 17:33
|
| 2 |
+
|
| 3 |
+
from django.db import migrations
|
| 4 |
+
|
| 5 |
+
|
| 6 |
+
class Migration(migrations.Migration):
|
| 7 |
+
|
| 8 |
+
dependencies = [
|
| 9 |
+
('backend', '0001_initial'),
|
| 10 |
+
]
|
| 11 |
+
|
| 12 |
+
operations = [
|
| 13 |
+
migrations.RemoveField(
|
| 14 |
+
model_name='requestcache',
|
| 15 |
+
name='room',
|
| 16 |
+
),
|
| 17 |
+
]
|
backend/migrations/__pycache__/0002_remove_requestcache_room.cpython-312.pyc
ADDED
|
Binary file (616 Bytes). View file
|
|
|
backend/models/__pycache__/model_cache.cpython-312.pyc
CHANGED
|
Binary files a/backend/models/__pycache__/model_cache.cpython-312.pyc and b/backend/models/__pycache__/model_cache.cpython-312.pyc differ
|
|
|
backend/models/model_cache.py
CHANGED
|
@@ -5,11 +5,8 @@ import uuid
|
|
| 5 |
def get_current_utc_time(): return date_utils.utc_time().get()
|
| 6 |
|
| 7 |
class RequestCache(models.Model):
|
| 8 |
-
room = models.TextField()
|
| 9 |
client = models.UUIDField(primary_key=True)
|
| 10 |
datetime = models.DateTimeField(default=get_current_utc_time)
|
| 11 |
-
|
| 12 |
-
|
| 13 |
|
| 14 |
class CloudflareTurnStileCache(models.Model):
|
| 15 |
token = models.TextField(primary_key=True)
|
|
|
|
| 5 |
def get_current_utc_time(): return date_utils.utc_time().get()
|
| 6 |
|
| 7 |
class RequestCache(models.Model):
|
|
|
|
| 8 |
client = models.UUIDField(primary_key=True)
|
| 9 |
datetime = models.DateTimeField(default=get_current_utc_time)
|
|
|
|
|
|
|
| 10 |
|
| 11 |
class CloudflareTurnStileCache(models.Model):
|
| 12 |
token = models.TextField(primary_key=True)
|
backend/urls.py
CHANGED
|
@@ -18,7 +18,6 @@ urlpatterns = [
|
|
| 18 |
path('web_scrap/search/', web_scrap.search),
|
| 19 |
path('web_scrap/get/', web_scrap.get),
|
| 20 |
path('web_scrap/get_cover/<str:source>/<str:id>/<str:cover_id>/', web_scrap.get_cover),
|
| 21 |
-
path('web_scrap/get_chapter/', web_scrap.get_chapter),
|
| 22 |
|
| 23 |
|
| 24 |
|
|
|
|
| 18 |
path('web_scrap/search/', web_scrap.search),
|
| 19 |
path('web_scrap/get/', web_scrap.get),
|
| 20 |
path('web_scrap/get_cover/<str:source>/<str:id>/<str:cover_id>/', web_scrap.get_cover),
|
|
|
|
| 21 |
|
| 22 |
|
| 23 |
|
core/__pycache__/middleware.cpython-312.pyc
CHANGED
|
Binary files a/core/__pycache__/middleware.cpython-312.pyc and b/core/__pycache__/middleware.cpython-312.pyc differ
|
|
|
core/middleware.py
CHANGED
|
@@ -35,7 +35,7 @@ class SequentialRequestMiddleware:
|
|
| 35 |
def __call__(self, request):
|
| 36 |
request_type = request.scope.get("type")
|
| 37 |
request_path = request.path
|
| 38 |
-
|
| 39 |
if request_type == "http":
|
| 40 |
|
| 41 |
with TimeoutContext(30) as executor:
|
|
|
|
| 35 |
def __call__(self, request):
|
| 36 |
request_type = request.scope.get("type")
|
| 37 |
request_path = request.path
|
| 38 |
+
print(request_path)
|
| 39 |
if request_type == "http":
|
| 40 |
|
| 41 |
with TimeoutContext(30) as executor:
|
frontend/app/_layout.tsx
CHANGED
|
@@ -70,7 +70,7 @@ SplashScreen.preventAutoHideAsync();
|
|
| 70 |
export default function RootLayout() {
|
| 71 |
const pathname = usePathname()
|
| 72 |
const Dimensions = useWindowDimensions();
|
| 73 |
-
const [showMenuContext,setShowMenuContext]:any = useState(
|
| 74 |
const [themeTypeContext,setThemeTypeContext]:any = useState("")
|
| 75 |
const [apiBaseContext, setApiBaseContext]:any = useState("")
|
| 76 |
const [socketBaseContext, setSocketBaseContext]:any = useState("")
|
|
@@ -134,7 +134,7 @@ return (<>{loaded && themeTypeContext && apiBaseContext && socketBaseContext &&
|
|
| 134 |
widgetContext, setWidgetContext,
|
| 135 |
showCloudflareTurnstileContext, setShowCloudflareTurnstileContext,
|
| 136 |
}}>
|
| 137 |
-
<View style={{width:
|
| 138 |
{showCloudflareTurnstileContext
|
| 139 |
? <View style={{position:"absolute",width:"100%",height:"100%",display:"flex",justifyContent:"center",alignItems:"center",backgroundColor:Theme[themeTypeContext].background_color}}>
|
| 140 |
<CloudflareTurnstile
|
|
@@ -167,17 +167,17 @@ return (<>{loaded && themeTypeContext && apiBaseContext && socketBaseContext &&
|
|
| 167 |
height:"100%",
|
| 168 |
display: 'flex',
|
| 169 |
flex: 1,
|
| 170 |
-
flexDirection:
|
| 171 |
|
| 172 |
}}>
|
| 173 |
|
| 174 |
<Stack screenOptions={{ headerShown: false}}>
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
</Stack>
|
| 179 |
<AnimatePresence exitBeforeEnter>
|
| 180 |
-
|
| 181 |
</AnimatePresence>
|
| 182 |
</View>
|
| 183 |
</>
|
|
|
|
| 70 |
export default function RootLayout() {
|
| 71 |
const pathname = usePathname()
|
| 72 |
const Dimensions = useWindowDimensions();
|
| 73 |
+
const [showMenuContext, setShowMenuContext]:any = useState(false)
|
| 74 |
const [themeTypeContext,setThemeTypeContext]:any = useState("")
|
| 75 |
const [apiBaseContext, setApiBaseContext]:any = useState("")
|
| 76 |
const [socketBaseContext, setSocketBaseContext]:any = useState("")
|
|
|
|
| 134 |
widgetContext, setWidgetContext,
|
| 135 |
showCloudflareTurnstileContext, setShowCloudflareTurnstileContext,
|
| 136 |
}}>
|
| 137 |
+
<View style={{width:Dimensions.width,height:Dimensions.height,backgroundColor: Theme[themeTypeContext].background_color}}>
|
| 138 |
{showCloudflareTurnstileContext
|
| 139 |
? <View style={{position:"absolute",width:"100%",height:"100%",display:"flex",justifyContent:"center",alignItems:"center",backgroundColor:Theme[themeTypeContext].background_color}}>
|
| 140 |
<CloudflareTurnstile
|
|
|
|
| 167 |
height:"100%",
|
| 168 |
display: 'flex',
|
| 169 |
flex: 1,
|
| 170 |
+
flexDirection: 'row-reverse',
|
| 171 |
|
| 172 |
}}>
|
| 173 |
|
| 174 |
<Stack screenOptions={{ headerShown: false}}>
|
| 175 |
+
<Stack.Screen name="index"/>
|
| 176 |
+
|
| 177 |
+
<Stack.Screen name="+not-found" />
|
| 178 |
</Stack>
|
| 179 |
<AnimatePresence exitBeforeEnter>
|
| 180 |
+
{showMenuContext !== null && <MemoMenu/>}
|
| 181 |
</AnimatePresence>
|
| 182 |
</View>
|
| 183 |
</>
|
frontend/app/explore/components/widgets.tsx
CHANGED
|
@@ -18,6 +18,7 @@ import ChapterStorage from '@/constants/module/storages/chapter_storage';
|
|
| 18 |
|
| 19 |
export const PageNavigationWidget = ({setPage}:any) =>{
|
| 20 |
const Dimensions = useWindowDimensions();
|
|
|
|
| 21 |
|
| 22 |
const {themeTypeContext, setThemeTypeContext}:any = useContext(CONTEXT)
|
| 23 |
const {widgetContext, setWidgetContext}:any = useContext(CONTEXT)
|
|
|
|
| 18 |
|
| 19 |
export const PageNavigationWidget = ({setPage}:any) =>{
|
| 20 |
const Dimensions = useWindowDimensions();
|
| 21 |
+
|
| 22 |
|
| 23 |
const {themeTypeContext, setThemeTypeContext}:any = useContext(CONTEXT)
|
| 24 |
const {widgetContext, setWidgetContext}:any = useContext(CONTEXT)
|
frontend/app/explore/index.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
import React, { useEffect, useState, useCallback, useContext, useRef } from 'react';
|
| 2 |
-
import { Link, router } from 'expo-router';
|
| 3 |
import Image from '@/components/Image';
|
| 4 |
import { StyleSheet, useWindowDimensions, ScrollView, Pressable, RefreshControl } from 'react-native';
|
| 5 |
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
@@ -45,9 +45,15 @@ const Index = ({}:any) => {
|
|
| 45 |
const controller = new AbortController();
|
| 46 |
const signal = controller.signal;
|
| 47 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 48 |
useEffect(() => {
|
| 49 |
(async ()=>{
|
| 50 |
-
|
| 51 |
setStyles(__styles(themeTypeContext,Dimensions))
|
| 52 |
|
| 53 |
let __translate:any = await Storage.get("explore_translate")
|
|
@@ -69,7 +75,6 @@ const Index = ({}:any) => {
|
|
| 69 |
|
| 70 |
const onRefresh = () => {
|
| 71 |
if (!(styles && themeTypeContext && apiBaseContext)) return
|
| 72 |
-
setShowMenuContext(true)
|
| 73 |
setIsLoading(true);
|
| 74 |
SET_CONTENT([])
|
| 75 |
get_list(setShowCloudflareTurnstileContext,setFeedBack,signal,setIsLoading,translate,SET_CONTENT,search,page)
|
|
@@ -81,29 +86,6 @@ const Index = ({}:any) => {
|
|
| 81 |
},[page])
|
| 82 |
|
| 83 |
|
| 84 |
-
const onScroll = useCallback((event:any) => {
|
| 85 |
-
const nativeEvent = event.nativeEvent
|
| 86 |
-
const { layoutMeasurement, contentOffset, contentSize } = nativeEvent;
|
| 87 |
-
var currentOffset = event.nativeEvent.contentOffset.y;
|
| 88 |
-
var direction = currentOffset > scrollOffset.current ? 'down' : 'up';
|
| 89 |
-
scrollOffset.current = currentOffset;
|
| 90 |
-
if (direction === 'down') {
|
| 91 |
-
if (contentOffset.y <= contentSize.height*0.025) {
|
| 92 |
-
setShowMenuContext(true)
|
| 93 |
-
}else{
|
| 94 |
-
setShowMenuContext(false)
|
| 95 |
-
}
|
| 96 |
-
}
|
| 97 |
-
else {
|
| 98 |
-
|
| 99 |
-
if (layoutMeasurement.height + contentOffset.y >= (contentSize.height - contentSize.height*0.025)) {
|
| 100 |
-
setShowMenuContext(false)
|
| 101 |
-
}else{
|
| 102 |
-
setShowMenuContext(true)
|
| 103 |
-
}
|
| 104 |
-
}
|
| 105 |
-
|
| 106 |
-
},[])
|
| 107 |
|
| 108 |
|
| 109 |
|
|
@@ -114,8 +96,6 @@ const Index = ({}:any) => {
|
|
| 114 |
if (!isLoading) onRefresh()
|
| 115 |
}} />
|
| 116 |
}
|
| 117 |
-
onScroll={(event) => {onScroll(event)}}
|
| 118 |
-
scrollEventThrottle={5}
|
| 119 |
>
|
| 120 |
|
| 121 |
<View style={styles.header_container}>
|
|
@@ -409,7 +389,7 @@ const Index = ({}:any) => {
|
|
| 409 |
{CONTENT.map((item:any,index:number)=>(
|
| 410 |
<TouchableRipple
|
| 411 |
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
| 412 |
-
onPress={()=>{router.
|
| 413 |
style={styles.item_box}
|
| 414 |
>
|
| 415 |
<>
|
|
|
|
| 1 |
import React, { useEffect, useState, useCallback, useContext, useRef } from 'react';
|
| 2 |
+
import { Link, router, useFocusEffect } from 'expo-router';
|
| 3 |
import Image from '@/components/Image';
|
| 4 |
import { StyleSheet, useWindowDimensions, ScrollView, Pressable, RefreshControl } from 'react-native';
|
| 5 |
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
|
|
| 45 |
const controller = new AbortController();
|
| 46 |
const signal = controller.signal;
|
| 47 |
|
| 48 |
+
useFocusEffect(useCallback(() => {
|
| 49 |
+
setShowMenuContext(true)
|
| 50 |
+
return () => {
|
| 51 |
+
|
| 52 |
+
};
|
| 53 |
+
}, []))
|
| 54 |
+
|
| 55 |
useEffect(() => {
|
| 56 |
(async ()=>{
|
|
|
|
| 57 |
setStyles(__styles(themeTypeContext,Dimensions))
|
| 58 |
|
| 59 |
let __translate:any = await Storage.get("explore_translate")
|
|
|
|
| 75 |
|
| 76 |
const onRefresh = () => {
|
| 77 |
if (!(styles && themeTypeContext && apiBaseContext)) return
|
|
|
|
| 78 |
setIsLoading(true);
|
| 79 |
SET_CONTENT([])
|
| 80 |
get_list(setShowCloudflareTurnstileContext,setFeedBack,signal,setIsLoading,translate,SET_CONTENT,search,page)
|
|
|
|
| 86 |
},[page])
|
| 87 |
|
| 88 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
|
| 90 |
|
| 91 |
|
|
|
|
| 96 |
if (!isLoading) onRefresh()
|
| 97 |
}} />
|
| 98 |
}
|
|
|
|
|
|
|
| 99 |
>
|
| 100 |
|
| 101 |
<View style={styles.header_container}>
|
|
|
|
| 389 |
{CONTENT.map((item:any,index:number)=>(
|
| 390 |
<TouchableRipple
|
| 391 |
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
| 392 |
+
onPress={()=>{router.navigate(`/view/${source}/${item.id}`)}} key={index}
|
| 393 |
style={styles.item_box}
|
| 394 |
>
|
| 395 |
<>
|
frontend/app/explore/stylesheet/show_list_styles.tsx
CHANGED
|
@@ -92,13 +92,13 @@ export const __styles:any = (theme_type:string,Dimensions:any) => {
|
|
| 92 |
alignItems:"center",
|
| 93 |
gap:15,
|
| 94 |
height:"auto",
|
| 95 |
-
width:Math.max(((Dimensions.width+Dimensions.height)/2)*0.
|
| 96 |
borderRadius:8,
|
| 97 |
|
| 98 |
},
|
| 99 |
item_cover:{
|
| 100 |
width:"100%",
|
| 101 |
-
height:Math.max(((Dimensions.width+Dimensions.height)/2)*0.
|
| 102 |
borderRadius:8,
|
| 103 |
shadowColor: "#000",
|
| 104 |
shadowOffset: {
|
|
|
|
| 92 |
alignItems:"center",
|
| 93 |
gap:15,
|
| 94 |
height:"auto",
|
| 95 |
+
width:Math.max(((Dimensions.width+Dimensions.height)/2)*0.225,100),
|
| 96 |
borderRadius:8,
|
| 97 |
|
| 98 |
},
|
| 99 |
item_cover:{
|
| 100 |
width:"100%",
|
| 101 |
+
height:Math.max(((Dimensions.width+Dimensions.height)/2)*0.325,125),
|
| 102 |
borderRadius:8,
|
| 103 |
shadowColor: "#000",
|
| 104 |
shadowOffset: {
|
frontend/app/index.tsx
CHANGED
|
@@ -9,7 +9,7 @@ const Index = () => {
|
|
| 9 |
const pathname = usePathname()
|
| 10 |
|
| 11 |
if (pathname === "/" || pathname === "") return (
|
| 12 |
-
<Redirect href="/
|
| 13 |
)
|
| 14 |
|
| 15 |
}
|
|
|
|
| 9 |
const pathname = usePathname()
|
| 10 |
|
| 11 |
if (pathname === "/" || pathname === "") return (
|
| 12 |
+
<Redirect href="/view/colamanga/manga-wp55334" />
|
| 13 |
)
|
| 14 |
|
| 15 |
}
|
frontend/app/read/[source]/[comic_id]/{index.tsx → [chapter_idx].tsx}
RENAMED
|
@@ -49,12 +49,14 @@ const Index = ({}:any) => {
|
|
| 49 |
const [isAdding, setIsAdding]:any = useState(false)
|
| 50 |
const [zoom, setZoom]:any = useState(0)
|
| 51 |
|
| 52 |
-
const CHAPTER_IDX = useRef(Number(useLocalSearchParams().
|
| 53 |
|
| 54 |
|
| 55 |
-
|
| 56 |
-
setShowMenuContext(
|
| 57 |
-
|
|
|
|
|
|
|
| 58 |
|
| 59 |
// First Load
|
| 60 |
useEffect(()=>{(async () => {
|
|
@@ -91,36 +93,39 @@ const Index = ({}:any) => {
|
|
| 91 |
},[]))
|
| 92 |
|
| 93 |
const renderItem = useCallback(({item,index}:any) => {
|
| 94 |
-
return <ChapterImage key={index} item={item} zoom={zoom} showOptions={showOptions} setShowOptions={setShowOptions}/>
|
| 95 |
},[zoom,showOptions,setShowOptions])
|
| 96 |
|
| 97 |
const onViewableItemsChanged = useCallback(async ({viewableItems, changed}:any) => {
|
| 98 |
-
const expect_chapter_idx = [CHAPTER_IDX.current + 1, CHAPTER_IDX.current - 1]
|
| 99 |
-
const current_count = viewableItems.filter((data:any) => data.item.chapter_idx === CHAPTER_IDX.current).length
|
| 100 |
-
const existed_count = viewableItems.filter((data:any) => expect_chapter_idx.includes(data.item.chapter_idx)).length
|
| 101 |
|
| 102 |
-
if (current_count || existed_count){
|
| 103 |
-
|
| 104 |
-
|
| 105 |
-
|
| 106 |
-
|
| 107 |
-
|
| 108 |
-
|
| 109 |
-
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
|
| 113 |
-
|
| 114 |
-
|
| 115 |
-
|
| 116 |
-
|
| 117 |
-
}
|
| 118 |
},[])
|
| 119 |
|
| 120 |
const onEndReached = useCallback(async () => {
|
| 121 |
-
|
|
|
|
|
|
|
|
|
|
| 122 |
|
| 123 |
-
SET_DATA([...
|
| 124 |
},[DATA])
|
| 125 |
|
| 126 |
return (<>
|
|
@@ -218,26 +223,12 @@ const Index = ({}:any) => {
|
|
| 218 |
<FlatList
|
| 219 |
data={DATA}
|
| 220 |
renderItem={renderItem}
|
| 221 |
-
onEndReachedThreshold={0.5}
|
| 222 |
windowSize={21}
|
| 223 |
ItemSeparatorComponent={undefined}
|
| 224 |
onEndReached={onEndReached}
|
| 225 |
onViewableItemsChanged={onViewableItemsChanged}
|
| 226 |
/>
|
| 227 |
-
{isAdding && (
|
| 228 |
-
<View
|
| 229 |
-
style={{
|
| 230 |
-
display:"flex",
|
| 231 |
-
width:"100%",
|
| 232 |
-
height:"auto",
|
| 233 |
-
justifyContent:"center",
|
| 234 |
-
padding:16,
|
| 235 |
-
}}
|
| 236 |
-
>
|
| 237 |
-
<ActivityIndicator animating={true} size={(0.03 * ((Dimensions.width+Dimensions.height)/2)) * (1 - zoom/100)}/>
|
| 238 |
-
</View>
|
| 239 |
-
)}
|
| 240 |
-
|
| 241 |
</View>
|
| 242 |
<AnimatePresence exitBeforeEnter>
|
| 243 |
{showOptions.state &&
|
|
|
|
| 49 |
const [isAdding, setIsAdding]:any = useState(false)
|
| 50 |
const [zoom, setZoom]:any = useState(0)
|
| 51 |
|
| 52 |
+
const CHAPTER_IDX = useRef(Number(useLocalSearchParams().chapter_idx as string));
|
| 53 |
|
| 54 |
|
| 55 |
+
useFocusEffect(useCallback(() => {
|
| 56 |
+
setShowMenuContext(null)
|
| 57 |
+
return () => {
|
| 58 |
+
}
|
| 59 |
+
},[]))
|
| 60 |
|
| 61 |
// First Load
|
| 62 |
useEffect(()=>{(async () => {
|
|
|
|
| 93 |
},[]))
|
| 94 |
|
| 95 |
const renderItem = useCallback(({item,index}:any) => {
|
| 96 |
+
return <ChapterImage key={index} item={item} zoom={zoom} showOptions={showOptions} setShowOptions={setShowOptions} setIsLoading={setIsLoading} SET_DATA={SET_DATA}/>
|
| 97 |
},[zoom,showOptions,setShowOptions])
|
| 98 |
|
| 99 |
const onViewableItemsChanged = useCallback(async ({viewableItems, changed}:any) => {
|
| 100 |
+
// const expect_chapter_idx = [CHAPTER_IDX.current + 1, CHAPTER_IDX.current - 1]
|
| 101 |
+
// const current_count = viewableItems.filter((data:any) => data.item.chapter_idx === CHAPTER_IDX.current).length
|
| 102 |
+
// const existed_count = viewableItems.filter((data:any) => expect_chapter_idx.includes(data.item.chapter_idx)).length
|
| 103 |
|
| 104 |
+
// if (current_count || existed_count){
|
| 105 |
+
// const choose_idx = current_count > existed_count ? CHAPTER_IDX.current : viewableItems.find((data:any) => expect_chapter_idx.includes(data.item.chapter_idx))?.item.chapter_idx
|
| 106 |
+
// if (choose_idx === CHAPTER_IDX.current) return
|
| 107 |
+
// const stored_chapter = await ChapterStorage.getByIdx(`${SOURCE}-${COMIC_ID}`,choose_idx, {exclude_fields:["data"]})
|
| 108 |
+
// setChapterInfo({
|
| 109 |
+
// chapter_id: stored_chapter?.id,
|
| 110 |
+
// chapter_idx: stored_chapter?.id,
|
| 111 |
+
// title: stored_chapter?.title,
|
| 112 |
+
// })
|
| 113 |
+
// const stored_comic = await ComicStorage.getByID(SOURCE, COMIC_ID)
|
| 114 |
+
// if (stored_comic.history.idx && choose_idx > stored_comic.history.idx) {
|
| 115 |
+
// await ComicStorage.updateHistory(SOURCE, COMIC_ID, {idx:stored_chapter?.idx,id:stored_chapter?.id,title:stored_chapter?.title})
|
| 116 |
+
// }
|
| 117 |
+
// router.setParams({idx:choose_idx})
|
| 118 |
+
// CHAPTER_IDX.current = choose_idx
|
| 119 |
+
// }
|
| 120 |
},[])
|
| 121 |
|
| 122 |
const onEndReached = useCallback(async () => {
|
| 123 |
+
console.log(DATA)
|
| 124 |
+
// const NEW_DATA = DATA.filter((data:any) => data.chapter_idx === CHAPTER_IDX.current-2)
|
| 125 |
+
|
| 126 |
+
// const chapter_current_data = await get_chapter(SOURCE,COMIC_ID,CHAPTER_IDX.current+1)
|
| 127 |
|
| 128 |
+
// SET_DATA([...NEW_DATA,...chapter_current_data])
|
| 129 |
},[DATA])
|
| 130 |
|
| 131 |
return (<>
|
|
|
|
| 223 |
<FlatList
|
| 224 |
data={DATA}
|
| 225 |
renderItem={renderItem}
|
| 226 |
+
// onEndReachedThreshold={0.5}
|
| 227 |
windowSize={21}
|
| 228 |
ItemSeparatorComponent={undefined}
|
| 229 |
onEndReached={onEndReached}
|
| 230 |
onViewableItemsChanged={onViewableItemsChanged}
|
| 231 |
/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 232 |
</View>
|
| 233 |
<AnimatePresence exitBeforeEnter>
|
| 234 |
{showOptions.state &&
|
frontend/app/read/components/chapter_image.tsx
CHANGED
|
@@ -20,8 +20,9 @@ import Image from '@/components/Image';
|
|
| 20 |
import {CONTEXT} from '@/constants/module/context';
|
| 21 |
import {blobToBase64, base64ToBlob} from "@/constants/module/file_manager";
|
| 22 |
import Theme from '@/constants/theme';
|
|
|
|
| 23 |
|
| 24 |
-
const ChapterImage = ({item, zoom, showOptions,setShowOptions}:any)=>{
|
| 25 |
const SOURCE = useLocalSearchParams().source;
|
| 26 |
const COMIC_ID = useLocalSearchParams().comic_id;
|
| 27 |
const CHAPTER_IDX = Number(useLocalSearchParams().chapter_idx as string);
|
|
@@ -140,7 +141,7 @@ const ChapterImage = ({item, zoom, showOptions,setShowOptions}:any)=>{
|
|
| 140 |
fontFamily:"roboto-bold",
|
| 141 |
}}
|
| 142 |
>
|
| 143 |
-
No more
|
| 144 |
</Text>
|
| 145 |
<Text selectable={false}
|
| 146 |
numberOfLines={1}
|
|
@@ -150,10 +151,135 @@ const ChapterImage = ({item, zoom, showOptions,setShowOptions}:any)=>{
|
|
| 150 |
fontFamily:"roboto-bold",
|
| 151 |
}}
|
| 152 |
>
|
| 153 |
-
You can go back and download more.
|
| 154 |
</Text>
|
| 155 |
</View>
|
| 156 |
)}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 157 |
</>)
|
| 158 |
: (
|
| 159 |
<View
|
|
|
|
| 20 |
import {CONTEXT} from '@/constants/module/context';
|
| 21 |
import {blobToBase64, base64ToBlob} from "@/constants/module/file_manager";
|
| 22 |
import Theme from '@/constants/theme';
|
| 23 |
+
import { get_chapter } from '../modules/get_chapter';
|
| 24 |
|
| 25 |
+
const ChapterImage = ({item, zoom, showOptions,setShowOptions, setIsLoading, SET_DATA}:any)=>{
|
| 26 |
const SOURCE = useLocalSearchParams().source;
|
| 27 |
const COMIC_ID = useLocalSearchParams().comic_id;
|
| 28 |
const CHAPTER_IDX = Number(useLocalSearchParams().chapter_idx as string);
|
|
|
|
| 141 |
fontFamily:"roboto-bold",
|
| 142 |
}}
|
| 143 |
>
|
| 144 |
+
No more chapters on local.
|
| 145 |
</Text>
|
| 146 |
<Text selectable={false}
|
| 147 |
numberOfLines={1}
|
|
|
|
| 151 |
fontFamily:"roboto-bold",
|
| 152 |
}}
|
| 153 |
>
|
| 154 |
+
You can go back and download more if available.
|
| 155 |
</Text>
|
| 156 |
</View>
|
| 157 |
)}
|
| 158 |
+
|
| 159 |
+
{item.type === "chapter-navigate" && (
|
| 160 |
+
|
| 161 |
+
<View
|
| 162 |
+
style={{
|
| 163 |
+
display:"flex",
|
| 164 |
+
flexDirection:"row",
|
| 165 |
+
justifyContent:"space-between",
|
| 166 |
+
alignItems:"center",
|
| 167 |
+
width:Dimensions.width > 720
|
| 168 |
+
? 0.8 * Dimensions.width * (1 - zoom / 100)
|
| 169 |
+
: `${100 - zoom}%`,
|
| 170 |
+
paddingHorizontal: 12,
|
| 171 |
+
paddingVertical: 18,
|
| 172 |
+
}}
|
| 173 |
+
>
|
| 174 |
+
<TouchableRipple
|
| 175 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
| 176 |
+
style={{
|
| 177 |
+
width:"auto",
|
| 178 |
+
display:"flex",
|
| 179 |
+
flexDirection:"column",
|
| 180 |
+
justifyContent:"center",
|
| 181 |
+
alignSelf:"center",
|
| 182 |
+
padding:8,
|
| 183 |
+
paddingHorizontal:18,
|
| 184 |
+
borderRadius:Dimensions.width*0.65/2,
|
| 185 |
+
backgroundColor:Theme[themeTypeContext].border_color,
|
| 186 |
+
|
| 187 |
+
shadowColor: Theme[themeTypeContext].shadow_color,
|
| 188 |
+
shadowOffset: { width: 0, height: 2 },
|
| 189 |
+
shadowOpacity: 0.25,
|
| 190 |
+
shadowRadius: 3.84,
|
| 191 |
+
elevation: 5,
|
| 192 |
+
|
| 193 |
+
}}
|
| 194 |
+
onPress={async ()=>{
|
| 195 |
+
const stored_chapter_info = await ChapterStorage.getByIdx(`${SOURCE}-${COMIC_ID}`,item.chapter_idx-1)
|
| 196 |
+
if (stored_chapter_info?.data_state === "completed"){
|
| 197 |
+
router.replace(`/read/${SOURCE}/${COMIC_ID}/${stored_chapter_info.idx}/`)
|
| 198 |
+
}else{
|
| 199 |
+
Toast.show({
|
| 200 |
+
type: 'info',
|
| 201 |
+
text1: 'Chapter not download yet.',
|
| 202 |
+
text2: "You can go back and download more.",
|
| 203 |
+
|
| 204 |
+
position: "bottom",
|
| 205 |
+
visibilityTime: 4000,
|
| 206 |
+
text1Style:{
|
| 207 |
+
fontFamily:"roboto-bold",
|
| 208 |
+
fontSize:((Dimensions.width+Dimensions.height)/2)*0.025
|
| 209 |
+
},
|
| 210 |
+
text2Style:{
|
| 211 |
+
fontFamily:"roboto-medium",
|
| 212 |
+
fontSize:((Dimensions.width+Dimensions.height)/2)*0.0185,
|
| 213 |
+
|
| 214 |
+
},
|
| 215 |
+
});
|
| 216 |
+
}
|
| 217 |
+
}}
|
| 218 |
+
>
|
| 219 |
+
<Text selectable={false}
|
| 220 |
+
style={{
|
| 221 |
+
color:Theme[themeTypeContext].text_color,
|
| 222 |
+
fontFamily:"roboto-medium",
|
| 223 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.03
|
| 224 |
+
}}
|
| 225 |
+
>Previous</Text>
|
| 226 |
+
</TouchableRipple>
|
| 227 |
+
|
| 228 |
+
<TouchableRipple
|
| 229 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
| 230 |
+
style={{
|
| 231 |
+
width:"auto",
|
| 232 |
+
display:"flex",
|
| 233 |
+
flexDirection:"column",
|
| 234 |
+
justifyContent:"center",
|
| 235 |
+
alignSelf:"center",
|
| 236 |
+
padding:8,
|
| 237 |
+
paddingHorizontal:18,
|
| 238 |
+
borderRadius:Dimensions.width*0.65/2,
|
| 239 |
+
backgroundColor:Theme[themeTypeContext].border_color,
|
| 240 |
+
|
| 241 |
+
shadowColor: Theme[themeTypeContext].shadow_color,
|
| 242 |
+
shadowOffset: { width: 0, height: 2 },
|
| 243 |
+
shadowOpacity: 0.25,
|
| 244 |
+
shadowRadius: 3.84,
|
| 245 |
+
elevation: 5,
|
| 246 |
+
}}
|
| 247 |
+
onPress={async ()=>{
|
| 248 |
+
const stored_chapter_info = await ChapterStorage.getByIdx(`${SOURCE}-${COMIC_ID}`,item.chapter_idx+1)
|
| 249 |
+
if (stored_chapter_info?.data_state === "completed"){
|
| 250 |
+
router.replace(`/read/${SOURCE}/${COMIC_ID}/${stored_chapter_info.idx}/`)
|
| 251 |
+
}else{
|
| 252 |
+
Toast.show({
|
| 253 |
+
type: 'info',
|
| 254 |
+
text1: 'Chapter not download yet.',
|
| 255 |
+
text2: "You can go back and download more.",
|
| 256 |
+
|
| 257 |
+
position: "bottom",
|
| 258 |
+
visibilityTime: 4000,
|
| 259 |
+
text1Style:{
|
| 260 |
+
fontFamily:"roboto-bold",
|
| 261 |
+
fontSize:((Dimensions.width+Dimensions.height)/2)*0.025
|
| 262 |
+
},
|
| 263 |
+
text2Style:{
|
| 264 |
+
fontFamily:"roboto-medium",
|
| 265 |
+
fontSize:((Dimensions.width+Dimensions.height)/2)*0.0185,
|
| 266 |
+
|
| 267 |
+
},
|
| 268 |
+
});
|
| 269 |
+
}
|
| 270 |
+
}}
|
| 271 |
+
>
|
| 272 |
+
<Text selectable={false}
|
| 273 |
+
style={{
|
| 274 |
+
color:Theme[themeTypeContext].text_color,
|
| 275 |
+
fontFamily:"roboto-medium",
|
| 276 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.03
|
| 277 |
+
}}
|
| 278 |
+
>Next</Text>
|
| 279 |
+
</TouchableRipple>
|
| 280 |
+
</View>
|
| 281 |
+
|
| 282 |
+
)}
|
| 283 |
</>)
|
| 284 |
: (
|
| 285 |
<View
|
frontend/app/read/modules/get_chapter.tsx
CHANGED
|
@@ -22,10 +22,11 @@ export const get_chapter = async (
|
|
| 22 |
DATA.push({type:"page",id:`${SOURCE}-${COMIC_ID}-${CHAPTER_IDX}-${i}`, chapter_idx: CHAPTER_IDX})
|
| 23 |
}
|
| 24 |
if (next_stored_chapter) {
|
| 25 |
-
DATA.push({type:"chapter-info-banner", value:{last:current_stored_chapter.title, next:next_stored_chapter.title}})
|
| 26 |
}else{
|
| 27 |
DATA.push({type:"no-chapter-banner"})
|
| 28 |
}
|
|
|
|
| 29 |
return DATA
|
| 30 |
}else{
|
| 31 |
return []
|
|
|
|
| 22 |
DATA.push({type:"page",id:`${SOURCE}-${COMIC_ID}-${CHAPTER_IDX}-${i}`, chapter_idx: CHAPTER_IDX})
|
| 23 |
}
|
| 24 |
if (next_stored_chapter) {
|
| 25 |
+
DATA.push({type:"chapter-info-banner", value:{last:current_stored_chapter.title, next:next_stored_chapter.title}, chapter_idx: CHAPTER_IDX})
|
| 26 |
}else{
|
| 27 |
DATA.push({type:"no-chapter-banner"})
|
| 28 |
}
|
| 29 |
+
DATA.push({type:"chapter-navigate", chapter_idx: CHAPTER_IDX})
|
| 30 |
return DATA
|
| 31 |
}else{
|
| 32 |
return []
|
frontend/app/view/[source]/[comic_id].tsx
CHANGED
|
@@ -22,7 +22,10 @@ import ChapterStorage from '@/constants/module/storages/chapter_storage';
|
|
| 22 |
import ComicStorage from '@/constants/module/storages/comic_storage';
|
| 23 |
import { CONTEXT } from '@/constants/module/context';
|
| 24 |
import Dropdown from '@/components/dropdown';
|
| 25 |
-
import
|
|
|
|
|
|
|
|
|
|
| 26 |
import ChapterComponent from '../componenets/chapter';
|
| 27 |
|
| 28 |
|
|
@@ -81,6 +84,7 @@ const Index = ({}:any) => {
|
|
| 81 |
// Test Section
|
| 82 |
useEffect(() => {
|
| 83 |
// console.log(CONTENT)
|
|
|
|
| 84 |
},[CONTENT])
|
| 85 |
|
| 86 |
|
|
@@ -153,8 +157,9 @@ const Index = ({}:any) => {
|
|
| 153 |
}
|
| 154 |
},[]))
|
| 155 |
|
| 156 |
-
// Clean up on unmount
|
| 157 |
useFocusEffect(useCallback(() => {
|
|
|
|
| 158 |
return () => {
|
| 159 |
controller.abort();
|
| 160 |
}
|
|
@@ -226,14 +231,13 @@ const Index = ({}:any) => {
|
|
| 226 |
// First load managing
|
| 227 |
useEffect(() => {
|
| 228 |
(async ()=>{
|
| 229 |
-
setShowMenuContext(false)
|
| 230 |
setStyles(__styles(themeTypeContext,Dimensions))
|
| 231 |
|
| 232 |
-
let __translate:any = await Storage.get("
|
| 233 |
|
| 234 |
if (!__translate) {
|
| 235 |
__translate = {state:false,from:"auto",to:"en"}
|
| 236 |
-
await Storage.store("
|
| 237 |
}else __translate = __translate
|
| 238 |
|
| 239 |
setTranslate(__translate)
|
|
@@ -438,7 +442,7 @@ const Index = ({}:any) => {
|
|
| 438 |
value={translate.from}
|
| 439 |
onChange={async (item:any) => {
|
| 440 |
setTranslate({...translate,from:item.value})
|
| 441 |
-
await Storage.store("
|
| 442 |
}}
|
| 443 |
/>
|
| 444 |
</View>
|
|
@@ -456,7 +460,7 @@ const Index = ({}:any) => {
|
|
| 456 |
value={translate.to}
|
| 457 |
onChange={async (item:any) => {
|
| 458 |
setTranslate({...translate,to:item.value})
|
| 459 |
-
await Storage.store("
|
| 460 |
}}
|
| 461 |
/>
|
| 462 |
</View>
|
|
@@ -477,10 +481,10 @@ const Index = ({}:any) => {
|
|
| 477 |
onPress={async () => {
|
| 478 |
if (translate.state){
|
| 479 |
setTranslate({...translate,state:false})
|
| 480 |
-
await Storage.store("
|
| 481 |
}else{
|
| 482 |
setTranslate({...translate,state:true})
|
| 483 |
-
await Storage.store("
|
| 484 |
}
|
| 485 |
|
| 486 |
}}
|
|
@@ -641,7 +645,7 @@ const Index = ({}:any) => {
|
|
| 641 |
|
| 642 |
}}
|
| 643 |
onPress={()=>{
|
| 644 |
-
router.push(`/read/${SOURCE}/${ID}/
|
| 645 |
}}
|
| 646 |
>
|
| 647 |
<View
|
|
@@ -707,7 +711,7 @@ const Index = ({}:any) => {
|
|
| 707 |
console.log(stored_chapter)
|
| 708 |
if (stored_chapter.data_state === "completed"){
|
| 709 |
await ComicStorage.updateHistory(SOURCE,ID,{idx:chapter.idx, id:chapter.id, title:chapter.title})
|
| 710 |
-
router.push(`/read/${SOURCE}/${ID}/
|
| 711 |
}else{
|
| 712 |
Toast.show({
|
| 713 |
type: 'error',
|
|
|
|
| 22 |
import ComicStorage from '@/constants/module/storages/comic_storage';
|
| 23 |
import { CONTEXT } from '@/constants/module/context';
|
| 24 |
import Dropdown from '@/components/dropdown';
|
| 25 |
+
import PageNavigationWidget from '../componenets/widgets/page_navigation';
|
| 26 |
+
import RequestChapterWidget from '../componenets/widgets/request_chapter';
|
| 27 |
+
import BookmarkWidget from '../componenets/widgets/bookmark';
|
| 28 |
+
|
| 29 |
import ChapterComponent from '../componenets/chapter';
|
| 30 |
|
| 31 |
|
|
|
|
| 84 |
// Test Section
|
| 85 |
useEffect(() => {
|
| 86 |
// console.log(CONTENT)
|
| 87 |
+
|
| 88 |
},[CONTENT])
|
| 89 |
|
| 90 |
|
|
|
|
| 157 |
}
|
| 158 |
},[]))
|
| 159 |
|
| 160 |
+
// Clean up on mount/unmount
|
| 161 |
useFocusEffect(useCallback(() => {
|
| 162 |
+
setShowMenuContext(null)
|
| 163 |
return () => {
|
| 164 |
controller.abort();
|
| 165 |
}
|
|
|
|
| 231 |
// First load managing
|
| 232 |
useEffect(() => {
|
| 233 |
(async ()=>{
|
|
|
|
| 234 |
setStyles(__styles(themeTypeContext,Dimensions))
|
| 235 |
|
| 236 |
+
let __translate:any = await Storage.get("view_show_translate")
|
| 237 |
|
| 238 |
if (!__translate) {
|
| 239 |
__translate = {state:false,from:"auto",to:"en"}
|
| 240 |
+
await Storage.store("view_show_translate",__translate)
|
| 241 |
}else __translate = __translate
|
| 242 |
|
| 243 |
setTranslate(__translate)
|
|
|
|
| 442 |
value={translate.from}
|
| 443 |
onChange={async (item:any) => {
|
| 444 |
setTranslate({...translate,from:item.value})
|
| 445 |
+
await Storage.store("view_show_translate",{...translate,from:item.value})
|
| 446 |
}}
|
| 447 |
/>
|
| 448 |
</View>
|
|
|
|
| 460 |
value={translate.to}
|
| 461 |
onChange={async (item:any) => {
|
| 462 |
setTranslate({...translate,to:item.value})
|
| 463 |
+
await Storage.store("view_show_translate",{...translate,to:item.value})
|
| 464 |
}}
|
| 465 |
/>
|
| 466 |
</View>
|
|
|
|
| 481 |
onPress={async () => {
|
| 482 |
if (translate.state){
|
| 483 |
setTranslate({...translate,state:false})
|
| 484 |
+
await Storage.store("view_show_translate",{...translate,state:false})
|
| 485 |
}else{
|
| 486 |
setTranslate({...translate,state:true})
|
| 487 |
+
await Storage.store("view_show_translate",{...translate,state:true})
|
| 488 |
}
|
| 489 |
|
| 490 |
}}
|
|
|
|
| 645 |
|
| 646 |
}}
|
| 647 |
onPress={()=>{
|
| 648 |
+
router.push(`/read/${SOURCE}/${ID}/${history.idx}/`)
|
| 649 |
}}
|
| 650 |
>
|
| 651 |
<View
|
|
|
|
| 711 |
console.log(stored_chapter)
|
| 712 |
if (stored_chapter.data_state === "completed"){
|
| 713 |
await ComicStorage.updateHistory(SOURCE,ID,{idx:chapter.idx, id:chapter.id, title:chapter.title})
|
| 714 |
+
router.push(`/read/${SOURCE}/${ID}/${stored_chapter.idx}/`)
|
| 715 |
}else{
|
| 716 |
Toast.show({
|
| 717 |
type: 'error',
|
frontend/app/view/componenets/chapter.tsx
CHANGED
|
@@ -22,7 +22,7 @@ import ChapterStorage from '@/constants/module/storages/chapter_storage';
|
|
| 22 |
import ComicStorage from '@/constants/module/storages/comic_storage';
|
| 23 |
import { CONTEXT } from '@/constants/module/context';
|
| 24 |
import Dropdown from '@/components/dropdown';
|
| 25 |
-
import
|
| 26 |
|
| 27 |
|
| 28 |
import { get, store_comic_cover, get_requested_info } from '../modules/content'
|
|
@@ -134,7 +134,7 @@ const ChapterComponent = ({
|
|
| 134 |
if (stored_chapter?.data_state === "completed") {
|
| 135 |
const stored_comic = await ComicStorage.getByID(SOURCE,ID)
|
| 136 |
if (!stored_comic.history.idx || chapter.idx > stored_comic.history.idx) await ComicStorage.updateHistory(SOURCE,ID,{idx:chapter.idx, id:chapter.id, title:chapter.title})
|
| 137 |
-
router.push(`/read/${SOURCE}/${ID}/
|
| 138 |
}else{
|
| 139 |
Toast.show({
|
| 140 |
type: 'error',
|
|
|
|
| 22 |
import ComicStorage from '@/constants/module/storages/comic_storage';
|
| 23 |
import { CONTEXT } from '@/constants/module/context';
|
| 24 |
import Dropdown from '@/components/dropdown';
|
| 25 |
+
import RequestChapterWidget from './widgets/request_chapter';
|
| 26 |
|
| 27 |
|
| 28 |
import { get, store_comic_cover, get_requested_info } from '../modules/content'
|
|
|
|
| 134 |
if (stored_chapter?.data_state === "completed") {
|
| 135 |
const stored_comic = await ComicStorage.getByID(SOURCE,ID)
|
| 136 |
if (!stored_comic.history.idx || chapter.idx > stored_comic.history.idx) await ComicStorage.updateHistory(SOURCE,ID,{idx:chapter.idx, id:chapter.id, title:chapter.title})
|
| 137 |
+
router.push(`/read/${SOURCE}/${ID}/${chapter.idx}/`)
|
| 138 |
}else{
|
| 139 |
Toast.show({
|
| 140 |
type: 'error',
|
frontend/app/view/componenets/widgets/bookmark.tsx
ADDED
|
@@ -0,0 +1,1116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import React, { useEffect, useState, useCallback, useContext, useRef, Fragment } from 'react';
|
| 3 |
+
import { Platform, useWindowDimensions, ScrollView } from 'react-native';
|
| 4 |
+
|
| 5 |
+
import { Icon, MD3Colors, Button, Text, TextInput, TouchableRipple, ActivityIndicator, Menu, Divider, PaperProvider, Portal } from 'react-native-paper';
|
| 6 |
+
import { View, AnimatePresence } from 'moti';
|
| 7 |
+
import Toast from 'react-native-toast-message';
|
| 8 |
+
import * as FileSystem from 'expo-file-system';
|
| 9 |
+
import axios from 'axios';
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
import Theme from '@/constants/theme';
|
| 13 |
+
import Dropdown from '@/components/dropdown';
|
| 14 |
+
import { CONTEXT } from '@/constants/module/context';
|
| 15 |
+
import { store_comic_cover } from '../../modules/content';
|
| 16 |
+
import Storage from '@/constants/module/storages/storage';
|
| 17 |
+
import ComicStorage from '@/constants/module/storages/comic_storage';
|
| 18 |
+
import ImageCacheStorage from '@/constants/module/storages/image_cache_storage';
|
| 19 |
+
import ChapterStorage from '@/constants/module/storages/chapter_storage';
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
interface BookmarkWidgetProps {
|
| 23 |
+
onRefresh: any;
|
| 24 |
+
SOURCE: string | string[];
|
| 25 |
+
ID: string | string[];
|
| 26 |
+
CONTENT: any;
|
| 27 |
+
}
|
| 28 |
+
|
| 29 |
+
const BookmarkWidget: React.FC<BookmarkWidgetProps> = ({
|
| 30 |
+
onRefresh,
|
| 31 |
+
SOURCE,
|
| 32 |
+
ID,
|
| 33 |
+
CONTENT
|
| 34 |
+
}) => {
|
| 35 |
+
const Dimensions = useWindowDimensions();
|
| 36 |
+
|
| 37 |
+
const {themeTypeContext, setThemeTypeContext}:any = useContext(CONTEXT)
|
| 38 |
+
const {widgetContext, setWidgetContext}:any = useContext(CONTEXT)
|
| 39 |
+
const {showCloudflareTurnstileContext, setShowCloudflareTurnstileContext}:any = useContext(CONTEXT)
|
| 40 |
+
const {apiBaseContext, setApiBaseContext}:any = useContext(CONTEXT)
|
| 41 |
+
|
| 42 |
+
const [BOOKMARK_DATA, SET_BOOKMARK_DATA]: any = useState([])
|
| 43 |
+
const [MIGRATE_BOOKMARK_DATA, SET_MIGRATE_BOOKMARK_DATA]:any = useState([])
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
const [showMenuOption, setShowMenuOption]:any = useState({state:false,positions:[0,0,0,0],id:""})
|
| 47 |
+
const [searchTag, setSearchTag]:any = useState("")
|
| 48 |
+
|
| 49 |
+
const [migrateTag,setMigrateTag]:any = useState("")
|
| 50 |
+
|
| 51 |
+
const [defaultTag, setDefaultTag]:any = useState("")
|
| 52 |
+
const [bookmark, setBookmark]:any = useState("")
|
| 53 |
+
const [manageBookmark, setManageBookmark]:any = useState({state:false,edit:"",delete:""})
|
| 54 |
+
const [createTag, setCreateTag]:any = useState({state:false,title:""})
|
| 55 |
+
const [removeTag, setRemoveTag]:any = useState({state:false, removing: false})
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
const controller = new AbortController();
|
| 59 |
+
const signal = controller.signal;
|
| 60 |
+
|
| 61 |
+
const RenderTag = ({item}:any) =>{
|
| 62 |
+
const [editTag, setEditTag]:any = useState(item.value)
|
| 63 |
+
return (<>
|
| 64 |
+
{item.value.includes(searchTag) &&
|
| 65 |
+
(
|
| 66 |
+
<View
|
| 67 |
+
style={{
|
| 68 |
+
display:"flex",
|
| 69 |
+
flexDirection:"row",
|
| 70 |
+
alignItems:"center",
|
| 71 |
+
justifyContent:"space-between",
|
| 72 |
+
gap:8,
|
| 73 |
+
zIndex:10,
|
| 74 |
+
}}
|
| 75 |
+
>
|
| 76 |
+
<>{manageBookmark.edit !== item.value && manageBookmark.delete !== item.value &&
|
| 77 |
+
(<View
|
| 78 |
+
style={{
|
| 79 |
+
width:"100%",
|
| 80 |
+
display:"flex",
|
| 81 |
+
flexDirection:"row",
|
| 82 |
+
justifyContent:"space-between",
|
| 83 |
+
alignItems:"center",
|
| 84 |
+
height:"auto",
|
| 85 |
+
gap:18,
|
| 86 |
+
}}
|
| 87 |
+
>
|
| 88 |
+
<Text
|
| 89 |
+
style={{
|
| 90 |
+
color:"white",
|
| 91 |
+
fontFamily:"roboto-medium",
|
| 92 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.025
|
| 93 |
+
}}
|
| 94 |
+
>{item.label}</Text>
|
| 95 |
+
<View
|
| 96 |
+
style={{
|
| 97 |
+
width:"auto",
|
| 98 |
+
height:"auto",
|
| 99 |
+
|
| 100 |
+
}}
|
| 101 |
+
>
|
| 102 |
+
|
| 103 |
+
<TouchableRipple
|
| 104 |
+
|
| 105 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
| 106 |
+
style={{
|
| 107 |
+
borderRadius:5,
|
| 108 |
+
borderWidth:0,
|
| 109 |
+
backgroundColor: "transparent",
|
| 110 |
+
padding:5,
|
| 111 |
+
|
| 112 |
+
}}
|
| 113 |
+
|
| 114 |
+
onPress={(event)=>{
|
| 115 |
+
if (manageBookmark.edit){
|
| 116 |
+
setManageBookmark({...manageBookmark,edit:""})
|
| 117 |
+
setEditTag("")
|
| 118 |
+
}
|
| 119 |
+
|
| 120 |
+
|
| 121 |
+
const x = event.nativeEvent.pageX
|
| 122 |
+
const y = event.nativeEvent.pageY
|
| 123 |
+
|
| 124 |
+
setShowMenuOption({
|
| 125 |
+
...showMenuOption,
|
| 126 |
+
state: showMenuOption.id === item.value ? false : true,
|
| 127 |
+
positions:[y+((Dimensions.width+Dimensions.height)/2)*0.0225,0,x-((Dimensions.width+Dimensions.height)/2)*0.18,0],
|
| 128 |
+
id:showMenuOption.id === item.value ? "" : item.value,
|
| 129 |
+
})
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
}}
|
| 134 |
+
>
|
| 135 |
+
|
| 136 |
+
<Icon source={"dots-vertical"} size={((Dimensions.width+Dimensions.height)/2)*0.035} color={Theme[themeTypeContext].icon_color}/>
|
| 137 |
+
</TouchableRipple>
|
| 138 |
+
</View>
|
| 139 |
+
</View>)
|
| 140 |
+
}</>
|
| 141 |
+
<>{manageBookmark.edit &&
|
| 142 |
+
(<View
|
| 143 |
+
style={{
|
| 144 |
+
display:"flex",
|
| 145 |
+
flexDirection:"row",
|
| 146 |
+
justifyContent:"space-between",
|
| 147 |
+
alignItems:"center",
|
| 148 |
+
width:"100%",
|
| 149 |
+
height:"auto",
|
| 150 |
+
gap:12,
|
| 151 |
+
padding:12,
|
| 152 |
+
}}
|
| 153 |
+
>
|
| 154 |
+
<View
|
| 155 |
+
style={{flex:1}}
|
| 156 |
+
>
|
| 157 |
+
<TextInput mode="outlined" label="Edit" textColor={Theme[themeTypeContext].text_color} maxLength={72}
|
| 158 |
+
right={<TextInput.Affix text={`| Max: 72`} />}
|
| 159 |
+
style={{
|
| 160 |
+
width:"100%",
|
| 161 |
+
height:"100%",
|
| 162 |
+
backgroundColor:Theme[themeTypeContext].background_color,
|
| 163 |
+
borderColor:Theme[themeTypeContext].border_color,
|
| 164 |
+
|
| 165 |
+
}}
|
| 166 |
+
outlineColor={Theme[themeTypeContext].text_input_border_color}
|
| 167 |
+
value={editTag}
|
| 168 |
+
onChange={(event)=>{
|
| 169 |
+
setEditTag(event.nativeEvent.text)
|
| 170 |
+
}}
|
| 171 |
+
/>
|
| 172 |
+
</View>
|
| 173 |
+
<View
|
| 174 |
+
style={{
|
| 175 |
+
display:"flex",
|
| 176 |
+
flexDirection:"row",
|
| 177 |
+
gap:8,
|
| 178 |
+
}}
|
| 179 |
+
>
|
| 180 |
+
<TouchableRipple
|
| 181 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
| 182 |
+
style={{
|
| 183 |
+
borderRadius:5,
|
| 184 |
+
borderWidth:0,
|
| 185 |
+
backgroundColor: "transparent",
|
| 186 |
+
padding:5,
|
| 187 |
+
}}
|
| 188 |
+
|
| 189 |
+
onPress={()=>{
|
| 190 |
+
setManageBookmark({...manageBookmark,edit:""})
|
| 191 |
+
setEditTag("")
|
| 192 |
+
setShowMenuOption({...showMenuOption,state:false,id:""})
|
| 193 |
+
}}
|
| 194 |
+
>
|
| 195 |
+
|
| 196 |
+
<Icon source={"close"} size={((Dimensions.width+Dimensions.height)/2)*0.035} color={"red"}/>
|
| 197 |
+
</TouchableRipple>
|
| 198 |
+
<TouchableRipple
|
| 199 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
| 200 |
+
style={{
|
| 201 |
+
borderRadius:5,
|
| 202 |
+
borderWidth:0,
|
| 203 |
+
backgroundColor: "transparent",
|
| 204 |
+
padding:5,
|
| 205 |
+
}}
|
| 206 |
+
|
| 207 |
+
onPress={async ()=>{
|
| 208 |
+
const stored_bookmark = await Storage.get("bookmark");
|
| 209 |
+
|
| 210 |
+
const index = stored_bookmark.findIndex((item:string) => item === manageBookmark.edit);
|
| 211 |
+
|
| 212 |
+
if (index !== -1){
|
| 213 |
+
stored_bookmark[index] = editTag;
|
| 214 |
+
await Storage.store("bookmark", stored_bookmark)
|
| 215 |
+
|
| 216 |
+
const stored_comics:any = await ComicStorage.getByTag(manageBookmark.edit)
|
| 217 |
+
for (const item of stored_comics){
|
| 218 |
+
await ComicStorage.replaceTag(item.source, item.id, editTag)
|
| 219 |
+
}
|
| 220 |
+
if (manageBookmark.edit === defaultTag) {
|
| 221 |
+
onRefresh();
|
| 222 |
+
setWidgetContext({state:false,component:<></>});
|
| 223 |
+
|
| 224 |
+
}else{
|
| 225 |
+
|
| 226 |
+
const index = BOOKMARK_DATA.findIndex((item:any) => item.value === manageBookmark.edit);
|
| 227 |
+
if (index !== -1){
|
| 228 |
+
BOOKMARK_DATA[index].label = editTag
|
| 229 |
+
BOOKMARK_DATA[index].value = editTag
|
| 230 |
+
}
|
| 231 |
+
SET_BOOKMARK_DATA(BOOKMARK_DATA)
|
| 232 |
+
setManageBookmark({...manageBookmark,edit:""})
|
| 233 |
+
setEditTag("")
|
| 234 |
+
}
|
| 235 |
+
|
| 236 |
+
}
|
| 237 |
+
setShowMenuOption({...showMenuOption,state:false,id:""})
|
| 238 |
+
}}
|
| 239 |
+
>
|
| 240 |
+
|
| 241 |
+
<Icon source={"check"} size={((Dimensions.width+Dimensions.height)/2)*0.035} color={"green"}/>
|
| 242 |
+
</TouchableRipple>
|
| 243 |
+
</View>
|
| 244 |
+
|
| 245 |
+
|
| 246 |
+
|
| 247 |
+
</View>)
|
| 248 |
+
|
| 249 |
+
}</>
|
| 250 |
+
|
| 251 |
+
|
| 252 |
+
|
| 253 |
+
|
| 254 |
+
</View>
|
| 255 |
+
|
| 256 |
+
)
|
| 257 |
+
}
|
| 258 |
+
</>)
|
| 259 |
+
}
|
| 260 |
+
|
| 261 |
+
|
| 262 |
+
|
| 263 |
+
const load_bookmark = async ()=>{
|
| 264 |
+
const stored_comic = await ComicStorage.getByID(SOURCE,CONTENT.id)
|
| 265 |
+
|
| 266 |
+
if (stored_comic) {
|
| 267 |
+
setDefaultTag(stored_comic.tag)
|
| 268 |
+
setBookmark(stored_comic.tag)
|
| 269 |
+
}
|
| 270 |
+
|
| 271 |
+
const stored_bookmark_data = await Storage.get("bookmark") || []
|
| 272 |
+
if (stored_bookmark_data.length) {
|
| 273 |
+
const bookmark_data:Array<Object> = []
|
| 274 |
+
for (const item of stored_bookmark_data) {
|
| 275 |
+
bookmark_data.push({
|
| 276 |
+
label:item,
|
| 277 |
+
value:item,
|
| 278 |
+
})
|
| 279 |
+
}
|
| 280 |
+
|
| 281 |
+
SET_BOOKMARK_DATA(bookmark_data.sort())
|
| 282 |
+
}else SET_BOOKMARK_DATA([])
|
| 283 |
+
}
|
| 284 |
+
|
| 285 |
+
useEffect(()=>{
|
| 286 |
+
console.log(BOOKMARK_DATA)
|
| 287 |
+
SET_MIGRATE_BOOKMARK_DATA([{label:"None",value:""},...BOOKMARK_DATA])
|
| 288 |
+
},[BOOKMARK_DATA])
|
| 289 |
+
|
| 290 |
+
useEffect(()=>{
|
| 291 |
+
load_bookmark()
|
| 292 |
+
return () => controller.abort();
|
| 293 |
+
},[])
|
| 294 |
+
|
| 295 |
+
return (<>{BOOKMARK_DATA !== null && <>
|
| 296 |
+
|
| 297 |
+
<View key={"BookmarkWidget"}
|
| 298 |
+
style={{
|
| 299 |
+
zIndex:10,
|
| 300 |
+
backgroundColor:Theme[themeTypeContext].background_color,
|
| 301 |
+
width:Dimensions.width*0.35,
|
| 302 |
+
minWidth:500,
|
| 303 |
+
|
| 304 |
+
borderColor:Theme[themeTypeContext].border_color,
|
| 305 |
+
borderWidth:2,
|
| 306 |
+
borderRadius:8,
|
| 307 |
+
padding:12,
|
| 308 |
+
display:"flex",
|
| 309 |
+
justifyContent:"center",
|
| 310 |
+
|
| 311 |
+
flexDirection:"column",
|
| 312 |
+
gap:12,
|
| 313 |
+
}}
|
| 314 |
+
from={{
|
| 315 |
+
opacity: 0,
|
| 316 |
+
scale: 0.9,
|
| 317 |
+
}}
|
| 318 |
+
animate={{
|
| 319 |
+
opacity: 1,
|
| 320 |
+
scale: 1,
|
| 321 |
+
}}
|
| 322 |
+
exit={{
|
| 323 |
+
opacity: 0,
|
| 324 |
+
scale: 0.5,
|
| 325 |
+
}}
|
| 326 |
+
transition={{
|
| 327 |
+
type: 'timing',
|
| 328 |
+
duration: 500,
|
| 329 |
+
}}
|
| 330 |
+
exitTransition={{
|
| 331 |
+
type: 'timing',
|
| 332 |
+
duration: 250,
|
| 333 |
+
}}
|
| 334 |
+
>
|
| 335 |
+
|
| 336 |
+
<>{!manageBookmark.state && !createTag.state && !removeTag.state &&
|
| 337 |
+
<>
|
| 338 |
+
<View
|
| 339 |
+
style={{
|
| 340 |
+
width:"100%",
|
| 341 |
+
height:"auto",
|
| 342 |
+
display:"flex",
|
| 343 |
+
flexDirection:"row",
|
| 344 |
+
alignItems:"flex-end",
|
| 345 |
+
justifyContent:"space-between",
|
| 346 |
+
gap:8,
|
| 347 |
+
}}
|
| 348 |
+
>
|
| 349 |
+
<View style={{flex:1}}>
|
| 350 |
+
<Dropdown
|
| 351 |
+
theme_type={themeTypeContext}
|
| 352 |
+
Dimensions={Dimensions}
|
| 353 |
+
|
| 354 |
+
label='Add to bookmark'
|
| 355 |
+
data={BOOKMARK_DATA}
|
| 356 |
+
value={bookmark}
|
| 357 |
+
onChange={(async (item:any) => {
|
| 358 |
+
setBookmark(item.value)
|
| 359 |
+
})}
|
| 360 |
+
/>
|
| 361 |
+
</View>
|
| 362 |
+
<>{bookmark &&
|
| 363 |
+
<TouchableRipple
|
| 364 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
| 365 |
+
style={{
|
| 366 |
+
padding:5,
|
| 367 |
+
borderRadius:5,
|
| 368 |
+
borderWidth:0,
|
| 369 |
+
backgroundColor: "transparent",
|
| 370 |
+
}}
|
| 371 |
+
onPress={(async ()=>{
|
| 372 |
+
const stored_comic = await ComicStorage.getByID(SOURCE,CONTENT.id)
|
| 373 |
+
if (stored_comic) setRemoveTag({...removeTag,state:true})
|
| 374 |
+
else setBookmark("")
|
| 375 |
+
})}
|
| 376 |
+
>
|
| 377 |
+
<Icon source={"tag-remove-outline"} size={((Dimensions.width+Dimensions.height)/2)*0.035} color={"red"}/>
|
| 378 |
+
</TouchableRipple>
|
| 379 |
+
}</>
|
| 380 |
+
</View>
|
| 381 |
+
|
| 382 |
+
|
| 383 |
+
<View
|
| 384 |
+
style={{
|
| 385 |
+
display:"flex",
|
| 386 |
+
flexDirection:"row",
|
| 387 |
+
width:"100%",
|
| 388 |
+
justifyContent:"space-around",
|
| 389 |
+
alignItems:"center",
|
| 390 |
+
}}
|
| 391 |
+
>
|
| 392 |
+
<Button mode='contained'
|
| 393 |
+
labelStyle={{
|
| 394 |
+
color:Theme[themeTypeContext].text_color,
|
| 395 |
+
fontFamily:"roboto-medium",
|
| 396 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.02,
|
| 397 |
+
paddingVertical:4,
|
| 398 |
+
}}
|
| 399 |
+
style={{backgroundColor:"blue",borderRadius:5}}
|
| 400 |
+
onPress={(()=>{
|
| 401 |
+
setManageBookmark({...manageBookmark,state:true})
|
| 402 |
+
})}
|
| 403 |
+
>Manage Bookmark</Button>
|
| 404 |
+
<Button mode='outlined'
|
| 405 |
+
labelStyle={{
|
| 406 |
+
color:Theme[themeTypeContext].text_color,
|
| 407 |
+
fontFamily:"roboto-medium",
|
| 408 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.02,
|
| 409 |
+
|
| 410 |
+
|
| 411 |
+
}}
|
| 412 |
+
style={{
|
| 413 |
+
|
| 414 |
+
borderRadius:5,
|
| 415 |
+
borderWidth:2,
|
| 416 |
+
borderColor:Theme[themeTypeContext].border_color
|
| 417 |
+
}}
|
| 418 |
+
onPress={(async ()=>{
|
| 419 |
+
if (defaultTag !== bookmark){
|
| 420 |
+
const stored_comic = await ComicStorage.getByID(SOURCE,CONTENT.id)
|
| 421 |
+
if (stored_comic) await ComicStorage.replaceTag(SOURCE, CONTENT.id, bookmark)
|
| 422 |
+
else {
|
| 423 |
+
const cover_result:any = await store_comic_cover(setShowCloudflareTurnstileContext,signal,SOURCE,ID,CONTENT)
|
| 424 |
+
|
| 425 |
+
await ComicStorage.store(SOURCE,CONTENT.id, bookmark, {
|
| 426 |
+
cover:cover_result,
|
| 427 |
+
title:CONTENT.title,
|
| 428 |
+
author:CONTENT.author,
|
| 429 |
+
category:CONTENT.category,
|
| 430 |
+
status:CONTENT.status,
|
| 431 |
+
synopsis:CONTENT.synopsis,
|
| 432 |
+
updated:CONTENT.updated,
|
| 433 |
+
})
|
| 434 |
+
}
|
| 435 |
+
onRefresh()
|
| 436 |
+
}
|
| 437 |
+
setWidgetContext({state:false,component:<></>})
|
| 438 |
+
|
| 439 |
+
})}
|
| 440 |
+
>Done</Button>
|
| 441 |
+
</View>
|
| 442 |
+
</>
|
| 443 |
+
}</>
|
| 444 |
+
|
| 445 |
+
<>{manageBookmark.state && !createTag.state && !removeTag.state && <>
|
| 446 |
+
<View
|
| 447 |
+
|
| 448 |
+
style={{
|
| 449 |
+
display:"flex",
|
| 450 |
+
flexDirection:"column",
|
| 451 |
+
gap:18,
|
| 452 |
+
}}
|
| 453 |
+
>
|
| 454 |
+
<>{BOOKMARK_DATA.length
|
| 455 |
+
? <>
|
| 456 |
+
<View
|
| 457 |
+
style={{flex:1}}
|
| 458 |
+
>
|
| 459 |
+
<TextInput mode="outlined" label="Search" textColor={Theme[themeTypeContext].text_color}
|
| 460 |
+
placeholder={""}
|
| 461 |
+
style={{
|
| 462 |
+
|
| 463 |
+
backgroundColor:Theme[themeTypeContext].background_color,
|
| 464 |
+
borderColor:Theme[themeTypeContext].border_color,
|
| 465 |
+
|
| 466 |
+
}}
|
| 467 |
+
outlineColor={Theme[themeTypeContext].text_input_border_color}
|
| 468 |
+
value={searchTag}
|
| 469 |
+
onChange={(event)=>{
|
| 470 |
+
setSearchTag(event.nativeEvent.text)
|
| 471 |
+
}}
|
| 472 |
+
/>
|
| 473 |
+
</View>
|
| 474 |
+
<View
|
| 475 |
+
style={{
|
| 476 |
+
maxHeight:Dimensions.height*0.7
|
| 477 |
+
}}
|
| 478 |
+
>
|
| 479 |
+
<ScrollView
|
| 480 |
+
contentContainerStyle={{
|
| 481 |
+
display:"flex",
|
| 482 |
+
flexDirection:"column",
|
| 483 |
+
justifyContent:"space-around",
|
| 484 |
+
gap:8,
|
| 485 |
+
|
| 486 |
+
height:"auto",
|
| 487 |
+
paddingVertical:12,
|
| 488 |
+
paddingHorizontal:8,
|
| 489 |
+
}}
|
| 490 |
+
style={{
|
| 491 |
+
|
| 492 |
+
}}
|
| 493 |
+
>
|
| 494 |
+
<>{BOOKMARK_DATA.map((item:any) => <Fragment key={item.value}>
|
| 495 |
+
<RenderTag item={item}/>
|
| 496 |
+
<View style={{width:"100%",height:2,backgroundColor:Theme[themeTypeContext].border_color}}/>
|
| 497 |
+
</Fragment>)}</>
|
| 498 |
+
</ScrollView>
|
| 499 |
+
</View>
|
| 500 |
+
</>
|
| 501 |
+
: <>
|
| 502 |
+
<Text style={{
|
| 503 |
+
width:"100%",
|
| 504 |
+
textAlign:"center",
|
| 505 |
+
color:Theme[themeTypeContext].text_color,
|
| 506 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.045,
|
| 507 |
+
fontFamily:"roboto-bold",
|
| 508 |
+
}}>No bookmark found</Text>
|
| 509 |
+
</>
|
| 510 |
+
|
| 511 |
+
}</>
|
| 512 |
+
|
| 513 |
+
<View
|
| 514 |
+
style={{
|
| 515 |
+
display:"flex",
|
| 516 |
+
flexDirection:"row",
|
| 517 |
+
width:"100%",
|
| 518 |
+
justifyContent:"space-around",
|
| 519 |
+
alignItems:"center",
|
| 520 |
+
}}
|
| 521 |
+
>
|
| 522 |
+
<Button mode='contained'
|
| 523 |
+
labelStyle={{
|
| 524 |
+
color:Theme[themeTypeContext].text_color,
|
| 525 |
+
fontFamily:"roboto-medium",
|
| 526 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.02
|
| 527 |
+
}}
|
| 528 |
+
style={{backgroundColor:"green",borderRadius:5}}
|
| 529 |
+
onPress={(()=>{
|
| 530 |
+
setCreateTag({state:true,title:""})
|
| 531 |
+
setShowMenuOption({...showMenuOption,state:false,id:""})
|
| 532 |
+
})}
|
| 533 |
+
>+ Create Bookmark</Button>
|
| 534 |
+
<Button mode='outlined'
|
| 535 |
+
labelStyle={{
|
| 536 |
+
color:Theme[themeTypeContext].text_color,
|
| 537 |
+
fontFamily:"roboto-medium",
|
| 538 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.02,
|
| 539 |
+
|
| 540 |
+
|
| 541 |
+
}}
|
| 542 |
+
style={{
|
| 543 |
+
|
| 544 |
+
borderRadius:5,
|
| 545 |
+
borderWidth:2,
|
| 546 |
+
borderColor:Theme[themeTypeContext].border_color
|
| 547 |
+
}}
|
| 548 |
+
onPress={(async ()=>{
|
| 549 |
+
setManageBookmark({...manageBookmark,state:false,edit:"",delete:""})
|
| 550 |
+
setShowMenuOption({...showMenuOption,state:false,id:""})
|
| 551 |
+
})}
|
| 552 |
+
>Back</Button>
|
| 553 |
+
</View>
|
| 554 |
+
</View>
|
| 555 |
+
</>}</>
|
| 556 |
+
|
| 557 |
+
<>{createTag.state &&
|
| 558 |
+
<>
|
| 559 |
+
<View
|
| 560 |
+
style={{
|
| 561 |
+
height:"auto",
|
| 562 |
+
display:"flex",
|
| 563 |
+
flexDirection:"column",
|
| 564 |
+
gap:12,
|
| 565 |
+
}}
|
| 566 |
+
>
|
| 567 |
+
<TextInput mode="outlined" label="Create Bookmark" textColor={Theme[themeTypeContext].text_color} maxLength={72}
|
| 568 |
+
placeholder="Bookmark Tag"
|
| 569 |
+
|
| 570 |
+
right={<TextInput.Affix text={`| Max: 72`} />}
|
| 571 |
+
style={{
|
| 572 |
+
|
| 573 |
+
backgroundColor:Theme[themeTypeContext].background_color,
|
| 574 |
+
borderColor:Theme[themeTypeContext].border_color,
|
| 575 |
+
|
| 576 |
+
}}
|
| 577 |
+
outlineColor={Theme[themeTypeContext].text_input_border_color}
|
| 578 |
+
value={createTag.title}
|
| 579 |
+
onChange={(event)=>{
|
| 580 |
+
setCreateTag({...createTag,title:event.nativeEvent.text})
|
| 581 |
+
}}
|
| 582 |
+
/>
|
| 583 |
+
</View>
|
| 584 |
+
<View
|
| 585 |
+
style={{
|
| 586 |
+
display:"flex",
|
| 587 |
+
flexDirection:"row",
|
| 588 |
+
width:"100%",
|
| 589 |
+
justifyContent:"space-around",
|
| 590 |
+
alignItems:"center",
|
| 591 |
+
}}
|
| 592 |
+
>
|
| 593 |
+
<Button mode='outlined'
|
| 594 |
+
labelStyle={{
|
| 595 |
+
color:Theme[themeTypeContext].text_color,
|
| 596 |
+
fontFamily:"roboto-medium",
|
| 597 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.02,
|
| 598 |
+
|
| 599 |
+
|
| 600 |
+
}}
|
| 601 |
+
style={{
|
| 602 |
+
|
| 603 |
+
borderRadius:5,
|
| 604 |
+
borderWidth:2,
|
| 605 |
+
borderColor:Theme[themeTypeContext].border_color
|
| 606 |
+
}}
|
| 607 |
+
onPress={(()=>{
|
| 608 |
+
|
| 609 |
+
setCreateTag({...createTag,state:false})
|
| 610 |
+
|
| 611 |
+
})}
|
| 612 |
+
>Cancel</Button>
|
| 613 |
+
<Button mode='contained'
|
| 614 |
+
labelStyle={{
|
| 615 |
+
color:Theme[themeTypeContext].text_color,
|
| 616 |
+
fontFamily:"roboto-medium",
|
| 617 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.02
|
| 618 |
+
}}
|
| 619 |
+
style={{backgroundColor:"green",borderRadius:5}}
|
| 620 |
+
onPress={(async()=>{
|
| 621 |
+
|
| 622 |
+
const title = createTag.title
|
| 623 |
+
if (!title) return
|
| 624 |
+
|
| 625 |
+
const stored_bookmark_data = await Storage.get("bookmark") || []
|
| 626 |
+
if (stored_bookmark_data.includes(title)){
|
| 627 |
+
Toast.show({
|
| 628 |
+
type: 'error',
|
| 629 |
+
text1: '🔖 Duplicate Bookmark',
|
| 630 |
+
text2: `Tag "${title}" already existed in your bookmark.`,
|
| 631 |
+
|
| 632 |
+
position: "bottom",
|
| 633 |
+
visibilityTime: 5000,
|
| 634 |
+
text1Style:{
|
| 635 |
+
fontFamily:"roboto-bold",
|
| 636 |
+
fontSize:((Dimensions.width+Dimensions.height)/2)*0.025
|
| 637 |
+
},
|
| 638 |
+
text2Style:{
|
| 639 |
+
fontFamily:"roboto-medium",
|
| 640 |
+
fontSize:((Dimensions.width+Dimensions.height)/2)*0.0185,
|
| 641 |
+
|
| 642 |
+
},
|
| 643 |
+
});
|
| 644 |
+
}else{
|
| 645 |
+
await Storage.store("bookmark", [...stored_bookmark_data,title].sort())
|
| 646 |
+
SET_BOOKMARK_DATA([...BOOKMARK_DATA,
|
| 647 |
+
{label:title,value:title}
|
| 648 |
+
].sort())
|
| 649 |
+
setCreateTag({state:false,title:""})
|
| 650 |
+
Toast.show({
|
| 651 |
+
type: 'info',
|
| 652 |
+
text1: '🔖 Create Bookmark',
|
| 653 |
+
text2: `Tag "${title}" added to your bookmark.`,
|
| 654 |
+
|
| 655 |
+
position: "bottom",
|
| 656 |
+
visibilityTime: 3000,
|
| 657 |
+
text1Style:{
|
| 658 |
+
fontFamily:"roboto-bold",
|
| 659 |
+
fontSize:((Dimensions.width+Dimensions.height)/2)*0.025
|
| 660 |
+
},
|
| 661 |
+
text2Style:{
|
| 662 |
+
fontFamily:"roboto-medium",
|
| 663 |
+
fontSize:((Dimensions.width+Dimensions.height)/2)*0.0185,
|
| 664 |
+
|
| 665 |
+
},
|
| 666 |
+
});
|
| 667 |
+
}
|
| 668 |
+
})}
|
| 669 |
+
>Add</Button>
|
| 670 |
+
</View>
|
| 671 |
+
</>
|
| 672 |
+
}</>
|
| 673 |
+
<>{removeTag.state &&
|
| 674 |
+
<>
|
| 675 |
+
<View
|
| 676 |
+
style={{
|
| 677 |
+
height:"auto",
|
| 678 |
+
display:"flex",
|
| 679 |
+
flexDirection:"column",
|
| 680 |
+
gap:12,
|
| 681 |
+
}}
|
| 682 |
+
>
|
| 683 |
+
<Text
|
| 684 |
+
style={{
|
| 685 |
+
color:Theme[themeTypeContext].text_color,
|
| 686 |
+
fontFamily:"roboto-bold",
|
| 687 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.03,
|
| 688 |
+
textAlign:"center",
|
| 689 |
+
}}
|
| 690 |
+
>Are you sure you want to remove this comic from bookmark?</Text>
|
| 691 |
+
|
| 692 |
+
<Text
|
| 693 |
+
style={{
|
| 694 |
+
color:Theme[themeTypeContext].text_color,
|
| 695 |
+
fontFamily:"roboto-medium",
|
| 696 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.025,
|
| 697 |
+
textAlign:"center",
|
| 698 |
+
}}
|
| 699 |
+
>This will remove all local saved info and chapters.</Text>
|
| 700 |
+
</View>
|
| 701 |
+
|
| 702 |
+
<View
|
| 703 |
+
style={{
|
| 704 |
+
display:"flex",
|
| 705 |
+
flexDirection:"row",
|
| 706 |
+
width:"100%",
|
| 707 |
+
justifyContent:"space-around",
|
| 708 |
+
alignItems:"center",
|
| 709 |
+
}}
|
| 710 |
+
>
|
| 711 |
+
<>{removeTag.removing
|
| 712 |
+
? <ActivityIndicator animating={true}/>
|
| 713 |
+
:<>
|
| 714 |
+
|
| 715 |
+
<Button mode='outlined' disabled={removeTag.removing}
|
| 716 |
+
labelStyle={{
|
| 717 |
+
color:Theme[themeTypeContext].text_color,
|
| 718 |
+
fontFamily:"roboto-medium",
|
| 719 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.02,
|
| 720 |
+
|
| 721 |
+
|
| 722 |
+
}}
|
| 723 |
+
style={{
|
| 724 |
+
|
| 725 |
+
borderRadius:5,
|
| 726 |
+
borderWidth:2,
|
| 727 |
+
borderColor:Theme[themeTypeContext].border_color
|
| 728 |
+
}}
|
| 729 |
+
onPress={(()=>{
|
| 730 |
+
setRemoveTag({...removeTag,state:false})
|
| 731 |
+
})}
|
| 732 |
+
>No</Button>
|
| 733 |
+
<Button mode='contained' disabled={removeTag.removing}
|
| 734 |
+
labelStyle={{
|
| 735 |
+
color:Theme[themeTypeContext].text_color,
|
| 736 |
+
fontFamily:"roboto-medium",
|
| 737 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.02
|
| 738 |
+
}}
|
| 739 |
+
style={{backgroundColor:"red",borderRadius:5}}
|
| 740 |
+
onPress={(async ()=>{
|
| 741 |
+
setRemoveTag({...removeTag,removing:false})
|
| 742 |
+
if (Platform.OS !== "web"){
|
| 743 |
+
const comic_dir = FileSystem.documentDirectory + "ComicMTL/" + `${SOURCE}/` + `${ID}/`
|
| 744 |
+
await FileSystem.deleteAsync(comic_dir, { idempotent: true })
|
| 745 |
+
}
|
| 746 |
+
|
| 747 |
+
await ChapterStorage.drop(`${SOURCE}-${CONTENT.id}`)
|
| 748 |
+
await ComicStorage.removeByID(SOURCE,CONTENT.id)
|
| 749 |
+
|
| 750 |
+
onRefresh()
|
| 751 |
+
setWidgetContext({state:false,component:<></>})
|
| 752 |
+
})}
|
| 753 |
+
>Yes</Button>
|
| 754 |
+
</>
|
| 755 |
+
}</>
|
| 756 |
+
|
| 757 |
+
</View>
|
| 758 |
+
</>
|
| 759 |
+
}</>
|
| 760 |
+
|
| 761 |
+
</View>
|
| 762 |
+
<>{showMenuOption.state && manageBookmark.state &&
|
| 763 |
+
<View
|
| 764 |
+
style={{
|
| 765 |
+
display:"flex",
|
| 766 |
+
position:"absolute",
|
| 767 |
+
zIndex:11,
|
| 768 |
+
justifyContent:"space-around",
|
| 769 |
+
flexDirection:"column",
|
| 770 |
+
|
| 771 |
+
backgroundColor:Theme[themeTypeContext].border_color,
|
| 772 |
+
top:showMenuOption.positions[0],
|
| 773 |
+
bottom:showMenuOption.positions[1],
|
| 774 |
+
left:showMenuOption.positions[2],
|
| 775 |
+
right:showMenuOption.positions[3],
|
| 776 |
+
|
| 777 |
+
width:(Dimensions.width+Dimensions.height)/2*0.2,
|
| 778 |
+
height:(Dimensions.width+Dimensions.height)/2*0.1,
|
| 779 |
+
|
| 780 |
+
borderRadius:5,
|
| 781 |
+
borderWidth:2,
|
| 782 |
+
borderColor:Theme[themeTypeContext].background_color
|
| 783 |
+
}}
|
| 784 |
+
>
|
| 785 |
+
<TouchableRipple
|
| 786 |
+
|
| 787 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
| 788 |
+
style={{
|
| 789 |
+
|
| 790 |
+
borderWidth:0,
|
| 791 |
+
backgroundColor: "transparent",
|
| 792 |
+
padding:5,
|
| 793 |
+
width:"100%",
|
| 794 |
+
|
| 795 |
+
}}
|
| 796 |
+
|
| 797 |
+
onPress={(event)=>{
|
| 798 |
+
setManageBookmark({...manageBookmark,edit:showMenuOption.id})
|
| 799 |
+
setShowMenuOption({...showMenuOption,state:false})
|
| 800 |
+
}}
|
| 801 |
+
>
|
| 802 |
+
<View
|
| 803 |
+
style={{
|
| 804 |
+
display:"flex",
|
| 805 |
+
flexDirection:"row",
|
| 806 |
+
justifyContent:"center",
|
| 807 |
+
alignItems:"center",
|
| 808 |
+
paddingHorizontal:18,
|
| 809 |
+
|
| 810 |
+
}}
|
| 811 |
+
>
|
| 812 |
+
<Icon source={"pencil"} size={((Dimensions.width+Dimensions.height)/2)*0.025} color={"blue"}/>
|
| 813 |
+
<View>
|
| 814 |
+
<Text selectable={false}
|
| 815 |
+
style={{
|
| 816 |
+
textAlign:"center",
|
| 817 |
+
color:"blue",
|
| 818 |
+
fontFamily:"roboto-medium",
|
| 819 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.02
|
| 820 |
+
}}
|
| 821 |
+
>Edit</Text>
|
| 822 |
+
</View>
|
| 823 |
+
</View>
|
| 824 |
+
</TouchableRipple>
|
| 825 |
+
<View style={{width:"100%",height:2,backgroundColor:Theme[themeTypeContext].background_color}}/>
|
| 826 |
+
<TouchableRipple
|
| 827 |
+
|
| 828 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
| 829 |
+
style={{
|
| 830 |
+
|
| 831 |
+
borderWidth:0,
|
| 832 |
+
backgroundColor: "transparent",
|
| 833 |
+
padding:5,
|
| 834 |
+
width:"100%",
|
| 835 |
+
}}
|
| 836 |
+
|
| 837 |
+
onPress={(event)=>{
|
| 838 |
+
setManageBookmark({...manageBookmark,edit:"",delete:showMenuOption.id})
|
| 839 |
+
setShowMenuOption({...showMenuOption,state:false})
|
| 840 |
+
}}
|
| 841 |
+
>
|
| 842 |
+
<View
|
| 843 |
+
style={{
|
| 844 |
+
display:"flex",
|
| 845 |
+
flexDirection:"row",
|
| 846 |
+
justifyContent:"center",
|
| 847 |
+
alignItems:"center",
|
| 848 |
+
paddingHorizontal:18,
|
| 849 |
+
|
| 850 |
+
}}
|
| 851 |
+
>
|
| 852 |
+
<Icon source={"trash-can"} size={((Dimensions.width+Dimensions.height)/2)*0.025} color={"red"}/>
|
| 853 |
+
<View>
|
| 854 |
+
<Text selectable={false}
|
| 855 |
+
style={{
|
| 856 |
+
textAlign:"center",
|
| 857 |
+
color:"red",
|
| 858 |
+
fontFamily:"roboto-medium",
|
| 859 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.02
|
| 860 |
+
}}
|
| 861 |
+
>Delete</Text>
|
| 862 |
+
</View>
|
| 863 |
+
</View>
|
| 864 |
+
</TouchableRipple>
|
| 865 |
+
|
| 866 |
+
</View>
|
| 867 |
+
|
| 868 |
+
}</>
|
| 869 |
+
<>{manageBookmark.delete && (
|
| 870 |
+
|
| 871 |
+
|
| 872 |
+
<View
|
| 873 |
+
style={{
|
| 874 |
+
top:0,
|
| 875 |
+
left:0,
|
| 876 |
+
position:"absolute",
|
| 877 |
+
width:Dimensions.width,
|
| 878 |
+
height:Dimensions.height,
|
| 879 |
+
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
| 880 |
+
zIndex:11,
|
| 881 |
+
display:"flex",
|
| 882 |
+
justifyContent:"center",
|
| 883 |
+
alignItems:"center",
|
| 884 |
+
}}
|
| 885 |
+
>
|
| 886 |
+
<View
|
| 887 |
+
style={{
|
| 888 |
+
backgroundColor:Theme[themeTypeContext].background_color,
|
| 889 |
+
width:Dimensions.width*0.35,
|
| 890 |
+
minWidth:500,
|
| 891 |
+
height:"auto",
|
| 892 |
+
|
| 893 |
+
borderColor:Theme[themeTypeContext].border_color,
|
| 894 |
+
borderWidth:2,
|
| 895 |
+
borderRadius:8,
|
| 896 |
+
padding:12,
|
| 897 |
+
display:"flex",
|
| 898 |
+
justifyContent:"center",
|
| 899 |
+
|
| 900 |
+
flexDirection:"column",
|
| 901 |
+
gap:18,
|
| 902 |
+
}}
|
| 903 |
+
>
|
| 904 |
+
<View
|
| 905 |
+
style={{
|
| 906 |
+
borderBottomWidth:2,
|
| 907 |
+
borderColor:Theme[themeTypeContext].border_color,
|
| 908 |
+
padding:8,
|
| 909 |
+
width:"100%",
|
| 910 |
+
}}
|
| 911 |
+
>
|
| 912 |
+
<Text
|
| 913 |
+
numberOfLines={1}
|
| 914 |
+
style={{
|
| 915 |
+
color:"red",
|
| 916 |
+
fontFamily:"roboto-bold",
|
| 917 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.03,
|
| 918 |
+
textAlign:"center",
|
| 919 |
+
}}
|
| 920 |
+
>Delete Tag: "{manageBookmark.delete}"</Text>
|
| 921 |
+
</View>
|
| 922 |
+
<View
|
| 923 |
+
style={{
|
| 924 |
+
width:"100%",
|
| 925 |
+
display:"flex",
|
| 926 |
+
flexDirection:"column",
|
| 927 |
+
gap:12,
|
| 928 |
+
}}
|
| 929 |
+
>
|
| 930 |
+
<View style={{flex:1}}>
|
| 931 |
+
<Dropdown
|
| 932 |
+
theme_type={themeTypeContext}
|
| 933 |
+
Dimensions={Dimensions}
|
| 934 |
+
|
| 935 |
+
label='Migrate comics to tag'
|
| 936 |
+
data={MIGRATE_BOOKMARK_DATA.filter((item:any) => item.value !== manageBookmark.delete)}
|
| 937 |
+
value={migrateTag}
|
| 938 |
+
onChange={(async (item:any) => {
|
| 939 |
+
setMigrateTag(item.value)
|
| 940 |
+
})}
|
| 941 |
+
/>
|
| 942 |
+
</View>
|
| 943 |
+
<>{!migrateTag && (
|
| 944 |
+
<Text
|
| 945 |
+
style={{
|
| 946 |
+
color:Theme[themeTypeContext].text_color,
|
| 947 |
+
fontFamily:"roboto-bold",
|
| 948 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.02,
|
| 949 |
+
textAlign:"center",
|
| 950 |
+
}}
|
| 951 |
+
>Setting migration to None will remove all comics and chapters for this bookmark tag.</Text>
|
| 952 |
+
)}</>
|
| 953 |
+
<View
|
| 954 |
+
style={{
|
| 955 |
+
display:"flex",
|
| 956 |
+
flexDirection:"row",
|
| 957 |
+
width:"100%",
|
| 958 |
+
justifyContent:"space-around",
|
| 959 |
+
alignItems:"center",
|
| 960 |
+
}}
|
| 961 |
+
>
|
| 962 |
+
<>{migrateTag
|
| 963 |
+
|
| 964 |
+
? <TouchableRipple
|
| 965 |
+
|
| 966 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
| 967 |
+
style={{
|
| 968 |
+
|
| 969 |
+
borderWidth:0,
|
| 970 |
+
backgroundColor: "blue",
|
| 971 |
+
padding:5,
|
| 972 |
+
borderRadius:8,
|
| 973 |
+
paddingHorizontal:12,
|
| 974 |
+
paddingVertical:8,
|
| 975 |
+
|
| 976 |
+
|
| 977 |
+
}}
|
| 978 |
+
|
| 979 |
+
onPress={async (event)=>{
|
| 980 |
+
const stored_bookmark = await Storage.get("bookmark")
|
| 981 |
+
const index = stored_bookmark.findIndex((item:any) => item === manageBookmark.delete)
|
| 982 |
+
if (index === -1) return
|
| 983 |
+
|
| 984 |
+
const stored_comics = await ComicStorage.getByTag(manageBookmark.delete)
|
| 985 |
+
for (const comic of stored_comics) {
|
| 986 |
+
const source = comic.source;
|
| 987 |
+
const comic_id = comic.id
|
| 988 |
+
await ComicStorage.replaceTag(source,comic_id,migrateTag)
|
| 989 |
+
|
| 990 |
+
}
|
| 991 |
+
|
| 992 |
+
stored_bookmark.splice(index, 1);
|
| 993 |
+
await Storage.store("bookmark",stored_bookmark);
|
| 994 |
+
|
| 995 |
+
if (defaultTag === manageBookmark.delete) {
|
| 996 |
+
setWidgetContext({state:false,component:<></>});
|
| 997 |
+
onRefresh();
|
| 998 |
+
}else {
|
| 999 |
+
const index_2 = BOOKMARK_DATA.findIndex((item:any) => item.value === manageBookmark.delete);
|
| 1000 |
+
if (index_2 !== -1){
|
| 1001 |
+
BOOKMARK_DATA.splice(index_2, 1);
|
| 1002 |
+
}
|
| 1003 |
+
SET_BOOKMARK_DATA(BOOKMARK_DATA)
|
| 1004 |
+
setManageBookmark({...manageBookmark,edit:"",delete:""})
|
| 1005 |
+
setShowMenuOption({...showMenuOption,state:false,id:""})
|
| 1006 |
+
setMigrateTag("")
|
| 1007 |
+
}
|
| 1008 |
+
|
| 1009 |
+
}}
|
| 1010 |
+
>
|
| 1011 |
+
|
| 1012 |
+
<Text selectable={false}
|
| 1013 |
+
style={{
|
| 1014 |
+
textAlign:"center",
|
| 1015 |
+
color:Theme[themeTypeContext].text_color,
|
| 1016 |
+
fontFamily:"roboto-medium",
|
| 1017 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.02
|
| 1018 |
+
}}
|
| 1019 |
+
>Migrate</Text>
|
| 1020 |
+
|
| 1021 |
+
</TouchableRipple>
|
| 1022 |
+
: <TouchableRipple
|
| 1023 |
+
|
| 1024 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
| 1025 |
+
style={{
|
| 1026 |
+
|
| 1027 |
+
borderWidth:0,
|
| 1028 |
+
backgroundColor: "red",
|
| 1029 |
+
padding:5,
|
| 1030 |
+
borderRadius:8,
|
| 1031 |
+
paddingHorizontal:12,
|
| 1032 |
+
paddingVertical:8,
|
| 1033 |
+
|
| 1034 |
+
|
| 1035 |
+
}}
|
| 1036 |
+
|
| 1037 |
+
onPress={async (event)=>{
|
| 1038 |
+
const stored_bookmark = await Storage.get("bookmark");
|
| 1039 |
+
const index = stored_bookmark.findIndex((item:any) => item === manageBookmark.delete)
|
| 1040 |
+
if (index === -1) return
|
| 1041 |
+
await ComicStorage.removeByTag(manageBookmark.delete);
|
| 1042 |
+
stored_bookmark.splice(index, 1);
|
| 1043 |
+
await Storage.store("bookmark",stored_bookmark);
|
| 1044 |
+
|
| 1045 |
+
if (defaultTag === manageBookmark.delete) {
|
| 1046 |
+
setWidgetContext({state:false,component:<></>});
|
| 1047 |
+
onRefresh();
|
| 1048 |
+
}else {
|
| 1049 |
+
const index_2 = BOOKMARK_DATA.findIndex((item:any) => item.value === manageBookmark.delete);
|
| 1050 |
+
if (index_2 !== -1){
|
| 1051 |
+
BOOKMARK_DATA.splice(index_2, 1);
|
| 1052 |
+
}
|
| 1053 |
+
SET_BOOKMARK_DATA(BOOKMARK_DATA)
|
| 1054 |
+
setManageBookmark({...manageBookmark,edit:"",delete:""})
|
| 1055 |
+
setShowMenuOption({...showMenuOption,state:false,id:""})
|
| 1056 |
+
setMigrateTag("")
|
| 1057 |
+
}
|
| 1058 |
+
|
| 1059 |
+
}}
|
| 1060 |
+
>
|
| 1061 |
+
|
| 1062 |
+
<Text selectable={false}
|
| 1063 |
+
style={{
|
| 1064 |
+
textAlign:"center",
|
| 1065 |
+
color:Theme[themeTypeContext].text_color,
|
| 1066 |
+
fontFamily:"roboto-medium",
|
| 1067 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.02
|
| 1068 |
+
}}
|
| 1069 |
+
>Delete</Text>
|
| 1070 |
+
|
| 1071 |
+
</TouchableRipple>
|
| 1072 |
+
}</>
|
| 1073 |
+
<TouchableRipple
|
| 1074 |
+
|
| 1075 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
| 1076 |
+
style={{
|
| 1077 |
+
|
| 1078 |
+
borderWidth:2,
|
| 1079 |
+
borderColor:Theme[themeTypeContext].border_color,
|
| 1080 |
+
backgroundColor: "transparent",
|
| 1081 |
+
padding:5,
|
| 1082 |
+
borderRadius:8,
|
| 1083 |
+
paddingHorizontal:12,
|
| 1084 |
+
paddingVertical:8,
|
| 1085 |
+
|
| 1086 |
+
|
| 1087 |
+
}}
|
| 1088 |
+
|
| 1089 |
+
onPress={(event)=>{
|
| 1090 |
+
setShowMenuOption({...showMenuOption,state:false,id:""})
|
| 1091 |
+
setManageBookmark({...manageBookmark,edit:"",delete:""})
|
| 1092 |
+
}}
|
| 1093 |
+
>
|
| 1094 |
+
|
| 1095 |
+
<Text selectable={false}
|
| 1096 |
+
style={{
|
| 1097 |
+
textAlign:"center",
|
| 1098 |
+
color:Theme[themeTypeContext].text_color,
|
| 1099 |
+
fontFamily:"roboto-medium",
|
| 1100 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.02
|
| 1101 |
+
}}
|
| 1102 |
+
>Cancel</Text>
|
| 1103 |
+
|
| 1104 |
+
</TouchableRipple>
|
| 1105 |
+
</View>
|
| 1106 |
+
|
| 1107 |
+
</View>
|
| 1108 |
+
</View>
|
| 1109 |
+
|
| 1110 |
+
</View>
|
| 1111 |
+
)}</>
|
| 1112 |
+
|
| 1113 |
+
</>}</>)
|
| 1114 |
+
}
|
| 1115 |
+
|
| 1116 |
+
export default BookmarkWidget;
|
frontend/app/view/componenets/widgets/page_navigation.tsx
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
|
| 2 |
+
import React, { useEffect, useState, useCallback, useContext, useRef, Fragment } from 'react';
|
| 3 |
+
import { Platform, useWindowDimensions, ScrollView } from 'react-native';
|
| 4 |
+
|
| 5 |
+
import { Icon, MD3Colors, Button, Text, TextInput, TouchableRipple, ActivityIndicator, Menu, Divider, PaperProvider, Portal } from 'react-native-paper';
|
| 6 |
+
import { View, AnimatePresence } from 'moti';
|
| 7 |
+
import Toast from 'react-native-toast-message';
|
| 8 |
+
import * as FileSystem from 'expo-file-system';
|
| 9 |
+
import axios from 'axios';
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
import Theme from '@/constants/theme';
|
| 13 |
+
import Dropdown from '@/components/dropdown';
|
| 14 |
+
import { CONTEXT } from '@/constants/module/context';
|
| 15 |
+
import { store_comic_cover } from '../../modules/content';
|
| 16 |
+
import Storage from '@/constants/module/storages/storage';
|
| 17 |
+
import ComicStorage from '@/constants/module/storages/comic_storage';
|
| 18 |
+
import ImageCacheStorage from '@/constants/module/storages/image_cache_storage';
|
| 19 |
+
import ChapterStorage from '@/constants/module/storages/chapter_storage';
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
const PageNavigationWidget = ({MAX_OFFSET,setPage,CONTENT}:any) =>{
|
| 24 |
+
const Dimensions = useWindowDimensions();
|
| 25 |
+
|
| 26 |
+
const {themeTypeContext, setThemeTypeContext}:any = useContext(CONTEXT)
|
| 27 |
+
const {widgetContext, setWidgetContext}:any = useContext(CONTEXT)
|
| 28 |
+
|
| 29 |
+
const [goToPage, setGoToPage] = useState("");
|
| 30 |
+
const [_feedBack, _setFeedBack] = useState("");
|
| 31 |
+
return (<View
|
| 32 |
+
style={{
|
| 33 |
+
backgroundColor:Theme[themeTypeContext].background_color,
|
| 34 |
+
maxWidth:500,
|
| 35 |
+
width:"100%",
|
| 36 |
+
|
| 37 |
+
borderColor:Theme[themeTypeContext].border_color,
|
| 38 |
+
borderWidth:2,
|
| 39 |
+
borderRadius:8,
|
| 40 |
+
padding:12,
|
| 41 |
+
display:"flex",
|
| 42 |
+
justifyContent:"center",
|
| 43 |
+
|
| 44 |
+
flexDirection:"column",
|
| 45 |
+
gap:12,
|
| 46 |
+
}}
|
| 47 |
+
from={{
|
| 48 |
+
opacity: 0,
|
| 49 |
+
scale: 0.9,
|
| 50 |
+
}}
|
| 51 |
+
animate={{
|
| 52 |
+
opacity: 1,
|
| 53 |
+
scale: 1,
|
| 54 |
+
}}
|
| 55 |
+
exit={{
|
| 56 |
+
opacity: 0,
|
| 57 |
+
scale: 0.5,
|
| 58 |
+
}}
|
| 59 |
+
transition={{
|
| 60 |
+
type: 'timing',
|
| 61 |
+
duration: 500,
|
| 62 |
+
}}
|
| 63 |
+
exitTransition={{
|
| 64 |
+
type: 'timing',
|
| 65 |
+
duration: 250,
|
| 66 |
+
}}
|
| 67 |
+
>
|
| 68 |
+
<View style={{height:"auto"}}>
|
| 69 |
+
<TextInput mode="outlined" label="Go to page" textColor={Theme[themeTypeContext].text_color} maxLength={1000000000}
|
| 70 |
+
placeholder="Go to page"
|
| 71 |
+
right={<TextInput.Affix text={`/${Math.ceil(CONTENT.chapters.length/MAX_OFFSET)}`} />}
|
| 72 |
+
style={{
|
| 73 |
+
|
| 74 |
+
backgroundColor:Theme[themeTypeContext].background_color,
|
| 75 |
+
borderColor:Theme[themeTypeContext].border_color,
|
| 76 |
+
|
| 77 |
+
}}
|
| 78 |
+
outlineColor={Theme[themeTypeContext].text_input_border_color}
|
| 79 |
+
value={goToPage}
|
| 80 |
+
onChange={(event)=>{
|
| 81 |
+
|
| 82 |
+
const value = event.nativeEvent.text
|
| 83 |
+
|
| 84 |
+
const isInt = /^-?\d+$/.test(value);
|
| 85 |
+
if (isInt || value === "") {
|
| 86 |
+
if (parseInt(value) > Math.ceil(CONTENT.chapters.length/MAX_OFFSET)){
|
| 87 |
+
_setFeedBack("Page is out of index.")
|
| 88 |
+
}else{
|
| 89 |
+
_setFeedBack("")
|
| 90 |
+
setGoToPage(value)
|
| 91 |
+
}
|
| 92 |
+
|
| 93 |
+
}
|
| 94 |
+
else _setFeedBack("Input is not a valid number.")
|
| 95 |
+
|
| 96 |
+
}}
|
| 97 |
+
/>
|
| 98 |
+
|
| 99 |
+
</View>
|
| 100 |
+
{_feedBack
|
| 101 |
+
? <Text
|
| 102 |
+
style={{
|
| 103 |
+
color:Theme[themeTypeContext].text_color,
|
| 104 |
+
fontFamily:"roboto-medium",
|
| 105 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.02,
|
| 106 |
+
textAlign:"center",
|
| 107 |
+
}}
|
| 108 |
+
|
| 109 |
+
>{_feedBack}</Text>
|
| 110 |
+
: <></>
|
| 111 |
+
}
|
| 112 |
+
<View
|
| 113 |
+
style={{
|
| 114 |
+
display:"flex",
|
| 115 |
+
flexDirection:"row",
|
| 116 |
+
width:"100%",
|
| 117 |
+
justifyContent:"space-around",
|
| 118 |
+
alignItems:"center",
|
| 119 |
+
}}
|
| 120 |
+
>
|
| 121 |
+
<Button mode='contained'
|
| 122 |
+
labelStyle={{
|
| 123 |
+
color:Theme[themeTypeContext].text_color,
|
| 124 |
+
fontFamily:"roboto-medium",
|
| 125 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.02
|
| 126 |
+
}}
|
| 127 |
+
style={{backgroundColor:"red",borderRadius:5}}
|
| 128 |
+
onPress={(()=>{
|
| 129 |
+
|
| 130 |
+
setWidgetContext({state:false,component:<></>})
|
| 131 |
+
|
| 132 |
+
})}
|
| 133 |
+
>Cancel</Button>
|
| 134 |
+
<Button mode='contained'
|
| 135 |
+
labelStyle={{
|
| 136 |
+
color:Theme[themeTypeContext].text_color,
|
| 137 |
+
fontFamily:"roboto-medium",
|
| 138 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.02
|
| 139 |
+
}}
|
| 140 |
+
style={{backgroundColor:"green",borderRadius:5}}
|
| 141 |
+
onPress={(()=>{
|
| 142 |
+
const isInt = /^-?\d+$/.test(goToPage);
|
| 143 |
+
if (isInt) {
|
| 144 |
+
if (parseInt(goToPage) > Math.ceil(CONTENT.chapters.length/MAX_OFFSET) || !parseInt(goToPage)){
|
| 145 |
+
_setFeedBack("Page is out of index.")
|
| 146 |
+
}else{
|
| 147 |
+
setPage(parseInt(goToPage))
|
| 148 |
+
setWidgetContext({state:false,component:<></>})
|
| 149 |
+
}
|
| 150 |
+
|
| 151 |
+
}else _setFeedBack("Input is not a valid number.")
|
| 152 |
+
})}
|
| 153 |
+
>Go</Button>
|
| 154 |
+
</View>
|
| 155 |
+
|
| 156 |
+
</View>)
|
| 157 |
+
}
|
| 158 |
+
|
| 159 |
+
export default PageNavigationWidget;
|
frontend/app/view/componenets/{widgets.tsx → widgets/request_chapter.tsx}
RENAMED
|
@@ -1,7 +1,8 @@
|
|
| 1 |
-
import React, { useEffect, useState, useCallback, useContext, useRef } from 'react';
|
| 2 |
-
import { Platform, useWindowDimensions } from 'react-native';
|
| 3 |
|
| 4 |
-
import {
|
|
|
|
|
|
|
|
|
|
| 5 |
import { View, AnimatePresence } from 'moti';
|
| 6 |
import Toast from 'react-native-toast-message';
|
| 7 |
import * as FileSystem from 'expo-file-system';
|
|
@@ -11,149 +12,13 @@ import axios from 'axios';
|
|
| 11 |
import Theme from '@/constants/theme';
|
| 12 |
import Dropdown from '@/components/dropdown';
|
| 13 |
import { CONTEXT } from '@/constants/module/context';
|
| 14 |
-
import { store_comic_cover } from '../modules/content';
|
| 15 |
import Storage from '@/constants/module/storages/storage';
|
| 16 |
import ComicStorage from '@/constants/module/storages/comic_storage';
|
| 17 |
import ImageCacheStorage from '@/constants/module/storages/image_cache_storage';
|
| 18 |
import ChapterStorage from '@/constants/module/storages/chapter_storage';
|
| 19 |
|
| 20 |
|
| 21 |
-
export const PageNavigationWidget = ({MAX_OFFSET,setPage,CONTENT}:any) =>{
|
| 22 |
-
const Dimensions = useWindowDimensions();
|
| 23 |
-
|
| 24 |
-
const {themeTypeContext, setThemeTypeContext}:any = useContext(CONTEXT)
|
| 25 |
-
const {widgetContext, setWidgetContext}:any = useContext(CONTEXT)
|
| 26 |
-
|
| 27 |
-
const [goToPage, setGoToPage] = useState("");
|
| 28 |
-
const [_feedBack, _setFeedBack] = useState("");
|
| 29 |
-
return (<View
|
| 30 |
-
style={{
|
| 31 |
-
backgroundColor:Theme[themeTypeContext].background_color,
|
| 32 |
-
maxWidth:500,
|
| 33 |
-
width:"100%",
|
| 34 |
-
|
| 35 |
-
borderColor:Theme[themeTypeContext].border_color,
|
| 36 |
-
borderWidth:2,
|
| 37 |
-
borderRadius:8,
|
| 38 |
-
padding:12,
|
| 39 |
-
display:"flex",
|
| 40 |
-
justifyContent:"center",
|
| 41 |
-
|
| 42 |
-
flexDirection:"column",
|
| 43 |
-
gap:12,
|
| 44 |
-
}}
|
| 45 |
-
from={{
|
| 46 |
-
opacity: 0,
|
| 47 |
-
scale: 0.9,
|
| 48 |
-
}}
|
| 49 |
-
animate={{
|
| 50 |
-
opacity: 1,
|
| 51 |
-
scale: 1,
|
| 52 |
-
}}
|
| 53 |
-
exit={{
|
| 54 |
-
opacity: 0,
|
| 55 |
-
scale: 0.5,
|
| 56 |
-
}}
|
| 57 |
-
transition={{
|
| 58 |
-
type: 'timing',
|
| 59 |
-
duration: 500,
|
| 60 |
-
}}
|
| 61 |
-
exitTransition={{
|
| 62 |
-
type: 'timing',
|
| 63 |
-
duration: 250,
|
| 64 |
-
}}
|
| 65 |
-
>
|
| 66 |
-
<View style={{height:"auto"}}>
|
| 67 |
-
<TextInput mode="outlined" label="Go to page" textColor={Theme[themeTypeContext].text_color} maxLength={1000000000}
|
| 68 |
-
placeholder="Go to page"
|
| 69 |
-
right={<TextInput.Affix text={`/${Math.ceil(CONTENT.chapters.length/MAX_OFFSET)}`} />}
|
| 70 |
-
style={{
|
| 71 |
-
|
| 72 |
-
backgroundColor:Theme[themeTypeContext].background_color,
|
| 73 |
-
borderColor:Theme[themeTypeContext].border_color,
|
| 74 |
-
|
| 75 |
-
}}
|
| 76 |
-
outlineColor={Theme[themeTypeContext].text_input_border_color}
|
| 77 |
-
value={goToPage}
|
| 78 |
-
onChange={(event)=>{
|
| 79 |
-
|
| 80 |
-
const value = event.nativeEvent.text
|
| 81 |
-
|
| 82 |
-
const isInt = /^-?\d+$/.test(value);
|
| 83 |
-
if (isInt || value === "") {
|
| 84 |
-
if (parseInt(value) > Math.ceil(CONTENT.chapters.length/MAX_OFFSET)){
|
| 85 |
-
_setFeedBack("Page is out of index.")
|
| 86 |
-
}else{
|
| 87 |
-
_setFeedBack("")
|
| 88 |
-
setGoToPage(value)
|
| 89 |
-
}
|
| 90 |
-
|
| 91 |
-
}
|
| 92 |
-
else _setFeedBack("Input is not a valid number.")
|
| 93 |
-
|
| 94 |
-
}}
|
| 95 |
-
/>
|
| 96 |
-
|
| 97 |
-
</View>
|
| 98 |
-
{_feedBack
|
| 99 |
-
? <Text
|
| 100 |
-
style={{
|
| 101 |
-
color:Theme[themeTypeContext].text_color,
|
| 102 |
-
fontFamily:"roboto-medium",
|
| 103 |
-
fontSize:(Dimensions.width+Dimensions.height)/2*0.02,
|
| 104 |
-
textAlign:"center",
|
| 105 |
-
}}
|
| 106 |
-
|
| 107 |
-
>{_feedBack}</Text>
|
| 108 |
-
: <></>
|
| 109 |
-
}
|
| 110 |
-
<View
|
| 111 |
-
style={{
|
| 112 |
-
display:"flex",
|
| 113 |
-
flexDirection:"row",
|
| 114 |
-
width:"100%",
|
| 115 |
-
justifyContent:"space-around",
|
| 116 |
-
alignItems:"center",
|
| 117 |
-
}}
|
| 118 |
-
>
|
| 119 |
-
<Button mode='contained'
|
| 120 |
-
labelStyle={{
|
| 121 |
-
color:Theme[themeTypeContext].text_color,
|
| 122 |
-
fontFamily:"roboto-medium",
|
| 123 |
-
fontSize:(Dimensions.width+Dimensions.height)/2*0.02
|
| 124 |
-
}}
|
| 125 |
-
style={{backgroundColor:"red",borderRadius:5}}
|
| 126 |
-
onPress={(()=>{
|
| 127 |
-
|
| 128 |
-
setWidgetContext({state:false,component:<></>})
|
| 129 |
-
|
| 130 |
-
})}
|
| 131 |
-
>Cancel</Button>
|
| 132 |
-
<Button mode='contained'
|
| 133 |
-
labelStyle={{
|
| 134 |
-
color:Theme[themeTypeContext].text_color,
|
| 135 |
-
fontFamily:"roboto-medium",
|
| 136 |
-
fontSize:(Dimensions.width+Dimensions.height)/2*0.02
|
| 137 |
-
}}
|
| 138 |
-
style={{backgroundColor:"green",borderRadius:5}}
|
| 139 |
-
onPress={(()=>{
|
| 140 |
-
const isInt = /^-?\d+$/.test(goToPage);
|
| 141 |
-
if (isInt) {
|
| 142 |
-
if (parseInt(goToPage) > Math.ceil(CONTENT.chapters.length/MAX_OFFSET) || !parseInt(goToPage)){
|
| 143 |
-
_setFeedBack("Page is out of index.")
|
| 144 |
-
}else{
|
| 145 |
-
setPage(parseInt(goToPage))
|
| 146 |
-
setWidgetContext({state:false,component:<></>})
|
| 147 |
-
}
|
| 148 |
-
|
| 149 |
-
}else _setFeedBack("Input is not a valid number.")
|
| 150 |
-
})}
|
| 151 |
-
>Go</Button>
|
| 152 |
-
</View>
|
| 153 |
-
|
| 154 |
-
</View>)
|
| 155 |
-
}
|
| 156 |
-
|
| 157 |
interface RequestChapterWidgetProps {
|
| 158 |
SOURCE: string | string[];
|
| 159 |
ID: string | string[];
|
|
@@ -165,7 +30,7 @@ interface RequestChapterWidgetProps {
|
|
| 165 |
get_requested_info: any;
|
| 166 |
}
|
| 167 |
|
| 168 |
-
|
| 169 |
SOURCE,
|
| 170 |
ID,
|
| 171 |
CHAPTER,
|
|
@@ -453,413 +318,4 @@ export const RequestChapterWidget: React.FC<RequestChapterWidgetProps> = ({
|
|
| 453 |
</View>)
|
| 454 |
}
|
| 455 |
|
| 456 |
-
|
| 457 |
-
onRefresh: any;
|
| 458 |
-
SOURCE: string | string[];
|
| 459 |
-
ID: string | string[];
|
| 460 |
-
CONTENT: any;
|
| 461 |
-
}
|
| 462 |
-
|
| 463 |
-
export const BookmarkWidget: React.FC<BookmarkWidgetProps> = ({
|
| 464 |
-
onRefresh,
|
| 465 |
-
SOURCE,
|
| 466 |
-
ID,
|
| 467 |
-
CONTENT
|
| 468 |
-
}) => {
|
| 469 |
-
const Dimensions = useWindowDimensions();
|
| 470 |
-
|
| 471 |
-
const {themeTypeContext, setThemeTypeContext}:any = useContext(CONTEXT)
|
| 472 |
-
const {widgetContext, setWidgetContext}:any = useContext(CONTEXT)
|
| 473 |
-
const {showCloudflareTurnstileContext, setShowCloudflareTurnstileContext}:any = useContext(CONTEXT)
|
| 474 |
-
const {apiBaseContext, setApiBaseContext}:any = useContext(CONTEXT)
|
| 475 |
-
|
| 476 |
-
const [BOOKMARK_DATA, SET_BOOKMARK_DATA]: any = useState(null)
|
| 477 |
-
|
| 478 |
-
const [defaultBookmark, setDefaultBookmark]:any = useState("")
|
| 479 |
-
const [bookmark, setBookmark]:any = useState("")
|
| 480 |
-
const [createBookmark, setCreateBookmark]:any = useState({state:false,title:""})
|
| 481 |
-
const [removeBookmark, setRemoveBookmark]:any = useState({state:false, removing: false})
|
| 482 |
-
|
| 483 |
-
const controller = new AbortController();
|
| 484 |
-
const signal = controller.signal;
|
| 485 |
-
|
| 486 |
-
|
| 487 |
-
useEffect(()=>{
|
| 488 |
-
(async ()=>{
|
| 489 |
-
const stored_comic = await ComicStorage.getByID(SOURCE,CONTENT.id)
|
| 490 |
-
|
| 491 |
-
if (stored_comic) {
|
| 492 |
-
setDefaultBookmark(stored_comic.tag)
|
| 493 |
-
setBookmark(stored_comic.tag)
|
| 494 |
-
}
|
| 495 |
-
|
| 496 |
-
const stored_bookmark_data = await Storage.get("bookmark") || []
|
| 497 |
-
if (stored_bookmark_data.length) {
|
| 498 |
-
const bookmark_data:Array<Object> = []
|
| 499 |
-
for (const item of stored_bookmark_data) {
|
| 500 |
-
bookmark_data.push({
|
| 501 |
-
label:item,
|
| 502 |
-
value:item,
|
| 503 |
-
})
|
| 504 |
-
}
|
| 505 |
-
|
| 506 |
-
SET_BOOKMARK_DATA(bookmark_data.sort())
|
| 507 |
-
}else SET_BOOKMARK_DATA([])
|
| 508 |
-
})()
|
| 509 |
-
return () => controller.abort();
|
| 510 |
-
},[])
|
| 511 |
-
|
| 512 |
-
return (<>{BOOKMARK_DATA !== null &&
|
| 513 |
-
|
| 514 |
-
<View key={"BookmarkWidget"}
|
| 515 |
-
style={{
|
| 516 |
-
backgroundColor:Theme[themeTypeContext].background_color,
|
| 517 |
-
maxWidth:500,
|
| 518 |
-
width:"100%",
|
| 519 |
-
|
| 520 |
-
borderColor:Theme[themeTypeContext].border_color,
|
| 521 |
-
borderWidth:2,
|
| 522 |
-
borderRadius:8,
|
| 523 |
-
padding:12,
|
| 524 |
-
display:"flex",
|
| 525 |
-
justifyContent:"center",
|
| 526 |
-
|
| 527 |
-
flexDirection:"column",
|
| 528 |
-
gap:12,
|
| 529 |
-
}}
|
| 530 |
-
from={{
|
| 531 |
-
opacity: 0,
|
| 532 |
-
scale: 0.9,
|
| 533 |
-
}}
|
| 534 |
-
animate={{
|
| 535 |
-
opacity: 1,
|
| 536 |
-
scale: 1,
|
| 537 |
-
}}
|
| 538 |
-
exit={{
|
| 539 |
-
opacity: 0,
|
| 540 |
-
scale: 0.5,
|
| 541 |
-
}}
|
| 542 |
-
transition={{
|
| 543 |
-
type: 'timing',
|
| 544 |
-
duration: 500,
|
| 545 |
-
}}
|
| 546 |
-
exitTransition={{
|
| 547 |
-
type: 'timing',
|
| 548 |
-
duration: 250,
|
| 549 |
-
}}
|
| 550 |
-
>
|
| 551 |
-
|
| 552 |
-
<>{!createBookmark.state && !removeBookmark.state &&
|
| 553 |
-
<>
|
| 554 |
-
<View
|
| 555 |
-
style={{
|
| 556 |
-
width:"100%",
|
| 557 |
-
height:"auto",
|
| 558 |
-
display:"flex",
|
| 559 |
-
flexDirection:"row",
|
| 560 |
-
alignItems:"flex-end",
|
| 561 |
-
justifyContent:"space-between",
|
| 562 |
-
gap:8,
|
| 563 |
-
}}
|
| 564 |
-
>
|
| 565 |
-
<View style={{flex:1}}>
|
| 566 |
-
<Dropdown
|
| 567 |
-
theme_type={themeTypeContext}
|
| 568 |
-
Dimensions={Dimensions}
|
| 569 |
-
|
| 570 |
-
label='Add to bookmark'
|
| 571 |
-
data={BOOKMARK_DATA}
|
| 572 |
-
value={bookmark}
|
| 573 |
-
onChange={(async (item:any) => {
|
| 574 |
-
setBookmark(item.value)
|
| 575 |
-
})}
|
| 576 |
-
/>
|
| 577 |
-
</View>
|
| 578 |
-
<>{bookmark &&
|
| 579 |
-
<TouchableRipple
|
| 580 |
-
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
| 581 |
-
style={{
|
| 582 |
-
padding:5,
|
| 583 |
-
borderRadius:5,
|
| 584 |
-
borderWidth:0,
|
| 585 |
-
backgroundColor: "transparent",
|
| 586 |
-
}}
|
| 587 |
-
onPress={(async ()=>{
|
| 588 |
-
const stored_comic = await ComicStorage.getByID(SOURCE,CONTENT.id)
|
| 589 |
-
if (stored_comic) setRemoveBookmark({...removeBookmark,state:true})
|
| 590 |
-
else setBookmark("")
|
| 591 |
-
})}
|
| 592 |
-
>
|
| 593 |
-
<Icon source={"tag-remove-outline"} size={((Dimensions.width+Dimensions.height)/2)*0.035} color={"red"}/>
|
| 594 |
-
</TouchableRipple>
|
| 595 |
-
}</>
|
| 596 |
-
</View>
|
| 597 |
-
<View
|
| 598 |
-
style={{
|
| 599 |
-
display:"flex",
|
| 600 |
-
flexDirection:"row",
|
| 601 |
-
width:"100%",
|
| 602 |
-
justifyContent:"space-around",
|
| 603 |
-
alignItems:"center",
|
| 604 |
-
}}
|
| 605 |
-
>
|
| 606 |
-
<Button mode='contained'
|
| 607 |
-
labelStyle={{
|
| 608 |
-
color:Theme[themeTypeContext].text_color,
|
| 609 |
-
fontFamily:"roboto-medium",
|
| 610 |
-
fontSize:(Dimensions.width+Dimensions.height)/2*0.02
|
| 611 |
-
}}
|
| 612 |
-
style={{backgroundColor:"blue",borderRadius:5}}
|
| 613 |
-
onPress={(()=>{
|
| 614 |
-
setCreateBookmark({state:true,title:""})
|
| 615 |
-
})}
|
| 616 |
-
>+ Create Bookmark</Button>
|
| 617 |
-
<Button mode='outlined'
|
| 618 |
-
labelStyle={{
|
| 619 |
-
color:Theme[themeTypeContext].text_color,
|
| 620 |
-
fontFamily:"roboto-medium",
|
| 621 |
-
fontSize:(Dimensions.width+Dimensions.height)/2*0.02,
|
| 622 |
-
|
| 623 |
-
|
| 624 |
-
}}
|
| 625 |
-
style={{
|
| 626 |
-
|
| 627 |
-
borderRadius:5,
|
| 628 |
-
borderWidth:2,
|
| 629 |
-
borderColor:Theme[themeTypeContext].border_color
|
| 630 |
-
}}
|
| 631 |
-
onPress={(async ()=>{
|
| 632 |
-
if (defaultBookmark !== bookmark){
|
| 633 |
-
const stored_comic = await ComicStorage.getByID(SOURCE,CONTENT.id)
|
| 634 |
-
if (stored_comic) await ComicStorage.replaceTag(SOURCE, CONTENT.id, bookmark)
|
| 635 |
-
else {
|
| 636 |
-
const cover_result:any = await store_comic_cover(setShowCloudflareTurnstileContext,signal,SOURCE,ID,CONTENT)
|
| 637 |
-
|
| 638 |
-
await ComicStorage.store(SOURCE,CONTENT.id, bookmark, {
|
| 639 |
-
cover:cover_result,
|
| 640 |
-
title:CONTENT.title,
|
| 641 |
-
author:CONTENT.author,
|
| 642 |
-
category:CONTENT.category,
|
| 643 |
-
status:CONTENT.status,
|
| 644 |
-
synopsis:CONTENT.synopsis,
|
| 645 |
-
updated:CONTENT.updated,
|
| 646 |
-
})
|
| 647 |
-
}
|
| 648 |
-
onRefresh()
|
| 649 |
-
}
|
| 650 |
-
setWidgetContext({state:false,component:<></>})
|
| 651 |
-
|
| 652 |
-
})}
|
| 653 |
-
>Done</Button>
|
| 654 |
-
</View>
|
| 655 |
-
</>
|
| 656 |
-
}</>
|
| 657 |
-
|
| 658 |
-
<>{createBookmark.state &&
|
| 659 |
-
<>
|
| 660 |
-
<View
|
| 661 |
-
style={{
|
| 662 |
-
height:"auto",
|
| 663 |
-
display:"flex",
|
| 664 |
-
flexDirection:"column",
|
| 665 |
-
gap:12,
|
| 666 |
-
}}
|
| 667 |
-
>
|
| 668 |
-
<TextInput mode="outlined" label="Create Bookmark" textColor={Theme[themeTypeContext].text_color} maxLength={72}
|
| 669 |
-
placeholder="Bookmark Tag"
|
| 670 |
-
|
| 671 |
-
right={<TextInput.Affix text={`| Max: 72`} />}
|
| 672 |
-
style={{
|
| 673 |
-
|
| 674 |
-
backgroundColor:Theme[themeTypeContext].background_color,
|
| 675 |
-
borderColor:Theme[themeTypeContext].border_color,
|
| 676 |
-
|
| 677 |
-
}}
|
| 678 |
-
outlineColor={Theme[themeTypeContext].text_input_border_color}
|
| 679 |
-
value={createBookmark.title}
|
| 680 |
-
onChange={(event)=>{
|
| 681 |
-
setCreateBookmark({...createBookmark,title:event.nativeEvent.text})
|
| 682 |
-
}}
|
| 683 |
-
/>
|
| 684 |
-
</View>
|
| 685 |
-
<View
|
| 686 |
-
style={{
|
| 687 |
-
display:"flex",
|
| 688 |
-
flexDirection:"row",
|
| 689 |
-
width:"100%",
|
| 690 |
-
justifyContent:"space-around",
|
| 691 |
-
alignItems:"center",
|
| 692 |
-
}}
|
| 693 |
-
>
|
| 694 |
-
<Button mode='outlined'
|
| 695 |
-
labelStyle={{
|
| 696 |
-
color:Theme[themeTypeContext].text_color,
|
| 697 |
-
fontFamily:"roboto-medium",
|
| 698 |
-
fontSize:(Dimensions.width+Dimensions.height)/2*0.02,
|
| 699 |
-
|
| 700 |
-
|
| 701 |
-
}}
|
| 702 |
-
style={{
|
| 703 |
-
|
| 704 |
-
borderRadius:5,
|
| 705 |
-
borderWidth:2,
|
| 706 |
-
borderColor:Theme[themeTypeContext].border_color
|
| 707 |
-
}}
|
| 708 |
-
onPress={(()=>{
|
| 709 |
-
|
| 710 |
-
setCreateBookmark({...createBookmark,state:false})
|
| 711 |
-
|
| 712 |
-
})}
|
| 713 |
-
>Cancel</Button>
|
| 714 |
-
<Button mode='contained'
|
| 715 |
-
labelStyle={{
|
| 716 |
-
color:Theme[themeTypeContext].text_color,
|
| 717 |
-
fontFamily:"roboto-medium",
|
| 718 |
-
fontSize:(Dimensions.width+Dimensions.height)/2*0.02
|
| 719 |
-
}}
|
| 720 |
-
style={{backgroundColor:"green",borderRadius:5}}
|
| 721 |
-
onPress={(async()=>{
|
| 722 |
-
|
| 723 |
-
const title = createBookmark.title
|
| 724 |
-
if (!title) return
|
| 725 |
-
|
| 726 |
-
const stored_bookmark_data = await Storage.get("bookmark") || []
|
| 727 |
-
if (stored_bookmark_data.includes(title)){
|
| 728 |
-
Toast.show({
|
| 729 |
-
type: 'error',
|
| 730 |
-
text1: '🔖 Duplicate Bookmark',
|
| 731 |
-
text2: `Tag "${title}" already existed in your bookmark.`,
|
| 732 |
-
|
| 733 |
-
position: "bottom",
|
| 734 |
-
visibilityTime: 5000,
|
| 735 |
-
text1Style:{
|
| 736 |
-
fontFamily:"roboto-bold",
|
| 737 |
-
fontSize:((Dimensions.width+Dimensions.height)/2)*0.025
|
| 738 |
-
},
|
| 739 |
-
text2Style:{
|
| 740 |
-
fontFamily:"roboto-medium",
|
| 741 |
-
fontSize:((Dimensions.width+Dimensions.height)/2)*0.0185,
|
| 742 |
-
|
| 743 |
-
},
|
| 744 |
-
});
|
| 745 |
-
}else{
|
| 746 |
-
await Storage.store("bookmark", [...stored_bookmark_data,title].sort())
|
| 747 |
-
SET_BOOKMARK_DATA([...BOOKMARK_DATA,
|
| 748 |
-
{label:title,value:title}
|
| 749 |
-
].sort())
|
| 750 |
-
setCreateBookmark({state:false,title:""})
|
| 751 |
-
Toast.show({
|
| 752 |
-
type: 'info',
|
| 753 |
-
text1: '🔖 Create Bookmark',
|
| 754 |
-
text2: `Tag "${title}" added to your bookmark.`,
|
| 755 |
-
|
| 756 |
-
position: "bottom",
|
| 757 |
-
visibilityTime: 3000,
|
| 758 |
-
text1Style:{
|
| 759 |
-
fontFamily:"roboto-bold",
|
| 760 |
-
fontSize:((Dimensions.width+Dimensions.height)/2)*0.025
|
| 761 |
-
},
|
| 762 |
-
text2Style:{
|
| 763 |
-
fontFamily:"roboto-medium",
|
| 764 |
-
fontSize:((Dimensions.width+Dimensions.height)/2)*0.0185,
|
| 765 |
-
|
| 766 |
-
},
|
| 767 |
-
});
|
| 768 |
-
}
|
| 769 |
-
})}
|
| 770 |
-
>Add</Button>
|
| 771 |
-
</View>
|
| 772 |
-
</>
|
| 773 |
-
}</>
|
| 774 |
-
<>{removeBookmark.state &&
|
| 775 |
-
<>
|
| 776 |
-
<View
|
| 777 |
-
style={{
|
| 778 |
-
height:"auto",
|
| 779 |
-
display:"flex",
|
| 780 |
-
flexDirection:"column",
|
| 781 |
-
gap:12,
|
| 782 |
-
}}
|
| 783 |
-
>
|
| 784 |
-
<Text
|
| 785 |
-
style={{
|
| 786 |
-
color:Theme[themeTypeContext].text_color,
|
| 787 |
-
fontFamily:"roboto-bold",
|
| 788 |
-
fontSize:(Dimensions.width+Dimensions.height)/2*0.03,
|
| 789 |
-
textAlign:"center",
|
| 790 |
-
}}
|
| 791 |
-
>Are you sure you want to remove this comic from bookmark?</Text>
|
| 792 |
-
|
| 793 |
-
<Text
|
| 794 |
-
style={{
|
| 795 |
-
color:Theme[themeTypeContext].text_color,
|
| 796 |
-
fontFamily:"roboto-medium",
|
| 797 |
-
fontSize:(Dimensions.width+Dimensions.height)/2*0.025,
|
| 798 |
-
textAlign:"center",
|
| 799 |
-
}}
|
| 800 |
-
>This will remove all local saved info and chapters.</Text>
|
| 801 |
-
</View>
|
| 802 |
-
|
| 803 |
-
<View
|
| 804 |
-
style={{
|
| 805 |
-
display:"flex",
|
| 806 |
-
flexDirection:"row",
|
| 807 |
-
width:"100%",
|
| 808 |
-
justifyContent:"space-around",
|
| 809 |
-
alignItems:"center",
|
| 810 |
-
}}
|
| 811 |
-
>
|
| 812 |
-
<>{removeBookmark.removing
|
| 813 |
-
? <ActivityIndicator animating={true}/>
|
| 814 |
-
:<>
|
| 815 |
-
|
| 816 |
-
<Button mode='outlined' disabled={removeBookmark.removing}
|
| 817 |
-
labelStyle={{
|
| 818 |
-
color:Theme[themeTypeContext].text_color,
|
| 819 |
-
fontFamily:"roboto-medium",
|
| 820 |
-
fontSize:(Dimensions.width+Dimensions.height)/2*0.02,
|
| 821 |
-
|
| 822 |
-
|
| 823 |
-
}}
|
| 824 |
-
style={{
|
| 825 |
-
|
| 826 |
-
borderRadius:5,
|
| 827 |
-
borderWidth:2,
|
| 828 |
-
borderColor:Theme[themeTypeContext].border_color
|
| 829 |
-
}}
|
| 830 |
-
onPress={(()=>{
|
| 831 |
-
setRemoveBookmark({...removeBookmark,state:false})
|
| 832 |
-
})}
|
| 833 |
-
>No</Button>
|
| 834 |
-
<Button mode='contained' disabled={removeBookmark.removing}
|
| 835 |
-
labelStyle={{
|
| 836 |
-
color:Theme[themeTypeContext].text_color,
|
| 837 |
-
fontFamily:"roboto-medium",
|
| 838 |
-
fontSize:(Dimensions.width+Dimensions.height)/2*0.02
|
| 839 |
-
}}
|
| 840 |
-
style={{backgroundColor:"red",borderRadius:5}}
|
| 841 |
-
onPress={(async ()=>{
|
| 842 |
-
setRemoveBookmark({...removeBookmark,removing:false})
|
| 843 |
-
if (Platform.OS !== "web"){
|
| 844 |
-
const comic_dir = FileSystem.documentDirectory + "ComicMTL/" + `${SOURCE}/` + `${ID}/`
|
| 845 |
-
await FileSystem.deleteAsync(comic_dir, { idempotent: true })
|
| 846 |
-
}
|
| 847 |
-
|
| 848 |
-
await ChapterStorage.drop(`${SOURCE}-${CONTENT.id}`)
|
| 849 |
-
await ComicStorage.removeByID(SOURCE,CONTENT.id)
|
| 850 |
-
|
| 851 |
-
onRefresh()
|
| 852 |
-
setWidgetContext({state:false,component:<></>})
|
| 853 |
-
})}
|
| 854 |
-
>Yes</Button>
|
| 855 |
-
</>
|
| 856 |
-
}</>
|
| 857 |
-
|
| 858 |
-
</View>
|
| 859 |
-
</>
|
| 860 |
-
}</>
|
| 861 |
-
|
| 862 |
-
</View>
|
| 863 |
-
|
| 864 |
-
}</>)
|
| 865 |
-
}
|
|
|
|
|
|
|
|
|
|
| 1 |
|
| 2 |
+
import React, { useEffect, useState, useCallback, useContext, useRef, Fragment } from 'react';
|
| 3 |
+
import { Platform, useWindowDimensions, ScrollView } from 'react-native';
|
| 4 |
+
|
| 5 |
+
import { Icon, MD3Colors, Button, Text, TextInput, TouchableRipple, ActivityIndicator, Menu, Divider, PaperProvider, Portal } from 'react-native-paper';
|
| 6 |
import { View, AnimatePresence } from 'moti';
|
| 7 |
import Toast from 'react-native-toast-message';
|
| 8 |
import * as FileSystem from 'expo-file-system';
|
|
|
|
| 12 |
import Theme from '@/constants/theme';
|
| 13 |
import Dropdown from '@/components/dropdown';
|
| 14 |
import { CONTEXT } from '@/constants/module/context';
|
| 15 |
+
import { store_comic_cover } from '../../modules/content';
|
| 16 |
import Storage from '@/constants/module/storages/storage';
|
| 17 |
import ComicStorage from '@/constants/module/storages/comic_storage';
|
| 18 |
import ImageCacheStorage from '@/constants/module/storages/image_cache_storage';
|
| 19 |
import ChapterStorage from '@/constants/module/storages/chapter_storage';
|
| 20 |
|
| 21 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 22 |
interface RequestChapterWidgetProps {
|
| 23 |
SOURCE: string | string[];
|
| 24 |
ID: string | string[];
|
|
|
|
| 30 |
get_requested_info: any;
|
| 31 |
}
|
| 32 |
|
| 33 |
+
const RequestChapterWidget: React.FC<RequestChapterWidgetProps> = ({
|
| 34 |
SOURCE,
|
| 35 |
ID,
|
| 36 |
CHAPTER,
|
|
|
|
| 318 |
</View>)
|
| 319 |
}
|
| 320 |
|
| 321 |
+
export default RequestChapterWidget;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/components/Image.tsx
CHANGED
|
@@ -61,6 +61,7 @@ const Image = ({source, style, onError, contentFit, transition, onLoad, onLoadEn
|
|
| 61 |
|
| 62 |
useFocusEffect(useCallback(() => {
|
| 63 |
return () => {
|
|
|
|
| 64 |
controller.abort();
|
| 65 |
};
|
| 66 |
},[]))
|
|
@@ -92,7 +93,10 @@ const Image = ({source, style, onError, contentFit, transition, onLoad, onLoadEn
|
|
| 92 |
style={style}
|
| 93 |
contentFit={contentFit}
|
| 94 |
transition={transition}
|
| 95 |
-
onLoad={
|
|
|
|
|
|
|
|
|
|
| 96 |
onLoadEnd={onLoadEnd}
|
| 97 |
/>
|
| 98 |
: <View style={{...style,display:'flex',justifyContent:"center",alignItems:"center"}}>
|
|
|
|
| 61 |
|
| 62 |
useFocusEffect(useCallback(() => {
|
| 63 |
return () => {
|
| 64 |
+
imageData.current = null
|
| 65 |
controller.abort();
|
| 66 |
};
|
| 67 |
},[]))
|
|
|
|
| 93 |
style={style}
|
| 94 |
contentFit={contentFit}
|
| 95 |
transition={transition}
|
| 96 |
+
onLoad={()=>{
|
| 97 |
+
if (onLoad) onLoad()
|
| 98 |
+
imageData.current = null
|
| 99 |
+
}}
|
| 100 |
onLoadEnd={onLoadEnd}
|
| 101 |
/>
|
| 102 |
: <View style={{...style,display:'flex',justifyContent:"center",alignItems:"center"}}>
|
frontend/components/dropdown.tsx
CHANGED
|
@@ -59,9 +59,11 @@ const __style = (Dimensions:any,theme_type:string) => StyleSheet.create({
|
|
| 59 |
elevation: 5,
|
| 60 |
},
|
| 61 |
inputSearchStyle:{
|
|
|
|
| 62 |
borderRadius:8,
|
| 63 |
color: Theme[theme_type].text_color,
|
| 64 |
fontSize: ((Dimensions.width+Dimensions.height)/2)*0.0225,
|
|
|
|
| 65 |
},
|
| 66 |
itemContainerStyle: {
|
| 67 |
borderColor: Theme[theme_type].border_color,
|
|
|
|
| 59 |
elevation: 5,
|
| 60 |
},
|
| 61 |
inputSearchStyle:{
|
| 62 |
+
borderWidth:0,
|
| 63 |
borderRadius:8,
|
| 64 |
color: Theme[theme_type].text_color,
|
| 65 |
fontSize: ((Dimensions.width+Dimensions.height)/2)*0.0225,
|
| 66 |
+
padding:0,
|
| 67 |
},
|
| 68 |
itemContainerStyle: {
|
| 69 |
borderColor: Theme[theme_type].border_color,
|
frontend/components/menu/components/menu_button.tsx
CHANGED
|
@@ -23,28 +23,7 @@ const MenuButton = ({pathname, label, icon}:any) => {
|
|
| 23 |
|
| 24 |
|
| 25 |
return (<>{style && <>
|
| 26 |
-
<View style={style.menu_button_box}
|
| 27 |
-
from={{
|
| 28 |
-
opacity: 0,
|
| 29 |
-
scale: 0.9,
|
| 30 |
-
}}
|
| 31 |
-
animate={{
|
| 32 |
-
opacity: 1,
|
| 33 |
-
scale: 1,
|
| 34 |
-
}}
|
| 35 |
-
exit={{
|
| 36 |
-
opacity: 0,
|
| 37 |
-
scale: 0.5,
|
| 38 |
-
}}
|
| 39 |
-
transition={{
|
| 40 |
-
type: 'timing',
|
| 41 |
-
duration: 500,
|
| 42 |
-
}}
|
| 43 |
-
exitTransition={{
|
| 44 |
-
type: 'timing',
|
| 45 |
-
duration: 250,
|
| 46 |
-
}}
|
| 47 |
-
>
|
| 48 |
{current_pathname === pathname
|
| 49 |
? <TouchableRipple
|
| 50 |
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
|
@@ -56,22 +35,36 @@ const MenuButton = ({pathname, label, icon}:any) => {
|
|
| 56 |
style={style.selected_menu_button}
|
| 57 |
>
|
| 58 |
|
| 59 |
-
<Icon source={icon} size={((Dimensions.width+Dimensions.height)/2)*0.
|
| 60 |
|
| 61 |
</TouchableRipple>
|
| 62 |
-
: <>
|
| 63 |
-
|
| 64 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 65 |
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 73 |
|
| 74 |
-
</
|
| 75 |
}
|
| 76 |
|
| 77 |
</View>
|
|
|
|
| 23 |
|
| 24 |
|
| 25 |
return (<>{style && <>
|
| 26 |
+
<View style={style.menu_button_box}>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 27 |
{current_pathname === pathname
|
| 28 |
? <TouchableRipple
|
| 29 |
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
|
|
|
| 35 |
style={style.selected_menu_button}
|
| 36 |
>
|
| 37 |
|
| 38 |
+
<Icon source={icon} size={((Dimensions.width+Dimensions.height)/2)*0.035} color={Theme[themeTypeContext].icon_color}/>
|
| 39 |
|
| 40 |
</TouchableRipple>
|
| 41 |
+
: <>
|
| 42 |
+
<TouchableRipple
|
| 43 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
| 44 |
+
onPress={() => {
|
| 45 |
+
|
| 46 |
+
router.push(pathname)
|
| 47 |
+
}}
|
| 48 |
|
| 49 |
+
style={style.menu_button}
|
| 50 |
+
|
| 51 |
+
>
|
| 52 |
+
<View
|
| 53 |
+
style={{
|
| 54 |
+
display:"flex",
|
| 55 |
+
alignItems:"center",
|
| 56 |
+
justifyContent:"center",
|
| 57 |
+
gap:8,
|
| 58 |
+
width:"100%",
|
| 59 |
+
height:"100%",
|
| 60 |
+
}}
|
| 61 |
+
>
|
| 62 |
+
<Icon source={icon} size={((Dimensions.width+Dimensions.height)/2)*0.035} color={Theme[themeTypeContext].icon_color}/>
|
| 63 |
+
<Text selectable={false} style={style.menu_button_text}>{label}</Text>
|
| 64 |
+
</View>
|
| 65 |
+
</TouchableRipple>
|
| 66 |
|
| 67 |
+
</>
|
| 68 |
}
|
| 69 |
|
| 70 |
</View>
|
frontend/components/menu/menu.tsx
CHANGED
|
@@ -1,40 +1,117 @@
|
|
| 1 |
-
import React, { useEffect, useState } from 'react';
|
| 2 |
-
import { Link, usePathname } from 'expo-router';
|
| 3 |
import { StyleSheet, View} from 'react-native';
|
| 4 |
import { __styles } from './stylesheet/styles';
|
| 5 |
import storage from '@/constants/module/storages/storage';
|
| 6 |
-
import { Icon, MD3Colors, Button } from 'react-native-paper';
|
| 7 |
import Theme from '@/constants/theme';
|
| 8 |
import {useWindowDimensions} from 'react-native';
|
| 9 |
import MenuButton from './components/menu_button';
|
| 10 |
|
|
|
|
|
|
|
|
|
|
| 11 |
const Menu = () => {
|
| 12 |
const [style, setStyle]:any = useState("")
|
| 13 |
-
const
|
|
|
|
|
|
|
| 14 |
const Dimensions = useWindowDimensions();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
|
| 16 |
|
| 17 |
useEffect(() => {
|
| 18 |
(async ()=>{
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
setStyle(__styles(theme_type,Dimensions))
|
| 22 |
})()
|
| 23 |
},[])
|
| 24 |
|
| 25 |
|
| 26 |
-
return (<>{style &&
|
| 27 |
-
|
| 28 |
-
|
| 29 |
-
style
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
>
|
| 31 |
-
|
| 32 |
-
<
|
| 33 |
-
|
| 34 |
-
|
| 35 |
-
|
| 36 |
-
|
| 37 |
-
|
| 38 |
|
| 39 |
</>}</>);
|
| 40 |
}
|
|
|
|
| 1 |
+
import React, { useEffect, useState, useContext, useCallback } from 'react';
|
| 2 |
+
import { Link, usePathname, useFocusEffect } from 'expo-router';
|
| 3 |
import { StyleSheet, View} from 'react-native';
|
| 4 |
import { __styles } from './stylesheet/styles';
|
| 5 |
import storage from '@/constants/module/storages/storage';
|
| 6 |
+
import { Icon, MD3Colors, Button, Text, TouchableRipple } from 'react-native-paper';
|
| 7 |
import Theme from '@/constants/theme';
|
| 8 |
import {useWindowDimensions} from 'react-native';
|
| 9 |
import MenuButton from './components/menu_button';
|
| 10 |
|
| 11 |
+
import { CONTEXT } from '@/constants/module/context';
|
| 12 |
+
import Storage from '@/constants/module/storages/storage';
|
| 13 |
+
|
| 14 |
const Menu = () => {
|
| 15 |
const [style, setStyle]:any = useState("")
|
| 16 |
+
const {themeTypeContext, setThemeTypeContext}:any = useContext(CONTEXT)
|
| 17 |
+
const [showMenuContext,setShowMenuContext]:any = useState(true)
|
| 18 |
+
|
| 19 |
const Dimensions = useWindowDimensions();
|
| 20 |
+
|
| 21 |
+
useFocusEffect(useCallback(() => {
|
| 22 |
+
(async ()=>{
|
| 23 |
+
const MENU_STATE = await Storage.get("MENU_STATE")
|
| 24 |
+
if (MENU_STATE === null || MENU_STATE === undefined) setShowMenuContext(true)
|
| 25 |
+
else setShowMenuContext(MENU_STATE)
|
| 26 |
+
|
| 27 |
+
})()
|
| 28 |
+
return () => {
|
| 29 |
+
|
| 30 |
+
}
|
| 31 |
+
},[]))
|
| 32 |
|
| 33 |
|
| 34 |
useEffect(() => {
|
| 35 |
(async ()=>{
|
| 36 |
+
console.log(showMenuContext)
|
| 37 |
+
setStyle(__styles(themeTypeContext,Dimensions))
|
|
|
|
| 38 |
})()
|
| 39 |
},[])
|
| 40 |
|
| 41 |
|
| 42 |
+
return (<>{style && <>
|
| 43 |
+
<View
|
| 44 |
+
style={{
|
| 45 |
+
...style.menu_container,
|
| 46 |
+
position: showMenuContext ? "relative" : "absolute",
|
| 47 |
+
height: showMenuContext ? "100%" : "auto",
|
| 48 |
+
backgroundColor: showMenuContext? Theme[themeTypeContext].background_color : "transparent",
|
| 49 |
+
marginBottom: showMenuContext ? 0 : Dimensions.height*0.015,
|
| 50 |
+
borderRightWidth: showMenuContext ? 0.5 : 0,
|
| 51 |
+
|
| 52 |
+
}}
|
| 53 |
+
>
|
| 54 |
+
|
| 55 |
+
<>{showMenuContext &&
|
| 56 |
+
<>
|
| 57 |
+
<View
|
| 58 |
+
style={{
|
| 59 |
+
paddingVertical: showMenuContext ? 12 : 18,
|
| 60 |
+
width:"100%",
|
| 61 |
+
height:"auto",
|
| 62 |
+
display:"flex",
|
| 63 |
+
justifyContent:"center",
|
| 64 |
+
alignItems:"center",
|
| 65 |
+
borderBottomWidth: 0.5,
|
| 66 |
+
borderColor: Theme[themeTypeContext].border_color,
|
| 67 |
+
}}
|
| 68 |
+
>
|
| 69 |
+
|
| 70 |
+
<Icon source={"menu-open"} size={((Dimensions.width+Dimensions.height)/2)*0.04} color={Theme[themeTypeContext].icon_color}/>
|
| 71 |
+
</View>
|
| 72 |
+
<View
|
| 73 |
+
style={style.menu_box}
|
| 74 |
+
>
|
| 75 |
+
<MenuButton pathname="/recent" label="Recent" icon="history"/>
|
| 76 |
+
<MenuButton pathname="/bookmark" label="Bookmark" icon="bookmark"/>
|
| 77 |
+
<MenuButton pathname="/explore" label="Explore" icon="compass"/>
|
| 78 |
+
<MenuButton pathname="/setting" label="Setting" icon="cog"/>
|
| 79 |
+
</View>
|
| 80 |
+
</>
|
| 81 |
+
}</>
|
| 82 |
+
|
| 83 |
+
<TouchableRipple
|
| 84 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
| 85 |
+
onPress={async () => {
|
| 86 |
+
await Storage.store("MENU_STATE", !showMenuContext)
|
| 87 |
+
setShowMenuContext(!showMenuContext)
|
| 88 |
+
}}
|
| 89 |
+
|
| 90 |
+
style={{
|
| 91 |
+
backgroundColor: showMenuContext ? Theme[themeTypeContext].background_color : Theme[themeTypeContext].button_color,
|
| 92 |
+
borderTopRightRadius: showMenuContext ? 0 : ((Dimensions.height+Dimensions.width)/2)*0.015,
|
| 93 |
+
borderBottomRightRadius: showMenuContext ? 0 : ((Dimensions.height+Dimensions.width)/2)*0.015,
|
| 94 |
+
padding:8,
|
| 95 |
+
height:"auto",
|
| 96 |
+
width:"auto",
|
| 97 |
+
paddingVertical: showMenuContext ? 12 : 18,
|
| 98 |
+
justifyContent:"center",
|
| 99 |
+
alignItems:"center",
|
| 100 |
+
|
| 101 |
+
borderRightWidth: showMenuContext ? 0 : 0.5,
|
| 102 |
+
borderBottomWidth: showMenuContext ? 0 : 0.5,
|
| 103 |
+
borderTopWidth: showMenuContext ? 0.5 : 0.5,
|
| 104 |
+
|
| 105 |
+
borderColor: Theme[themeTypeContext].border_color,
|
| 106 |
+
}}
|
| 107 |
>
|
| 108 |
+
|
| 109 |
+
<Icon source={showMenuContext ? "chevron-left" : "chevron-right"} size={((Dimensions.width+Dimensions.height)/2)*0.0375} color={Theme[themeTypeContext].icon_color}/>
|
| 110 |
+
|
| 111 |
+
</TouchableRipple>
|
| 112 |
+
|
| 113 |
+
</View>
|
| 114 |
+
|
| 115 |
|
| 116 |
</>}</>);
|
| 117 |
}
|
frontend/components/menu/stylesheet/styles.tsx
CHANGED
|
@@ -4,18 +4,24 @@ import Theme from "@/constants/theme";
|
|
| 4 |
export const __styles:any = (theme_type:string,Dimensions:any) => {
|
| 5 |
return StyleSheet.create({
|
| 6 |
menu_container: {
|
| 7 |
-
position: "absolute",
|
| 8 |
bottom: 0,
|
|
|
|
| 9 |
display: "flex",
|
| 10 |
-
flexDirection: "
|
| 11 |
-
justifyContent: "space-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
backgroundColor: Theme[theme_type].background_color,
|
| 15 |
-
padding: 8,
|
| 16 |
-
borderTopWidth: 0.5,
|
| 17 |
borderColor: Theme[theme_type].border_color,
|
| 18 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
menu_button_box:{
|
| 20 |
display:"flex",
|
| 21 |
alignItems:"center",
|
|
@@ -36,7 +42,8 @@ export const __styles:any = (theme_type:string,Dimensions:any) => {
|
|
| 36 |
|
| 37 |
menu_button_text: {
|
| 38 |
color: Theme[theme_type].text_color,
|
| 39 |
-
fontSize: ((Dimensions.width+Dimensions.height)/2)*0.
|
|
|
|
| 40 |
height:"auto",
|
| 41 |
}
|
| 42 |
})}
|
|
|
|
| 4 |
export const __styles:any = (theme_type:string,Dimensions:any) => {
|
| 5 |
return StyleSheet.create({
|
| 6 |
menu_container: {
|
|
|
|
| 7 |
bottom: 0,
|
| 8 |
+
left:0,
|
| 9 |
display: "flex",
|
| 10 |
+
flexDirection: "column",
|
| 11 |
+
justifyContent: "space-between",
|
| 12 |
+
height:"auto",
|
| 13 |
+
|
|
|
|
|
|
|
|
|
|
| 14 |
borderColor: Theme[theme_type].border_color,
|
| 15 |
},
|
| 16 |
+
menu_box:{
|
| 17 |
+
flex:1,
|
| 18 |
+
display:"flex",
|
| 19 |
+
flexDirection:"column",
|
| 20 |
+
gap: 18,
|
| 21 |
+
alignItems:"center",
|
| 22 |
+
paddingHorizontal: Dimensions.width*0.005,
|
| 23 |
+
paddingVertical: 12,
|
| 24 |
+
},
|
| 25 |
menu_button_box:{
|
| 26 |
display:"flex",
|
| 27 |
alignItems:"center",
|
|
|
|
| 42 |
|
| 43 |
menu_button_text: {
|
| 44 |
color: Theme[theme_type].text_color,
|
| 45 |
+
fontSize: ((Dimensions.width+Dimensions.height)/2)*0.02,
|
| 46 |
+
fontFamily: "roboto-light",
|
| 47 |
height:"auto",
|
| 48 |
}
|
| 49 |
})}
|
frontend/components/navigation/TabBarIcon.tsx
DELETED
|
@@ -1,9 +0,0 @@
|
|
| 1 |
-
// You can explore the built-in icon families and icons on the web at https://icons.expo.fyi/
|
| 2 |
-
|
| 3 |
-
import Ionicons from '@expo/vector-icons/Ionicons';
|
| 4 |
-
import { type IconProps } from '@expo/vector-icons/build/createIconSet';
|
| 5 |
-
import { type ComponentProps } from 'react';
|
| 6 |
-
|
| 7 |
-
export function TabBarIcon({ style, ...rest }: IconProps<ComponentProps<typeof Ionicons>['name']>) {
|
| 8 |
-
return <Ionicons size={28} style={[{ marginBottom: -3 }, style]} {...rest} />;
|
| 9 |
-
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
frontend/constants/module/storages/chapter_data_storage.tsx
CHANGED
|
@@ -9,10 +9,13 @@ class Chapter_Data_Storage_Web {
|
|
| 9 |
private static getDB(): Promise<IDBDatabase> {
|
| 10 |
if (!this.dbPromise) {
|
| 11 |
this.dbPromise = new Promise((resolve, reject) => {
|
| 12 |
-
const request = indexedDB.open(DATABASE_NAME,
|
| 13 |
|
| 14 |
request.onupgradeneeded = (event) => {
|
| 15 |
const db = (event.target as IDBOpenDBRequest).result;
|
|
|
|
|
|
|
|
|
|
| 16 |
const store = db.createObjectStore('dataStore', { keyPath: 'id' });
|
| 17 |
store.createIndex('comic_id', 'comic_id', { unique: false });
|
| 18 |
store.createIndex('chapter_idx', 'chapter_idx', { unique: false });
|
|
@@ -87,24 +90,24 @@ class Chapter_Data_Storage_Web {
|
|
| 87 |
});
|
| 88 |
}
|
| 89 |
|
| 90 |
-
static async removeByComicID(comic_id:
|
| 91 |
const db = await this.getDB();
|
| 92 |
return new Promise((resolve, reject) => {
|
| 93 |
const transaction = db.transaction('dataStore', 'readwrite');
|
| 94 |
const store = transaction.objectStore('dataStore');
|
| 95 |
const index = store.index('comic_id');
|
| 96 |
-
const request = index.openCursor(comic_id);
|
| 97 |
-
|
| 98 |
request.onsuccess = (event) => {
|
| 99 |
const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result;
|
| 100 |
if (cursor) {
|
| 101 |
-
|
| 102 |
-
|
| 103 |
} else {
|
| 104 |
-
|
| 105 |
}
|
| 106 |
};
|
| 107 |
-
|
| 108 |
request.onerror = () => {
|
| 109 |
reject(request.error);
|
| 110 |
};
|
|
|
|
| 9 |
private static getDB(): Promise<IDBDatabase> {
|
| 10 |
if (!this.dbPromise) {
|
| 11 |
this.dbPromise = new Promise((resolve, reject) => {
|
| 12 |
+
const request = indexedDB.open(DATABASE_NAME, 3);
|
| 13 |
|
| 14 |
request.onupgradeneeded = (event) => {
|
| 15 |
const db = (event.target as IDBOpenDBRequest).result;
|
| 16 |
+
|
| 17 |
+
if (db.objectStoreNames.contains('dataStore')) db.deleteObjectStore('dataStore');
|
| 18 |
+
|
| 19 |
const store = db.createObjectStore('dataStore', { keyPath: 'id' });
|
| 20 |
store.createIndex('comic_id', 'comic_id', { unique: false });
|
| 21 |
store.createIndex('chapter_idx', 'chapter_idx', { unique: false });
|
|
|
|
| 90 |
});
|
| 91 |
}
|
| 92 |
|
| 93 |
+
public static async removeByComicID(comic_id:string): Promise<void> {
|
| 94 |
const db = await this.getDB();
|
| 95 |
return new Promise((resolve, reject) => {
|
| 96 |
const transaction = db.transaction('dataStore', 'readwrite');
|
| 97 |
const store = transaction.objectStore('dataStore');
|
| 98 |
const index = store.index('comic_id');
|
| 99 |
+
const request = index.openCursor(IDBKeyRange.only(comic_id));
|
| 100 |
+
|
| 101 |
request.onsuccess = (event) => {
|
| 102 |
const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result;
|
| 103 |
if (cursor) {
|
| 104 |
+
store.delete(cursor.primaryKey);
|
| 105 |
+
cursor.continue();
|
| 106 |
} else {
|
| 107 |
+
resolve();
|
| 108 |
}
|
| 109 |
};
|
| 110 |
+
|
| 111 |
request.onerror = () => {
|
| 112 |
reject(request.error);
|
| 113 |
};
|
frontend/constants/module/storages/chapter_storage.tsx
CHANGED
|
@@ -8,7 +8,7 @@ import { ensure_safe_table_name } from "../ensure_safe_table_name";
|
|
| 8 |
const DATABASE_NAME = 'ChapterDB';
|
| 9 |
|
| 10 |
class Chapter_Storage_Web {
|
| 11 |
-
private static DATABASE_VERSION: number =
|
| 12 |
|
| 13 |
private static async openDB(): Promise<IDBDatabase> {
|
| 14 |
return new Promise((resolve, reject) => {
|
|
@@ -16,11 +16,13 @@ class Chapter_Storage_Web {
|
|
| 16 |
|
| 17 |
request.onupgradeneeded = (event) => {
|
| 18 |
const db = (event.target as IDBOpenDBRequest).result;
|
| 19 |
-
|
| 20 |
-
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
}
|
|
|
|
|
|
|
| 24 |
};
|
| 25 |
|
| 26 |
request.onsuccess = () => {
|
|
|
|
| 8 |
const DATABASE_NAME = 'ChapterDB';
|
| 9 |
|
| 10 |
class Chapter_Storage_Web {
|
| 11 |
+
private static DATABASE_VERSION: number = 3;
|
| 12 |
|
| 13 |
private static async openDB(): Promise<IDBDatabase> {
|
| 14 |
return new Promise((resolve, reject) => {
|
|
|
|
| 16 |
|
| 17 |
request.onupgradeneeded = (event) => {
|
| 18 |
const db = (event.target as IDBOpenDBRequest).result;
|
| 19 |
+
|
| 20 |
+
if (db.objectStoreNames.contains('dataStore')) db.deleteObjectStore('dataStore');
|
| 21 |
+
|
| 22 |
+
const store = db.createObjectStore('dataStore', { keyPath: 'id' });
|
| 23 |
+
store.createIndex('item', 'item', { unique: false });
|
| 24 |
+
store.createIndex('idx', 'idx', { unique: false });
|
| 25 |
+
|
| 26 |
};
|
| 27 |
|
| 28 |
request.onsuccess = () => {
|
frontend/constants/module/storages/comic_storage.tsx
CHANGED
|
@@ -1,5 +1,7 @@
|
|
| 1 |
import { Platform } from "react-native";
|
| 2 |
import * as SQLite from 'expo-sqlite';
|
|
|
|
|
|
|
| 3 |
|
| 4 |
const DATABASE_NAME = 'ComicStorageDB'
|
| 5 |
|
|
@@ -9,10 +11,11 @@ class Comic_Storage_Web {
|
|
| 9 |
private static getDB(): Promise<IDBDatabase> {
|
| 10 |
if (!this.dbPromise) {
|
| 11 |
this.dbPromise = new Promise((resolve, reject) => {
|
| 12 |
-
const request = indexedDB.open(DATABASE_NAME,
|
| 13 |
|
| 14 |
request.onupgradeneeded = (event) => {
|
| 15 |
const db = (event.target as IDBOpenDBRequest).result;
|
|
|
|
| 16 |
const store = db.createObjectStore('dataStore', { keyPath: 'id' });
|
| 17 |
store.createIndex('tag', 'tag', { unique: false });
|
| 18 |
store.createIndex('source', 'source', { unique: false });
|
|
@@ -256,8 +259,17 @@ class Comic_Storage_Web {
|
|
| 256 |
request.onsuccess = (event) => {
|
| 257 |
const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result;
|
| 258 |
if (cursor) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 259 |
cursor.delete();
|
| 260 |
cursor.continue();
|
|
|
|
| 261 |
} else {
|
| 262 |
resolve();
|
| 263 |
}
|
|
|
|
| 1 |
import { Platform } from "react-native";
|
| 2 |
import * as SQLite from 'expo-sqlite';
|
| 3 |
+
import ChapterStorage from "./chapter_storage";
|
| 4 |
+
import ChapterDataStorage from "./chapter_data_storage";
|
| 5 |
|
| 6 |
const DATABASE_NAME = 'ComicStorageDB'
|
| 7 |
|
|
|
|
| 11 |
private static getDB(): Promise<IDBDatabase> {
|
| 12 |
if (!this.dbPromise) {
|
| 13 |
this.dbPromise = new Promise((resolve, reject) => {
|
| 14 |
+
const request = indexedDB.open(DATABASE_NAME, 3);
|
| 15 |
|
| 16 |
request.onupgradeneeded = (event) => {
|
| 17 |
const db = (event.target as IDBOpenDBRequest).result;
|
| 18 |
+
if (db.objectStoreNames.contains('dataStore')) db.deleteObjectStore('dataStore');
|
| 19 |
const store = db.createObjectStore('dataStore', { keyPath: 'id' });
|
| 20 |
store.createIndex('tag', 'tag', { unique: false });
|
| 21 |
store.createIndex('source', 'source', { unique: false });
|
|
|
|
| 259 |
request.onsuccess = (event) => {
|
| 260 |
const cursor = (event.target as IDBRequest<IDBCursorWithValue>).result;
|
| 261 |
if (cursor) {
|
| 262 |
+
|
| 263 |
+
const data = cursor.value;
|
| 264 |
+
const source = data.source
|
| 265 |
+
const comic_id = data.id;
|
| 266 |
+
|
| 267 |
+
ChapterStorage.drop(`${source}-${comic_id}`),
|
| 268 |
+
ChapterDataStorage.removeByComicID(comic_id)
|
| 269 |
+
|
| 270 |
cursor.delete();
|
| 271 |
cursor.continue();
|
| 272 |
+
|
| 273 |
} else {
|
| 274 |
resolve();
|
| 275 |
}
|
frontend/constants/module/storages/image_cache_storage.tsx
CHANGED
|
@@ -25,13 +25,13 @@ class ImageStorage_Web {
|
|
| 25 |
// Initialize the database
|
| 26 |
private static async initDB(): Promise<IDBDatabase> {
|
| 27 |
return new Promise((resolve, reject) => {
|
| 28 |
-
const request = indexedDB.open(DATABASE_NAME,
|
| 29 |
|
| 30 |
request.onupgradeneeded = (event: IDBVersionChangeEvent) => {
|
| 31 |
const db = (event.target as IDBOpenDBRequest).result;
|
| 32 |
-
if (
|
| 33 |
-
|
| 34 |
-
|
| 35 |
};
|
| 36 |
|
| 37 |
request.onsuccess = () => {
|
|
|
|
| 25 |
// Initialize the database
|
| 26 |
private static async initDB(): Promise<IDBDatabase> {
|
| 27 |
return new Promise((resolve, reject) => {
|
| 28 |
+
const request = indexedDB.open(DATABASE_NAME, 3);
|
| 29 |
|
| 30 |
request.onupgradeneeded = (event: IDBVersionChangeEvent) => {
|
| 31 |
const db = (event.target as IDBOpenDBRequest).result;
|
| 32 |
+
if (db.objectStoreNames.contains('images')) db.deleteObjectStore('images');
|
| 33 |
+
db.createObjectStore('images', { keyPath: 'link' });
|
| 34 |
+
|
| 35 |
};
|
| 36 |
|
| 37 |
request.onsuccess = () => {
|
frontend/constants/module/storages/storage.tsx
CHANGED
|
@@ -9,10 +9,11 @@ class Storage_Web {
|
|
| 9 |
private static getDB(): Promise<IDBDatabase> {
|
| 10 |
if (!this.dbPromise) {
|
| 11 |
this.dbPromise = new Promise((resolve, reject) => {
|
| 12 |
-
const request = indexedDB.open(DATABASE_NAME,
|
| 13 |
|
| 14 |
request.onupgradeneeded = (event) => {
|
| 15 |
const db = (event.target as IDBOpenDBRequest).result;
|
|
|
|
| 16 |
db.createObjectStore('dataStore');
|
| 17 |
};
|
| 18 |
|
|
|
|
| 9 |
private static getDB(): Promise<IDBDatabase> {
|
| 10 |
if (!this.dbPromise) {
|
| 11 |
this.dbPromise = new Promise((resolve, reject) => {
|
| 12 |
+
const request = indexedDB.open(DATABASE_NAME, 2);
|
| 13 |
|
| 14 |
request.onupgradeneeded = (event) => {
|
| 15 |
const db = (event.target as IDBOpenDBRequest).result;
|
| 16 |
+
if (db.objectStoreNames.contains('dataStore')) db.deleteObjectStore('dataStore');
|
| 17 |
db.createObjectStore('dataStore');
|
| 18 |
};
|
| 19 |
|