Spaces:
Running
Running
BloodyInside
commited on
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 '
|
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 |
|