Spaces:
Running
Running
Commit
·
cd23862
1
Parent(s):
0ab8c90
12-1-2024
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- backend/__pycache__/urls.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/__pycache__/web_scrape.cpython-312.pyc +0 -0
- backend/api/queue.py +2 -1
- backend/api/stream_file.py +2 -1
- backend/api/{web_scrap.py → web_scrape.py} +69 -16
- backend/invoke_worker/__pycache__/chapter_queue.cpython-312.pyc +0 -0
- backend/invoke_worker/chapter_queue.py +119 -82
- backend/migrations/0001_initial.py +3 -3
- backend/migrations/0002_remove_requestcache_room.py +0 -17
- backend/migrations/0002_webscrapegetcovercache.py +24 -0
- backend/migrations/__pycache__/0001_initial.cpython-312.pyc +0 -0
- backend/migrations/__pycache__/0002_remove_requestcache_room.cpython-312.pyc +0 -0
- backend/migrations/__pycache__/0002_webscrapegetcovercache.cpython-312.pyc +0 -0
- backend/models/__pycache__/model_1.cpython-312.pyc +0 -0
- backend/models/__pycache__/model_cache.cpython-312.pyc +0 -0
- backend/models/model_1.py +24 -4
- backend/models/model_cache.py +0 -12
- backend/module/utils/__pycache__/manage_image.cpython-312.pyc +0 -0
- backend/module/utils/manage_image.py +52 -1
- backend/module/web_scrap/__pycache__/utils.cpython-312.pyc +0 -0
- backend/module/web_scrap/utils.py +0 -1
- backend/urls.py +4 -5
- core/__pycache__/settings.cpython-312.pyc +0 -0
- core/settings.py +21 -32
- frontend/app/_layout.tsx +11 -3
- frontend/app/bookmark/_layout.tsx +19 -0
- frontend/app/bookmark/components/bookmark_component.tsx +87 -0
- frontend/app/bookmark/components/comic_component.tsx +85 -0
- frontend/app/bookmark/components/widgets/bookmark.tsx +904 -0
- frontend/app/bookmark/index.tsx +291 -0
- frontend/app/bookmark/stylesheet/styles.tsx +68 -0
- frontend/app/explore/index.tsx +4 -4
- frontend/app/explore/stylesheet/{show_list_styles.tsx → styles.tsx} +0 -0
- frontend/app/index.tsx +1 -1
- frontend/app/read/[source]/[comic_id]/[chapter_idx].tsx +33 -57
- frontend/app/read/_layout.tsx +0 -1
- frontend/app/read/components/chapter_image.tsx +29 -6
- frontend/app/read/components/disqus.tsx +1 -6
- frontend/app/recent/_layout.tsx +19 -0
- frontend/app/recent/components/comic_component.tsx +194 -0
- frontend/app/recent/components/widgets/bookmark.tsx +904 -0
- frontend/app/recent/index.tsx +151 -0
- frontend/app/recent/stylesheet/styles.tsx +63 -0
- frontend/app/view/[source]/[comic_id].tsx +131 -115
- frontend/app/view/componenets/chapter.tsx +213 -137
- frontend/app/view/componenets/widgets/bookmark.tsx +195 -187
- frontend/app/view/componenets/widgets/request_chapter.tsx +6 -14
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__/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/__pycache__/web_scrape.cpython-312.pyc
ADDED
Binary file (7.98 kB). View file
|
|
backend/api/queue.py
CHANGED
@@ -9,7 +9,8 @@ from asgiref.sync import sync_to_async
|
|
9 |
|
10 |
from backend.module import web_scrap
|
11 |
from backend.module.utils import manage_image
|
12 |
-
from backend.models.model_cache import SocketRequestChapterQueueCache
|
|
|
13 |
from core.settings import BASE_DIR
|
14 |
from backend.module.utils import cloudflare_turnstile
|
15 |
|
|
|
9 |
|
10 |
from backend.module import web_scrap
|
11 |
from backend.module.utils import manage_image
|
12 |
+
from backend.models.model_cache import SocketRequestChapterQueueCache
|
13 |
+
from backend.models.model_1 import ComicStorageCache
|
14 |
from core.settings import BASE_DIR
|
15 |
from backend.module.utils import cloudflare_turnstile
|
16 |
|
backend/api/stream_file.py
CHANGED
@@ -3,7 +3,8 @@ from core.settings import BASE_DIR
|
|
3 |
from django_ratelimit.decorators import ratelimit
|
4 |
from django.views.decorators.csrf import csrf_exempt
|
5 |
from backend.module.utils import cloudflare_turnstile
|
6 |
-
from backend.models.model_cache import SocketRequestChapterQueueCache
|
|
|
7 |
|
8 |
import os, json, sys
|
9 |
|
|
|
3 |
from django_ratelimit.decorators import ratelimit
|
4 |
from django.views.decorators.csrf import csrf_exempt
|
5 |
from backend.module.utils import cloudflare_turnstile
|
6 |
+
from backend.models.model_cache import SocketRequestChapterQueueCache
|
7 |
+
from backend.models.model_1 import ComicStorageCache
|
8 |
|
9 |
import os, json, sys
|
10 |
|
backend/api/{web_scrap.py → web_scrape.py}
RENAMED
@@ -1,8 +1,8 @@
|
|
1 |
|
2 |
import json, environ, requests, os, subprocess
|
3 |
-
import asyncio, uuid
|
4 |
|
5 |
-
from django.http import HttpResponse, JsonResponse, HttpResponseBadRequest
|
6 |
from django_ratelimit.decorators import ratelimit
|
7 |
from django.views.decorators.csrf import csrf_exempt
|
8 |
from asgiref.sync import sync_to_async
|
@@ -12,10 +12,15 @@ from backend.module.utils import manage_image
|
|
12 |
from backend.models.model_cache import RequestCache
|
13 |
from core.settings import BASE_DIR
|
14 |
from backend.module.utils import cloudflare_turnstile
|
|
|
|
|
|
|
15 |
|
16 |
|
17 |
env = environ.Env()
|
18 |
|
|
|
|
|
19 |
|
20 |
@csrf_exempt
|
21 |
@ratelimit(key='ip', rate='20/m')
|
@@ -29,22 +34,17 @@ def get_list(request):
|
|
29 |
page = payload.get("page")
|
30 |
source = payload.get("source")
|
31 |
|
|
|
|
|
|
|
|
|
|
|
32 |
if search.get("text"): DATA = web_scrap.source_control[source].search.scrap(search=search,page=page)
|
33 |
else: DATA = web_scrap.source_control["colamanga"].get_list.scrap(page=page)
|
34 |
|
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:
|
42 |
-
DATA = web_scrap.source_control["colamanga"].search.scrap(search="妖")
|
43 |
-
return JsonResponse({"data":DATA})
|
44 |
-
except Exception as e:
|
45 |
-
return HttpResponseBadRequest(str(e), status=500)
|
46 |
-
|
47 |
-
|
48 |
|
49 |
@csrf_exempt
|
50 |
@ratelimit(key='ip', rate='20/m')
|
@@ -70,11 +70,64 @@ def get_cover(request,source,id,cover_id):
|
|
70 |
token = request.META.get('HTTP_X_CLOUDFLARE_TURNSTILE_TOKEN')
|
71 |
if not cloudflare_turnstile.check(token): return HttpResponseBadRequest('Cloudflare turnstile token not existed or expired!', status=511)
|
72 |
|
|
|
|
|
|
|
|
|
|
|
73 |
try:
|
74 |
-
|
75 |
-
if
|
76 |
-
|
77 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
78 |
return response
|
79 |
except Exception as e:
|
80 |
return HttpResponseBadRequest(str(e), status=500)
|
|
|
1 |
|
2 |
import json, environ, requests, os, subprocess
|
3 |
+
import asyncio, uuid, shutil
|
4 |
|
5 |
+
from django.http import HttpResponse, JsonResponse, HttpResponseBadRequest, StreamingHttpResponse
|
6 |
from django_ratelimit.decorators import ratelimit
|
7 |
from django.views.decorators.csrf import csrf_exempt
|
8 |
from asgiref.sync import sync_to_async
|
|
|
12 |
from backend.models.model_cache import RequestCache
|
13 |
from core.settings import BASE_DIR
|
14 |
from backend.module.utils import cloudflare_turnstile
|
15 |
+
from backend.models.model_1 import WebscrapeGetCoverCache
|
16 |
+
|
17 |
+
from backend.module.utils import directory_info, date_utils
|
18 |
|
19 |
|
20 |
env = environ.Env()
|
21 |
|
22 |
+
STORAGE_DIR = os.path.join(BASE_DIR,"storage")
|
23 |
+
if not os.path.exists(STORAGE_DIR): os.makedirs(STORAGE_DIR)
|
24 |
|
25 |
@csrf_exempt
|
26 |
@ratelimit(key='ip', rate='20/m')
|
|
|
34 |
page = payload.get("page")
|
35 |
source = payload.get("source")
|
36 |
|
37 |
+
|
38 |
+
|
39 |
+
|
40 |
+
|
41 |
+
|
42 |
if search.get("text"): DATA = web_scrap.source_control[source].search.scrap(search=search,page=page)
|
43 |
else: DATA = web_scrap.source_control["colamanga"].get_list.scrap(page=page)
|
44 |
|
45 |
return JsonResponse({"data":DATA})
|
46 |
|
47 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
48 |
|
49 |
@csrf_exempt
|
50 |
@ratelimit(key='ip', rate='20/m')
|
|
|
70 |
token = request.META.get('HTTP_X_CLOUDFLARE_TURNSTILE_TOKEN')
|
71 |
if not cloudflare_turnstile.check(token): return HttpResponseBadRequest('Cloudflare turnstile token not existed or expired!', status=511)
|
72 |
|
73 |
+
file_path = ""
|
74 |
+
file_name = ""
|
75 |
+
chunk_size = 8192
|
76 |
+
MAX_COVER_STORAGE_SIZE = 10 * 1024 * 1024 * 1024
|
77 |
+
|
78 |
try:
|
79 |
+
query_result = WebscrapeGetCoverCache.objects.filter(source=source,comic_id=id,cover_id=cover_id).first()
|
80 |
+
if (
|
81 |
+
query_result
|
82 |
+
and os.path.exists(query_result.file_path)
|
83 |
+
and query_result.datetime >= date_utils.utc_time().add(-5,'hour').get()
|
84 |
+
):
|
85 |
+
file_path = query_result.file_path
|
86 |
+
file_name = os.path.basename(file_path)
|
87 |
+
|
88 |
+
else:
|
89 |
+
if not os.path.exists(os.path.join(STORAGE_DIR,"covers")): os.makedirs(os.path.join(STORAGE_DIR,"covers"))
|
90 |
+
|
91 |
+
while True:
|
92 |
+
storage_size = directory_info.GetDirectorySize(directory=os.path.join(STORAGE_DIR,"covers"),max_threads=5)
|
93 |
+
if (storage_size >= MAX_COVER_STORAGE_SIZE):
|
94 |
+
query_result = WebscrapeGetCoverCache.objects.order_by("datetime").first()
|
95 |
+
if (query_result):
|
96 |
+
file_path = query_result.file_path
|
97 |
+
if os.path.exists(file_path): shutil.rmtree(file_path)
|
98 |
+
WebscrapeGetCoverCache.objects.filter(file_path=query_result.file_path).delete()
|
99 |
+
else:
|
100 |
+
shutil.rmtree(os.path.join(STORAGE_DIR,"covers"))
|
101 |
+
break
|
102 |
+
else: break
|
103 |
+
print(storage_size)
|
104 |
+
|
105 |
+
DATA = web_scrap.source_control[source].get_cover.scrap(id=id,cover_id=cover_id)
|
106 |
+
if not DATA: HttpResponseBadRequest('Image Not found!', status=404)
|
107 |
+
|
108 |
+
file_path = os.path.join(STORAGE_DIR,"covers",f'{source}-{id}-{cover_id}.png')
|
109 |
+
file_name = os.path.basename(file_path)
|
110 |
+
|
111 |
+
with open(file_path, "wb") as f: f.write(DATA)
|
112 |
+
|
113 |
+
WebscrapeGetCoverCache(
|
114 |
+
file_path=file_path,
|
115 |
+
source=source,
|
116 |
+
comic_id=id,
|
117 |
+
cover_id=cover_id,
|
118 |
+
).save()
|
119 |
+
|
120 |
+
|
121 |
+
|
122 |
+
def file_iterator():
|
123 |
+
with open(file_path, 'rb') as f:
|
124 |
+
while chunk := f.read(chunk_size):
|
125 |
+
yield chunk
|
126 |
+
|
127 |
+
response = StreamingHttpResponse(file_iterator())
|
128 |
+
response['Content-Type'] = 'application/octet-stream'
|
129 |
+
response['Content-Length'] = os.path.getsize(file_path)
|
130 |
+
response['Content-Disposition'] = f'attachment; filename="{file_name}"'
|
131 |
return response
|
132 |
except Exception as e:
|
133 |
return HttpResponseBadRequest(str(e), status=500)
|
backend/invoke_worker/__pycache__/chapter_queue.cpython-312.pyc
CHANGED
Binary files a/backend/invoke_worker/__pycache__/chapter_queue.cpython-312.pyc and b/backend/invoke_worker/__pycache__/chapter_queue.cpython-312.pyc differ
|
|
backend/invoke_worker/chapter_queue.py
CHANGED
@@ -2,7 +2,8 @@ from django_thread import Thread
|
|
2 |
from time import sleep
|
3 |
from backend.module.utils import date_utils
|
4 |
from django.db import connections
|
5 |
-
from backend.models.model_cache import SocketRequestChapterQueueCache
|
|
|
6 |
from core.settings import BASE_DIR
|
7 |
from backend.module import web_scrap
|
8 |
from backend.module.utils import manage_image
|
@@ -22,6 +23,8 @@ env = environ.Env()
|
|
22 |
|
23 |
STORAGE_DIR = os.path.join(BASE_DIR,"storage")
|
24 |
if not os.path.exists(STORAGE_DIR): os.makedirs(STORAGE_DIR)
|
|
|
|
|
25 |
|
26 |
LOG_DIR = os.path.join(BASE_DIR, "log")
|
27 |
if not os.path.exists(LOG_DIR): os.makedirs(LOG_DIR)
|
@@ -37,7 +40,7 @@ class Job(Thread):
|
|
37 |
|
38 |
query_result = SocketRequestChapterQueueCache.objects.order_by("datetime").first()
|
39 |
while True:
|
40 |
-
if (GetDirectorySize(
|
41 |
query_result_2 = ComicStorageCache.objects.order_by("datetime").first()
|
42 |
if (query_result_2):
|
43 |
file_path = query_result_2.file_path
|
@@ -73,33 +76,44 @@ class Job(Thread):
|
|
73 |
else:
|
74 |
connections['cache'].close()
|
75 |
|
76 |
-
|
77 |
-
|
78 |
-
|
|
|
79 |
|
80 |
-
|
81 |
-
|
82 |
-
|
83 |
-
|
84 |
-
|
85 |
-
|
86 |
-
|
87 |
-
|
88 |
-
|
89 |
-
|
90 |
-
|
91 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
92 |
|
93 |
-
|
94 |
|
95 |
|
96 |
-
|
97 |
-
if (options.get("colorize") or options.get("translate").get("state")):
|
98 |
if os.path.exists(input_dir): shutil.rmtree(input_dir)
|
|
|
99 |
if os.path.exists(managed_output_dir): shutil.rmtree(managed_output_dir)
|
100 |
|
101 |
job = web_scrap.source_control[source].get_chapter.scrap(comic_id=comic_id,chapter_id=chapter_id,output_dir=input_dir)
|
|
|
102 |
if job.get("status") == "success":
|
|
|
|
|
|
|
103 |
|
104 |
with open(os.path.join(LOG_DIR,"image_translator_output.log"), "w") as file:
|
105 |
result = subprocess.run(
|
@@ -113,7 +127,7 @@ class Job(Thread):
|
|
113 |
)
|
114 |
if result.returncode != 0: raise Exception("Image Translator Execution error!")
|
115 |
os.makedirs(managed_output_dir,exist_ok=True)
|
116 |
-
shutil.rmtree(
|
117 |
|
118 |
with zipfile.ZipFile(managed_output_dir + '.zip', 'w') as zipf:
|
119 |
for foldername, subfolders, filenames in os.walk(managed_output_dir):
|
@@ -139,63 +153,80 @@ class Job(Thread):
|
|
139 |
|
140 |
query_result_3 = SocketRequestChapterQueueCache.objects.filter(id=query_result.id).first()
|
141 |
channel_name = query_result_3.channel_name if query_result_3 else ""
|
142 |
-
channel_layer = get_channel_layer()
|
143 |
-
async_to_sync(channel_layer.send)(channel_name, {
|
144 |
-
'type': 'event_send',
|
145 |
-
'data': {
|
146 |
-
"type": "chapter_ready_to_download",
|
147 |
-
"data": {
|
148 |
-
"source": source,
|
149 |
-
"comic_id": comic_id,
|
150 |
-
"chapter_id": chapter_id,
|
151 |
-
"chapter_idx": chapter_idx
|
152 |
-
}
|
153 |
-
}
|
154 |
-
})
|
155 |
SocketRequestChapterQueueCache.objects.filter(id=query_result.id).delete()
|
156 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
157 |
else:
|
158 |
-
input_dir = os.path.join(
|
|
|
|
|
159 |
if os.path.exists(input_dir): shutil.rmtree(input_dir)
|
|
|
160 |
|
161 |
job = web_scrap.source_control["colamanga"].get_chapter.scrap(comic_id=comic_id,chapter_id=chapter_id,output_dir=input_dir)
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
199 |
connections['cache'].close()
|
200 |
else:
|
201 |
connections['cache'].close()
|
@@ -206,18 +237,24 @@ class Job(Thread):
|
|
206 |
if os.path.exists(input_dir): shutil.rmtree(input_dir)
|
207 |
if (managed_output_dir):
|
208 |
if os.path.exists(managed_output_dir): shutil.rmtree(managed_output_dir)
|
|
|
|
|
209 |
query_result_3 = SocketRequestChapterQueueCache.objects.filter(id=query_result.id).first()
|
210 |
channel_name = query_result_3.channel_name if query_result_3 else ""
|
211 |
-
channel_layer = get_channel_layer()
|
212 |
-
async_to_sync(channel_layer.send)(channel_name, {
|
213 |
-
'type': 'event_send',
|
214 |
-
'data': {
|
215 |
-
"type": "chapter_ready_to_download",
|
216 |
-
"data": {"state":"error"}
|
217 |
-
}
|
218 |
-
})
|
219 |
-
|
220 |
SocketRequestChapterQueueCache.objects.filter(id=query_result.id).delete()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
221 |
connections['cache'].close()
|
222 |
sleep(10)
|
223 |
|
|
|
2 |
from time import sleep
|
3 |
from backend.module.utils import date_utils
|
4 |
from django.db import connections
|
5 |
+
from backend.models.model_cache import SocketRequestChapterQueueCache
|
6 |
+
from backend.models.model_1 import ComicStorageCache
|
7 |
from core.settings import BASE_DIR
|
8 |
from backend.module import web_scrap
|
9 |
from backend.module.utils import manage_image
|
|
|
23 |
|
24 |
STORAGE_DIR = os.path.join(BASE_DIR,"storage")
|
25 |
if not os.path.exists(STORAGE_DIR): os.makedirs(STORAGE_DIR)
|
26 |
+
COMIC_STORAGE_DIR = os.path.join(STORAGE_DIR,"comics")
|
27 |
+
if not os.path.exists(COMIC_STORAGE_DIR): os.makedirs(COMIC_STORAGE_DIR)
|
28 |
|
29 |
LOG_DIR = os.path.join(BASE_DIR, "log")
|
30 |
if not os.path.exists(LOG_DIR): os.makedirs(LOG_DIR)
|
|
|
40 |
|
41 |
query_result = SocketRequestChapterQueueCache.objects.order_by("datetime").first()
|
42 |
while True:
|
43 |
+
if (GetDirectorySize(COMIC_STORAGE_DIR) >= MAX_STORAGE_SIZE):
|
44 |
query_result_2 = ComicStorageCache.objects.order_by("datetime").first()
|
45 |
if (query_result_2):
|
46 |
file_path = query_result_2.file_path
|
|
|
76 |
else:
|
77 |
connections['cache'].close()
|
78 |
|
79 |
+
if (options.get("colorize") or options.get("translate").get("state")):
|
80 |
+
script = []
|
81 |
+
input_dir = os.path.join(COMIC_STORAGE_DIR,source,comic_id,str(chapter_idx),"temp")
|
82 |
+
merge_output_dir = os.path.join(COMIC_STORAGE_DIR,source,comic_id,str(chapter_idx),"merged")
|
83 |
|
84 |
+
if (options.get("translate").get("state") and options.get("colorize")):
|
85 |
+
managed_output_dir = os.path.join(COMIC_STORAGE_DIR,source,comic_id,str(chapter_idx),f"{options.get("translate").get("target")}_translated_colorized")
|
86 |
+
script = [
|
87 |
+
"python", "-m", "manga_translator", "-v", "--overwrite", "--attempts=3", "--ocr=mocr", "--no-text-lang-skip", "--det-auto-rotate", "--det-gamma-correct", "--colorize=mc2", "--translator=m2m100_big",
|
88 |
+
"-l", f"{options.get("translate").get("target")}", "-i", f"{merge_output_dir}", "-o", f"{managed_output_dir}"
|
89 |
+
]
|
90 |
+
elif (options.get("translate").get("state") and not options.get("colorize")):
|
91 |
+
managed_output_dir = os.path.join(COMIC_STORAGE_DIR,source,comic_id,str(chapter_idx),f"{options.get("translate").get("target")}_translated")
|
92 |
+
script = [
|
93 |
+
"python", "-m", "manga_translator", "-v", "--overwrite", "--attempts=3", "--ocr=mocr", "--no-text-lang-skip", "--det-auto-rotate", "--det-gamma-correct", "--translator=m2m100_big",
|
94 |
+
"-l", f"{options.get("translate").get("target")}", "-i", f"{merge_output_dir}", "-o", f"{managed_output_dir}"
|
95 |
+
]
|
96 |
+
elif (options.get("colorize") and not options.get("translate").get("state")):
|
97 |
+
|
98 |
+
managed_output_dir = os.path.join(COMIC_STORAGE_DIR,source,comic_id,str(chapter_idx),"colorized")
|
99 |
+
script = [
|
100 |
+
"python", "-m", "manga_translator", "-v", "--overwrite", "--attempts=3", "--detector=none", "--translator=original", "--colorize=mc2", "--colorization-size=-1",
|
101 |
+
"-i", f"{merge_output_dir}", "-o", f"{managed_output_dir}"
|
102 |
+
]
|
103 |
|
104 |
+
if target_lang == "ENG": script.append("--manga2eng")
|
105 |
|
106 |
|
|
|
|
|
107 |
if os.path.exists(input_dir): shutil.rmtree(input_dir)
|
108 |
+
if os.path.exists(merge_output_dir): shutil.rmtree(merge_output_dir)
|
109 |
if os.path.exists(managed_output_dir): shutil.rmtree(managed_output_dir)
|
110 |
|
111 |
job = web_scrap.source_control[source].get_chapter.scrap(comic_id=comic_id,chapter_id=chapter_id,output_dir=input_dir)
|
112 |
+
|
113 |
if job.get("status") == "success":
|
114 |
+
manage_image.merge_images_vertically(input_dir, merge_output_dir, max_height=1800)
|
115 |
+
shutil.rmtree(input_dir)
|
116 |
+
|
117 |
|
118 |
with open(os.path.join(LOG_DIR,"image_translator_output.log"), "w") as file:
|
119 |
result = subprocess.run(
|
|
|
127 |
)
|
128 |
if result.returncode != 0: raise Exception("Image Translator Execution error!")
|
129 |
os.makedirs(managed_output_dir,exist_ok=True)
|
130 |
+
shutil.rmtree(merge_output_dir)
|
131 |
|
132 |
with zipfile.ZipFile(managed_output_dir + '.zip', 'w') as zipf:
|
133 |
for foldername, subfolders, filenames in os.walk(managed_output_dir):
|
|
|
153 |
|
154 |
query_result_3 = SocketRequestChapterQueueCache.objects.filter(id=query_result.id).first()
|
155 |
channel_name = query_result_3.channel_name if query_result_3 else ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
156 |
SocketRequestChapterQueueCache.objects.filter(id=query_result.id).delete()
|
157 |
+
if channel_name:
|
158 |
+
channel_layer = get_channel_layer()
|
159 |
+
async_to_sync(channel_layer.send)(channel_name, {
|
160 |
+
'type': 'event_send',
|
161 |
+
'data': {
|
162 |
+
"type": "chapter_ready_to_download",
|
163 |
+
"data": {
|
164 |
+
"source": source,
|
165 |
+
"comic_id": comic_id,
|
166 |
+
"chapter_id": chapter_id,
|
167 |
+
"chapter_idx": chapter_idx
|
168 |
+
}
|
169 |
+
}
|
170 |
+
})
|
171 |
+
connections['cache'].close()
|
172 |
+
|
173 |
+
else:
|
174 |
+
connections['cache'].close()
|
175 |
+
raise Exception("#1 Dowload chapter error!")
|
176 |
else:
|
177 |
+
input_dir = os.path.join(COMIC_STORAGE_DIR,source,comic_id,str(chapter_idx),"original")
|
178 |
+
merge_output_dir = os.path.join(COMIC_STORAGE_DIR,source,comic_id,str(chapter_idx),"origin-merged")
|
179 |
+
|
180 |
if os.path.exists(input_dir): shutil.rmtree(input_dir)
|
181 |
+
if os.path.exists(merge_output_dir): shutil.rmtree(merge_output_dir)
|
182 |
|
183 |
job = web_scrap.source_control["colamanga"].get_chapter.scrap(comic_id=comic_id,chapter_id=chapter_id,output_dir=input_dir)
|
184 |
+
if job.get("status") == "success":
|
185 |
+
manage_image.merge_images_vertically(input_dir, merge_output_dir, max_height=1800)
|
186 |
+
if os.path.exists(input_dir): shutil.rmtree(input_dir)
|
187 |
+
|
188 |
+
with zipfile.ZipFile(input_dir + '.zip', 'w') as zipf:
|
189 |
+
for foldername, subfolders, filenames in os.walk(merge_output_dir):
|
190 |
+
for filename in filenames:
|
191 |
+
if filename.endswith(('.png', '.jpg', '.jpeg')):
|
192 |
+
file_path = os.path.join(foldername, filename)
|
193 |
+
zipf.write(file_path, os.path.basename(file_path))
|
194 |
+
shutil.rmtree(merge_output_dir)
|
195 |
+
|
196 |
+
ComicStorageCache(
|
197 |
+
source = source,
|
198 |
+
comic_id = comic_id,
|
199 |
+
chapter_id = chapter_id,
|
200 |
+
chapter_idx = chapter_idx,
|
201 |
+
file_path = input_dir + '.zip',
|
202 |
+
colorize = False,
|
203 |
+
translate = False,
|
204 |
+
target_lang = "",
|
205 |
+
|
206 |
+
).save()
|
207 |
+
|
208 |
+
|
209 |
+
query_result_3 = SocketRequestChapterQueueCache.objects.filter(id=query_result.id).first()
|
210 |
+
channel_name = query_result_3.channel_name if query_result_3 else ""
|
211 |
+
SocketRequestChapterQueueCache.objects.filter(id=query_result.id).delete()
|
212 |
+
|
213 |
+
if channel_name:
|
214 |
+
channel_layer = get_channel_layer()
|
215 |
+
async_to_sync(channel_layer.send)(channel_name, {
|
216 |
+
'type': 'event_send',
|
217 |
+
'data': {
|
218 |
+
"type": "chapter_ready_to_download",
|
219 |
+
"data": {
|
220 |
+
"source": source,
|
221 |
+
"comic_id": comic_id,
|
222 |
+
"chapter_id": chapter_id,
|
223 |
+
"chapter_idx": chapter_idx
|
224 |
+
}
|
225 |
+
}
|
226 |
+
})
|
227 |
+
else:
|
228 |
+
connections['cache'].close()
|
229 |
+
raise Exception("#2 Dowload chapter error!")
|
230 |
connections['cache'].close()
|
231 |
else:
|
232 |
connections['cache'].close()
|
|
|
237 |
if os.path.exists(input_dir): shutil.rmtree(input_dir)
|
238 |
if (managed_output_dir):
|
239 |
if os.path.exists(managed_output_dir): shutil.rmtree(managed_output_dir)
|
240 |
+
|
241 |
+
|
242 |
query_result_3 = SocketRequestChapterQueueCache.objects.filter(id=query_result.id).first()
|
243 |
channel_name = query_result_3.channel_name if query_result_3 else ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
244 |
SocketRequestChapterQueueCache.objects.filter(id=query_result.id).delete()
|
245 |
+
|
246 |
+
if (channel_name):
|
247 |
+
channel_layer = get_channel_layer()
|
248 |
+
async_to_sync(channel_layer.send)(channel_name, {
|
249 |
+
'type': 'event_send',
|
250 |
+
'data': {
|
251 |
+
"type": "chapter_ready_to_download",
|
252 |
+
"data": {"state":"error"}
|
253 |
+
}
|
254 |
+
})
|
255 |
+
|
256 |
+
|
257 |
+
|
258 |
connections['cache'].close()
|
259 |
sleep(10)
|
260 |
|
backend/migrations/0001_initial.py
CHANGED
@@ -1,5 +1,6 @@
|
|
1 |
-
# Generated by Django 5.1.1 on 2024-
|
2 |
|
|
|
3 |
import backend.models.model_cache
|
4 |
import uuid
|
5 |
from django.db import migrations, models
|
@@ -32,13 +33,12 @@ class Migration(migrations.Migration):
|
|
32 |
('colorize', models.BooleanField()),
|
33 |
('translate', models.BooleanField()),
|
34 |
('target_lang', models.TextField()),
|
35 |
-
('datetime', models.DateTimeField(default=backend.models.
|
36 |
],
|
37 |
),
|
38 |
migrations.CreateModel(
|
39 |
name='RequestCache',
|
40 |
fields=[
|
41 |
-
('room', models.TextField()),
|
42 |
('client', models.UUIDField(primary_key=True, serialize=False)),
|
43 |
('datetime', models.DateTimeField(default=backend.models.model_cache.get_current_utc_time)),
|
44 |
],
|
|
|
1 |
+
# Generated by Django 5.1.1 on 2024-12-01 11:29
|
2 |
|
3 |
+
import backend.models.model_1
|
4 |
import backend.models.model_cache
|
5 |
import uuid
|
6 |
from django.db import migrations, models
|
|
|
33 |
('colorize', models.BooleanField()),
|
34 |
('translate', models.BooleanField()),
|
35 |
('target_lang', models.TextField()),
|
36 |
+
('datetime', models.DateTimeField(default=backend.models.model_1.get_current_utc_time)),
|
37 |
],
|
38 |
),
|
39 |
migrations.CreateModel(
|
40 |
name='RequestCache',
|
41 |
fields=[
|
|
|
42 |
('client', models.UUIDField(primary_key=True, serialize=False)),
|
43 |
('datetime', models.DateTimeField(default=backend.models.model_cache.get_current_utc_time)),
|
44 |
],
|
backend/migrations/0002_remove_requestcache_room.py
DELETED
@@ -1,17 +0,0 @@
|
|
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/0002_webscrapegetcovercache.py
ADDED
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# Generated by Django 5.1.1 on 2024-12-01 11:38
|
2 |
+
|
3 |
+
import backend.models.model_1
|
4 |
+
from django.db import migrations, models
|
5 |
+
|
6 |
+
|
7 |
+
class Migration(migrations.Migration):
|
8 |
+
|
9 |
+
dependencies = [
|
10 |
+
('backend', '0001_initial'),
|
11 |
+
]
|
12 |
+
|
13 |
+
operations = [
|
14 |
+
migrations.CreateModel(
|
15 |
+
name='WebscrapeGetCoverCache',
|
16 |
+
fields=[
|
17 |
+
('file_path', models.TextField(primary_key=True, serialize=False)),
|
18 |
+
('source', models.TextField()),
|
19 |
+
('comic_id', models.TextField()),
|
20 |
+
('cover_id', models.TextField()),
|
21 |
+
('datetime', models.DateTimeField(default=backend.models.model_1.get_current_utc_time)),
|
22 |
+
],
|
23 |
+
),
|
24 |
+
]
|
backend/migrations/__pycache__/0001_initial.cpython-312.pyc
CHANGED
Binary files a/backend/migrations/__pycache__/0001_initial.cpython-312.pyc and b/backend/migrations/__pycache__/0001_initial.cpython-312.pyc differ
|
|
backend/migrations/__pycache__/0002_remove_requestcache_room.cpython-312.pyc
DELETED
Binary file (616 Bytes)
|
|
backend/migrations/__pycache__/0002_webscrapegetcovercache.cpython-312.pyc
ADDED
Binary file (1.2 kB). View file
|
|
backend/models/__pycache__/model_1.cpython-312.pyc
CHANGED
Binary files a/backend/models/__pycache__/model_1.cpython-312.pyc and b/backend/models/__pycache__/model_1.cpython-312.pyc differ
|
|
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_1.py
CHANGED
@@ -1,8 +1,28 @@
|
|
1 |
from django.db import models
|
2 |
-
|
|
|
3 |
|
|
|
4 |
|
5 |
-
|
6 |
-
|
7 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
8 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
from django.db import models
|
2 |
+
from backend.module.utils import date_utils
|
3 |
+
from core.settings import BASE_DIR
|
4 |
|
5 |
+
import uuid, os
|
6 |
|
7 |
+
def get_current_utc_time(): return date_utils.utc_time().get()
|
8 |
+
|
9 |
+
|
10 |
+
|
11 |
+
class ComicStorageCache(models.Model):
|
12 |
+
id = models.UUIDField(primary_key = True, default = uuid.uuid4, editable = False)
|
13 |
+
source = models.TextField()
|
14 |
+
comic_id = models.TextField()
|
15 |
+
chapter_id = models.TextField()
|
16 |
+
chapter_idx = models.IntegerField()
|
17 |
+
file_path = models.TextField()
|
18 |
+
colorize = models.BooleanField()
|
19 |
+
translate = models.BooleanField()
|
20 |
+
target_lang = models.TextField()
|
21 |
+
datetime = models.DateTimeField(default=get_current_utc_time)
|
22 |
|
23 |
+
class WebscrapeGetCoverCache(models.Model):
|
24 |
+
file_path = models.TextField(primary_key=True)
|
25 |
+
source = models.TextField()
|
26 |
+
comic_id = models.TextField()
|
27 |
+
cover_id = models.TextField()
|
28 |
+
datetime = models.DateTimeField(default=get_current_utc_time)
|
backend/models/model_cache.py
CHANGED
@@ -12,18 +12,6 @@ class CloudflareTurnStileCache(models.Model):
|
|
12 |
token = models.TextField(primary_key=True)
|
13 |
datetime = models.DateTimeField(default=get_current_utc_time)
|
14 |
|
15 |
-
class ComicStorageCache(models.Model):
|
16 |
-
id = models.UUIDField(primary_key = True, default = uuid.uuid4, editable = False)
|
17 |
-
source = models.TextField()
|
18 |
-
comic_id = models.TextField()
|
19 |
-
chapter_id = models.TextField()
|
20 |
-
chapter_idx = models.IntegerField()
|
21 |
-
file_path = models.TextField()
|
22 |
-
colorize = models.BooleanField()
|
23 |
-
translate = models.BooleanField()
|
24 |
-
target_lang = models.TextField()
|
25 |
-
datetime = models.DateTimeField(default=get_current_utc_time)
|
26 |
-
|
27 |
|
28 |
class SocketRequestChapterQueueCache(models.Model):
|
29 |
id = models.UUIDField(primary_key = True, default = uuid.uuid4, editable = False)
|
|
|
12 |
token = models.TextField(primary_key=True)
|
13 |
datetime = models.DateTimeField(default=get_current_utc_time)
|
14 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
15 |
|
16 |
class SocketRequestChapterQueueCache(models.Model):
|
17 |
id = models.UUIDField(primary_key = True, default = uuid.uuid4, editable = False)
|
backend/module/utils/__pycache__/manage_image.cpython-312.pyc
CHANGED
Binary files a/backend/module/utils/__pycache__/manage_image.cpython-312.pyc and b/backend/module/utils/__pycache__/manage_image.cpython-312.pyc differ
|
|
backend/module/utils/manage_image.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1 |
import os
|
2 |
from PIL import Image
|
3 |
|
4 |
-
def
|
5 |
os.makedirs(output_dir,exist_ok=True)
|
6 |
|
7 |
filenames = sorted(os.listdir(input_dir), key=lambda x: int(x.split(".")[0]))
|
@@ -58,6 +58,57 @@ def merge_images_vertically(input_dir, output_dir, max_size=10 * 1024 * 1024):
|
|
58 |
merged_image.save(os.path.join(output_dir, f"{merged_file_index}.png"))
|
59 |
|
60 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
61 |
def split_image_vertically(input_dir, output_dir):
|
62 |
os.makedirs(output_dir,exist_ok=True)
|
63 |
|
|
|
1 |
import os
|
2 |
from PIL import Image
|
3 |
|
4 |
+
def merge_images_vertically_old(input_dir, output_dir, max_size=1800):
|
5 |
os.makedirs(output_dir,exist_ok=True)
|
6 |
|
7 |
filenames = sorted(os.listdir(input_dir), key=lambda x: int(x.split(".")[0]))
|
|
|
58 |
merged_image.save(os.path.join(output_dir, f"{merged_file_index}.png"))
|
59 |
|
60 |
|
61 |
+
def merge_images_vertically(input_dir, output_dir, max_height=1800):
|
62 |
+
os.makedirs(output_dir, exist_ok=True)
|
63 |
+
|
64 |
+
filenames = sorted(os.listdir(input_dir), key=lambda x: int(x.split(".")[0]))
|
65 |
+
|
66 |
+
merged_image = None
|
67 |
+
merged_file_index = 0
|
68 |
+
|
69 |
+
index = 0
|
70 |
+
while True:
|
71 |
+
if index > (len(filenames) - 1): break
|
72 |
+
file = filenames[index]
|
73 |
+
if not merged_image:
|
74 |
+
image = Image.open(os.path.join(input_dir, file))
|
75 |
+
|
76 |
+
width, height = image.size
|
77 |
+
|
78 |
+
new_image = Image.new("RGBA", (width, height))
|
79 |
+
|
80 |
+
# Paste the image onto the new image
|
81 |
+
new_image.paste(image, (0, 0))
|
82 |
+
merged_image = new_image
|
83 |
+
index += 1
|
84 |
+
else:
|
85 |
+
merged_width, merged_height = merged_image.size
|
86 |
+
|
87 |
+
if merged_height >= max_height:
|
88 |
+
output_path = os.path.join(output_dir, f"{merged_file_index}.png")
|
89 |
+
merged_image.save(output_path)
|
90 |
+
merged_image = None
|
91 |
+
merged_file_index += 1
|
92 |
+
else:
|
93 |
+
image = Image.open(os.path.join(input_dir, file))
|
94 |
+
width, height = image.size
|
95 |
+
|
96 |
+
# Create a new image with the combined width and the height of the tallest image
|
97 |
+
new_width = max(merged_width, width)
|
98 |
+
new_height = merged_height + height
|
99 |
+
new_image = Image.new("RGB", (new_width, new_height))
|
100 |
+
|
101 |
+
# Paste the two images onto the new image
|
102 |
+
new_image.paste(merged_image, (0, 0))
|
103 |
+
new_image.paste(image, (0, merged_height))
|
104 |
+
merged_image = new_image
|
105 |
+
index += 1
|
106 |
+
|
107 |
+
if merged_image:
|
108 |
+
output_path = os.path.join(output_dir, f"{merged_file_index}.png")
|
109 |
+
merged_image.save(output_path)
|
110 |
+
|
111 |
+
|
112 |
def split_image_vertically(input_dir, output_dir):
|
113 |
os.makedirs(output_dir,exist_ok=True)
|
114 |
|
backend/module/web_scrap/__pycache__/utils.cpython-312.pyc
CHANGED
Binary files a/backend/module/web_scrap/__pycache__/utils.cpython-312.pyc and b/backend/module/web_scrap/__pycache__/utils.cpython-312.pyc differ
|
|
backend/module/web_scrap/utils.py
CHANGED
@@ -17,7 +17,6 @@ class SeleniumScraper:
|
|
17 |
options.add_argument('--no-sandbox')
|
18 |
options.add_argument("--no-quit")
|
19 |
options.add_argument('--disable-extensions')
|
20 |
-
options.add_argument('--disable-gpu')
|
21 |
options.add_argument('--disable-dev-shm-usage')
|
22 |
options.set_capability('goog:loggingPrefs', {'performance': 'ALL'})
|
23 |
self.__driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options)
|
|
|
17 |
options.add_argument('--no-sandbox')
|
18 |
options.add_argument("--no-quit")
|
19 |
options.add_argument('--disable-extensions')
|
|
|
20 |
options.add_argument('--disable-dev-shm-usage')
|
21 |
options.set_capability('goog:loggingPrefs', {'performance': 'ALL'})
|
22 |
self.__driver = webdriver.Chrome(service=Service(ChromeDriverManager().install()), options=options)
|
backend/urls.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1 |
|
2 |
from django.contrib import admin
|
3 |
from django.urls import path, include, re_path
|
4 |
-
from backend.api import test, stream_file,
|
5 |
|
6 |
|
7 |
|
@@ -14,10 +14,9 @@ urlpatterns = [
|
|
14 |
# /api/queue/request_info/
|
15 |
path("cloudflare_turnstile/verify/", cloudflare_turnstile.verify),
|
16 |
|
17 |
-
path('web_scrap/get_list/',
|
18 |
-
path('web_scrap/
|
19 |
-
path('web_scrap/
|
20 |
-
path('web_scrap/get_cover/<str:source>/<str:id>/<str:cover_id>/', web_scrap.get_cover),
|
21 |
|
22 |
|
23 |
|
|
|
1 |
|
2 |
from django.contrib import admin
|
3 |
from django.urls import path, include, re_path
|
4 |
+
from backend.api import test, stream_file, cloudflare_turnstile, queue, web_scrape
|
5 |
|
6 |
|
7 |
|
|
|
14 |
# /api/queue/request_info/
|
15 |
path("cloudflare_turnstile/verify/", cloudflare_turnstile.verify),
|
16 |
|
17 |
+
path('web_scrap/get_list/', web_scrape.get_list),
|
18 |
+
path('web_scrap/get/', web_scrape.get),
|
19 |
+
path('web_scrap/get_cover/<str:source>/<str:id>/<str:cover_id>/', web_scrape.get_cover),
|
|
|
20 |
|
21 |
|
22 |
|
core/__pycache__/settings.cpython-312.pyc
CHANGED
Binary files a/core/__pycache__/settings.cpython-312.pyc and b/core/__pycache__/settings.cpython-312.pyc differ
|
|
core/settings.py
CHANGED
@@ -21,8 +21,7 @@ for key, value in os.environ.items():
|
|
21 |
|
22 |
|
23 |
# SECURITY WARNING: don't run with debug turned on in production!
|
24 |
-
DEBUG =
|
25 |
-
DEBUG_DB = True
|
26 |
|
27 |
# settings.py
|
28 |
|
@@ -117,37 +116,27 @@ WSGI_APPLICATION = 'core.wsgi.application'
|
|
117 |
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases
|
118 |
DATABASE_ROUTERS = ['core.routers.Router']
|
119 |
|
120 |
-
if DEBUG or DEBUG_DB:
|
121 |
-
DATABASES = {
|
122 |
-
'default': {
|
123 |
-
'ENGINE': 'django.db.backends.sqlite3',
|
124 |
-
'NAME': BASE_DIR / 'database.sqlite3',
|
125 |
-
},
|
126 |
-
'cache': {
|
127 |
-
'ENGINE': 'django.db.backends.sqlite3',
|
128 |
-
'NAME': BASE_DIR / 'cache.sqlite3',
|
129 |
-
},
|
130 |
-
'DB1': {
|
131 |
-
'ENGINE': 'django.db.backends.sqlite3',
|
132 |
-
'NAME': BASE_DIR / 'db1.sqlite3',
|
133 |
-
},
|
134 |
-
'DB2': {
|
135 |
-
'ENGINE': 'django.db.backends.sqlite3',
|
136 |
-
'NAME': BASE_DIR / 'db2.sqlite3',
|
137 |
-
},
|
138 |
|
139 |
-
|
140 |
-
|
141 |
-
|
142 |
-
'
|
143 |
-
|
144 |
-
|
145 |
-
|
146 |
-
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
151 |
|
152 |
|
153 |
|
|
|
21 |
|
22 |
|
23 |
# SECURITY WARNING: don't run with debug turned on in production!
|
24 |
+
DEBUG = False
|
|
|
25 |
|
26 |
# settings.py
|
27 |
|
|
|
116 |
# https://docs.djangoproject.com/en/4.0/ref/settings/#databases
|
117 |
DATABASE_ROUTERS = ['core.routers.Router']
|
118 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
119 |
|
120 |
+
DATABASES = {
|
121 |
+
'default': {
|
122 |
+
'ENGINE': 'django.db.backends.sqlite3',
|
123 |
+
'NAME': BASE_DIR / 'database.sqlite3',
|
124 |
+
},
|
125 |
+
'cache': {
|
126 |
+
'ENGINE': 'django.db.backends.sqlite3',
|
127 |
+
'NAME': BASE_DIR / 'cache.sqlite3',
|
128 |
+
},
|
129 |
+
'DB1': {
|
130 |
+
'ENGINE': 'django.db.backends.sqlite3',
|
131 |
+
'NAME': BASE_DIR / 'db1.sqlite3',
|
132 |
+
},
|
133 |
+
'DB2': {
|
134 |
+
'ENGINE': 'django.db.backends.sqlite3',
|
135 |
+
'NAME': BASE_DIR / 'db2.sqlite3',
|
136 |
+
},
|
137 |
+
|
138 |
+
}
|
139 |
+
|
140 |
|
141 |
|
142 |
|
frontend/app/_layout.tsx
CHANGED
@@ -3,7 +3,7 @@ import { useFonts } from 'expo-font';
|
|
3 |
import { Stack, router, usePathname } from 'expo-router';
|
4 |
import * as SplashScreen from 'expo-splash-screen';
|
5 |
import { useEffect, useState, useContext, createContext, memo } from 'react';
|
6 |
-
import { useWindowDimensions, View, Text, Pressable } from 'react-native';
|
7 |
import 'react-native-reanimated';
|
8 |
import { SafeAreaView } from 'react-native-safe-area-context';
|
9 |
import Menu from '@/components/menu/menu';
|
@@ -134,7 +134,15 @@ return (<>{loaded && themeTypeContext && apiBaseContext && socketBaseContext &&
|
|
134 |
widgetContext, setWidgetContext,
|
135 |
showCloudflareTurnstileContext, setShowCloudflareTurnstileContext,
|
136 |
}}>
|
137 |
-
<
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
138 |
{showCloudflareTurnstileContext
|
139 |
? <View style={{position:"absolute",width:"100%",height:"100%",display:"flex",justifyContent:"center",alignItems:"center",backgroundColor:Theme[themeTypeContext].background_color}}>
|
140 |
<CloudflareTurnstile
|
@@ -184,7 +192,7 @@ return (<>{loaded && themeTypeContext && apiBaseContext && socketBaseContext &&
|
|
184 |
}
|
185 |
|
186 |
|
187 |
-
</
|
188 |
<Toast config={toastConfig} />
|
189 |
</CONTEXT.Provider>
|
190 |
</SafeAreaView>
|
|
|
3 |
import { Stack, router, usePathname } from 'expo-router';
|
4 |
import * as SplashScreen from 'expo-splash-screen';
|
5 |
import { useEffect, useState, useContext, createContext, memo } from 'react';
|
6 |
+
import { useWindowDimensions, View, Text, Pressable, KeyboardAvoidingView } from 'react-native';
|
7 |
import 'react-native-reanimated';
|
8 |
import { SafeAreaView } from 'react-native-safe-area-context';
|
9 |
import Menu from '@/components/menu/menu';
|
|
|
134 |
widgetContext, setWidgetContext,
|
135 |
showCloudflareTurnstileContext, setShowCloudflareTurnstileContext,
|
136 |
}}>
|
137 |
+
<KeyboardAvoidingView
|
138 |
+
behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
|
139 |
+
style={{
|
140 |
+
width:Dimensions.width,
|
141 |
+
height:Dimensions.height,
|
142 |
+
|
143 |
+
backgroundColor: Theme[themeTypeContext].background_color
|
144 |
+
}}
|
145 |
+
>
|
146 |
{showCloudflareTurnstileContext
|
147 |
? <View style={{position:"absolute",width:"100%",height:"100%",display:"flex",justifyContent:"center",alignItems:"center",backgroundColor:Theme[themeTypeContext].background_color}}>
|
148 |
<CloudflareTurnstile
|
|
|
192 |
}
|
193 |
|
194 |
|
195 |
+
</KeyboardAvoidingView>
|
196 |
<Toast config={toastConfig} />
|
197 |
</CONTEXT.Provider>
|
198 |
</SafeAreaView>
|
frontend/app/bookmark/_layout.tsx
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Tabs, Stack } from 'expo-router';
|
2 |
+
import React from 'react';
|
3 |
+
import {View, Text} from 'react-native';
|
4 |
+
|
5 |
+
import { Colors } from '@/constants/Colors';
|
6 |
+
import { useColorScheme } from '@/hooks/useColorScheme';
|
7 |
+
import { SafeAreaView } from 'react-native-safe-area-context';
|
8 |
+
export default function TabLayout() {
|
9 |
+
const colorScheme = useColorScheme();
|
10 |
+
|
11 |
+
return (
|
12 |
+
<Stack
|
13 |
+
screenOptions={{
|
14 |
+
headerShown: false,
|
15 |
+
}}>
|
16 |
+
|
17 |
+
</Stack>
|
18 |
+
);
|
19 |
+
}
|
frontend/app/bookmark/components/bookmark_component.tsx
ADDED
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React, { useEffect, useState, useCallback, useContext, useRef, useMemo } from 'react';
|
2 |
+
import { Link, router, useLocalSearchParams, useFocusEffect } from 'expo-router';
|
3 |
+
import { Image as RNImage, StyleSheet, useWindowDimensions, ScrollView, Pressable, RefreshControl, Platform, FlatList, TouchableOpacity } from 'react-native';
|
4 |
+
import { SafeAreaView } from 'react-native-safe-area-context';
|
5 |
+
import { Icon, MD3Colors, Button, Text, TextInput, TouchableRipple } from 'react-native-paper';
|
6 |
+
import CircularProgress from 'react-native-circular-progress-indicator';
|
7 |
+
import { ActivityIndicator } from 'react-native-paper';
|
8 |
+
import { FlashList } from "@shopify/flash-list";
|
9 |
+
|
10 |
+
|
11 |
+
import uuid from 'react-native-uuid';
|
12 |
+
import Toast from 'react-native-toast-message';
|
13 |
+
import { View, AnimatePresence } from 'moti';
|
14 |
+
import * as Clipboard from 'expo-clipboard';
|
15 |
+
import * as FileSystem from 'expo-file-system';
|
16 |
+
import NetInfo from "@react-native-community/netinfo";
|
17 |
+
import { Marquee } from '@animatereactnative/marquee';
|
18 |
+
import { Slider } from '@rneui/themed-edge';
|
19 |
+
|
20 |
+
import { __styles } from '../stylesheet/styles';
|
21 |
+
import Storage from '@/constants/module/storages/storage';
|
22 |
+
import ChapterStorage from '@/constants/module/storages/chapter_storage';
|
23 |
+
import Image from '@/components/Image';
|
24 |
+
import {CONTEXT} from '@/constants/module/context';
|
25 |
+
import {blobToBase64, base64ToBlob, getImageLayout} from "@/constants/module/file_manager";
|
26 |
+
import Theme from '@/constants/theme';
|
27 |
+
import ComicStorage from '@/constants/module/storages/comic_storage';
|
28 |
+
|
29 |
+
const BookmarkComponent = ({item, SELECTED_BOOKMARK, SET_SELECTED_BOOKMARK}:any) => {
|
30 |
+
const Dimensions = useWindowDimensions();
|
31 |
+
const controller = new AbortController();
|
32 |
+
const signal = controller.signal;
|
33 |
+
|
34 |
+
const {showMenuContext, setShowMenuContext}:any = useContext(CONTEXT)
|
35 |
+
const {themeTypeContext, setThemeTypeContext}:any = useContext(CONTEXT)
|
36 |
+
const {apiBaseContext, setApiBaseContext}:any = useContext(CONTEXT)
|
37 |
+
const {widgetContext, setWidgetContext}:any = useContext(CONTEXT)
|
38 |
+
const {showCloudflareTurnstileContext, setShowCloudflareTurnstileContext}:any = useContext(CONTEXT)
|
39 |
+
|
40 |
+
|
41 |
+
useFocusEffect(useCallback(() => {
|
42 |
+
|
43 |
+
|
44 |
+
return () => {
|
45 |
+
controller.abort();
|
46 |
+
};
|
47 |
+
},[]))
|
48 |
+
|
49 |
+
return (<>
|
50 |
+
<TouchableRipple
|
51 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
52 |
+
style={{
|
53 |
+
width:"auto",
|
54 |
+
height:"auto",
|
55 |
+
backgroundColor: SELECTED_BOOKMARK === item ? Theme[themeTypeContext].border_color : "transparent",
|
56 |
+
borderBottomWidth: SELECTED_BOOKMARK === item ? 3.5 : 0,
|
57 |
+
borderColor:Theme[themeTypeContext].button_color,
|
58 |
+
paddingHorizontal:18,
|
59 |
+
paddingVertical:14,
|
60 |
+
borderRadius:2,
|
61 |
+
}}
|
62 |
+
onPress={()=>{SET_SELECTED_BOOKMARK(item)}}
|
63 |
+
>
|
64 |
+
<View
|
65 |
+
style={{
|
66 |
+
width:"auto",
|
67 |
+
height:"auto",
|
68 |
+
}}
|
69 |
+
>
|
70 |
+
<Text selectable={false}
|
71 |
+
style={{
|
72 |
+
color:Theme[themeTypeContext].text_color,
|
73 |
+
fontFamily:"roboto-bold",
|
74 |
+
fontSize:((Dimensions.width+Dimensions.height)/2)*0.0185,
|
75 |
+
}}
|
76 |
+
>
|
77 |
+
{item}
|
78 |
+
</Text>
|
79 |
+
</View>
|
80 |
+
</TouchableRipple>
|
81 |
+
</>)
|
82 |
+
|
83 |
+
|
84 |
+
}
|
85 |
+
|
86 |
+
export default BookmarkComponent;
|
87 |
+
|
frontend/app/bookmark/components/comic_component.tsx
ADDED
@@ -0,0 +1,85 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React, { useEffect, useState, useCallback, useContext, useRef, useMemo } from 'react';
|
2 |
+
import { Link, router, useLocalSearchParams, useFocusEffect } from 'expo-router';
|
3 |
+
import { Image as RNImage, StyleSheet, useWindowDimensions, ScrollView, Pressable, RefreshControl, Platform, FlatList, TouchableOpacity } from 'react-native';
|
4 |
+
import { SafeAreaView } from 'react-native-safe-area-context';
|
5 |
+
import { Icon, MD3Colors, Button, Text, TextInput, TouchableRipple } from 'react-native-paper';
|
6 |
+
import CircularProgress from 'react-native-circular-progress-indicator';
|
7 |
+
import { ActivityIndicator } from 'react-native-paper';
|
8 |
+
import { FlashList } from "@shopify/flash-list";
|
9 |
+
|
10 |
+
|
11 |
+
import uuid from 'react-native-uuid';
|
12 |
+
import Toast from 'react-native-toast-message';
|
13 |
+
import { View, AnimatePresence } from 'moti';
|
14 |
+
import * as Clipboard from 'expo-clipboard';
|
15 |
+
import * as FileSystem from 'expo-file-system';
|
16 |
+
import NetInfo from "@react-native-community/netinfo";
|
17 |
+
import { Marquee } from '@animatereactnative/marquee';
|
18 |
+
import { Slider } from '@rneui/themed-edge';
|
19 |
+
|
20 |
+
import { __styles } from '../stylesheet/styles';
|
21 |
+
import Storage from '@/constants/module/storages/storage';
|
22 |
+
import ChapterStorage from '@/constants/module/storages/chapter_storage';
|
23 |
+
import CoverStorage from '@/constants/module/storages/cover_storage';
|
24 |
+
|
25 |
+
import Image from '@/components/Image';
|
26 |
+
import {CONTEXT} from '@/constants/module/context';
|
27 |
+
import {blobToBase64, base64ToBlob, getImageLayout} from "@/constants/module/file_manager";
|
28 |
+
import Theme from '@/constants/theme';
|
29 |
+
import ComicStorage from '@/constants/module/storages/comic_storage';
|
30 |
+
|
31 |
+
const ComicComponent = ({item, SELECTED_BOOKMARK, SET_SELECTED_BOOKMARK}:any) => {
|
32 |
+
const Dimensions = useWindowDimensions();
|
33 |
+
const controller = new AbortController();
|
34 |
+
const signal = controller.signal;
|
35 |
+
|
36 |
+
const {showMenuContext, setShowMenuContext}:any = useContext(CONTEXT)
|
37 |
+
const {themeTypeContext, setThemeTypeContext}:any = useContext(CONTEXT)
|
38 |
+
const {apiBaseContext, setApiBaseContext}:any = useContext(CONTEXT)
|
39 |
+
const {widgetContext, setWidgetContext}:any = useContext(CONTEXT)
|
40 |
+
const {showCloudflareTurnstileContext, setShowCloudflareTurnstileContext}:any = useContext(CONTEXT)
|
41 |
+
|
42 |
+
const [styles, setStyles]:any = useState(null)
|
43 |
+
const [isLoading, setIsLoading] = useState<boolean>(true)
|
44 |
+
|
45 |
+
const cover:any = useRef("")
|
46 |
+
|
47 |
+
useFocusEffect(useCallback(() => {
|
48 |
+
(async ()=>{
|
49 |
+
setIsLoading(true)
|
50 |
+
setStyles(__styles(themeTypeContext,Dimensions))
|
51 |
+
const stored_bookmark = await Storage.get("bookmark") || []
|
52 |
+
console.log(stored_bookmark)
|
53 |
+
cover.current = await CoverStorage.get(`${item.source}-${item.id}`) || ""
|
54 |
+
setIsLoading(false)
|
55 |
+
})()
|
56 |
+
|
57 |
+
return () => {
|
58 |
+
cover.current = ""
|
59 |
+
controller.abort();
|
60 |
+
};
|
61 |
+
},[]))
|
62 |
+
|
63 |
+
return (<>{styles && !isLoading && <>
|
64 |
+
<TouchableRipple
|
65 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
66 |
+
onPress={()=>{router.navigate(`/view/${item.source}/${item.id}/?mode=local`)}}
|
67 |
+
style={styles.item_box}
|
68 |
+
>
|
69 |
+
<>
|
70 |
+
<Image onError={(error:any)=>{console.log("load image error",error)}} source={cover.current}
|
71 |
+
style={styles.item_cover}
|
72 |
+
contentFit="cover" transition={1000}
|
73 |
+
onLoadEnd={()=>{cover.current = ""}}
|
74 |
+
/>
|
75 |
+
|
76 |
+
<Text style={styles.item_title}>{item.info.title}</Text>
|
77 |
+
</>
|
78 |
+
</TouchableRipple>
|
79 |
+
</>}</>)
|
80 |
+
|
81 |
+
|
82 |
+
}
|
83 |
+
|
84 |
+
export default ComicComponent;
|
85 |
+
|
frontend/app/bookmark/components/widgets/bookmark.tsx
ADDED
@@ -0,0 +1,904 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
import React, { useEffect, useState, useCallback, useContext, useRef, Fragment, memo } 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 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 |
+
import ChapterDataStorage from '@/constants/module/storages/chapter_data_storage';
|
20 |
+
|
21 |
+
interface BookmarkWidgetProps {
|
22 |
+
setIsLoading: any;
|
23 |
+
onRefresh: any;
|
24 |
+
}
|
25 |
+
|
26 |
+
const BookmarkWidget: React.FC<BookmarkWidgetProps> = ({
|
27 |
+
setIsLoading,
|
28 |
+
onRefresh,
|
29 |
+
}) => {
|
30 |
+
const Dimensions = useWindowDimensions();
|
31 |
+
|
32 |
+
const {themeTypeContext, setThemeTypeContext}:any = useContext(CONTEXT)
|
33 |
+
const {widgetContext, setWidgetContext}:any = useContext(CONTEXT)
|
34 |
+
const {showCloudflareTurnstileContext, setShowCloudflareTurnstileContext}:any = useContext(CONTEXT)
|
35 |
+
const {apiBaseContext, setApiBaseContext}:any = useContext(CONTEXT)
|
36 |
+
|
37 |
+
const [BOOKMARK_DATA, SET_BOOKMARK_DATA]: any = useState([])
|
38 |
+
const [MIGRATE_BOOKMARK_DATA, SET_MIGRATE_BOOKMARK_DATA]:any = useState([])
|
39 |
+
|
40 |
+
|
41 |
+
const [showMenuOption, setShowMenuOption]:any = useState({state:false,positions:[0,0,0,0],id:""})
|
42 |
+
const [searchTag, setSearchTag]:any = useState("")
|
43 |
+
|
44 |
+
const [migrateTag,setMigrateTag]:any = useState("")
|
45 |
+
|
46 |
+
const [manageBookmark, setManageBookmark]:any = useState({edit:"",delete:""})
|
47 |
+
const [createTag, setCreateTag]:any = useState({state:false,title:""})
|
48 |
+
const [removeTag, setRemoveTag]:any = useState({state:false, removing: false})
|
49 |
+
|
50 |
+
|
51 |
+
const controller = new AbortController();
|
52 |
+
const signal = controller.signal;
|
53 |
+
|
54 |
+
const RenderTag = useCallback(({item}:any) =>{
|
55 |
+
const [editTag, setEditTag]:any = useState(item.value)
|
56 |
+
useEffect(()=>{
|
57 |
+
},[manageBookmark])
|
58 |
+
|
59 |
+
return (<>
|
60 |
+
{item.value.includes(searchTag) &&
|
61 |
+
(
|
62 |
+
<View
|
63 |
+
style={{
|
64 |
+
display:"flex",
|
65 |
+
flexDirection:"row",
|
66 |
+
alignItems:"center",
|
67 |
+
justifyContent:"space-between",
|
68 |
+
gap:8,
|
69 |
+
zIndex:10,
|
70 |
+
}}
|
71 |
+
>
|
72 |
+
<>{manageBookmark.edit !== item.value && manageBookmark.delete !== item.value &&
|
73 |
+
(<View
|
74 |
+
style={{
|
75 |
+
width:"100%",
|
76 |
+
display:"flex",
|
77 |
+
flexDirection:"row",
|
78 |
+
justifyContent:"space-between",
|
79 |
+
alignItems:"center",
|
80 |
+
height:"auto",
|
81 |
+
gap:18,
|
82 |
+
}}
|
83 |
+
>
|
84 |
+
<Text
|
85 |
+
style={{
|
86 |
+
color:"white",
|
87 |
+
fontFamily:"roboto-medium",
|
88 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.025
|
89 |
+
}}
|
90 |
+
>{item.label}</Text>
|
91 |
+
<View
|
92 |
+
style={{
|
93 |
+
width:"auto",
|
94 |
+
height:"auto",
|
95 |
+
|
96 |
+
}}
|
97 |
+
>
|
98 |
+
|
99 |
+
<TouchableRipple
|
100 |
+
|
101 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
102 |
+
style={{
|
103 |
+
borderRadius:5,
|
104 |
+
borderWidth:0,
|
105 |
+
backgroundColor: "transparent",
|
106 |
+
padding:5,
|
107 |
+
|
108 |
+
}}
|
109 |
+
|
110 |
+
onPress={(event)=>{
|
111 |
+
if (manageBookmark.edit){
|
112 |
+
setManageBookmark({...manageBookmark,edit:""})
|
113 |
+
setEditTag("")
|
114 |
+
}
|
115 |
+
|
116 |
+
const x = event.nativeEvent.pageX
|
117 |
+
const y = event.nativeEvent.pageY
|
118 |
+
|
119 |
+
setShowMenuOption({
|
120 |
+
...showMenuOption,
|
121 |
+
state: showMenuOption.id === item.value ? false : true,
|
122 |
+
positions:[y+((Dimensions.width+Dimensions.height)/2)*0.0225,0,x-((Dimensions.width+Dimensions.height)/2)*0.18,0],
|
123 |
+
id:showMenuOption.id === item.value ? "" : item.value,
|
124 |
+
})
|
125 |
+
|
126 |
+
|
127 |
+
|
128 |
+
}}
|
129 |
+
>
|
130 |
+
|
131 |
+
<Icon source={"dots-vertical"} size={((Dimensions.width+Dimensions.height)/2)*0.035} color={Theme[themeTypeContext].icon_color}/>
|
132 |
+
</TouchableRipple>
|
133 |
+
</View>
|
134 |
+
</View>)
|
135 |
+
}</>
|
136 |
+
<>{manageBookmark.edit === item.value &&
|
137 |
+
(<View
|
138 |
+
style={{
|
139 |
+
display:"flex",
|
140 |
+
flexDirection:"row",
|
141 |
+
justifyContent:"space-between",
|
142 |
+
alignItems:"center",
|
143 |
+
width:"100%",
|
144 |
+
height:"auto",
|
145 |
+
gap:12,
|
146 |
+
padding:12,
|
147 |
+
}}
|
148 |
+
>
|
149 |
+
<View
|
150 |
+
style={{flex:1}}
|
151 |
+
>
|
152 |
+
<TextInput mode="outlined" label="Edit" textColor={Theme[themeTypeContext].text_color} maxLength={72}
|
153 |
+
autoFocus={true}
|
154 |
+
right={<TextInput.Affix text={`| Max: 72`} />}
|
155 |
+
style={{
|
156 |
+
backgroundColor:Theme[themeTypeContext].background_color,
|
157 |
+
borderColor:Theme[themeTypeContext].border_color,
|
158 |
+
|
159 |
+
}}
|
160 |
+
outlineColor={Theme[themeTypeContext].text_input_border_color}
|
161 |
+
value={editTag}
|
162 |
+
onChangeText={(text)=>{
|
163 |
+
setEditTag(text)
|
164 |
+
}}
|
165 |
+
/>
|
166 |
+
</View>
|
167 |
+
<View
|
168 |
+
style={{
|
169 |
+
display:"flex",
|
170 |
+
flexDirection:"row",
|
171 |
+
gap:8,
|
172 |
+
}}
|
173 |
+
>
|
174 |
+
<TouchableRipple
|
175 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
176 |
+
style={{
|
177 |
+
borderRadius:5,
|
178 |
+
borderWidth:0,
|
179 |
+
backgroundColor: "transparent",
|
180 |
+
padding:5,
|
181 |
+
}}
|
182 |
+
|
183 |
+
onPress={()=>{
|
184 |
+
setManageBookmark({...manageBookmark,edit:""})
|
185 |
+
setEditTag("")
|
186 |
+
setShowMenuOption({...showMenuOption,state:false,id:""})
|
187 |
+
}}
|
188 |
+
>
|
189 |
+
|
190 |
+
<Icon source={"close"} size={((Dimensions.width+Dimensions.height)/2)*0.035} color={"red"}/>
|
191 |
+
</TouchableRipple>
|
192 |
+
<TouchableRipple
|
193 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
194 |
+
style={{
|
195 |
+
borderRadius:5,
|
196 |
+
borderWidth:0,
|
197 |
+
backgroundColor: "transparent",
|
198 |
+
padding:5,
|
199 |
+
}}
|
200 |
+
|
201 |
+
onPress={async ()=>{
|
202 |
+
const stored_bookmark = await Storage.get("bookmark");
|
203 |
+
|
204 |
+
const index = stored_bookmark.findIndex((item:string) => item === manageBookmark.edit);
|
205 |
+
|
206 |
+
if (index !== -1){
|
207 |
+
stored_bookmark[index] = editTag;
|
208 |
+
await Storage.store("bookmark", stored_bookmark)
|
209 |
+
|
210 |
+
const stored_comics:any = await ComicStorage.getByTag(manageBookmark.edit)
|
211 |
+
for (const item of stored_comics){
|
212 |
+
await ComicStorage.replaceTag(item.source, item.id, editTag)
|
213 |
+
}
|
214 |
+
|
215 |
+
|
216 |
+
|
217 |
+
|
218 |
+
const index_2 = BOOKMARK_DATA.findIndex((item:any) => item.value === manageBookmark.edit);
|
219 |
+
if (index_2 !== -1){
|
220 |
+
BOOKMARK_DATA[index_2].label = editTag
|
221 |
+
BOOKMARK_DATA[index_2].value = editTag
|
222 |
+
}
|
223 |
+
SET_BOOKMARK_DATA(BOOKMARK_DATA)
|
224 |
+
setManageBookmark({...manageBookmark,edit:""})
|
225 |
+
setEditTag("")
|
226 |
+
|
227 |
+
onRefresh();
|
228 |
+
}
|
229 |
+
setShowMenuOption({...showMenuOption,state:false,id:""})
|
230 |
+
}}
|
231 |
+
>
|
232 |
+
|
233 |
+
<Icon source={"check"} size={((Dimensions.width+Dimensions.height)/2)*0.035} color={"green"}/>
|
234 |
+
</TouchableRipple>
|
235 |
+
</View>
|
236 |
+
|
237 |
+
|
238 |
+
|
239 |
+
</View>)
|
240 |
+
|
241 |
+
}</>
|
242 |
+
|
243 |
+
|
244 |
+
|
245 |
+
|
246 |
+
</View>
|
247 |
+
|
248 |
+
)
|
249 |
+
}
|
250 |
+
</>)
|
251 |
+
}
|
252 |
+
,[Theme,themeTypeContext,manageBookmark,searchTag,removeTag,createTag,showMenuOption,MIGRATE_BOOKMARK_DATA,BOOKMARK_DATA])
|
253 |
+
|
254 |
+
|
255 |
+
const load_bookmark = async ()=>{
|
256 |
+
const stored_bookmark_data = await Storage.get("bookmark") || []
|
257 |
+
if (stored_bookmark_data.length) {
|
258 |
+
const bookmark_data:Array<Object> = []
|
259 |
+
for (const item of stored_bookmark_data) {
|
260 |
+
bookmark_data.push({
|
261 |
+
label:item,
|
262 |
+
value:item,
|
263 |
+
})
|
264 |
+
}
|
265 |
+
|
266 |
+
SET_BOOKMARK_DATA(bookmark_data.sort())
|
267 |
+
}else SET_BOOKMARK_DATA([])
|
268 |
+
}
|
269 |
+
|
270 |
+
useEffect(()=>{
|
271 |
+
SET_MIGRATE_BOOKMARK_DATA([{label:"None",value:""},...BOOKMARK_DATA])
|
272 |
+
},[BOOKMARK_DATA])
|
273 |
+
|
274 |
+
useEffect(()=>{
|
275 |
+
load_bookmark()
|
276 |
+
return () => controller.abort();
|
277 |
+
},[])
|
278 |
+
|
279 |
+
return (<>{BOOKMARK_DATA !== null && <>
|
280 |
+
|
281 |
+
<View key={"BookmarkWidget"}
|
282 |
+
style={{
|
283 |
+
zIndex:10,
|
284 |
+
backgroundColor:Theme[themeTypeContext].background_color,
|
285 |
+
maxWidth:500,
|
286 |
+
width:"100%",
|
287 |
+
|
288 |
+
borderColor:Theme[themeTypeContext].border_color,
|
289 |
+
borderWidth:2,
|
290 |
+
borderRadius:8,
|
291 |
+
padding:12,
|
292 |
+
display:"flex",
|
293 |
+
justifyContent:"center",
|
294 |
+
|
295 |
+
flexDirection:"column",
|
296 |
+
gap:12,
|
297 |
+
}}
|
298 |
+
from={{
|
299 |
+
opacity: 0,
|
300 |
+
scale: 0.9,
|
301 |
+
}}
|
302 |
+
animate={{
|
303 |
+
opacity: 1,
|
304 |
+
scale: 1,
|
305 |
+
}}
|
306 |
+
exit={{
|
307 |
+
opacity: 0,
|
308 |
+
scale: 0.5,
|
309 |
+
}}
|
310 |
+
transition={{
|
311 |
+
type: 'timing',
|
312 |
+
duration: 500,
|
313 |
+
}}
|
314 |
+
exitTransition={{
|
315 |
+
type: 'timing',
|
316 |
+
duration: 250,
|
317 |
+
}}
|
318 |
+
>
|
319 |
+
|
320 |
+
<>{!createTag.state && !removeTag.state && <>
|
321 |
+
<View
|
322 |
+
|
323 |
+
style={{
|
324 |
+
display:"flex",
|
325 |
+
flexDirection:"column",
|
326 |
+
gap:18,
|
327 |
+
}}
|
328 |
+
>
|
329 |
+
<>{BOOKMARK_DATA.length
|
330 |
+
? <>
|
331 |
+
<View
|
332 |
+
style={{flex:1}}
|
333 |
+
>
|
334 |
+
<TextInput mode="outlined" label="Search" textColor={Theme[themeTypeContext].text_color}
|
335 |
+
|
336 |
+
style={{
|
337 |
+
|
338 |
+
backgroundColor:Theme[themeTypeContext].background_color,
|
339 |
+
borderColor:Theme[themeTypeContext].border_color,
|
340 |
+
|
341 |
+
}}
|
342 |
+
outlineColor={Theme[themeTypeContext].text_input_border_color}
|
343 |
+
value={searchTag}
|
344 |
+
onChange={(event)=>{
|
345 |
+
setSearchTag(event.nativeEvent.text)
|
346 |
+
}}
|
347 |
+
/>
|
348 |
+
</View>
|
349 |
+
<View
|
350 |
+
style={{
|
351 |
+
maxHeight:Dimensions.height*0.7
|
352 |
+
}}
|
353 |
+
>
|
354 |
+
<ScrollView
|
355 |
+
contentContainerStyle={{
|
356 |
+
display:"flex",
|
357 |
+
flexDirection:"column",
|
358 |
+
justifyContent:"space-around",
|
359 |
+
gap:8,
|
360 |
+
|
361 |
+
height:"auto",
|
362 |
+
paddingVertical:12,
|
363 |
+
paddingHorizontal:8,
|
364 |
+
}}
|
365 |
+
style={{
|
366 |
+
|
367 |
+
}}
|
368 |
+
>
|
369 |
+
<>{BOOKMARK_DATA.map((item:any) =>
|
370 |
+
(
|
371 |
+
<View key={item.value}>
|
372 |
+
<RenderTag item={item}/>
|
373 |
+
<View style={{width:"100%",height:2,backgroundColor:Theme[themeTypeContext].border_color}}/>
|
374 |
+
</View>
|
375 |
+
)
|
376 |
+
)}</>
|
377 |
+
</ScrollView>
|
378 |
+
</View>
|
379 |
+
</>
|
380 |
+
: <>
|
381 |
+
<Text style={{
|
382 |
+
width:"100%",
|
383 |
+
textAlign:"center",
|
384 |
+
color:Theme[themeTypeContext].text_color,
|
385 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.045,
|
386 |
+
fontFamily:"roboto-bold",
|
387 |
+
}}>No tag found</Text>
|
388 |
+
</>
|
389 |
+
|
390 |
+
}</>
|
391 |
+
|
392 |
+
<View
|
393 |
+
style={{
|
394 |
+
display:"flex",
|
395 |
+
flexDirection:"row",
|
396 |
+
width:"100%",
|
397 |
+
justifyContent:"space-around",
|
398 |
+
alignItems:"center",
|
399 |
+
}}
|
400 |
+
>
|
401 |
+
<Button mode='contained'
|
402 |
+
labelStyle={{
|
403 |
+
color:Theme[themeTypeContext].text_color,
|
404 |
+
fontFamily:"roboto-medium",
|
405 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.02
|
406 |
+
}}
|
407 |
+
style={{backgroundColor:"green",borderRadius:5}}
|
408 |
+
onPress={(()=>{
|
409 |
+
setCreateTag({state:true,title:""})
|
410 |
+
setShowMenuOption({...showMenuOption,state:false,id:""})
|
411 |
+
})}
|
412 |
+
>+ Create</Button>
|
413 |
+
<Button mode='outlined'
|
414 |
+
labelStyle={{
|
415 |
+
color:Theme[themeTypeContext].text_color,
|
416 |
+
fontFamily:"roboto-medium",
|
417 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.02,
|
418 |
+
|
419 |
+
|
420 |
+
}}
|
421 |
+
style={{
|
422 |
+
|
423 |
+
borderRadius:5,
|
424 |
+
borderWidth:2,
|
425 |
+
borderColor:Theme[themeTypeContext].border_color
|
426 |
+
}}
|
427 |
+
onPress={(async ()=>{
|
428 |
+
setWidgetContext({state:false,component:<></>})
|
429 |
+
})}
|
430 |
+
>Done</Button>
|
431 |
+
</View>
|
432 |
+
</View>
|
433 |
+
</>}</>
|
434 |
+
|
435 |
+
<>{createTag.state &&
|
436 |
+
<>
|
437 |
+
<View
|
438 |
+
style={{
|
439 |
+
height:"auto",
|
440 |
+
display:"flex",
|
441 |
+
flexDirection:"column",
|
442 |
+
gap:12,
|
443 |
+
}}
|
444 |
+
>
|
445 |
+
<TextInput mode="outlined" label="Create Tag" textColor={Theme[themeTypeContext].text_color} maxLength={72}
|
446 |
+
placeholder="Bookmark Tag"
|
447 |
+
|
448 |
+
right={<TextInput.Affix text={`| Max: 72`} />}
|
449 |
+
style={{
|
450 |
+
|
451 |
+
backgroundColor:Theme[themeTypeContext].background_color,
|
452 |
+
borderColor:Theme[themeTypeContext].border_color,
|
453 |
+
|
454 |
+
}}
|
455 |
+
outlineColor={Theme[themeTypeContext].text_input_border_color}
|
456 |
+
value={createTag.title}
|
457 |
+
onChange={(event)=>{
|
458 |
+
setCreateTag({...createTag,title:event.nativeEvent.text})
|
459 |
+
}}
|
460 |
+
/>
|
461 |
+
</View>
|
462 |
+
<View
|
463 |
+
style={{
|
464 |
+
display:"flex",
|
465 |
+
flexDirection:"row",
|
466 |
+
width:"100%",
|
467 |
+
justifyContent:"space-around",
|
468 |
+
alignItems:"center",
|
469 |
+
}}
|
470 |
+
>
|
471 |
+
<Button mode='outlined'
|
472 |
+
labelStyle={{
|
473 |
+
color:Theme[themeTypeContext].text_color,
|
474 |
+
fontFamily:"roboto-medium",
|
475 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.02,
|
476 |
+
|
477 |
+
|
478 |
+
}}
|
479 |
+
style={{
|
480 |
+
|
481 |
+
borderRadius:5,
|
482 |
+
borderWidth:2,
|
483 |
+
borderColor:Theme[themeTypeContext].border_color
|
484 |
+
}}
|
485 |
+
onPress={(()=>{
|
486 |
+
|
487 |
+
setCreateTag({...createTag,state:false})
|
488 |
+
|
489 |
+
})}
|
490 |
+
>Cancel</Button>
|
491 |
+
<Button mode='contained'
|
492 |
+
labelStyle={{
|
493 |
+
color:Theme[themeTypeContext].text_color,
|
494 |
+
fontFamily:"roboto-medium",
|
495 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.02
|
496 |
+
}}
|
497 |
+
style={{backgroundColor:"green",borderRadius:5}}
|
498 |
+
onPress={(async()=>{
|
499 |
+
|
500 |
+
const title = createTag.title
|
501 |
+
if (!title) return
|
502 |
+
|
503 |
+
const stored_bookmark_data = await Storage.get("bookmark") || []
|
504 |
+
if (stored_bookmark_data.includes(title)){
|
505 |
+
Toast.show({
|
506 |
+
type: 'error',
|
507 |
+
text1: '🔖 Duplicate Bookmark',
|
508 |
+
text2: `Tag "${title}" already existed in your bookmark.`,
|
509 |
+
|
510 |
+
position: "bottom",
|
511 |
+
visibilityTime: 5000,
|
512 |
+
text1Style:{
|
513 |
+
fontFamily:"roboto-bold",
|
514 |
+
fontSize:((Dimensions.width+Dimensions.height)/2)*0.025
|
515 |
+
},
|
516 |
+
text2Style:{
|
517 |
+
fontFamily:"roboto-medium",
|
518 |
+
fontSize:((Dimensions.width+Dimensions.height)/2)*0.0185,
|
519 |
+
|
520 |
+
},
|
521 |
+
});
|
522 |
+
}else{
|
523 |
+
await Storage.store("bookmark", [...stored_bookmark_data,title].sort())
|
524 |
+
SET_BOOKMARK_DATA([...BOOKMARK_DATA,
|
525 |
+
{label:title,value:title}
|
526 |
+
].sort())
|
527 |
+
setCreateTag({state:false,title:""})
|
528 |
+
Toast.show({
|
529 |
+
type: 'info',
|
530 |
+
text1: '🔖 Create Bookmark',
|
531 |
+
text2: `Tag "${title}" added to your bookmark.`,
|
532 |
+
|
533 |
+
position: "bottom",
|
534 |
+
visibilityTime: 3000,
|
535 |
+
text1Style:{
|
536 |
+
fontFamily:"roboto-bold",
|
537 |
+
fontSize:((Dimensions.width+Dimensions.height)/2)*0.025
|
538 |
+
},
|
539 |
+
text2Style:{
|
540 |
+
fontFamily:"roboto-medium",
|
541 |
+
fontSize:((Dimensions.width+Dimensions.height)/2)*0.0185,
|
542 |
+
|
543 |
+
},
|
544 |
+
});
|
545 |
+
onRefresh()
|
546 |
+
}
|
547 |
+
})}
|
548 |
+
>Add</Button>
|
549 |
+
</View>
|
550 |
+
</>
|
551 |
+
}</>
|
552 |
+
|
553 |
+
</View>
|
554 |
+
<>{showMenuOption.state &&
|
555 |
+
<View
|
556 |
+
style={{
|
557 |
+
display:"flex",
|
558 |
+
position:"absolute",
|
559 |
+
zIndex:11,
|
560 |
+
justifyContent:"space-around",
|
561 |
+
flexDirection:"column",
|
562 |
+
|
563 |
+
backgroundColor:Theme[themeTypeContext].border_color,
|
564 |
+
top:showMenuOption.positions[0],
|
565 |
+
bottom:showMenuOption.positions[1],
|
566 |
+
left:showMenuOption.positions[2],
|
567 |
+
right:showMenuOption.positions[3],
|
568 |
+
|
569 |
+
width:(Dimensions.width+Dimensions.height)/2*0.2,
|
570 |
+
height:(Dimensions.width+Dimensions.height)/2*0.1,
|
571 |
+
|
572 |
+
borderRadius:5,
|
573 |
+
borderWidth:2,
|
574 |
+
borderColor:Theme[themeTypeContext].background_color
|
575 |
+
}}
|
576 |
+
>
|
577 |
+
<TouchableRipple
|
578 |
+
|
579 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
580 |
+
style={{
|
581 |
+
|
582 |
+
borderWidth:0,
|
583 |
+
backgroundColor: "transparent",
|
584 |
+
padding:5,
|
585 |
+
width:"100%",
|
586 |
+
|
587 |
+
}}
|
588 |
+
|
589 |
+
onPress={(event)=>{
|
590 |
+
setManageBookmark({...manageBookmark,edit:showMenuOption.id})
|
591 |
+
setShowMenuOption({...showMenuOption,state:false})
|
592 |
+
}}
|
593 |
+
>
|
594 |
+
<View
|
595 |
+
style={{
|
596 |
+
display:"flex",
|
597 |
+
flexDirection:"row",
|
598 |
+
justifyContent:"center",
|
599 |
+
alignItems:"center",
|
600 |
+
paddingHorizontal:18,
|
601 |
+
|
602 |
+
}}
|
603 |
+
>
|
604 |
+
<Icon source={"pencil"} size={((Dimensions.width+Dimensions.height)/2)*0.025} color={"cyan"}/>
|
605 |
+
<View>
|
606 |
+
<Text selectable={false}
|
607 |
+
style={{
|
608 |
+
textAlign:"center",
|
609 |
+
color:"cyan",
|
610 |
+
fontFamily:"roboto-medium",
|
611 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.02
|
612 |
+
}}
|
613 |
+
>Edit</Text>
|
614 |
+
</View>
|
615 |
+
</View>
|
616 |
+
</TouchableRipple>
|
617 |
+
<View style={{width:"100%",height:2,backgroundColor:Theme[themeTypeContext].background_color}}/>
|
618 |
+
<TouchableRipple
|
619 |
+
|
620 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
621 |
+
style={{
|
622 |
+
|
623 |
+
borderWidth:0,
|
624 |
+
backgroundColor: "transparent",
|
625 |
+
padding:5,
|
626 |
+
width:"100%",
|
627 |
+
}}
|
628 |
+
|
629 |
+
onPress={(event)=>{
|
630 |
+
setManageBookmark({...manageBookmark,edit:"",delete:showMenuOption.id})
|
631 |
+
setShowMenuOption({...showMenuOption,state:false})
|
632 |
+
}}
|
633 |
+
>
|
634 |
+
<View
|
635 |
+
style={{
|
636 |
+
display:"flex",
|
637 |
+
flexDirection:"row",
|
638 |
+
justifyContent:"center",
|
639 |
+
alignItems:"center",
|
640 |
+
paddingHorizontal:18,
|
641 |
+
|
642 |
+
}}
|
643 |
+
>
|
644 |
+
<Icon source={"trash-can"} size={((Dimensions.width+Dimensions.height)/2)*0.025} color={"red"}/>
|
645 |
+
<View>
|
646 |
+
<Text selectable={false}
|
647 |
+
style={{
|
648 |
+
textAlign:"center",
|
649 |
+
color:"red",
|
650 |
+
fontFamily:"roboto-medium",
|
651 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.02
|
652 |
+
}}
|
653 |
+
>Delete</Text>
|
654 |
+
</View>
|
655 |
+
</View>
|
656 |
+
</TouchableRipple>
|
657 |
+
|
658 |
+
</View>
|
659 |
+
|
660 |
+
}</>
|
661 |
+
<>{manageBookmark.delete && (
|
662 |
+
|
663 |
+
|
664 |
+
<View
|
665 |
+
style={{
|
666 |
+
top:0,
|
667 |
+
left:0,
|
668 |
+
position:"absolute",
|
669 |
+
width:Dimensions.width,
|
670 |
+
height:Dimensions.height,
|
671 |
+
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
672 |
+
zIndex:11,
|
673 |
+
display:"flex",
|
674 |
+
justifyContent:"center",
|
675 |
+
alignItems:"center",
|
676 |
+
padding:15,
|
677 |
+
}}
|
678 |
+
>
|
679 |
+
<View
|
680 |
+
style={{
|
681 |
+
backgroundColor:Theme[themeTypeContext].background_color,
|
682 |
+
maxWidth:500,
|
683 |
+
width:"100%",
|
684 |
+
height:"auto",
|
685 |
+
|
686 |
+
borderColor:Theme[themeTypeContext].border_color,
|
687 |
+
borderWidth:2,
|
688 |
+
borderRadius:8,
|
689 |
+
padding:12,
|
690 |
+
display:"flex",
|
691 |
+
justifyContent:"center",
|
692 |
+
|
693 |
+
flexDirection:"column",
|
694 |
+
gap:18,
|
695 |
+
}}
|
696 |
+
>
|
697 |
+
<View
|
698 |
+
style={{
|
699 |
+
borderBottomWidth:2,
|
700 |
+
borderColor:Theme[themeTypeContext].border_color,
|
701 |
+
padding:8,
|
702 |
+
width:"100%",
|
703 |
+
}}
|
704 |
+
>
|
705 |
+
<Text
|
706 |
+
numberOfLines={1}
|
707 |
+
style={{
|
708 |
+
color:"red",
|
709 |
+
fontFamily:"roboto-bold",
|
710 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.03,
|
711 |
+
textAlign:"center",
|
712 |
+
}}
|
713 |
+
>Delete Tag: "{manageBookmark.delete}"</Text>
|
714 |
+
</View>
|
715 |
+
<View
|
716 |
+
style={{
|
717 |
+
width:"100%",
|
718 |
+
display:"flex",
|
719 |
+
flexDirection:"column",
|
720 |
+
gap:12,
|
721 |
+
}}
|
722 |
+
>
|
723 |
+
<View style={{flex:1}}>
|
724 |
+
<Dropdown
|
725 |
+
theme_type={themeTypeContext}
|
726 |
+
Dimensions={Dimensions}
|
727 |
+
|
728 |
+
label='Migrate comics to tag'
|
729 |
+
data={MIGRATE_BOOKMARK_DATA.filter((item:any) => item.value !== manageBookmark.delete)}
|
730 |
+
value={migrateTag}
|
731 |
+
onChange={(async (item:any) => {
|
732 |
+
setMigrateTag(item.value)
|
733 |
+
})}
|
734 |
+
/>
|
735 |
+
</View>
|
736 |
+
<>{!migrateTag && (
|
737 |
+
<Text
|
738 |
+
style={{
|
739 |
+
color:Theme[themeTypeContext].text_color,
|
740 |
+
fontFamily:"roboto-bold",
|
741 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.02,
|
742 |
+
textAlign:"center",
|
743 |
+
}}
|
744 |
+
>Setting migration to None will remove all comics and chapters for this bookmark tag.</Text>
|
745 |
+
)}</>
|
746 |
+
<View
|
747 |
+
style={{
|
748 |
+
display:"flex",
|
749 |
+
flexDirection:"row",
|
750 |
+
width:"100%",
|
751 |
+
justifyContent:"space-around",
|
752 |
+
alignItems:"center",
|
753 |
+
}}
|
754 |
+
>
|
755 |
+
<>{migrateTag
|
756 |
+
|
757 |
+
? <TouchableRipple
|
758 |
+
|
759 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
760 |
+
style={{
|
761 |
+
|
762 |
+
borderWidth:0,
|
763 |
+
backgroundColor: "blue",
|
764 |
+
padding:5,
|
765 |
+
borderRadius:8,
|
766 |
+
paddingHorizontal:12,
|
767 |
+
paddingVertical:8,
|
768 |
+
|
769 |
+
|
770 |
+
}}
|
771 |
+
|
772 |
+
onPress={async (event)=>{
|
773 |
+
const stored_bookmark = await Storage.get("bookmark")
|
774 |
+
const index = stored_bookmark.findIndex((item:any) => item === manageBookmark.delete)
|
775 |
+
if (index === -1) return
|
776 |
+
|
777 |
+
const stored_comics = await ComicStorage.getByTag(manageBookmark.delete)
|
778 |
+
for (const comic of stored_comics) {
|
779 |
+
const source = comic.source;
|
780 |
+
const comic_id = comic.id
|
781 |
+
await ComicStorage.replaceTag(source,comic_id,migrateTag)
|
782 |
+
|
783 |
+
}
|
784 |
+
|
785 |
+
stored_bookmark.splice(index, 1);
|
786 |
+
await Storage.store("bookmark",stored_bookmark);
|
787 |
+
|
788 |
+
|
789 |
+
|
790 |
+
|
791 |
+
const index_2 = BOOKMARK_DATA.findIndex((item:any) => item.value === manageBookmark.delete);
|
792 |
+
if (index_2 !== -1){
|
793 |
+
BOOKMARK_DATA.splice(index_2, 1);
|
794 |
+
}
|
795 |
+
SET_BOOKMARK_DATA(BOOKMARK_DATA)
|
796 |
+
setManageBookmark({...manageBookmark,edit:"",delete:""})
|
797 |
+
setShowMenuOption({...showMenuOption,state:false,id:""})
|
798 |
+
setMigrateTag("")
|
799 |
+
|
800 |
+
onRefresh();
|
801 |
+
}}
|
802 |
+
>
|
803 |
+
|
804 |
+
<Text selectable={false}
|
805 |
+
style={{
|
806 |
+
textAlign:"center",
|
807 |
+
color:Theme[themeTypeContext].text_color,
|
808 |
+
fontFamily:"roboto-medium",
|
809 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.02
|
810 |
+
}}
|
811 |
+
>Migrate</Text>
|
812 |
+
|
813 |
+
</TouchableRipple>
|
814 |
+
: <TouchableRipple
|
815 |
+
|
816 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
817 |
+
style={{
|
818 |
+
|
819 |
+
borderWidth:0,
|
820 |
+
backgroundColor: "red",
|
821 |
+
padding:5,
|
822 |
+
borderRadius:8,
|
823 |
+
paddingHorizontal:12,
|
824 |
+
paddingVertical:8,
|
825 |
+
|
826 |
+
|
827 |
+
}}
|
828 |
+
|
829 |
+
onPress={async (event)=>{
|
830 |
+
const stored_bookmark = await Storage.get("bookmark");
|
831 |
+
const index = stored_bookmark.findIndex((item:any) => item === manageBookmark.delete)
|
832 |
+
if (index === -1) return
|
833 |
+
await ComicStorage.removeByTag(manageBookmark.delete);
|
834 |
+
stored_bookmark.splice(index, 1);
|
835 |
+
await Storage.store("bookmark",stored_bookmark);
|
836 |
+
|
837 |
+
const index_2 = BOOKMARK_DATA.findIndex((item:any) => item.value === manageBookmark.delete);
|
838 |
+
if (index_2 !== -1){
|
839 |
+
BOOKMARK_DATA.splice(index_2, 1);
|
840 |
+
}
|
841 |
+
SET_BOOKMARK_DATA(BOOKMARK_DATA)
|
842 |
+
setManageBookmark({...manageBookmark,edit:"",delete:""})
|
843 |
+
setShowMenuOption({...showMenuOption,state:false,id:""})
|
844 |
+
setMigrateTag("")
|
845 |
+
onRefresh()
|
846 |
+
|
847 |
+
}}
|
848 |
+
>
|
849 |
+
|
850 |
+
<Text selectable={false}
|
851 |
+
style={{
|
852 |
+
textAlign:"center",
|
853 |
+
color:Theme[themeTypeContext].text_color,
|
854 |
+
fontFamily:"roboto-medium",
|
855 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.02
|
856 |
+
}}
|
857 |
+
>Delete</Text>
|
858 |
+
|
859 |
+
</TouchableRipple>
|
860 |
+
}</>
|
861 |
+
<TouchableRipple
|
862 |
+
|
863 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
864 |
+
style={{
|
865 |
+
|
866 |
+
borderWidth:2,
|
867 |
+
borderColor:Theme[themeTypeContext].border_color,
|
868 |
+
backgroundColor: "transparent",
|
869 |
+
padding:5,
|
870 |
+
borderRadius:8,
|
871 |
+
paddingHorizontal:12,
|
872 |
+
paddingVertical:8,
|
873 |
+
|
874 |
+
|
875 |
+
}}
|
876 |
+
|
877 |
+
onPress={(event)=>{
|
878 |
+
setShowMenuOption({...showMenuOption,state:false,id:""})
|
879 |
+
setManageBookmark({...manageBookmark,edit:"",delete:""})
|
880 |
+
}}
|
881 |
+
>
|
882 |
+
|
883 |
+
<Text selectable={false}
|
884 |
+
style={{
|
885 |
+
textAlign:"center",
|
886 |
+
color:Theme[themeTypeContext].text_color,
|
887 |
+
fontFamily:"roboto-medium",
|
888 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.02
|
889 |
+
}}
|
890 |
+
>Cancel</Text>
|
891 |
+
|
892 |
+
</TouchableRipple>
|
893 |
+
</View>
|
894 |
+
|
895 |
+
</View>
|
896 |
+
</View>
|
897 |
+
|
898 |
+
</View>
|
899 |
+
)}</>
|
900 |
+
|
901 |
+
</>}</>)
|
902 |
+
}
|
903 |
+
|
904 |
+
export default BookmarkWidget;
|
frontend/app/bookmark/index.tsx
ADDED
@@ -0,0 +1,291 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React, { useEffect, useState, useCallback, useContext, useRef, useMemo } from 'react';
|
2 |
+
import { Link, router, useLocalSearchParams, useFocusEffect } from 'expo-router';
|
3 |
+
import { Image as RNImage, StyleSheet, useWindowDimensions, ScrollView, Pressable, RefreshControl, Platform, FlatList, TouchableOpacity } from 'react-native';
|
4 |
+
import { SafeAreaView } from 'react-native-safe-area-context';
|
5 |
+
import { Icon, MD3Colors, Button, Text, TextInput, TouchableRipple } from 'react-native-paper';
|
6 |
+
import CircularProgress from 'react-native-circular-progress-indicator';
|
7 |
+
import { ActivityIndicator } from 'react-native-paper';
|
8 |
+
import { FlashList } from "@shopify/flash-list";
|
9 |
+
|
10 |
+
|
11 |
+
import uuid from 'react-native-uuid';
|
12 |
+
import Toast from 'react-native-toast-message';
|
13 |
+
import { View, AnimatePresence } from 'moti';
|
14 |
+
import * as Clipboard from 'expo-clipboard';
|
15 |
+
import * as FileSystem from 'expo-file-system';
|
16 |
+
import NetInfo from "@react-native-community/netinfo";
|
17 |
+
import { Marquee } from '@animatereactnative/marquee';
|
18 |
+
import { Slider } from '@rneui/themed-edge';
|
19 |
+
|
20 |
+
import BookmarkComponent from './components/bookmark_component';
|
21 |
+
import ComicComponent from './components/comic_component';
|
22 |
+
import BookmarkWidget from './components/widgets/bookmark';
|
23 |
+
|
24 |
+
import { __styles } from './stylesheet/styles';
|
25 |
+
import Storage from '@/constants/module/storages/storage';
|
26 |
+
import ChapterStorage from '@/constants/module/storages/chapter_storage';
|
27 |
+
import Image from '@/components/Image';
|
28 |
+
import {CONTEXT} from '@/constants/module/context';
|
29 |
+
import {blobToBase64, base64ToBlob, getImageLayout} from "@/constants/module/file_manager";
|
30 |
+
import Theme from '@/constants/theme';
|
31 |
+
import ComicStorage from '@/constants/module/storages/comic_storage';
|
32 |
+
|
33 |
+
const Index = ({}:any) => {
|
34 |
+
const Dimensions = useWindowDimensions();
|
35 |
+
const controller = new AbortController();
|
36 |
+
const signal = controller.signal;
|
37 |
+
|
38 |
+
const {showMenuContext, setShowMenuContext}:any = useContext(CONTEXT)
|
39 |
+
const {themeTypeContext, setThemeTypeContext}:any = useContext(CONTEXT)
|
40 |
+
const {apiBaseContext, setApiBaseContext}:any = useContext(CONTEXT)
|
41 |
+
const {widgetContext, setWidgetContext}:any = useContext(CONTEXT)
|
42 |
+
const {showCloudflareTurnstileContext, setShowCloudflareTurnstileContext}:any = useContext(CONTEXT)
|
43 |
+
|
44 |
+
const [styles, setStyles]:any = useState("")
|
45 |
+
const [isLoading, setIsLoading] = useState<boolean>(true)
|
46 |
+
const [onRefresh, setOnRefresh] = useState(false)
|
47 |
+
const [search, setSearch] = useState<any>({state:false,text:""})
|
48 |
+
|
49 |
+
const [BOOKMARK_DATA, SET_BOOKMARK_DATA]:any = useState([])
|
50 |
+
const [SELECTED_BOOKMARK, SET_SELECTED_BOOKMARK] = useState<string>("")
|
51 |
+
|
52 |
+
const [COMIC_DATA, SET_COMIC_DATA] = useState<any>([])
|
53 |
+
|
54 |
+
useEffect(() => {
|
55 |
+
(async ()=>{
|
56 |
+
if (!SELECTED_BOOKMARK) return
|
57 |
+
const stored_comic = await ComicStorage.getByTag(SELECTED_BOOKMARK)
|
58 |
+
console.log(stored_comic)
|
59 |
+
SET_COMIC_DATA(stored_comic)
|
60 |
+
})()
|
61 |
+
},[SELECTED_BOOKMARK,onRefresh])
|
62 |
+
|
63 |
+
useFocusEffect(useCallback(() => {
|
64 |
+
(async ()=>{
|
65 |
+
setIsLoading(true)
|
66 |
+
const stored_bookmark = await Storage.get("bookmark") || []
|
67 |
+
console.log(stored_bookmark)
|
68 |
+
|
69 |
+
SET_BOOKMARK_DATA(stored_bookmark)
|
70 |
+
console.log("AA",stored_bookmark.length )
|
71 |
+
if (stored_bookmark.length) {
|
72 |
+
SET_SELECTED_BOOKMARK(stored_bookmark[0])
|
73 |
+
}
|
74 |
+
setIsLoading(false)
|
75 |
+
})()
|
76 |
+
},[onRefresh]))
|
77 |
+
|
78 |
+
const renderBookmarkComponent = useCallback(({item,index}:any) => {
|
79 |
+
return <BookmarkComponent key={index.toString()} item={item}
|
80 |
+
SELECTED_BOOKMARK={SELECTED_BOOKMARK} SET_SELECTED_BOOKMARK={SET_SELECTED_BOOKMARK}
|
81 |
+
/>
|
82 |
+
},[SELECTED_BOOKMARK])
|
83 |
+
|
84 |
+
const renderComicComponent = useCallback(({item,index}:any) => {
|
85 |
+
return <ComicComponent key={index.toString()} item={item}
|
86 |
+
|
87 |
+
/>
|
88 |
+
},[SELECTED_BOOKMARK])
|
89 |
+
|
90 |
+
useFocusEffect(useCallback(() => {
|
91 |
+
setIsLoading(true)
|
92 |
+
setShowMenuContext(true)
|
93 |
+
setStyles(__styles(themeTypeContext,Dimensions))
|
94 |
+
|
95 |
+
return () => {
|
96 |
+
controller.abort();
|
97 |
+
};
|
98 |
+
},[]))
|
99 |
+
|
100 |
+
return (<>{styles && ! isLoading
|
101 |
+
? <>
|
102 |
+
<View style={styles.screen_container}
|
103 |
+
|
104 |
+
>
|
105 |
+
<View style={styles.header_container}>
|
106 |
+
<Text style={styles.header_text}>Bookmark</Text>
|
107 |
+
|
108 |
+
<View style={styles.header_button_box}>
|
109 |
+
|
110 |
+
<TouchableRipple
|
111 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
112 |
+
style={{
|
113 |
+
...styles.header_search_button,
|
114 |
+
backgroundColor: search.state ? Theme[themeTypeContext].button_selected_color : "transparent",
|
115 |
+
}}
|
116 |
+
|
117 |
+
onPress={() => {
|
118 |
+
setSearch({...search,state:!search.state})
|
119 |
+
}}
|
120 |
+
>
|
121 |
+
<Icon source={"magnify"} size={((Dimensions.width+Dimensions.height)/2)*0.04} color={Theme[themeTypeContext].icon_color}/>
|
122 |
+
</TouchableRipple>
|
123 |
+
</View>
|
124 |
+
|
125 |
+
|
126 |
+
</View>
|
127 |
+
<>{search.state && (
|
128 |
+
<View
|
129 |
+
style={{
|
130 |
+
width:"100%",
|
131 |
+
height:"auto",
|
132 |
+
paddingHorizontal:12,
|
133 |
+
paddingVertical:18,
|
134 |
+
borderBottomWidth:2,
|
135 |
+
borderColor:Theme[themeTypeContext].border_color,
|
136 |
+
}}
|
137 |
+
>
|
138 |
+
<TextInput mode="outlined" label="Search" textColor={Theme[themeTypeContext].text_color}
|
139 |
+
|
140 |
+
style={{
|
141 |
+
|
142 |
+
backgroundColor:Theme[themeTypeContext].background_color,
|
143 |
+
borderColor:Theme[themeTypeContext].border_color,
|
144 |
+
|
145 |
+
}}
|
146 |
+
outlineColor={Theme[themeTypeContext].text_input_border_color}
|
147 |
+
value={search.text}
|
148 |
+
onChange={(event)=>{
|
149 |
+
setSearch({...search,text:event.nativeEvent.text})
|
150 |
+
}}
|
151 |
+
/>
|
152 |
+
|
153 |
+
</View>
|
154 |
+
)}</>
|
155 |
+
<View
|
156 |
+
style={{
|
157 |
+
display:"flex",
|
158 |
+
flexDirection:"row",
|
159 |
+
width:"100%",
|
160 |
+
height:"auto",
|
161 |
+
backgroundColor:"transparent",
|
162 |
+
borderBottomWidth:2,
|
163 |
+
borderColor:Theme[themeTypeContext].border_color,
|
164 |
+
}}
|
165 |
+
>
|
166 |
+
<FlatList
|
167 |
+
contentContainerStyle={{
|
168 |
+
flex:1,
|
169 |
+
flexGrow: 1,
|
170 |
+
justifyContent: 'center',
|
171 |
+
}}
|
172 |
+
horizontal={true}
|
173 |
+
data={BOOKMARK_DATA}
|
174 |
+
renderItem={renderBookmarkComponent}
|
175 |
+
ItemSeparatorComponent={undefined}
|
176 |
+
/>
|
177 |
+
<TouchableRipple
|
178 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
179 |
+
style={{
|
180 |
+
borderRadius:5,
|
181 |
+
borderWidth:0,
|
182 |
+
backgroundColor: "transparent",
|
183 |
+
padding:5,
|
184 |
+
justifyContent:"center",
|
185 |
+
alignItems:"center",
|
186 |
+
}}
|
187 |
+
onPress={() => {
|
188 |
+
setWidgetContext({state:true,component:
|
189 |
+
<BookmarkWidget
|
190 |
+
setIsLoading={setIsLoading}
|
191 |
+
onRefresh={()=>{setOnRefresh(!onRefresh)}}
|
192 |
+
|
193 |
+
/>
|
194 |
+
})
|
195 |
+
}}
|
196 |
+
>
|
197 |
+
<Icon source={require("@/assets/icons/tag-edit-outline.png")} size={((Dimensions.width+Dimensions.height)/2)*0.0325} color={Theme[themeTypeContext].icon_color}/>
|
198 |
+
</TouchableRipple>
|
199 |
+
</View>
|
200 |
+
<FlatList
|
201 |
+
contentContainerStyle={{
|
202 |
+
flexGrow: 1,
|
203 |
+
padding:12,
|
204 |
+
flexDirection:"row",
|
205 |
+
gap:Math.max((Dimensions.width+Dimensions.height)/2*0.015,8),
|
206 |
+
flexWrap:"wrap",
|
207 |
+
}}
|
208 |
+
renderItem={renderComicComponent}
|
209 |
+
ItemSeparatorComponent={undefined}
|
210 |
+
data={COMIC_DATA.filter((item:any) => item.info.title.toLowerCase().includes(search.text.toLowerCase()))}
|
211 |
+
ListEmptyComponent={
|
212 |
+
<View
|
213 |
+
style={{
|
214 |
+
width:"100%",
|
215 |
+
height:"100%",
|
216 |
+
backgroundColor:"transparent",
|
217 |
+
display:"flex",
|
218 |
+
justifyContent:"center",
|
219 |
+
alignItems:"center",
|
220 |
+
flexDirection:"row",
|
221 |
+
gap:12,
|
222 |
+
}}
|
223 |
+
>
|
224 |
+
<>{BOOKMARK_DATA.length
|
225 |
+
? <>
|
226 |
+
{search.text && COMIC_DATA.length
|
227 |
+
? <>
|
228 |
+
<Icon source={"magnify-scan"} color={Theme[themeTypeContext].icon_color} size={((Dimensions.width+Dimensions.height)/2)*0.03}/>
|
229 |
+
<Text selectable={false}
|
230 |
+
style={{
|
231 |
+
fontFamily:"roboto-bold",
|
232 |
+
fontSize:((Dimensions.width+Dimensions.height)/2)*0.025,
|
233 |
+
color:Theme[themeTypeContext].text_color,
|
234 |
+
}}
|
235 |
+
>Search no result</Text>
|
236 |
+
</>
|
237 |
+
: <>
|
238 |
+
<Icon source={require("@/assets/icons/tag-hidden.png")} color={Theme[themeTypeContext].icon_color} size={((Dimensions.width+Dimensions.height)/2)*0.03}/>
|
239 |
+
<Text selectable={false}
|
240 |
+
style={{
|
241 |
+
fontFamily:"roboto-bold",
|
242 |
+
fontSize:((Dimensions.width+Dimensions.height)/2)*0.025,
|
243 |
+
color:Theme[themeTypeContext].text_color,
|
244 |
+
}}
|
245 |
+
>This tag is empty.</Text>
|
246 |
+
</>
|
247 |
+
}
|
248 |
+
</>
|
249 |
+
: <View style={{
|
250 |
+
display:"flex",
|
251 |
+
flexDirection:"column",
|
252 |
+
justifyContent:"center",
|
253 |
+
alignItems:"center",
|
254 |
+
width:"100%",
|
255 |
+
height:"auto",
|
256 |
+
}}>
|
257 |
+
|
258 |
+
<Text selectable={false}
|
259 |
+
style={{
|
260 |
+
fontFamily:"roboto-bold",
|
261 |
+
fontSize:((Dimensions.width+Dimensions.height)/2)*0.025,
|
262 |
+
color:Theme[themeTypeContext].text_color,
|
263 |
+
textAlign:"center",
|
264 |
+
}}
|
265 |
+
>
|
266 |
+
No tag found. {"\n"}Press{" "}
|
267 |
+
<Icon source={require("@/assets/icons/tag-edit-outline.png")} color={Theme[themeTypeContext].icon_color} size={((Dimensions.width+Dimensions.height)/2)*0.03}/>
|
268 |
+
{" "}to create bookmark tag.
|
269 |
+
</Text>
|
270 |
+
</View>
|
271 |
+
|
272 |
+
}
|
273 |
+
</>
|
274 |
+
|
275 |
+
</View>
|
276 |
+
|
277 |
+
}
|
278 |
+
/>
|
279 |
+
</View>
|
280 |
+
|
281 |
+
</>
|
282 |
+
: <View style={{zIndex:5,width:"100%",height:"100%",display:"flex",justifyContent:"center",alignItems:"center",backgroundColor:Theme[themeTypeContext].background_color}}>
|
283 |
+
<Image setShowCloudflareTurnstile={setShowCloudflareTurnstileContext} source={require("@/assets/gif/cat-loading.gif")} style={{width:((Dimensions.width+Dimensions.height)/2)*0.15,height:((Dimensions.width+Dimensions.height)/2)*0.15}}/>
|
284 |
+
</View>
|
285 |
+
}</>)
|
286 |
+
|
287 |
+
|
288 |
+
}
|
289 |
+
|
290 |
+
export default Index;
|
291 |
+
|
frontend/app/bookmark/stylesheet/styles.tsx
ADDED
@@ -0,0 +1,68 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { StyleSheet } from "react-native";
|
2 |
+
import Theme from "@/constants/theme";
|
3 |
+
|
4 |
+
export const __styles:any = (theme_type:string,Dimensions:any) => {
|
5 |
+
return StyleSheet.create({
|
6 |
+
screen_container: {
|
7 |
+
display: "flex",
|
8 |
+
width: "100%",
|
9 |
+
height: "100%",
|
10 |
+
backgroundColor: Theme[theme_type].background_color,
|
11 |
+
},
|
12 |
+
header_container: {
|
13 |
+
display: "flex",
|
14 |
+
flexDirection: "row",
|
15 |
+
justifyContent: "space-between",
|
16 |
+
alignItems: "center",
|
17 |
+
paddingHorizontal: 15,
|
18 |
+
paddingVertical:10,
|
19 |
+
backgroundColor: Theme[theme_type].background_color,
|
20 |
+
borderBottomWidth: 0.5,
|
21 |
+
borderColor: Theme[theme_type].border_color,
|
22 |
+
},
|
23 |
+
header_text:{
|
24 |
+
fontFamily: "roboto-medium",
|
25 |
+
fontSize: ((Dimensions.width+Dimensions.height)/2)*0.04,
|
26 |
+
color: Theme[theme_type].text_color,
|
27 |
+
|
28 |
+
},
|
29 |
+
header_search_button:{
|
30 |
+
borderRadius:5,
|
31 |
+
borderWidth:0,
|
32 |
+
padding:5,
|
33 |
+
},
|
34 |
+
|
35 |
+
item_box:{
|
36 |
+
display:"flex",
|
37 |
+
flexDirection:"column",
|
38 |
+
alignItems:"center",
|
39 |
+
gap:15,
|
40 |
+
height:"auto",
|
41 |
+
width:Math.max(((Dimensions.width+Dimensions.height)/2)*0.225,100),
|
42 |
+
borderRadius:8,
|
43 |
+
|
44 |
+
},
|
45 |
+
item_cover:{
|
46 |
+
width:"100%",
|
47 |
+
height:Math.max(((Dimensions.width+Dimensions.height)/2)*0.325,125),
|
48 |
+
borderRadius:8,
|
49 |
+
shadowColor: "#000",
|
50 |
+
shadowOffset: {
|
51 |
+
width: 0,
|
52 |
+
height: 1,
|
53 |
+
},
|
54 |
+
shadowOpacity: 0.22,
|
55 |
+
shadowRadius: 2.22,
|
56 |
+
|
57 |
+
elevation: 3,
|
58 |
+
},
|
59 |
+
item_title:{
|
60 |
+
color: Theme[theme_type].text_color,
|
61 |
+
fontFamily: "roboto-medium",
|
62 |
+
fontSize: ((Dimensions.width+Dimensions.height)/2)*0.025,
|
63 |
+
width:"100%",
|
64 |
+
height:"auto",
|
65 |
+
textAlign:"center",
|
66 |
+
flexShrink:1,
|
67 |
+
}
|
68 |
+
})}
|
frontend/app/explore/index.tsx
CHANGED
@@ -9,7 +9,7 @@ import Dropdown from '@/components/dropdown';
|
|
9 |
|
10 |
|
11 |
import Theme from '@/constants/theme';
|
12 |
-
import { __styles } from './stylesheet/
|
13 |
import Storage from '@/constants/module/storages/storage';
|
14 |
import ImageStorage from '@/constants/module/storages/image_cache_storage';
|
15 |
import { CONTEXT } from '@/constants/module/context';
|
@@ -52,7 +52,7 @@ const Index = ({}:any) => {
|
|
52 |
};
|
53 |
}, []))
|
54 |
|
55 |
-
|
56 |
(async ()=>{
|
57 |
setStyles(__styles(themeTypeContext,Dimensions))
|
58 |
|
@@ -69,7 +69,7 @@ const Index = ({}:any) => {
|
|
69 |
return () => {
|
70 |
controller.abort();
|
71 |
};
|
72 |
-
},[])
|
73 |
|
74 |
|
75 |
|
@@ -397,7 +397,7 @@ const Index = ({}:any) => {
|
|
397 |
contentFit="cover" transition={1000}
|
398 |
/>
|
399 |
|
400 |
-
<Text style={styles.item_title}>{item.title}</Text>
|
401 |
</>
|
402 |
</TouchableRipple>
|
403 |
|
|
|
9 |
|
10 |
|
11 |
import Theme from '@/constants/theme';
|
12 |
+
import { __styles } from './stylesheet/styles';
|
13 |
import Storage from '@/constants/module/storages/storage';
|
14 |
import ImageStorage from '@/constants/module/storages/image_cache_storage';
|
15 |
import { CONTEXT } from '@/constants/module/context';
|
|
|
52 |
};
|
53 |
}, []))
|
54 |
|
55 |
+
useFocusEffect(useCallback(() => {
|
56 |
(async ()=>{
|
57 |
setStyles(__styles(themeTypeContext,Dimensions))
|
58 |
|
|
|
69 |
return () => {
|
70 |
controller.abort();
|
71 |
};
|
72 |
+
},[]))
|
73 |
|
74 |
|
75 |
|
|
|
397 |
contentFit="cover" transition={1000}
|
398 |
/>
|
399 |
|
400 |
+
<Text selectable={false} style={styles.item_title}>{item.title}</Text>
|
401 |
</>
|
402 |
</TouchableRipple>
|
403 |
|
frontend/app/explore/stylesheet/{show_list_styles.tsx → styles.tsx}
RENAMED
File without changes
|
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="/bookmark" />
|
13 |
)
|
14 |
|
15 |
}
|
frontend/app/read/[source]/[comic_id]/[chapter_idx].tsx
CHANGED
@@ -16,6 +16,7 @@ import * as FileSystem from 'expo-file-system';
|
|
16 |
import NetInfo from "@react-native-community/netinfo";
|
17 |
import { Marquee } from '@animatereactnative/marquee';
|
18 |
import { Slider } from '@rneui/themed-edge';
|
|
|
19 |
|
20 |
import Storage from '@/constants/module/storages/storage';
|
21 |
import ChapterStorage from '@/constants/module/storages/chapter_storage';
|
@@ -28,12 +29,12 @@ import Menu from '../../components/menu/menu';
|
|
28 |
import Disqus from '../../components/disqus';
|
29 |
import { get_chapter } from '../../modules/get_chapter';
|
30 |
import ComicStorage from '@/constants/module/storages/comic_storage';
|
|
|
31 |
|
32 |
const Index = ({}:any) => {
|
33 |
const SOURCE = useLocalSearchParams().source;
|
34 |
const COMIC_ID = useLocalSearchParams().comic_id;
|
35 |
const Dimensions = useWindowDimensions();
|
36 |
-
const StaticDimensions = useMemo(() => Dimensions, [])
|
37 |
|
38 |
|
39 |
const {showMenuContext, setShowMenuContext}:any = useContext(CONTEXT)
|
@@ -46,9 +47,11 @@ const Index = ({}:any) => {
|
|
46 |
const [chapterInfo, setChapterInfo]:any = useState({})
|
47 |
const [showOptions, setShowOptions]:any = useState({type:"general",state:false})
|
48 |
const [DATA, SET_DATA]:any = useState([])
|
49 |
-
|
|
|
50 |
const [zoom, setZoom]:any = useState(0)
|
51 |
|
|
|
52 |
const CHAPTER_IDX = useRef(Number(useLocalSearchParams().chapter_idx as string));
|
53 |
|
54 |
|
@@ -60,6 +63,27 @@ const Index = ({}:any) => {
|
|
60 |
|
61 |
// First Load
|
62 |
useEffect(()=>{(async () => {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
63 |
if (!SOURCE || !COMIC_ID || !CHAPTER_IDX.current){
|
64 |
setIsError({state:true,text:"Invalid source, comic id or chapter idx!"})
|
65 |
return
|
@@ -94,39 +118,8 @@ const Index = ({}:any) => {
|
|
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
|
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 (<>
|
132 |
{isError.state
|
@@ -167,7 +160,7 @@ const Index = ({}:any) => {
|
|
167 |
}}
|
168 |
|
169 |
onPress={()=>{
|
170 |
-
router.replace(
|
171 |
}}
|
172 |
>
|
173 |
<Icon source={"arrow-left-thin"} size={((Dimensions.width+Dimensions.height)/2)*0.05} color={Theme[themeTypeContext].icon_color}/>
|
@@ -220,18 +213,15 @@ const Index = ({}:any) => {
|
|
220 |
zIndex:0
|
221 |
}}
|
222 |
>
|
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 &&
|
235 |
<View
|
236 |
style={{
|
237 |
position:"absolute",
|
@@ -304,7 +294,7 @@ const Index = ({}:any) => {
|
|
304 |
}}
|
305 |
|
306 |
onPress={()=>{
|
307 |
-
router.replace(`/view/${SOURCE}/${COMIC_ID}
|
308 |
}}
|
309 |
>
|
310 |
<Icon source={"arrow-left-thin"} size={((Dimensions.width+Dimensions.height)/2)*0.05} color={Theme[themeTypeContext].icon_color}/>
|
@@ -325,21 +315,7 @@ const Index = ({}:any) => {
|
|
325 |
{chapterInfo.title}
|
326 |
</Text>
|
327 |
</View>
|
328 |
-
|
329 |
-
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
330 |
-
style={{
|
331 |
-
borderRadius:5,
|
332 |
-
borderWidth:0,
|
333 |
-
backgroundColor: "transparent",
|
334 |
-
padding:5,
|
335 |
-
}}
|
336 |
-
|
337 |
-
onPress={()=>{
|
338 |
-
console.log("HO2h2")
|
339 |
-
}}
|
340 |
-
>
|
341 |
-
<Icon source={"cloud-refresh"} size={((Dimensions.width+Dimensions.height)/2)*0.05} color={Theme[themeTypeContext].icon_color}/>
|
342 |
-
</TouchableRipple>
|
343 |
</View>
|
344 |
<View
|
345 |
style={{
|
|
|
16 |
import NetInfo from "@react-native-community/netinfo";
|
17 |
import { Marquee } from '@animatereactnative/marquee';
|
18 |
import { Slider } from '@rneui/themed-edge';
|
19 |
+
import dayjs from 'dayjs'; import utc from 'dayjs/plugin/utc'; dayjs.extend(utc);
|
20 |
|
21 |
import Storage from '@/constants/module/storages/storage';
|
22 |
import ChapterStorage from '@/constants/module/storages/chapter_storage';
|
|
|
29 |
import Disqus from '../../components/disqus';
|
30 |
import { get_chapter } from '../../modules/get_chapter';
|
31 |
import ComicStorage from '@/constants/module/storages/comic_storage';
|
32 |
+
import { set } from 'lodash';
|
33 |
|
34 |
const Index = ({}:any) => {
|
35 |
const SOURCE = useLocalSearchParams().source;
|
36 |
const COMIC_ID = useLocalSearchParams().comic_id;
|
37 |
const Dimensions = useWindowDimensions();
|
|
|
38 |
|
39 |
|
40 |
const {showMenuContext, setShowMenuContext}:any = useContext(CONTEXT)
|
|
|
47 |
const [chapterInfo, setChapterInfo]:any = useState({})
|
48 |
const [showOptions, setShowOptions]:any = useState({type:"general",state:false})
|
49 |
const [DATA, SET_DATA]:any = useState([])
|
50 |
+
|
51 |
+
|
52 |
const [zoom, setZoom]:any = useState(0)
|
53 |
|
54 |
+
|
55 |
const CHAPTER_IDX = useRef(Number(useLocalSearchParams().chapter_idx as string));
|
56 |
|
57 |
|
|
|
63 |
|
64 |
// First Load
|
65 |
useEffect(()=>{(async () => {
|
66 |
+
setIsLoading(true)
|
67 |
+
|
68 |
+
const stored_recent = await Storage.get("RECENT") || []
|
69 |
+
stored_recent.sort((a:any,b:any) => b.timestamp - a.timestamp)
|
70 |
+
|
71 |
+
let exist = false
|
72 |
+
for (const i of stored_recent) {
|
73 |
+
if (i.source === SOURCE && i.comic_id === COMIC_ID) {
|
74 |
+
i.timestamp = dayjs().utc().valueOf()
|
75 |
+
exist = true
|
76 |
+
break
|
77 |
+
}
|
78 |
+
}
|
79 |
+
if (!exist) {
|
80 |
+
if (stored_recent.length >= 25) stored_recent.pop()
|
81 |
+
stored_recent.push({source:SOURCE,comic_id:COMIC_ID,timestamp:dayjs().utc().valueOf()})
|
82 |
+
}
|
83 |
+
|
84 |
+
await Storage.store("RECENT",stored_recent)
|
85 |
+
|
86 |
+
|
87 |
if (!SOURCE || !COMIC_ID || !CHAPTER_IDX.current){
|
88 |
setIsError({state:true,text:"Invalid source, comic id or chapter idx!"})
|
89 |
return
|
|
|
118 |
|
119 |
const renderItem = useCallback(({item,index}:any) => {
|
120 |
return <ChapterImage key={index} item={item} zoom={zoom} showOptions={showOptions} setShowOptions={setShowOptions} setIsLoading={setIsLoading} SET_DATA={SET_DATA}/>
|
121 |
+
},[zoom,showOptions])
|
122 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
123 |
|
124 |
return (<>
|
125 |
{isError.state
|
|
|
160 |
}}
|
161 |
|
162 |
onPress={()=>{
|
163 |
+
router.replace(`/view/${SOURCE}/${COMIC_ID}/?mode=local`)
|
164 |
}}
|
165 |
>
|
166 |
<Icon source={"arrow-left-thin"} size={((Dimensions.width+Dimensions.height)/2)*0.05} color={Theme[themeTypeContext].icon_color}/>
|
|
|
213 |
zIndex:0
|
214 |
}}
|
215 |
>
|
216 |
+
<FlatList
|
217 |
data={DATA}
|
218 |
renderItem={renderItem}
|
|
|
219 |
windowSize={21}
|
220 |
ItemSeparatorComponent={undefined}
|
|
|
|
|
221 |
/>
|
222 |
</View>
|
223 |
<AnimatePresence exitBeforeEnter>
|
224 |
+
{showOptions.state &&
|
225 |
<View
|
226 |
style={{
|
227 |
position:"absolute",
|
|
|
294 |
}}
|
295 |
|
296 |
onPress={()=>{
|
297 |
+
router.replace(`/view/${SOURCE}/${COMIC_ID}/?mode=local`)
|
298 |
}}
|
299 |
>
|
300 |
<Icon source={"arrow-left-thin"} size={((Dimensions.width+Dimensions.height)/2)*0.05} color={Theme[themeTypeContext].icon_color}/>
|
|
|
315 |
{chapterInfo.title}
|
316 |
</Text>
|
317 |
</View>
|
318 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
319 |
</View>
|
320 |
<View
|
321 |
style={{
|
frontend/app/read/_layout.tsx
CHANGED
@@ -2,7 +2,6 @@ import { Tabs, Stack } from 'expo-router';
|
|
2 |
import React from 'react';
|
3 |
import {View, Text} from 'react-native';
|
4 |
|
5 |
-
import { TabBarIcon } from '@/components/navigation/TabBarIcon';
|
6 |
import { Colors } from '@/constants/Colors';
|
7 |
import { useColorScheme } from '@/hooks/useColorScheme';
|
8 |
import { SafeAreaView } from 'react-native-safe-area-context';
|
|
|
2 |
import React from 'react';
|
3 |
import {View, Text} from 'react-native';
|
4 |
|
|
|
5 |
import { Colors } from '@/constants/Colors';
|
6 |
import { useColorScheme } from '@/hooks/useColorScheme';
|
7 |
import { SafeAreaView } from 'react-native-safe-area-context';
|
frontend/app/read/components/chapter_image.tsx
CHANGED
@@ -14,6 +14,7 @@ import * as FileSystem from 'expo-file-system';
|
|
14 |
import NetInfo from "@react-native-community/netinfo";
|
15 |
import JSZip from 'jszip';
|
16 |
|
|
|
17 |
import ChapterStorage from '@/constants/module/storages/chapter_storage';
|
18 |
import ChapterDataStorage from '@/constants/module/storages/chapter_data_storage';
|
19 |
import Image from '@/components/Image';
|
@@ -22,7 +23,7 @@ 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);
|
@@ -34,6 +35,7 @@ const ChapterImage = ({item, zoom, showOptions,setShowOptions, setIsLoading, SET
|
|
34 |
|
35 |
const [isReady, setIsReady] = useState(false);
|
36 |
const [isError, setIsError] = useState({state:false,text:""});
|
|
|
37 |
|
38 |
const image = useRef<any>(null);
|
39 |
const image_layout = useRef<any>(null);
|
@@ -53,9 +55,17 @@ const ChapterImage = ({item, zoom, showOptions,setShowOptions, setIsLoading, SET
|
|
53 |
|
54 |
})()},[])
|
55 |
|
|
|
|
|
|
|
|
|
|
|
|
|
56 |
|
57 |
return ( <Pressable
|
58 |
-
onPress={()=>{
|
|
|
|
|
59 |
style={{
|
60 |
display:"flex",
|
61 |
width:"100%",
|
@@ -76,6 +86,7 @@ const ChapterImage = ({item, zoom, showOptions,setShowOptions, setIsLoading, SET
|
|
76 |
aspectRatio: image_layout.current.width / image_layout.current.height,
|
77 |
}}
|
78 |
onLoadEnd={()=>{
|
|
|
79 |
}}
|
80 |
/>
|
81 |
)}
|
@@ -171,7 +182,7 @@ const ChapterImage = ({item, zoom, showOptions,setShowOptions, setIsLoading, SET
|
|
171 |
paddingVertical: 18,
|
172 |
}}
|
173 |
>
|
174 |
-
<TouchableRipple
|
175 |
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
176 |
style={{
|
177 |
width:"auto",
|
@@ -192,8 +203,10 @@ const ChapterImage = ({item, zoom, showOptions,setShowOptions, setIsLoading, SET
|
|
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({
|
@@ -214,18 +227,19 @@ const ChapterImage = ({item, zoom, showOptions,setShowOptions, setIsLoading, SET
|
|
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",
|
@@ -245,8 +259,16 @@ const ChapterImage = ({item, zoom, showOptions,setShowOptions, setIsLoading, SET
|
|
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({
|
@@ -267,13 +289,14 @@ const ChapterImage = ({item, zoom, showOptions,setShowOptions, setIsLoading, SET
|
|
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>
|
|
|
14 |
import NetInfo from "@react-native-community/netinfo";
|
15 |
import JSZip from 'jszip';
|
16 |
|
17 |
+
import ComicStorage from '@/constants/module/storages/comic_storage';
|
18 |
import ChapterStorage from '@/constants/module/storages/chapter_storage';
|
19 |
import ChapterDataStorage from '@/constants/module/storages/chapter_data_storage';
|
20 |
import Image from '@/components/Image';
|
|
|
23 |
import Theme from '@/constants/theme';
|
24 |
import { get_chapter } from '../modules/get_chapter';
|
25 |
|
26 |
+
const ChapterImage = ({item, zoom, showOptions, setShowOptions, setIsLoading, SET_DATA}:any)=>{
|
27 |
const SOURCE = useLocalSearchParams().source;
|
28 |
const COMIC_ID = useLocalSearchParams().comic_id;
|
29 |
const CHAPTER_IDX = Number(useLocalSearchParams().chapter_idx as string);
|
|
|
35 |
|
36 |
const [isReady, setIsReady] = useState(false);
|
37 |
const [isError, setIsError] = useState({state:false,text:""});
|
38 |
+
const [isNavigate, setIsNavigate] = useState(false);
|
39 |
|
40 |
const image = useRef<any>(null);
|
41 |
const image_layout = useRef<any>(null);
|
|
|
55 |
|
56 |
})()},[])
|
57 |
|
58 |
+
useFocusEffect(useCallback(() => {
|
59 |
+
return () => {
|
60 |
+
image.current = null
|
61 |
+
};
|
62 |
+
},[]))
|
63 |
+
|
64 |
|
65 |
return ( <Pressable
|
66 |
+
onPress={()=>{
|
67 |
+
setShowOptions({type:"general",state:!showOptions.state})
|
68 |
+
}}
|
69 |
style={{
|
70 |
display:"flex",
|
71 |
width:"100%",
|
|
|
86 |
aspectRatio: image_layout.current.width / image_layout.current.height,
|
87 |
}}
|
88 |
onLoadEnd={()=>{
|
89 |
+
image.current = ""
|
90 |
}}
|
91 |
/>
|
92 |
)}
|
|
|
182 |
paddingVertical: 18,
|
183 |
}}
|
184 |
>
|
185 |
+
<TouchableRipple disabled={isNavigate}
|
186 |
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
187 |
style={{
|
188 |
width:"auto",
|
|
|
203 |
|
204 |
}}
|
205 |
onPress={async ()=>{
|
206 |
+
setIsNavigate(true);
|
207 |
const stored_chapter_info = await ChapterStorage.getByIdx(`${SOURCE}-${COMIC_ID}`,item.chapter_idx-1)
|
208 |
if (stored_chapter_info?.data_state === "completed"){
|
209 |
+
setIsLoading(true);
|
210 |
router.replace(`/read/${SOURCE}/${COMIC_ID}/${stored_chapter_info.idx}/`)
|
211 |
}else{
|
212 |
Toast.show({
|
|
|
227 |
},
|
228 |
});
|
229 |
}
|
230 |
+
setIsNavigate(false);
|
231 |
}}
|
232 |
>
|
233 |
<Text selectable={false}
|
234 |
style={{
|
235 |
color:Theme[themeTypeContext].text_color,
|
236 |
fontFamily:"roboto-medium",
|
237 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.03 * (1 - zoom / 100)
|
238 |
}}
|
239 |
>Previous</Text>
|
240 |
</TouchableRipple>
|
241 |
|
242 |
+
<TouchableRipple disabled={isNavigate}
|
243 |
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
244 |
style={{
|
245 |
width:"auto",
|
|
|
259 |
elevation: 5,
|
260 |
}}
|
261 |
onPress={async ()=>{
|
262 |
+
setIsNavigate(true);
|
263 |
const stored_chapter_info = await ChapterStorage.getByIdx(`${SOURCE}-${COMIC_ID}`,item.chapter_idx+1)
|
264 |
if (stored_chapter_info?.data_state === "completed"){
|
265 |
+
setIsLoading(true)
|
266 |
+
|
267 |
+
const stored_comic = await ComicStorage.getByID(SOURCE, COMIC_ID)
|
268 |
+
if (stored_comic.history.idx && item.chapter_idx+1 > stored_comic.history.idx) {
|
269 |
+
await ComicStorage.updateHistory(SOURCE, COMIC_ID, {idx:stored_chapter_info?.idx, id:stored_chapter_info?.id, title:stored_chapter_info?.title})
|
270 |
+
}
|
271 |
+
|
272 |
router.replace(`/read/${SOURCE}/${COMIC_ID}/${stored_chapter_info.idx}/`)
|
273 |
}else{
|
274 |
Toast.show({
|
|
|
289 |
},
|
290 |
});
|
291 |
}
|
292 |
+
setIsNavigate(false);
|
293 |
}}
|
294 |
>
|
295 |
<Text selectable={false}
|
296 |
style={{
|
297 |
color:Theme[themeTypeContext].text_color,
|
298 |
fontFamily:"roboto-medium",
|
299 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.03 * (1 - zoom / 100)
|
300 |
}}
|
301 |
>Next</Text>
|
302 |
</TouchableRipple>
|
frontend/app/read/components/disqus.tsx
CHANGED
@@ -17,15 +17,10 @@ const Disqus = ({url,identifier,title, paddingVertical=0, paddingHorizontal=0}:a
|
|
17 |
const Dimensions = useWindowDimensions();
|
18 |
const shortname = 'comicmtl';
|
19 |
|
20 |
-
|
21 |
-
const {showMenuContext, setShowMenuContext}:any = useContext(CONTEXT)
|
22 |
const {themeTypeContext, setThemeTypeContext}:any = useContext(CONTEXT)
|
23 |
-
|
24 |
-
const {apiBaseContext, setApiBaseContext}:any = useContext(CONTEXT)
|
25 |
|
26 |
if (Platform.OS === "web") {
|
27 |
-
|
28 |
-
|
29 |
return (
|
30 |
<ScrollView
|
31 |
style={{
|
|
|
17 |
const Dimensions = useWindowDimensions();
|
18 |
const shortname = 'comicmtl';
|
19 |
|
|
|
|
|
20 |
const {themeTypeContext, setThemeTypeContext}:any = useContext(CONTEXT)
|
21 |
+
|
|
|
22 |
|
23 |
if (Platform.OS === "web") {
|
|
|
|
|
24 |
return (
|
25 |
<ScrollView
|
26 |
style={{
|
frontend/app/recent/_layout.tsx
ADDED
@@ -0,0 +1,19 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Tabs, Stack } from 'expo-router';
|
2 |
+
import React from 'react';
|
3 |
+
import {View, Text} from 'react-native';
|
4 |
+
|
5 |
+
import { Colors } from '@/constants/Colors';
|
6 |
+
import { useColorScheme } from '@/hooks/useColorScheme';
|
7 |
+
import { SafeAreaView } from 'react-native-safe-area-context';
|
8 |
+
export default function TabLayout() {
|
9 |
+
const colorScheme = useColorScheme();
|
10 |
+
|
11 |
+
return (
|
12 |
+
<Stack
|
13 |
+
screenOptions={{
|
14 |
+
headerShown: false,
|
15 |
+
}}>
|
16 |
+
|
17 |
+
</Stack>
|
18 |
+
);
|
19 |
+
}
|
frontend/app/recent/components/comic_component.tsx
ADDED
@@ -0,0 +1,194 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React, { useEffect, useState, useCallback, useContext, useRef, useMemo } from 'react';
|
2 |
+
import { Link, router, useLocalSearchParams, useFocusEffect } from 'expo-router';
|
3 |
+
import { Image as RNImage, StyleSheet, useWindowDimensions, ScrollView, Pressable, RefreshControl, Platform, FlatList, TouchableOpacity } from 'react-native';
|
4 |
+
import { SafeAreaView } from 'react-native-safe-area-context';
|
5 |
+
import { Icon, MD3Colors, Button, Text, TextInput, TouchableRipple } from 'react-native-paper';
|
6 |
+
import CircularProgress from 'react-native-circular-progress-indicator';
|
7 |
+
import { ActivityIndicator } from 'react-native-paper';
|
8 |
+
import { FlashList } from "@shopify/flash-list";
|
9 |
+
|
10 |
+
|
11 |
+
import uuid from 'react-native-uuid';
|
12 |
+
import Toast from 'react-native-toast-message';
|
13 |
+
import { View, AnimatePresence } from 'moti';
|
14 |
+
import * as Clipboard from 'expo-clipboard';
|
15 |
+
import * as FileSystem from 'expo-file-system';
|
16 |
+
import NetInfo from "@react-native-community/netinfo";
|
17 |
+
import { Marquee } from '@animatereactnative/marquee';
|
18 |
+
import { Slider } from '@rneui/themed-edge';
|
19 |
+
|
20 |
+
import { __styles } from '../stylesheet/styles';
|
21 |
+
import Storage from '@/constants/module/storages/storage';
|
22 |
+
import ChapterStorage from '@/constants/module/storages/chapter_storage';
|
23 |
+
import CoverStorage from '@/constants/module/storages/cover_storage';
|
24 |
+
|
25 |
+
import Image from '@/components/Image';
|
26 |
+
import {CONTEXT} from '@/constants/module/context';
|
27 |
+
import {blobToBase64, base64ToBlob, getImageLayout} from "@/constants/module/file_manager";
|
28 |
+
import Theme from '@/constants/theme';
|
29 |
+
import ComicStorage from '@/constants/module/storages/comic_storage';
|
30 |
+
|
31 |
+
const ComicComponent = ({index, item, SELECTED_BOOKMARK, SET_SELECTED_BOOKMARK}:any) => {
|
32 |
+
const Dimensions = useWindowDimensions();
|
33 |
+
const controller = new AbortController();
|
34 |
+
const signal = controller.signal;
|
35 |
+
|
36 |
+
const {showMenuContext, setShowMenuContext}:any = useContext(CONTEXT)
|
37 |
+
const {themeTypeContext, setThemeTypeContext}:any = useContext(CONTEXT)
|
38 |
+
const {apiBaseContext, setApiBaseContext}:any = useContext(CONTEXT)
|
39 |
+
const {widgetContext, setWidgetContext}:any = useContext(CONTEXT)
|
40 |
+
const {showCloudflareTurnstileContext, setShowCloudflareTurnstileContext}:any = useContext(CONTEXT)
|
41 |
+
|
42 |
+
const [styles, setStyles]:any = useState(null)
|
43 |
+
const [isLoading, setIsLoading] = useState<boolean>(true)
|
44 |
+
|
45 |
+
const cover:any = useRef("")
|
46 |
+
|
47 |
+
useFocusEffect(useCallback(() => {
|
48 |
+
(async ()=>{
|
49 |
+
setIsLoading(true)
|
50 |
+
setStyles(__styles(themeTypeContext,Dimensions))
|
51 |
+
const stored_bookmark = await Storage.get("bookmark") || []
|
52 |
+
console.log(stored_bookmark)
|
53 |
+
cover.current = await CoverStorage.get(`${item.source}-${item.id}`) || ""
|
54 |
+
setIsLoading(false)
|
55 |
+
})()
|
56 |
+
|
57 |
+
return () => {
|
58 |
+
cover.current = ""
|
59 |
+
controller.abort();
|
60 |
+
};
|
61 |
+
},[]))
|
62 |
+
|
63 |
+
return (<>{styles && !isLoading && <>
|
64 |
+
<>{index === 0
|
65 |
+
? <View
|
66 |
+
style={{
|
67 |
+
width:"100%",
|
68 |
+
height:"auto",
|
69 |
+
paddingHorizontal:0,
|
70 |
+
paddingVertical:18,
|
71 |
+
borderBottomWidth:2,
|
72 |
+
borderColor:Theme[themeTypeContext].border_color,
|
73 |
+
display:"flex",
|
74 |
+
flexDirection:Dimensions.width >= 700 ? "row" : "column",
|
75 |
+
justifyContent:"space-around",
|
76 |
+
alignItems:"center",
|
77 |
+
gap:12,
|
78 |
+
}}
|
79 |
+
>
|
80 |
+
|
81 |
+
<TouchableRipple
|
82 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
83 |
+
onPress={()=>{router.navigate(`/view/${item.source}/${item.id}/?mode=local`)}}
|
84 |
+
style={{...styles.item_box, marginHorizontal:12,}}
|
85 |
+
>
|
86 |
+
<>
|
87 |
+
<Image onError={(error:any)=>{console.log("load image error",error)}} source={cover.current}
|
88 |
+
style={styles.item_cover}
|
89 |
+
contentFit="cover" transition={1000}
|
90 |
+
onLoadEnd={()=>{cover.current = ""}}
|
91 |
+
/>
|
92 |
+
</>
|
93 |
+
</TouchableRipple>
|
94 |
+
<View
|
95 |
+
style={{
|
96 |
+
flex:1,
|
97 |
+
width:"auto",
|
98 |
+
height:Dimensions.width >= 700 ? "100%" : "auto",
|
99 |
+
paddingVertical:12,
|
100 |
+
alignItems:"center",
|
101 |
+
justifyContent:"space-around",
|
102 |
+
gap:12,
|
103 |
+
}}
|
104 |
+
>
|
105 |
+
<Text style={{
|
106 |
+
color:Theme[themeTypeContext].text_color,
|
107 |
+
fontSize:((Dimensions.width+Dimensions.height)/2)*0.035,
|
108 |
+
fontFamily:"roboto-bold",
|
109 |
+
textAlign:"center",
|
110 |
+
width:"100%",
|
111 |
+
}}>{item.info.title}</Text>
|
112 |
+
|
113 |
+
<TouchableRipple
|
114 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
115 |
+
style={{
|
116 |
+
width:Dimensions.width*0.40,
|
117 |
+
display:"flex",
|
118 |
+
flexDirection:"column",
|
119 |
+
justifyContent:"center",
|
120 |
+
padding:8,
|
121 |
+
borderRadius:Dimensions.width*0.60/2,
|
122 |
+
backgroundColor:Theme[themeTypeContext].border_color,
|
123 |
+
|
124 |
+
shadowColor: Theme[themeTypeContext].shadow_color,
|
125 |
+
shadowOffset: { width: 0, height: 2 },
|
126 |
+
shadowOpacity: 0.25,
|
127 |
+
shadowRadius: 3.84,
|
128 |
+
elevation: 5,
|
129 |
+
|
130 |
+
}}
|
131 |
+
onPress={()=>{
|
132 |
+
router.replace(`/read/${item.source}/${item.id}/${item.history.idx}/`)
|
133 |
+
}}
|
134 |
+
>
|
135 |
+
<View
|
136 |
+
style={{
|
137 |
+
display:"flex",
|
138 |
+
flexDirection:"column",
|
139 |
+
gap:12,
|
140 |
+
alignItems:"center",
|
141 |
+
}}
|
142 |
+
>
|
143 |
+
<Text selectable={false}
|
144 |
+
numberOfLines={1}
|
145 |
+
style={{
|
146 |
+
color:Theme[themeTypeContext].text_color,
|
147 |
+
fontSize:((Dimensions.width+Dimensions.height)/2)*0.0225,
|
148 |
+
fontFamily:"roboto-bold",
|
149 |
+
}}
|
150 |
+
>
|
151 |
+
Continue
|
152 |
+
</Text>
|
153 |
+
<Text selectable={false}
|
154 |
+
numberOfLines={1}
|
155 |
+
style={{
|
156 |
+
color:Theme[themeTypeContext].text_color,
|
157 |
+
fontSize:((Dimensions.width+Dimensions.height)/2)*0.02,
|
158 |
+
fontFamily:"roboto-bold",
|
159 |
+
}}
|
160 |
+
>
|
161 |
+
{item.history.title}
|
162 |
+
</Text>
|
163 |
+
</View>
|
164 |
+
</TouchableRipple>
|
165 |
+
|
166 |
+
|
167 |
+
|
168 |
+
</View>
|
169 |
+
|
170 |
+
</View>
|
171 |
+
|
172 |
+
: <TouchableRipple
|
173 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
174 |
+
onPress={()=>{router.navigate(`/view/${item.source}/${item.id}/?mode=local`)}}
|
175 |
+
style={styles.item_box}
|
176 |
+
>
|
177 |
+
<>
|
178 |
+
<Image onError={(error:any)=>{console.log("load image error",error)}} source={cover.current}
|
179 |
+
style={styles.item_cover}
|
180 |
+
contentFit="cover" transition={1000}
|
181 |
+
onLoadEnd={()=>{cover.current = ""}}
|
182 |
+
/>
|
183 |
+
|
184 |
+
<Text style={styles.item_title}>{item.info.title}</Text>
|
185 |
+
</>
|
186 |
+
</TouchableRipple>
|
187 |
+
}</>
|
188 |
+
</>}</>)
|
189 |
+
|
190 |
+
|
191 |
+
}
|
192 |
+
|
193 |
+
export default ComicComponent;
|
194 |
+
|
frontend/app/recent/components/widgets/bookmark.tsx
ADDED
@@ -0,0 +1,904 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
|
2 |
+
import React, { useEffect, useState, useCallback, useContext, useRef, Fragment, memo } 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 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 |
+
import ChapterDataStorage from '@/constants/module/storages/chapter_data_storage';
|
20 |
+
|
21 |
+
interface BookmarkWidgetProps {
|
22 |
+
setIsLoading: any;
|
23 |
+
onRefresh: any;
|
24 |
+
}
|
25 |
+
|
26 |
+
const BookmarkWidget: React.FC<BookmarkWidgetProps> = ({
|
27 |
+
setIsLoading,
|
28 |
+
onRefresh,
|
29 |
+
}) => {
|
30 |
+
const Dimensions = useWindowDimensions();
|
31 |
+
|
32 |
+
const {themeTypeContext, setThemeTypeContext}:any = useContext(CONTEXT)
|
33 |
+
const {widgetContext, setWidgetContext}:any = useContext(CONTEXT)
|
34 |
+
const {showCloudflareTurnstileContext, setShowCloudflareTurnstileContext}:any = useContext(CONTEXT)
|
35 |
+
const {apiBaseContext, setApiBaseContext}:any = useContext(CONTEXT)
|
36 |
+
|
37 |
+
const [BOOKMARK_DATA, SET_BOOKMARK_DATA]: any = useState([])
|
38 |
+
const [MIGRATE_BOOKMARK_DATA, SET_MIGRATE_BOOKMARK_DATA]:any = useState([])
|
39 |
+
|
40 |
+
|
41 |
+
const [showMenuOption, setShowMenuOption]:any = useState({state:false,positions:[0,0,0,0],id:""})
|
42 |
+
const [searchTag, setSearchTag]:any = useState("")
|
43 |
+
|
44 |
+
const [migrateTag,setMigrateTag]:any = useState("")
|
45 |
+
|
46 |
+
const [manageBookmark, setManageBookmark]:any = useState({edit:"",delete:""})
|
47 |
+
const [createTag, setCreateTag]:any = useState({state:false,title:""})
|
48 |
+
const [removeTag, setRemoveTag]:any = useState({state:false, removing: false})
|
49 |
+
|
50 |
+
|
51 |
+
const controller = new AbortController();
|
52 |
+
const signal = controller.signal;
|
53 |
+
|
54 |
+
const RenderTag = useCallback(({item}:any) =>{
|
55 |
+
const [editTag, setEditTag]:any = useState(item.value)
|
56 |
+
useEffect(()=>{
|
57 |
+
},[manageBookmark])
|
58 |
+
|
59 |
+
return (<>
|
60 |
+
{item.value.includes(searchTag) &&
|
61 |
+
(
|
62 |
+
<View
|
63 |
+
style={{
|
64 |
+
display:"flex",
|
65 |
+
flexDirection:"row",
|
66 |
+
alignItems:"center",
|
67 |
+
justifyContent:"space-between",
|
68 |
+
gap:8,
|
69 |
+
zIndex:10,
|
70 |
+
}}
|
71 |
+
>
|
72 |
+
<>{manageBookmark.edit !== item.value && manageBookmark.delete !== item.value &&
|
73 |
+
(<View
|
74 |
+
style={{
|
75 |
+
width:"100%",
|
76 |
+
display:"flex",
|
77 |
+
flexDirection:"row",
|
78 |
+
justifyContent:"space-between",
|
79 |
+
alignItems:"center",
|
80 |
+
height:"auto",
|
81 |
+
gap:18,
|
82 |
+
}}
|
83 |
+
>
|
84 |
+
<Text
|
85 |
+
style={{
|
86 |
+
color:"white",
|
87 |
+
fontFamily:"roboto-medium",
|
88 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.025
|
89 |
+
}}
|
90 |
+
>{item.label}</Text>
|
91 |
+
<View
|
92 |
+
style={{
|
93 |
+
width:"auto",
|
94 |
+
height:"auto",
|
95 |
+
|
96 |
+
}}
|
97 |
+
>
|
98 |
+
|
99 |
+
<TouchableRipple
|
100 |
+
|
101 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
102 |
+
style={{
|
103 |
+
borderRadius:5,
|
104 |
+
borderWidth:0,
|
105 |
+
backgroundColor: "transparent",
|
106 |
+
padding:5,
|
107 |
+
|
108 |
+
}}
|
109 |
+
|
110 |
+
onPress={(event)=>{
|
111 |
+
if (manageBookmark.edit){
|
112 |
+
setManageBookmark({...manageBookmark,edit:""})
|
113 |
+
setEditTag("")
|
114 |
+
}
|
115 |
+
|
116 |
+
const x = event.nativeEvent.pageX
|
117 |
+
const y = event.nativeEvent.pageY
|
118 |
+
|
119 |
+
setShowMenuOption({
|
120 |
+
...showMenuOption,
|
121 |
+
state: showMenuOption.id === item.value ? false : true,
|
122 |
+
positions:[y+((Dimensions.width+Dimensions.height)/2)*0.0225,0,x-((Dimensions.width+Dimensions.height)/2)*0.18,0],
|
123 |
+
id:showMenuOption.id === item.value ? "" : item.value,
|
124 |
+
})
|
125 |
+
|
126 |
+
|
127 |
+
|
128 |
+
}}
|
129 |
+
>
|
130 |
+
|
131 |
+
<Icon source={"dots-vertical"} size={((Dimensions.width+Dimensions.height)/2)*0.035} color={Theme[themeTypeContext].icon_color}/>
|
132 |
+
</TouchableRipple>
|
133 |
+
</View>
|
134 |
+
</View>)
|
135 |
+
}</>
|
136 |
+
<>{manageBookmark.edit === item.value &&
|
137 |
+
(<View
|
138 |
+
style={{
|
139 |
+
display:"flex",
|
140 |
+
flexDirection:"row",
|
141 |
+
justifyContent:"space-between",
|
142 |
+
alignItems:"center",
|
143 |
+
width:"100%",
|
144 |
+
height:"auto",
|
145 |
+
gap:12,
|
146 |
+
padding:12,
|
147 |
+
}}
|
148 |
+
>
|
149 |
+
<View
|
150 |
+
style={{flex:1}}
|
151 |
+
>
|
152 |
+
<TextInput mode="outlined" label="Edit" textColor={Theme[themeTypeContext].text_color} maxLength={72}
|
153 |
+
autoFocus={true}
|
154 |
+
right={<TextInput.Affix text={`| Max: 72`} />}
|
155 |
+
style={{
|
156 |
+
backgroundColor:Theme[themeTypeContext].background_color,
|
157 |
+
borderColor:Theme[themeTypeContext].border_color,
|
158 |
+
|
159 |
+
}}
|
160 |
+
outlineColor={Theme[themeTypeContext].text_input_border_color}
|
161 |
+
value={editTag}
|
162 |
+
onChangeText={(text)=>{
|
163 |
+
setEditTag(text)
|
164 |
+
}}
|
165 |
+
/>
|
166 |
+
</View>
|
167 |
+
<View
|
168 |
+
style={{
|
169 |
+
display:"flex",
|
170 |
+
flexDirection:"row",
|
171 |
+
gap:8,
|
172 |
+
}}
|
173 |
+
>
|
174 |
+
<TouchableRipple
|
175 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
176 |
+
style={{
|
177 |
+
borderRadius:5,
|
178 |
+
borderWidth:0,
|
179 |
+
backgroundColor: "transparent",
|
180 |
+
padding:5,
|
181 |
+
}}
|
182 |
+
|
183 |
+
onPress={()=>{
|
184 |
+
setManageBookmark({...manageBookmark,edit:""})
|
185 |
+
setEditTag("")
|
186 |
+
setShowMenuOption({...showMenuOption,state:false,id:""})
|
187 |
+
}}
|
188 |
+
>
|
189 |
+
|
190 |
+
<Icon source={"close"} size={((Dimensions.width+Dimensions.height)/2)*0.035} color={"red"}/>
|
191 |
+
</TouchableRipple>
|
192 |
+
<TouchableRipple
|
193 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
194 |
+
style={{
|
195 |
+
borderRadius:5,
|
196 |
+
borderWidth:0,
|
197 |
+
backgroundColor: "transparent",
|
198 |
+
padding:5,
|
199 |
+
}}
|
200 |
+
|
201 |
+
onPress={async ()=>{
|
202 |
+
const stored_bookmark = await Storage.get("bookmark");
|
203 |
+
|
204 |
+
const index = stored_bookmark.findIndex((item:string) => item === manageBookmark.edit);
|
205 |
+
|
206 |
+
if (index !== -1){
|
207 |
+
stored_bookmark[index] = editTag;
|
208 |
+
await Storage.store("bookmark", stored_bookmark)
|
209 |
+
|
210 |
+
const stored_comics:any = await ComicStorage.getByTag(manageBookmark.edit)
|
211 |
+
for (const item of stored_comics){
|
212 |
+
await ComicStorage.replaceTag(item.source, item.id, editTag)
|
213 |
+
}
|
214 |
+
|
215 |
+
|
216 |
+
|
217 |
+
|
218 |
+
const index_2 = BOOKMARK_DATA.findIndex((item:any) => item.value === manageBookmark.edit);
|
219 |
+
if (index_2 !== -1){
|
220 |
+
BOOKMARK_DATA[index_2].label = editTag
|
221 |
+
BOOKMARK_DATA[index_2].value = editTag
|
222 |
+
}
|
223 |
+
SET_BOOKMARK_DATA(BOOKMARK_DATA)
|
224 |
+
setManageBookmark({...manageBookmark,edit:""})
|
225 |
+
setEditTag("")
|
226 |
+
|
227 |
+
onRefresh();
|
228 |
+
}
|
229 |
+
setShowMenuOption({...showMenuOption,state:false,id:""})
|
230 |
+
}}
|
231 |
+
>
|
232 |
+
|
233 |
+
<Icon source={"check"} size={((Dimensions.width+Dimensions.height)/2)*0.035} color={"green"}/>
|
234 |
+
</TouchableRipple>
|
235 |
+
</View>
|
236 |
+
|
237 |
+
|
238 |
+
|
239 |
+
</View>)
|
240 |
+
|
241 |
+
}</>
|
242 |
+
|
243 |
+
|
244 |
+
|
245 |
+
|
246 |
+
</View>
|
247 |
+
|
248 |
+
)
|
249 |
+
}
|
250 |
+
</>)
|
251 |
+
}
|
252 |
+
,[Theme,themeTypeContext,manageBookmark,searchTag,removeTag,createTag,showMenuOption,MIGRATE_BOOKMARK_DATA,BOOKMARK_DATA])
|
253 |
+
|
254 |
+
|
255 |
+
const load_bookmark = async ()=>{
|
256 |
+
const stored_bookmark_data = await Storage.get("bookmark") || []
|
257 |
+
if (stored_bookmark_data.length) {
|
258 |
+
const bookmark_data:Array<Object> = []
|
259 |
+
for (const item of stored_bookmark_data) {
|
260 |
+
bookmark_data.push({
|
261 |
+
label:item,
|
262 |
+
value:item,
|
263 |
+
})
|
264 |
+
}
|
265 |
+
|
266 |
+
SET_BOOKMARK_DATA(bookmark_data.sort())
|
267 |
+
}else SET_BOOKMARK_DATA([])
|
268 |
+
}
|
269 |
+
|
270 |
+
useEffect(()=>{
|
271 |
+
SET_MIGRATE_BOOKMARK_DATA([{label:"None",value:""},...BOOKMARK_DATA])
|
272 |
+
},[BOOKMARK_DATA])
|
273 |
+
|
274 |
+
useEffect(()=>{
|
275 |
+
load_bookmark()
|
276 |
+
return () => controller.abort();
|
277 |
+
},[])
|
278 |
+
|
279 |
+
return (<>{BOOKMARK_DATA !== null && <>
|
280 |
+
|
281 |
+
<View key={"BookmarkWidget"}
|
282 |
+
style={{
|
283 |
+
zIndex:10,
|
284 |
+
backgroundColor:Theme[themeTypeContext].background_color,
|
285 |
+
maxWidth:500,
|
286 |
+
width:"100%",
|
287 |
+
|
288 |
+
borderColor:Theme[themeTypeContext].border_color,
|
289 |
+
borderWidth:2,
|
290 |
+
borderRadius:8,
|
291 |
+
padding:12,
|
292 |
+
display:"flex",
|
293 |
+
justifyContent:"center",
|
294 |
+
|
295 |
+
flexDirection:"column",
|
296 |
+
gap:12,
|
297 |
+
}}
|
298 |
+
from={{
|
299 |
+
opacity: 0,
|
300 |
+
scale: 0.9,
|
301 |
+
}}
|
302 |
+
animate={{
|
303 |
+
opacity: 1,
|
304 |
+
scale: 1,
|
305 |
+
}}
|
306 |
+
exit={{
|
307 |
+
opacity: 0,
|
308 |
+
scale: 0.5,
|
309 |
+
}}
|
310 |
+
transition={{
|
311 |
+
type: 'timing',
|
312 |
+
duration: 500,
|
313 |
+
}}
|
314 |
+
exitTransition={{
|
315 |
+
type: 'timing',
|
316 |
+
duration: 250,
|
317 |
+
}}
|
318 |
+
>
|
319 |
+
|
320 |
+
<>{!createTag.state && !removeTag.state && <>
|
321 |
+
<View
|
322 |
+
|
323 |
+
style={{
|
324 |
+
display:"flex",
|
325 |
+
flexDirection:"column",
|
326 |
+
gap:18,
|
327 |
+
}}
|
328 |
+
>
|
329 |
+
<>{BOOKMARK_DATA.length
|
330 |
+
? <>
|
331 |
+
<View
|
332 |
+
style={{flex:1}}
|
333 |
+
>
|
334 |
+
<TextInput mode="outlined" label="Search" textColor={Theme[themeTypeContext].text_color}
|
335 |
+
|
336 |
+
style={{
|
337 |
+
|
338 |
+
backgroundColor:Theme[themeTypeContext].background_color,
|
339 |
+
borderColor:Theme[themeTypeContext].border_color,
|
340 |
+
|
341 |
+
}}
|
342 |
+
outlineColor={Theme[themeTypeContext].text_input_border_color}
|
343 |
+
value={searchTag}
|
344 |
+
onChange={(event)=>{
|
345 |
+
setSearchTag(event.nativeEvent.text)
|
346 |
+
}}
|
347 |
+
/>
|
348 |
+
</View>
|
349 |
+
<View
|
350 |
+
style={{
|
351 |
+
maxHeight:Dimensions.height*0.7
|
352 |
+
}}
|
353 |
+
>
|
354 |
+
<ScrollView
|
355 |
+
contentContainerStyle={{
|
356 |
+
display:"flex",
|
357 |
+
flexDirection:"column",
|
358 |
+
justifyContent:"space-around",
|
359 |
+
gap:8,
|
360 |
+
|
361 |
+
height:"auto",
|
362 |
+
paddingVertical:12,
|
363 |
+
paddingHorizontal:8,
|
364 |
+
}}
|
365 |
+
style={{
|
366 |
+
|
367 |
+
}}
|
368 |
+
>
|
369 |
+
<>{BOOKMARK_DATA.map((item:any) =>
|
370 |
+
(
|
371 |
+
<View key={item.value}>
|
372 |
+
<RenderTag item={item}/>
|
373 |
+
<View style={{width:"100%",height:2,backgroundColor:Theme[themeTypeContext].border_color}}/>
|
374 |
+
</View>
|
375 |
+
)
|
376 |
+
)}</>
|
377 |
+
</ScrollView>
|
378 |
+
</View>
|
379 |
+
</>
|
380 |
+
: <>
|
381 |
+
<Text style={{
|
382 |
+
width:"100%",
|
383 |
+
textAlign:"center",
|
384 |
+
color:Theme[themeTypeContext].text_color,
|
385 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.045,
|
386 |
+
fontFamily:"roboto-bold",
|
387 |
+
}}>No tag found</Text>
|
388 |
+
</>
|
389 |
+
|
390 |
+
}</>
|
391 |
+
|
392 |
+
<View
|
393 |
+
style={{
|
394 |
+
display:"flex",
|
395 |
+
flexDirection:"row",
|
396 |
+
width:"100%",
|
397 |
+
justifyContent:"space-around",
|
398 |
+
alignItems:"center",
|
399 |
+
}}
|
400 |
+
>
|
401 |
+
<Button mode='contained'
|
402 |
+
labelStyle={{
|
403 |
+
color:Theme[themeTypeContext].text_color,
|
404 |
+
fontFamily:"roboto-medium",
|
405 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.02
|
406 |
+
}}
|
407 |
+
style={{backgroundColor:"green",borderRadius:5}}
|
408 |
+
onPress={(()=>{
|
409 |
+
setCreateTag({state:true,title:""})
|
410 |
+
setShowMenuOption({...showMenuOption,state:false,id:""})
|
411 |
+
})}
|
412 |
+
>+ Create</Button>
|
413 |
+
<Button mode='outlined'
|
414 |
+
labelStyle={{
|
415 |
+
color:Theme[themeTypeContext].text_color,
|
416 |
+
fontFamily:"roboto-medium",
|
417 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.02,
|
418 |
+
|
419 |
+
|
420 |
+
}}
|
421 |
+
style={{
|
422 |
+
|
423 |
+
borderRadius:5,
|
424 |
+
borderWidth:2,
|
425 |
+
borderColor:Theme[themeTypeContext].border_color
|
426 |
+
}}
|
427 |
+
onPress={(async ()=>{
|
428 |
+
setWidgetContext({state:false,component:<></>})
|
429 |
+
})}
|
430 |
+
>Done</Button>
|
431 |
+
</View>
|
432 |
+
</View>
|
433 |
+
</>}</>
|
434 |
+
|
435 |
+
<>{createTag.state &&
|
436 |
+
<>
|
437 |
+
<View
|
438 |
+
style={{
|
439 |
+
height:"auto",
|
440 |
+
display:"flex",
|
441 |
+
flexDirection:"column",
|
442 |
+
gap:12,
|
443 |
+
}}
|
444 |
+
>
|
445 |
+
<TextInput mode="outlined" label="Create Tag" textColor={Theme[themeTypeContext].text_color} maxLength={72}
|
446 |
+
placeholder="Bookmark Tag"
|
447 |
+
|
448 |
+
right={<TextInput.Affix text={`| Max: 72`} />}
|
449 |
+
style={{
|
450 |
+
|
451 |
+
backgroundColor:Theme[themeTypeContext].background_color,
|
452 |
+
borderColor:Theme[themeTypeContext].border_color,
|
453 |
+
|
454 |
+
}}
|
455 |
+
outlineColor={Theme[themeTypeContext].text_input_border_color}
|
456 |
+
value={createTag.title}
|
457 |
+
onChange={(event)=>{
|
458 |
+
setCreateTag({...createTag,title:event.nativeEvent.text})
|
459 |
+
}}
|
460 |
+
/>
|
461 |
+
</View>
|
462 |
+
<View
|
463 |
+
style={{
|
464 |
+
display:"flex",
|
465 |
+
flexDirection:"row",
|
466 |
+
width:"100%",
|
467 |
+
justifyContent:"space-around",
|
468 |
+
alignItems:"center",
|
469 |
+
}}
|
470 |
+
>
|
471 |
+
<Button mode='outlined'
|
472 |
+
labelStyle={{
|
473 |
+
color:Theme[themeTypeContext].text_color,
|
474 |
+
fontFamily:"roboto-medium",
|
475 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.02,
|
476 |
+
|
477 |
+
|
478 |
+
}}
|
479 |
+
style={{
|
480 |
+
|
481 |
+
borderRadius:5,
|
482 |
+
borderWidth:2,
|
483 |
+
borderColor:Theme[themeTypeContext].border_color
|
484 |
+
}}
|
485 |
+
onPress={(()=>{
|
486 |
+
|
487 |
+
setCreateTag({...createTag,state:false})
|
488 |
+
|
489 |
+
})}
|
490 |
+
>Cancel</Button>
|
491 |
+
<Button mode='contained'
|
492 |
+
labelStyle={{
|
493 |
+
color:Theme[themeTypeContext].text_color,
|
494 |
+
fontFamily:"roboto-medium",
|
495 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.02
|
496 |
+
}}
|
497 |
+
style={{backgroundColor:"green",borderRadius:5}}
|
498 |
+
onPress={(async()=>{
|
499 |
+
|
500 |
+
const title = createTag.title
|
501 |
+
if (!title) return
|
502 |
+
|
503 |
+
const stored_bookmark_data = await Storage.get("bookmark") || []
|
504 |
+
if (stored_bookmark_data.includes(title)){
|
505 |
+
Toast.show({
|
506 |
+
type: 'error',
|
507 |
+
text1: '🔖 Duplicate Bookmark',
|
508 |
+
text2: `Tag "${title}" already existed in your bookmark.`,
|
509 |
+
|
510 |
+
position: "bottom",
|
511 |
+
visibilityTime: 5000,
|
512 |
+
text1Style:{
|
513 |
+
fontFamily:"roboto-bold",
|
514 |
+
fontSize:((Dimensions.width+Dimensions.height)/2)*0.025
|
515 |
+
},
|
516 |
+
text2Style:{
|
517 |
+
fontFamily:"roboto-medium",
|
518 |
+
fontSize:((Dimensions.width+Dimensions.height)/2)*0.0185,
|
519 |
+
|
520 |
+
},
|
521 |
+
});
|
522 |
+
}else{
|
523 |
+
await Storage.store("bookmark", [...stored_bookmark_data,title].sort())
|
524 |
+
SET_BOOKMARK_DATA([...BOOKMARK_DATA,
|
525 |
+
{label:title,value:title}
|
526 |
+
].sort())
|
527 |
+
setCreateTag({state:false,title:""})
|
528 |
+
Toast.show({
|
529 |
+
type: 'info',
|
530 |
+
text1: '🔖 Create Bookmark',
|
531 |
+
text2: `Tag "${title}" added to your bookmark.`,
|
532 |
+
|
533 |
+
position: "bottom",
|
534 |
+
visibilityTime: 3000,
|
535 |
+
text1Style:{
|
536 |
+
fontFamily:"roboto-bold",
|
537 |
+
fontSize:((Dimensions.width+Dimensions.height)/2)*0.025
|
538 |
+
},
|
539 |
+
text2Style:{
|
540 |
+
fontFamily:"roboto-medium",
|
541 |
+
fontSize:((Dimensions.width+Dimensions.height)/2)*0.0185,
|
542 |
+
|
543 |
+
},
|
544 |
+
});
|
545 |
+
onRefresh()
|
546 |
+
}
|
547 |
+
})}
|
548 |
+
>Add</Button>
|
549 |
+
</View>
|
550 |
+
</>
|
551 |
+
}</>
|
552 |
+
|
553 |
+
</View>
|
554 |
+
<>{showMenuOption.state &&
|
555 |
+
<View
|
556 |
+
style={{
|
557 |
+
display:"flex",
|
558 |
+
position:"absolute",
|
559 |
+
zIndex:11,
|
560 |
+
justifyContent:"space-around",
|
561 |
+
flexDirection:"column",
|
562 |
+
|
563 |
+
backgroundColor:Theme[themeTypeContext].border_color,
|
564 |
+
top:showMenuOption.positions[0],
|
565 |
+
bottom:showMenuOption.positions[1],
|
566 |
+
left:showMenuOption.positions[2],
|
567 |
+
right:showMenuOption.positions[3],
|
568 |
+
|
569 |
+
width:(Dimensions.width+Dimensions.height)/2*0.2,
|
570 |
+
height:(Dimensions.width+Dimensions.height)/2*0.1,
|
571 |
+
|
572 |
+
borderRadius:5,
|
573 |
+
borderWidth:2,
|
574 |
+
borderColor:Theme[themeTypeContext].background_color
|
575 |
+
}}
|
576 |
+
>
|
577 |
+
<TouchableRipple
|
578 |
+
|
579 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
580 |
+
style={{
|
581 |
+
|
582 |
+
borderWidth:0,
|
583 |
+
backgroundColor: "transparent",
|
584 |
+
padding:5,
|
585 |
+
width:"100%",
|
586 |
+
|
587 |
+
}}
|
588 |
+
|
589 |
+
onPress={(event)=>{
|
590 |
+
setManageBookmark({...manageBookmark,edit:showMenuOption.id})
|
591 |
+
setShowMenuOption({...showMenuOption,state:false})
|
592 |
+
}}
|
593 |
+
>
|
594 |
+
<View
|
595 |
+
style={{
|
596 |
+
display:"flex",
|
597 |
+
flexDirection:"row",
|
598 |
+
justifyContent:"center",
|
599 |
+
alignItems:"center",
|
600 |
+
paddingHorizontal:18,
|
601 |
+
|
602 |
+
}}
|
603 |
+
>
|
604 |
+
<Icon source={"pencil"} size={((Dimensions.width+Dimensions.height)/2)*0.025} color={"cyan"}/>
|
605 |
+
<View>
|
606 |
+
<Text selectable={false}
|
607 |
+
style={{
|
608 |
+
textAlign:"center",
|
609 |
+
color:"cyan",
|
610 |
+
fontFamily:"roboto-medium",
|
611 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.02
|
612 |
+
}}
|
613 |
+
>Edit</Text>
|
614 |
+
</View>
|
615 |
+
</View>
|
616 |
+
</TouchableRipple>
|
617 |
+
<View style={{width:"100%",height:2,backgroundColor:Theme[themeTypeContext].background_color}}/>
|
618 |
+
<TouchableRipple
|
619 |
+
|
620 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
621 |
+
style={{
|
622 |
+
|
623 |
+
borderWidth:0,
|
624 |
+
backgroundColor: "transparent",
|
625 |
+
padding:5,
|
626 |
+
width:"100%",
|
627 |
+
}}
|
628 |
+
|
629 |
+
onPress={(event)=>{
|
630 |
+
setManageBookmark({...manageBookmark,edit:"",delete:showMenuOption.id})
|
631 |
+
setShowMenuOption({...showMenuOption,state:false})
|
632 |
+
}}
|
633 |
+
>
|
634 |
+
<View
|
635 |
+
style={{
|
636 |
+
display:"flex",
|
637 |
+
flexDirection:"row",
|
638 |
+
justifyContent:"center",
|
639 |
+
alignItems:"center",
|
640 |
+
paddingHorizontal:18,
|
641 |
+
|
642 |
+
}}
|
643 |
+
>
|
644 |
+
<Icon source={"trash-can"} size={((Dimensions.width+Dimensions.height)/2)*0.025} color={"red"}/>
|
645 |
+
<View>
|
646 |
+
<Text selectable={false}
|
647 |
+
style={{
|
648 |
+
textAlign:"center",
|
649 |
+
color:"red",
|
650 |
+
fontFamily:"roboto-medium",
|
651 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.02
|
652 |
+
}}
|
653 |
+
>Delete</Text>
|
654 |
+
</View>
|
655 |
+
</View>
|
656 |
+
</TouchableRipple>
|
657 |
+
|
658 |
+
</View>
|
659 |
+
|
660 |
+
}</>
|
661 |
+
<>{manageBookmark.delete && (
|
662 |
+
|
663 |
+
|
664 |
+
<View
|
665 |
+
style={{
|
666 |
+
top:0,
|
667 |
+
left:0,
|
668 |
+
position:"absolute",
|
669 |
+
width:Dimensions.width,
|
670 |
+
height:Dimensions.height,
|
671 |
+
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
672 |
+
zIndex:11,
|
673 |
+
display:"flex",
|
674 |
+
justifyContent:"center",
|
675 |
+
alignItems:"center",
|
676 |
+
padding:15,
|
677 |
+
}}
|
678 |
+
>
|
679 |
+
<View
|
680 |
+
style={{
|
681 |
+
backgroundColor:Theme[themeTypeContext].background_color,
|
682 |
+
maxWidth:500,
|
683 |
+
width:"100%",
|
684 |
+
height:"auto",
|
685 |
+
|
686 |
+
borderColor:Theme[themeTypeContext].border_color,
|
687 |
+
borderWidth:2,
|
688 |
+
borderRadius:8,
|
689 |
+
padding:12,
|
690 |
+
display:"flex",
|
691 |
+
justifyContent:"center",
|
692 |
+
|
693 |
+
flexDirection:"column",
|
694 |
+
gap:18,
|
695 |
+
}}
|
696 |
+
>
|
697 |
+
<View
|
698 |
+
style={{
|
699 |
+
borderBottomWidth:2,
|
700 |
+
borderColor:Theme[themeTypeContext].border_color,
|
701 |
+
padding:8,
|
702 |
+
width:"100%",
|
703 |
+
}}
|
704 |
+
>
|
705 |
+
<Text
|
706 |
+
numberOfLines={1}
|
707 |
+
style={{
|
708 |
+
color:"red",
|
709 |
+
fontFamily:"roboto-bold",
|
710 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.03,
|
711 |
+
textAlign:"center",
|
712 |
+
}}
|
713 |
+
>Delete Tag: "{manageBookmark.delete}"</Text>
|
714 |
+
</View>
|
715 |
+
<View
|
716 |
+
style={{
|
717 |
+
width:"100%",
|
718 |
+
display:"flex",
|
719 |
+
flexDirection:"column",
|
720 |
+
gap:12,
|
721 |
+
}}
|
722 |
+
>
|
723 |
+
<View style={{flex:1}}>
|
724 |
+
<Dropdown
|
725 |
+
theme_type={themeTypeContext}
|
726 |
+
Dimensions={Dimensions}
|
727 |
+
|
728 |
+
label='Migrate comics to tag'
|
729 |
+
data={MIGRATE_BOOKMARK_DATA.filter((item:any) => item.value !== manageBookmark.delete)}
|
730 |
+
value={migrateTag}
|
731 |
+
onChange={(async (item:any) => {
|
732 |
+
setMigrateTag(item.value)
|
733 |
+
})}
|
734 |
+
/>
|
735 |
+
</View>
|
736 |
+
<>{!migrateTag && (
|
737 |
+
<Text
|
738 |
+
style={{
|
739 |
+
color:Theme[themeTypeContext].text_color,
|
740 |
+
fontFamily:"roboto-bold",
|
741 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.02,
|
742 |
+
textAlign:"center",
|
743 |
+
}}
|
744 |
+
>Setting migration to None will remove all comics and chapters for this bookmark tag.</Text>
|
745 |
+
)}</>
|
746 |
+
<View
|
747 |
+
style={{
|
748 |
+
display:"flex",
|
749 |
+
flexDirection:"row",
|
750 |
+
width:"100%",
|
751 |
+
justifyContent:"space-around",
|
752 |
+
alignItems:"center",
|
753 |
+
}}
|
754 |
+
>
|
755 |
+
<>{migrateTag
|
756 |
+
|
757 |
+
? <TouchableRipple
|
758 |
+
|
759 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
760 |
+
style={{
|
761 |
+
|
762 |
+
borderWidth:0,
|
763 |
+
backgroundColor: "blue",
|
764 |
+
padding:5,
|
765 |
+
borderRadius:8,
|
766 |
+
paddingHorizontal:12,
|
767 |
+
paddingVertical:8,
|
768 |
+
|
769 |
+
|
770 |
+
}}
|
771 |
+
|
772 |
+
onPress={async (event)=>{
|
773 |
+
const stored_bookmark = await Storage.get("bookmark")
|
774 |
+
const index = stored_bookmark.findIndex((item:any) => item === manageBookmark.delete)
|
775 |
+
if (index === -1) return
|
776 |
+
|
777 |
+
const stored_comics = await ComicStorage.getByTag(manageBookmark.delete)
|
778 |
+
for (const comic of stored_comics) {
|
779 |
+
const source = comic.source;
|
780 |
+
const comic_id = comic.id
|
781 |
+
await ComicStorage.replaceTag(source,comic_id,migrateTag)
|
782 |
+
|
783 |
+
}
|
784 |
+
|
785 |
+
stored_bookmark.splice(index, 1);
|
786 |
+
await Storage.store("bookmark",stored_bookmark);
|
787 |
+
|
788 |
+
|
789 |
+
|
790 |
+
|
791 |
+
const index_2 = BOOKMARK_DATA.findIndex((item:any) => item.value === manageBookmark.delete);
|
792 |
+
if (index_2 !== -1){
|
793 |
+
BOOKMARK_DATA.splice(index_2, 1);
|
794 |
+
}
|
795 |
+
SET_BOOKMARK_DATA(BOOKMARK_DATA)
|
796 |
+
setManageBookmark({...manageBookmark,edit:"",delete:""})
|
797 |
+
setShowMenuOption({...showMenuOption,state:false,id:""})
|
798 |
+
setMigrateTag("")
|
799 |
+
|
800 |
+
onRefresh();
|
801 |
+
}}
|
802 |
+
>
|
803 |
+
|
804 |
+
<Text selectable={false}
|
805 |
+
style={{
|
806 |
+
textAlign:"center",
|
807 |
+
color:Theme[themeTypeContext].text_color,
|
808 |
+
fontFamily:"roboto-medium",
|
809 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.02
|
810 |
+
}}
|
811 |
+
>Migrate</Text>
|
812 |
+
|
813 |
+
</TouchableRipple>
|
814 |
+
: <TouchableRipple
|
815 |
+
|
816 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
817 |
+
style={{
|
818 |
+
|
819 |
+
borderWidth:0,
|
820 |
+
backgroundColor: "red",
|
821 |
+
padding:5,
|
822 |
+
borderRadius:8,
|
823 |
+
paddingHorizontal:12,
|
824 |
+
paddingVertical:8,
|
825 |
+
|
826 |
+
|
827 |
+
}}
|
828 |
+
|
829 |
+
onPress={async (event)=>{
|
830 |
+
const stored_bookmark = await Storage.get("bookmark");
|
831 |
+
const index = stored_bookmark.findIndex((item:any) => item === manageBookmark.delete)
|
832 |
+
if (index === -1) return
|
833 |
+
await ComicStorage.removeByTag(manageBookmark.delete);
|
834 |
+
stored_bookmark.splice(index, 1);
|
835 |
+
await Storage.store("bookmark",stored_bookmark);
|
836 |
+
|
837 |
+
const index_2 = BOOKMARK_DATA.findIndex((item:any) => item.value === manageBookmark.delete);
|
838 |
+
if (index_2 !== -1){
|
839 |
+
BOOKMARK_DATA.splice(index_2, 1);
|
840 |
+
}
|
841 |
+
SET_BOOKMARK_DATA(BOOKMARK_DATA)
|
842 |
+
setManageBookmark({...manageBookmark,edit:"",delete:""})
|
843 |
+
setShowMenuOption({...showMenuOption,state:false,id:""})
|
844 |
+
setMigrateTag("")
|
845 |
+
onRefresh()
|
846 |
+
|
847 |
+
}}
|
848 |
+
>
|
849 |
+
|
850 |
+
<Text selectable={false}
|
851 |
+
style={{
|
852 |
+
textAlign:"center",
|
853 |
+
color:Theme[themeTypeContext].text_color,
|
854 |
+
fontFamily:"roboto-medium",
|
855 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.02
|
856 |
+
}}
|
857 |
+
>Delete</Text>
|
858 |
+
|
859 |
+
</TouchableRipple>
|
860 |
+
}</>
|
861 |
+
<TouchableRipple
|
862 |
+
|
863 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
864 |
+
style={{
|
865 |
+
|
866 |
+
borderWidth:2,
|
867 |
+
borderColor:Theme[themeTypeContext].border_color,
|
868 |
+
backgroundColor: "transparent",
|
869 |
+
padding:5,
|
870 |
+
borderRadius:8,
|
871 |
+
paddingHorizontal:12,
|
872 |
+
paddingVertical:8,
|
873 |
+
|
874 |
+
|
875 |
+
}}
|
876 |
+
|
877 |
+
onPress={(event)=>{
|
878 |
+
setShowMenuOption({...showMenuOption,state:false,id:""})
|
879 |
+
setManageBookmark({...manageBookmark,edit:"",delete:""})
|
880 |
+
}}
|
881 |
+
>
|
882 |
+
|
883 |
+
<Text selectable={false}
|
884 |
+
style={{
|
885 |
+
textAlign:"center",
|
886 |
+
color:Theme[themeTypeContext].text_color,
|
887 |
+
fontFamily:"roboto-medium",
|
888 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.02
|
889 |
+
}}
|
890 |
+
>Cancel</Text>
|
891 |
+
|
892 |
+
</TouchableRipple>
|
893 |
+
</View>
|
894 |
+
|
895 |
+
</View>
|
896 |
+
</View>
|
897 |
+
|
898 |
+
</View>
|
899 |
+
)}</>
|
900 |
+
|
901 |
+
</>}</>)
|
902 |
+
}
|
903 |
+
|
904 |
+
export default BookmarkWidget;
|
frontend/app/recent/index.tsx
ADDED
@@ -0,0 +1,151 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import React, { useEffect, useState, useCallback, useContext, useRef, useMemo } from 'react';
|
2 |
+
import { Link, router, useLocalSearchParams, useFocusEffect } from 'expo-router';
|
3 |
+
import { Image as RNImage, StyleSheet, useWindowDimensions, ScrollView, Pressable, RefreshControl, Platform, FlatList, TouchableOpacity } from 'react-native';
|
4 |
+
import { SafeAreaView } from 'react-native-safe-area-context';
|
5 |
+
import { Icon, MD3Colors, Button, Text, TextInput, TouchableRipple } from 'react-native-paper';
|
6 |
+
import CircularProgress from 'react-native-circular-progress-indicator';
|
7 |
+
import { ActivityIndicator } from 'react-native-paper';
|
8 |
+
import { FlashList } from "@shopify/flash-list";
|
9 |
+
|
10 |
+
|
11 |
+
import uuid from 'react-native-uuid';
|
12 |
+
import Toast from 'react-native-toast-message';
|
13 |
+
import { View, AnimatePresence } from 'moti';
|
14 |
+
import * as Clipboard from 'expo-clipboard';
|
15 |
+
import * as FileSystem from 'expo-file-system';
|
16 |
+
import NetInfo from "@react-native-community/netinfo";
|
17 |
+
import { Marquee } from '@animatereactnative/marquee';
|
18 |
+
import { Slider } from '@rneui/themed-edge';
|
19 |
+
|
20 |
+
|
21 |
+
import ComicComponent from './components/comic_component';
|
22 |
+
import BookmarkWidget from './components/widgets/bookmark';
|
23 |
+
|
24 |
+
import { __styles } from './stylesheet/styles';
|
25 |
+
import Storage from '@/constants/module/storages/storage';
|
26 |
+
import ChapterStorage from '@/constants/module/storages/chapter_storage';
|
27 |
+
import Image from '@/components/Image';
|
28 |
+
import {CONTEXT} from '@/constants/module/context';
|
29 |
+
import {blobToBase64, base64ToBlob, getImageLayout} from "@/constants/module/file_manager";
|
30 |
+
import Theme from '@/constants/theme';
|
31 |
+
import ComicStorage from '@/constants/module/storages/comic_storage';
|
32 |
+
|
33 |
+
const Index = ({}:any) => {
|
34 |
+
const Dimensions = useWindowDimensions();
|
35 |
+
const controller = new AbortController();
|
36 |
+
const signal = controller.signal;
|
37 |
+
|
38 |
+
const {showMenuContext, setShowMenuContext}:any = useContext(CONTEXT)
|
39 |
+
const {themeTypeContext, setThemeTypeContext}:any = useContext(CONTEXT)
|
40 |
+
const {apiBaseContext, setApiBaseContext}:any = useContext(CONTEXT)
|
41 |
+
const {widgetContext, setWidgetContext}:any = useContext(CONTEXT)
|
42 |
+
const {showCloudflareTurnstileContext, setShowCloudflareTurnstileContext}:any = useContext(CONTEXT)
|
43 |
+
|
44 |
+
const [styles, setStyles]:any = useState("")
|
45 |
+
const [isLoading, setIsLoading] = useState<boolean>(true)
|
46 |
+
const [onRefresh, setOnRefresh] = useState(false)
|
47 |
+
|
48 |
+
|
49 |
+
const [COMIC_DATA, SET_COMIC_DATA] = useState<any>([])
|
50 |
+
|
51 |
+
|
52 |
+
useFocusEffect(useCallback(() => {
|
53 |
+
(async ()=>{
|
54 |
+
setIsLoading(true)
|
55 |
+
const stored_recent = await Storage.get("RECENT") || []
|
56 |
+
stored_recent.sort((a:any,b:any) => b.timestamp - a.timestamp)
|
57 |
+
const stored_comic = []
|
58 |
+
for (const item of stored_recent) {
|
59 |
+
const comic = await ComicStorage.getByID(item.source,item.comic_id)
|
60 |
+
if (comic) stored_comic.push(comic)
|
61 |
+
}
|
62 |
+
SET_COMIC_DATA(stored_comic)
|
63 |
+
setIsLoading(false)
|
64 |
+
})()
|
65 |
+
},[onRefresh]))
|
66 |
+
|
67 |
+
|
68 |
+
|
69 |
+
const RenderComicComponent = useCallback(({item,index}:any) => {
|
70 |
+
console.log(item,index)
|
71 |
+
return <ComicComponent index={index} item={item}
|
72 |
+
/>
|
73 |
+
},[])
|
74 |
+
|
75 |
+
useFocusEffect(useCallback(() => {
|
76 |
+
setIsLoading(true)
|
77 |
+
setShowMenuContext(true)
|
78 |
+
setStyles(__styles(themeTypeContext,Dimensions))
|
79 |
+
|
80 |
+
return () => {
|
81 |
+
controller.abort();
|
82 |
+
};
|
83 |
+
},[]))
|
84 |
+
|
85 |
+
return (<>{styles && ! isLoading
|
86 |
+
? <>
|
87 |
+
<View style={styles.screen_container}
|
88 |
+
|
89 |
+
>
|
90 |
+
<View style={styles.header_container}>
|
91 |
+
<Text style={styles.header_text}>Recent</Text>
|
92 |
+
</View>
|
93 |
+
<ScrollView
|
94 |
+
contentContainerStyle={{
|
95 |
+
width:"100%",
|
96 |
+
height:COMIC_DATA.length ? "auto" : "100%",
|
97 |
+
maxHeight:"auto",
|
98 |
+
display:"flex",
|
99 |
+
paddingHorizontal:12,
|
100 |
+
paddingVertical:18,
|
101 |
+
flexDirection:"row",
|
102 |
+
justifyContent:"flex-start",
|
103 |
+
gap:Math.max((Dimensions.width+Dimensions.height)/2*0.015,8),
|
104 |
+
flexWrap:"wrap",
|
105 |
+
}}
|
106 |
+
|
107 |
+
>
|
108 |
+
<>{COMIC_DATA.length
|
109 |
+
? <>{COMIC_DATA.map((item:any,index:number) => (
|
110 |
+
<RenderComicComponent key={index} item={item} index={index} />
|
111 |
+
))
|
112 |
+
}</>
|
113 |
+
: <View
|
114 |
+
style={{
|
115 |
+
width:"100%",
|
116 |
+
height:"100%",
|
117 |
+
backgroundColor:"transparent",
|
118 |
+
display:"flex",
|
119 |
+
justifyContent:"center",
|
120 |
+
alignItems:"center",
|
121 |
+
flexDirection:"row",
|
122 |
+
gap:12,
|
123 |
+
}}
|
124 |
+
>
|
125 |
+
<>
|
126 |
+
<Icon source={"dots-hexagon"} color={Theme[themeTypeContext].icon_color} size={((Dimensions.width+Dimensions.height)/2)*0.03}/>
|
127 |
+
<Text selectable={false}
|
128 |
+
style={{
|
129 |
+
fontFamily:"roboto-bold",
|
130 |
+
fontSize:((Dimensions.width+Dimensions.height)/2)*0.025,
|
131 |
+
color:Theme[themeTypeContext].text_color,
|
132 |
+
}}
|
133 |
+
>There no recent read.</Text>
|
134 |
+
</>
|
135 |
+
</View>
|
136 |
+
}</>
|
137 |
+
|
138 |
+
</ScrollView>
|
139 |
+
</View>
|
140 |
+
|
141 |
+
</>
|
142 |
+
: <View style={{zIndex:5,width:"100%",height:"100%",display:"flex",justifyContent:"center",alignItems:"center",backgroundColor:Theme[themeTypeContext].background_color}}>
|
143 |
+
<Image setShowCloudflareTurnstile={setShowCloudflareTurnstileContext} source={require("@/assets/gif/cat-loading.gif")} style={{width:((Dimensions.width+Dimensions.height)/2)*0.15,height:((Dimensions.width+Dimensions.height)/2)*0.15}}/>
|
144 |
+
</View>
|
145 |
+
}</>)
|
146 |
+
|
147 |
+
|
148 |
+
}
|
149 |
+
|
150 |
+
export default Index;
|
151 |
+
|
frontend/app/recent/stylesheet/styles.tsx
ADDED
@@ -0,0 +1,63 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { StyleSheet } from "react-native";
|
2 |
+
import Theme from "@/constants/theme";
|
3 |
+
|
4 |
+
export const __styles:any = (theme_type:string,Dimensions:any) => {
|
5 |
+
return StyleSheet.create({
|
6 |
+
screen_container: {
|
7 |
+
display: "flex",
|
8 |
+
width: "100%",
|
9 |
+
height: "100%",
|
10 |
+
backgroundColor: Theme[theme_type].background_color,
|
11 |
+
},
|
12 |
+
header_container: {
|
13 |
+
display: "flex",
|
14 |
+
flexDirection: "row",
|
15 |
+
justifyContent: "space-between",
|
16 |
+
alignItems: "center",
|
17 |
+
paddingHorizontal: 15,
|
18 |
+
paddingVertical:10,
|
19 |
+
backgroundColor: Theme[theme_type].background_color,
|
20 |
+
borderBottomWidth: 0.5,
|
21 |
+
borderColor: Theme[theme_type].border_color,
|
22 |
+
},
|
23 |
+
header_text:{
|
24 |
+
fontFamily: "roboto-medium",
|
25 |
+
fontSize: ((Dimensions.width+Dimensions.height)/2)*0.04,
|
26 |
+
color: Theme[theme_type].text_color,
|
27 |
+
|
28 |
+
},
|
29 |
+
|
30 |
+
item_box:{
|
31 |
+
display:"flex",
|
32 |
+
flexDirection:"column",
|
33 |
+
alignItems:"center",
|
34 |
+
gap:15,
|
35 |
+
height:"auto",
|
36 |
+
width:Math.max(((Dimensions.width+Dimensions.height)/2)*0.225,100),
|
37 |
+
borderRadius:8,
|
38 |
+
|
39 |
+
},
|
40 |
+
item_cover:{
|
41 |
+
width:"100%",
|
42 |
+
height:Math.max(((Dimensions.width+Dimensions.height)/2)*0.325,125),
|
43 |
+
borderRadius:8,
|
44 |
+
shadowColor: "#000",
|
45 |
+
shadowOffset: {
|
46 |
+
width: 0,
|
47 |
+
height: 1,
|
48 |
+
},
|
49 |
+
shadowOpacity: 0.22,
|
50 |
+
shadowRadius: 2.22,
|
51 |
+
|
52 |
+
elevation: 3,
|
53 |
+
},
|
54 |
+
item_title:{
|
55 |
+
color: Theme[theme_type].text_color,
|
56 |
+
fontFamily: "roboto-medium",
|
57 |
+
fontSize: ((Dimensions.width+Dimensions.height)/2)*0.025,
|
58 |
+
width:"100%",
|
59 |
+
height:"auto",
|
60 |
+
textAlign:"center",
|
61 |
+
flexShrink:1,
|
62 |
+
}
|
63 |
+
})}
|
frontend/app/view/[source]/[comic_id].tsx
CHANGED
@@ -12,6 +12,8 @@ import Toast from 'react-native-toast-message';
|
|
12 |
import { View, AnimatePresence } from 'moti';
|
13 |
import * as Clipboard from 'expo-clipboard';
|
14 |
import NetInfo from "@react-native-community/netinfo";
|
|
|
|
|
15 |
|
16 |
|
17 |
import Theme from '@/constants/theme';
|
@@ -20,6 +22,8 @@ import Storage from '@/constants/module/storages/storage';
|
|
20 |
import ImageCacheStorage from '@/constants/module/storages/image_cache_storage';
|
21 |
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 PageNavigationWidget from '../componenets/widgets/page_navigation';
|
@@ -39,6 +43,8 @@ import { createSocket, setupSocketNetworkListener } from '../modules/socket';
|
|
39 |
const Index = ({}:any) => {
|
40 |
const SOURCE = useLocalSearchParams().source;
|
41 |
const ID = useLocalSearchParams().comic_id;
|
|
|
|
|
42 |
|
43 |
const {showMenuContext, setShowMenuContext}:any = useContext(CONTEXT)
|
44 |
const {themeTypeContext, setThemeTypeContext}:any = useContext(CONTEXT)
|
@@ -61,10 +67,9 @@ const Index = ({}:any) => {
|
|
61 |
|
62 |
|
63 |
const [CONTENT, SET_CONTENT]:any = useState({})
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
const [chapterQueue, setChapterQueue]:any = useState({})
|
68 |
const [isLoading, setIsLoading]:any = useState(true);
|
69 |
const [feedBack, setFeedBack]:any = useState("");
|
70 |
const [showOption, setShowOption]:any = useState({type:null})
|
@@ -77,6 +82,11 @@ const Index = ({}:any) => {
|
|
77 |
|
78 |
const socketNetWorkListener:any = useRef(null)
|
79 |
const socket:any = useRef(null)
|
|
|
|
|
|
|
|
|
|
|
80 |
|
81 |
const controller = new AbortController();
|
82 |
const signal = controller.signal;
|
@@ -87,30 +97,40 @@ const Index = ({}:any) => {
|
|
87 |
|
88 |
},[CONTENT])
|
89 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
90 |
|
91 |
// Worker for downloading chapter
|
92 |
const download_chapter_interval:any = useRef(null)
|
93 |
const isDownloading:any = useRef(false)
|
94 |
-
|
95 |
clearInterval(download_chapter_interval.current)
|
96 |
-
|
97 |
download_chapter_interval.current = setInterval(() => {
|
98 |
-
if (!isDownloading.current && Object.keys(
|
99 |
isDownloading.current = true
|
100 |
-
console.log(isDownloading.current,chapterToDownload)
|
101 |
-
console.log("Downloading HERE")
|
102 |
download_chapter(
|
103 |
setShowCloudflareTurnstileContext, isDownloading, SOURCE, ID,
|
104 |
-
|
105 |
-
chapterToDownload, setChapterToDownload,
|
106 |
-
downloadProgress, setDownloadProgress,
|
107 |
-
signal,
|
108 |
)
|
109 |
}
|
110 |
},1000)
|
111 |
|
112 |
return () => clearInterval(download_chapter_interval.current)
|
113 |
-
},[
|
114 |
|
115 |
// Setting up socket listener
|
116 |
useFocusEffect(useCallback(() => {
|
@@ -130,10 +150,9 @@ const Index = ({}:any) => {
|
|
130 |
if (!stored_comic) return
|
131 |
const event = result.event
|
132 |
if (event.type === "chapter_queue_info"){
|
133 |
-
|
134 |
-
setChapterQueue(event.chapter_queue)
|
135 |
}else if (event.type === "chapter_ready_to_download"){
|
136 |
-
get_requested_info(setShowCloudflareTurnstileContext,
|
137 |
}
|
138 |
}
|
139 |
}
|
@@ -166,11 +185,11 @@ const Index = ({}:any) => {
|
|
166 |
},[]))
|
167 |
|
168 |
|
169 |
-
const
|
170 |
Toast.show({
|
171 |
type: 'info',
|
172 |
-
text1: '
|
173 |
-
text2: `
|
174 |
|
175 |
position: "bottom",
|
176 |
visibilityTime: 6000,
|
@@ -191,6 +210,7 @@ const Index = ({}:any) => {
|
|
191 |
if (stored_comic) {
|
192 |
const DATA:any = {}
|
193 |
DATA["id"] = ID
|
|
|
194 |
|
195 |
for (const [key, value] of Object.entries(stored_comic.info)) {
|
196 |
DATA[key] = value
|
@@ -244,18 +264,18 @@ const Index = ({}:any) => {
|
|
244 |
|
245 |
const stored_comic = await ComicStorage.getByID(SOURCE,ID)
|
246 |
if (stored_comic) {
|
247 |
-
await get_requested_info(setShowCloudflareTurnstileContext, setChapterRequested, setChapterToDownload, signal, SOURCE, ID)
|
248 |
-
|
249 |
setBookmarked({state:true,tag:stored_comic.tag})
|
250 |
setHistory(stored_comic.history)
|
251 |
}
|
252 |
else setBookmarked({state:false,tag:""})
|
253 |
|
254 |
const net_info = await NetInfo.fetch()
|
255 |
-
if (net_info.isConnected){
|
|
|
256 |
get(setShowCloudflareTurnstileContext, setIsLoading, signal, __translate, setFeedBack, SOURCE, ID, SET_CONTENT)
|
257 |
}else{
|
258 |
-
|
|
|
259 |
}
|
260 |
|
261 |
})()
|
@@ -280,11 +300,10 @@ const Index = ({}:any) => {
|
|
280 |
if (net_info.isConnected){
|
281 |
get(setShowCloudflareTurnstileContext, setIsLoading, signal, translate, setFeedBack, SOURCE, ID, SET_CONTENT)
|
282 |
if (stored_comic) {
|
283 |
-
|
284 |
-
get_requested_info(setShowCloudflareTurnstileContext, setChapterRequested, setChapterToDownload, signal, SOURCE, ID)
|
285 |
}
|
286 |
}else{
|
287 |
-
|
288 |
}
|
289 |
}
|
290 |
|
@@ -312,7 +331,7 @@ const Index = ({}:any) => {
|
|
312 |
|
313 |
onPress={()=>{
|
314 |
if (router.canGoBack()) router.back()
|
315 |
-
else router.replace("/
|
316 |
}}
|
317 |
>
|
318 |
<Icon source={"arrow-left-thin"} size={((Dimensions.width+Dimensions.height)/2)*0.045} color={Theme[themeTypeContext].icon_color}/>
|
@@ -354,7 +373,7 @@ const Index = ({}:any) => {
|
|
354 |
onRefresh()
|
355 |
}}
|
356 |
>
|
357 |
-
<Icon source={"
|
358 |
</TouchableRipple>
|
359 |
<TouchableRipple
|
360 |
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
@@ -370,7 +389,7 @@ const Index = ({}:any) => {
|
|
370 |
Toast.show({
|
371 |
type: 'info',
|
372 |
text1: '📋 Copied to your clipboard.',
|
373 |
-
text2:
|
374 |
|
375 |
position: "bottom",
|
376 |
visibilityTime: 3000,
|
@@ -583,6 +602,7 @@ const Index = ({}:any) => {
|
|
583 |
onPress={()=>{
|
584 |
setWidgetContext({state:true,component:
|
585 |
<BookmarkWidget
|
|
|
586 |
onRefresh={onRefresh}
|
587 |
SOURCE={SOURCE}
|
588 |
ID={ID}
|
@@ -645,7 +665,7 @@ const Index = ({}:any) => {
|
|
645 |
|
646 |
}}
|
647 |
onPress={()=>{
|
648 |
-
router.
|
649 |
}}
|
650 |
>
|
651 |
<View
|
@@ -679,44 +699,63 @@ const Index = ({}:any) => {
|
|
679 |
</View>
|
680 |
</TouchableRipple>
|
681 |
: <TouchableRipple
|
682 |
-
|
683 |
-
|
684 |
-
|
685 |
-
|
686 |
-
|
687 |
-
|
688 |
-
|
689 |
-
|
690 |
-
|
691 |
-
|
692 |
-
|
693 |
-
|
694 |
-
|
695 |
-
|
696 |
-
|
697 |
-
|
698 |
-
|
699 |
-
|
700 |
-
|
701 |
-
|
702 |
-
|
703 |
-
|
704 |
-
|
705 |
-
|
706 |
-
|
707 |
-
|
708 |
-
|
709 |
-
|
710 |
-
|
711 |
-
|
712 |
-
|
713 |
-
|
714 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
715 |
}else{
|
716 |
Toast.show({
|
717 |
type: 'error',
|
718 |
-
text1: '
|
719 |
-
text2:
|
720 |
|
721 |
position: "bottom",
|
722 |
visibilityTime: 4000,
|
@@ -730,30 +769,11 @@ const Index = ({}:any) => {
|
|
730 |
|
731 |
},
|
732 |
});
|
733 |
-
}
|
734 |
-
}else{
|
735 |
-
Toast.show({
|
736 |
-
type: 'error',
|
737 |
-
text1: '🔖 Bookmark required.',
|
738 |
-
text2: `Add this comic to your bookmark to start reading.`,
|
739 |
-
|
740 |
-
position: "bottom",
|
741 |
-
visibilityTime: 4000,
|
742 |
-
text1Style:{
|
743 |
-
fontFamily:"roboto-bold",
|
744 |
-
fontSize:((Dimensions.width+Dimensions.height)/2)*0.025
|
745 |
-
},
|
746 |
-
text2Style:{
|
747 |
-
fontFamily:"roboto-medium",
|
748 |
-
fontSize:((Dimensions.width+Dimensions.height)/2)*0.0185,
|
749 |
-
|
750 |
-
},
|
751 |
-
});
|
752 |
-
|
753 |
|
754 |
-
|
755 |
-
|
756 |
-
|
|
|
757 |
<View
|
758 |
style={{
|
759 |
display:"flex",
|
@@ -796,19 +816,31 @@ const Index = ({}:any) => {
|
|
796 |
>Synopsis:</Text>
|
797 |
</View>
|
798 |
|
799 |
-
<
|
|
|
800 |
onPress={() => {
|
801 |
if (showMoreSynopsis) setShowMoreSynopsis(false)
|
802 |
else setShowMoreSynopsis(true)
|
803 |
}}
|
804 |
-
style={{
|
805 |
-
|
806 |
-
|
807 |
-
|
808 |
-
|
809 |
-
|
|
|
810 |
}}
|
811 |
-
>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
812 |
|
813 |
</View>
|
814 |
|
@@ -821,6 +853,7 @@ const Index = ({}:any) => {
|
|
821 |
borderColor: Theme[themeTypeContext].border_color,
|
822 |
borderBottomWidth:showMoreSynopsis ? 0 : 5,
|
823 |
borderRadius:8,
|
|
|
824 |
}}
|
825 |
numberOfLines={showMoreSynopsis ? 0 : 2}
|
826 |
ellipsizeMode='tail'
|
@@ -870,24 +903,7 @@ const Index = ({}:any) => {
|
|
870 |
<View style={styles.chapter_box}>
|
871 |
<>{CONTENT.chapters.length
|
872 |
? <>{CONTENT.chapters.slice((page-1)*MAX_OFFSET,((page-1)*MAX_OFFSET)+MAX_OFFSET).map((chapter:any,index:number) =>
|
873 |
-
<
|
874 |
-
key={index}
|
875 |
-
SOURCE={SOURCE}
|
876 |
-
ID={ID}
|
877 |
-
page={page}
|
878 |
-
sort={sort}
|
879 |
-
chapter={chapter}
|
880 |
-
signal={signal}
|
881 |
-
isDownloading={isDownloading}
|
882 |
-
chapterRequested={chapterRequested}
|
883 |
-
setChapterRequested={setChapterRequested}
|
884 |
-
chapterToDownload={chapterToDownload}
|
885 |
-
setChapterToDownload={setChapterToDownload}
|
886 |
-
downloadProgress={downloadProgress}
|
887 |
-
setDownloadProgress={setDownloadProgress}
|
888 |
-
setChapterQueue={setChapterQueue}
|
889 |
-
chapterQueue={chapterQueue}
|
890 |
-
/>
|
891 |
)}</>
|
892 |
: <Text
|
893 |
style={{
|
|
|
12 |
import { View, AnimatePresence } from 'moti';
|
13 |
import * as Clipboard from 'expo-clipboard';
|
14 |
import NetInfo from "@react-native-community/netinfo";
|
15 |
+
import _ from 'lodash'
|
16 |
+
|
17 |
|
18 |
|
19 |
import Theme from '@/constants/theme';
|
|
|
22 |
import ImageCacheStorage from '@/constants/module/storages/image_cache_storage';
|
23 |
import ChapterStorage from '@/constants/module/storages/chapter_storage';
|
24 |
import ComicStorage from '@/constants/module/storages/comic_storage';
|
25 |
+
import CoverStorage from '@/constants/module/storages/cover_storage';
|
26 |
+
|
27 |
import { CONTEXT } from '@/constants/module/context';
|
28 |
import Dropdown from '@/components/dropdown';
|
29 |
import PageNavigationWidget from '../componenets/widgets/page_navigation';
|
|
|
43 |
const Index = ({}:any) => {
|
44 |
const SOURCE = useLocalSearchParams().source;
|
45 |
const ID = useLocalSearchParams().comic_id;
|
46 |
+
const MODE = useLocalSearchParams().mode;
|
47 |
+
console.log(MODE)
|
48 |
|
49 |
const {showMenuContext, setShowMenuContext}:any = useContext(CONTEXT)
|
50 |
const {themeTypeContext, setThemeTypeContext}:any = useContext(CONTEXT)
|
|
|
67 |
|
68 |
|
69 |
const [CONTENT, SET_CONTENT]:any = useState({})
|
70 |
+
|
71 |
+
|
72 |
+
|
|
|
73 |
const [isLoading, setIsLoading]:any = useState(true);
|
74 |
const [feedBack, setFeedBack]:any = useState("");
|
75 |
const [showOption, setShowOption]:any = useState({type:null})
|
|
|
82 |
|
83 |
const socketNetWorkListener:any = useRef(null)
|
84 |
const socket:any = useRef(null)
|
85 |
+
|
86 |
+
const download_progress:any = useRef({})
|
87 |
+
const chapter_queue:any = useRef({})
|
88 |
+
const chapter_requested:any = useRef({})
|
89 |
+
const chapter_to_download:any = useRef({})
|
90 |
|
91 |
const controller = new AbortController();
|
92 |
const signal = controller.signal;
|
|
|
97 |
|
98 |
},[CONTENT])
|
99 |
|
100 |
+
const RenderChapter = useCallback(({chapter}:any) => {
|
101 |
+
return <ChapterComponent
|
102 |
+
SOURCE={SOURCE}
|
103 |
+
ID={ID}
|
104 |
+
page={page}
|
105 |
+
sort={sort}
|
106 |
+
chapter={chapter}
|
107 |
+
signal={signal}
|
108 |
+
isDownloading={isDownloading}
|
109 |
+
chapter_requested={chapter_requested}
|
110 |
+
chapter_to_download={chapter_to_download}
|
111 |
+
download_progress={download_progress}
|
112 |
+
chapter_queue={chapter_queue}
|
113 |
+
/>
|
114 |
+
},[page,sort])
|
115 |
+
|
116 |
|
117 |
// Worker for downloading chapter
|
118 |
const download_chapter_interval:any = useRef(null)
|
119 |
const isDownloading:any = useRef(false)
|
120 |
+
useFocusEffect(useCallback(() => {
|
121 |
clearInterval(download_chapter_interval.current)
|
|
|
122 |
download_chapter_interval.current = setInterval(() => {
|
123 |
+
if (!isDownloading.current && Object.keys(chapter_to_download.current).length){
|
124 |
isDownloading.current = true
|
|
|
|
|
125 |
download_chapter(
|
126 |
setShowCloudflareTurnstileContext, isDownloading, SOURCE, ID,
|
127 |
+
chapter_requested, chapter_to_download, download_progress, signal,
|
|
|
|
|
|
|
128 |
)
|
129 |
}
|
130 |
},1000)
|
131 |
|
132 |
return () => clearInterval(download_chapter_interval.current)
|
133 |
+
},[]))
|
134 |
|
135 |
// Setting up socket listener
|
136 |
useFocusEffect(useCallback(() => {
|
|
|
150 |
if (!stored_comic) return
|
151 |
const event = result.event
|
152 |
if (event.type === "chapter_queue_info"){
|
153 |
+
chapter_queue.current = event.chapter_queue;
|
|
|
154 |
}else if (event.type === "chapter_ready_to_download"){
|
155 |
+
get_requested_info(setShowCloudflareTurnstileContext, chapter_requested, chapter_to_download, signal, SOURCE, ID)
|
156 |
}
|
157 |
}
|
158 |
}
|
|
|
185 |
},[]))
|
186 |
|
187 |
|
188 |
+
const Load_Local = async () => {
|
189 |
Toast.show({
|
190 |
type: 'info',
|
191 |
+
text1: '💾 Local mode',
|
192 |
+
text2: `Press refresh button to fetch new updates.`,
|
193 |
|
194 |
position: "bottom",
|
195 |
visibilityTime: 6000,
|
|
|
210 |
if (stored_comic) {
|
211 |
const DATA:any = {}
|
212 |
DATA["id"] = ID
|
213 |
+
DATA["cover"] = await CoverStorage.get(`${SOURCE}-${ID}`)
|
214 |
|
215 |
for (const [key, value] of Object.entries(stored_comic.info)) {
|
216 |
DATA[key] = value
|
|
|
264 |
|
265 |
const stored_comic = await ComicStorage.getByID(SOURCE,ID)
|
266 |
if (stored_comic) {
|
|
|
|
|
267 |
setBookmarked({state:true,tag:stored_comic.tag})
|
268 |
setHistory(stored_comic.history)
|
269 |
}
|
270 |
else setBookmarked({state:false,tag:""})
|
271 |
|
272 |
const net_info = await NetInfo.fetch()
|
273 |
+
if (net_info.isConnected && MODE !== "local"){
|
274 |
+
if (stored_comic) await get_requested_info(setShowCloudflareTurnstileContext, chapter_requested, chapter_to_download, signal, SOURCE, ID)
|
275 |
get(setShowCloudflareTurnstileContext, setIsLoading, signal, __translate, setFeedBack, SOURCE, ID, SET_CONTENT)
|
276 |
}else{
|
277 |
+
if (net_info.isConnected && stored_comic) await get_requested_info(setShowCloudflareTurnstileContext, chapter_requested, chapter_to_download, signal, SOURCE, ID)
|
278 |
+
Load_Local()
|
279 |
}
|
280 |
|
281 |
})()
|
|
|
300 |
if (net_info.isConnected){
|
301 |
get(setShowCloudflareTurnstileContext, setIsLoading, signal, translate, setFeedBack, SOURCE, ID, SET_CONTENT)
|
302 |
if (stored_comic) {
|
303 |
+
get_requested_info(setShowCloudflareTurnstileContext, chapter_requested, chapter_to_download, signal, SOURCE, ID)
|
|
|
304 |
}
|
305 |
}else{
|
306 |
+
Load_Local()
|
307 |
}
|
308 |
}
|
309 |
|
|
|
331 |
|
332 |
onPress={()=>{
|
333 |
if (router.canGoBack()) router.back()
|
334 |
+
else router.replace("/bookmark")
|
335 |
}}
|
336 |
>
|
337 |
<Icon source={"arrow-left-thin"} size={((Dimensions.width+Dimensions.height)/2)*0.045} color={Theme[themeTypeContext].icon_color}/>
|
|
|
373 |
onRefresh()
|
374 |
}}
|
375 |
>
|
376 |
+
<Icon source={"update"} size={((Dimensions.width+Dimensions.height)/2)*0.04} color={Theme[themeTypeContext].icon_color}/>
|
377 |
</TouchableRipple>
|
378 |
<TouchableRipple
|
379 |
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
|
|
389 |
Toast.show({
|
390 |
type: 'info',
|
391 |
text1: '📋 Copied to your clipboard.',
|
392 |
+
text2: `https://comicmtl.netlify.app/view/${SOURCE}/${ID}/`,
|
393 |
|
394 |
position: "bottom",
|
395 |
visibilityTime: 3000,
|
|
|
602 |
onPress={()=>{
|
603 |
setWidgetContext({state:true,component:
|
604 |
<BookmarkWidget
|
605 |
+
setIsLoading={setIsLoading}
|
606 |
onRefresh={onRefresh}
|
607 |
SOURCE={SOURCE}
|
608 |
ID={ID}
|
|
|
665 |
|
666 |
}}
|
667 |
onPress={()=>{
|
668 |
+
router.replace(`/read/${SOURCE}/${ID}/${history.idx}/`)
|
669 |
}}
|
670 |
>
|
671 |
<View
|
|
|
699 |
</View>
|
700 |
</TouchableRipple>
|
701 |
: <TouchableRipple
|
702 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
703 |
+
style={{
|
704 |
+
width:Dimensions.width*0.60,
|
705 |
+
display:"flex",
|
706 |
+
flexDirection:"column",
|
707 |
+
justifyContent:"center",
|
708 |
+
alignSelf:"center",
|
709 |
+
padding:8,
|
710 |
+
paddingVertical:12,
|
711 |
+
borderRadius:Dimensions.width*0.60/2,
|
712 |
+
backgroundColor:Theme[themeTypeContext].border_color,
|
713 |
+
|
714 |
+
shadowColor: Theme[themeTypeContext].shadow_color,
|
715 |
+
shadowOffset: { width: 0, height: 2 },
|
716 |
+
shadowOpacity: 0.25,
|
717 |
+
shadowRadius: 3.84,
|
718 |
+
elevation: 5,
|
719 |
+
|
720 |
+
}}
|
721 |
+
onPress={async ()=>{
|
722 |
+
|
723 |
+
if (bookmarked.state){
|
724 |
+
let chapter
|
725 |
+
if (sort === "descending"){
|
726 |
+
chapter = CONTENT?.chapters[CONTENT.chapters.length - 1]
|
727 |
+
}else{
|
728 |
+
chapter = CONTENT?.chapters[0]
|
729 |
+
}
|
730 |
+
const stored_chapter = await ChapterStorage.get(`${SOURCE}-${ID}`,chapter.id)
|
731 |
+
if (stored_chapter.data_state === "completed"){
|
732 |
+
await ComicStorage.updateHistory(SOURCE,ID,{idx:chapter.idx, id:chapter.id, title:chapter.title})
|
733 |
+
router.replace(`/read/${SOURCE}/${ID}/${stored_chapter.idx}/`)
|
734 |
+
|
735 |
+
}else{
|
736 |
+
Toast.show({
|
737 |
+
type: 'error',
|
738 |
+
text1: 'Chapter not download yet.',
|
739 |
+
text2: "Press the button next to chapter title to download.",
|
740 |
+
|
741 |
+
position: "bottom",
|
742 |
+
visibilityTime: 4000,
|
743 |
+
text1Style:{
|
744 |
+
fontFamily:"roboto-bold",
|
745 |
+
fontSize:((Dimensions.width+Dimensions.height)/2)*0.025
|
746 |
+
},
|
747 |
+
text2Style:{
|
748 |
+
fontFamily:"roboto-medium",
|
749 |
+
fontSize:((Dimensions.width+Dimensions.height)/2)*0.0185,
|
750 |
+
|
751 |
+
},
|
752 |
+
});
|
753 |
+
}
|
754 |
}else{
|
755 |
Toast.show({
|
756 |
type: 'error',
|
757 |
+
text1: '🔖 Bookmark required.',
|
758 |
+
text2: `Add this comic to your bookmark to start reading.`,
|
759 |
|
760 |
position: "bottom",
|
761 |
visibilityTime: 4000,
|
|
|
769 |
|
770 |
},
|
771 |
});
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
772 |
|
773 |
+
|
774 |
+
}
|
775 |
+
}}
|
776 |
+
>
|
777 |
<View
|
778 |
style={{
|
779 |
display:"flex",
|
|
|
816 |
>Synopsis:</Text>
|
817 |
</View>
|
818 |
|
819 |
+
<TouchableRipple
|
820 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
821 |
onPress={() => {
|
822 |
if (showMoreSynopsis) setShowMoreSynopsis(false)
|
823 |
else setShowMoreSynopsis(true)
|
824 |
}}
|
825 |
+
style={{
|
826 |
+
borderWidth:0,
|
827 |
+
display:"flex",
|
828 |
+
justifyContent:"center",
|
829 |
+
borderRadius:((Dimensions.width+Dimensions.height)/2)*0.015,
|
830 |
+
paddingHorizontal:12,
|
831 |
+
backgroundColor:"transparent",
|
832 |
}}
|
833 |
+
>
|
834 |
+
<Text selectable={false}
|
835 |
+
style={{
|
836 |
+
fontSize:((Dimensions.width+Dimensions.height)/2)*0.025,
|
837 |
+
fontFamily:"roboto-medium",
|
838 |
+
color:"cyan",
|
839 |
+
textAlign:"center",
|
840 |
+
textDecorationLine: showMoreSynopsis ? "underline" : "none",
|
841 |
+
}}
|
842 |
+
>{showMoreSynopsis ? "Show Less" : "Show More"}</Text>
|
843 |
+
</TouchableRipple>
|
844 |
|
845 |
</View>
|
846 |
|
|
|
853 |
borderColor: Theme[themeTypeContext].border_color,
|
854 |
borderBottomWidth:showMoreSynopsis ? 0 : 5,
|
855 |
borderRadius:8,
|
856 |
+
|
857 |
}}
|
858 |
numberOfLines={showMoreSynopsis ? 0 : 2}
|
859 |
ellipsizeMode='tail'
|
|
|
903 |
<View style={styles.chapter_box}>
|
904 |
<>{CONTENT.chapters.length
|
905 |
? <>{CONTENT.chapters.slice((page-1)*MAX_OFFSET,((page-1)*MAX_OFFSET)+MAX_OFFSET).map((chapter:any,index:number) =>
|
906 |
+
<RenderChapter key={index} chapter={chapter}/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
907 |
)}</>
|
908 |
: <Text
|
909 |
style={{
|
frontend/app/view/componenets/chapter.tsx
CHANGED
@@ -12,6 +12,7 @@ import Toast from 'react-native-toast-message';
|
|
12 |
import { View, AnimatePresence } from 'moti';
|
13 |
import * as Clipboard from 'expo-clipboard';
|
14 |
import NetInfo from "@react-native-community/netinfo";
|
|
|
15 |
|
16 |
|
17 |
import Theme from '@/constants/theme';
|
@@ -36,14 +37,10 @@ const ChapterComponent = ({
|
|
36 |
chapter,
|
37 |
signal,
|
38 |
isDownloading,
|
39 |
-
|
40 |
-
|
41 |
-
|
42 |
-
|
43 |
-
chapterToDownload,
|
44 |
-
setChapterToDownload,
|
45 |
-
setChapterQueue,
|
46 |
-
chapterQueue,
|
47 |
|
48 |
}:any) => {
|
49 |
const {showMenuContext, setShowMenuContext}:any = useContext(CONTEXT)
|
@@ -57,9 +54,87 @@ const ChapterComponent = ({
|
|
57 |
|
58 |
const [styles, setStyles]:any = useState("")
|
59 |
const [is_saved, set_is_saved] = useState(false)
|
|
|
60 |
const [is_net_connected, set_is_net_connected]:any = useState(false)
|
61 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
62 |
useEffect(() => {(async () => {
|
|
|
63 |
const net_info = await NetInfo.fetch()
|
64 |
set_is_net_connected(net_info.isConnected)
|
65 |
|
@@ -67,27 +142,26 @@ const ChapterComponent = ({
|
|
67 |
const stored_chapter = await ChapterStorage.get(`${SOURCE}-${ID}`,chapter.id)
|
68 |
if (stored_chapter?.data_state === "completed") set_is_saved(true)
|
69 |
else set_is_saved(false)
|
|
|
70 |
})()}, [])
|
71 |
|
72 |
useEffect(()=>{(async () => {
|
|
|
73 |
const stored_chapter = await ChapterStorage.get(`${SOURCE}-${ID}`,chapter.id)
|
74 |
if (stored_chapter?.data_state === "completed") set_is_saved(true)
|
75 |
else set_is_saved(false)
|
|
|
76 |
})()},[page,sort])
|
77 |
|
78 |
const Request_Download = async (CHAPTER:any) => {
|
79 |
-
|
80 |
const stored_comic = await ComicStorage.getByID(SOURCE,ID)
|
81 |
if (stored_comic) {
|
82 |
setWidgetContext({state:true,component:<RequestChapterWidget
|
83 |
SOURCE={SOURCE}
|
84 |
ID={ID}
|
85 |
CHAPTER={CHAPTER}
|
86 |
-
|
87 |
-
|
88 |
-
chapterRequested={chapterRequested}
|
89 |
-
setChapterRequested={setChapterRequested}
|
90 |
-
get_requested_info={() => get_requested_info(setShowCloudflareTurnstileContext, setChapterRequested, setChapterToDownload, signal, SOURCE, ID)}
|
91 |
/>})
|
92 |
}
|
93 |
else{
|
@@ -134,7 +208,8 @@ 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 |
-
|
|
|
138 |
}else{
|
139 |
Toast.show({
|
140 |
type: 'error',
|
@@ -158,142 +233,143 @@ const ChapterComponent = ({
|
|
158 |
>
|
159 |
{chapter.title}
|
160 |
</Button>
|
161 |
-
{
|
162 |
-
?
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
|
|
|
|
|
|
169 |
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
onPress={()=>{
|
180 |
-
Toast.show({
|
181 |
-
type: 'error',
|
182 |
-
text1: '❓Request not found in server.',
|
183 |
-
text2: "You request this chapter but the server doesn't have this in queue.\nTry request again.",
|
184 |
-
|
185 |
-
position: "bottom",
|
186 |
-
visibilityTime: 12000,
|
187 |
-
text1Style:{
|
188 |
-
fontFamily:"roboto-bold",
|
189 |
-
fontSize:((Dimensions.width+Dimensions.height)/2)*0.025
|
190 |
-
},
|
191 |
-
text2Style:{
|
192 |
-
fontFamily:"roboto-medium",
|
193 |
-
fontSize:((Dimensions.width+Dimensions.height)/2)*0.0185,
|
194 |
-
|
195 |
-
},
|
196 |
-
});
|
197 |
-
Request_Download(chapter)
|
198 |
-
}}
|
199 |
-
>
|
200 |
-
<Icon source={"alert-circle"} size={((Dimensions.width+Dimensions.height)/2)*0.04} color={"red"}/>
|
201 |
-
|
202 |
-
</TouchableRipple>
|
203 |
-
}</>
|
204 |
-
|
205 |
-
<>{chapterRequested[chapter.id]?.state === "ready"
|
206 |
-
&& <>{chapterToDownload[chapter.id]?.state === "downloading"
|
207 |
-
? <CircularProgress
|
208 |
-
value={downloadProgress[chapter.id].progress}
|
209 |
-
maxValue={downloadProgress[chapter.id].total}
|
210 |
-
radius={((Dimensions.width+Dimensions.height)/2)*0.0225}
|
211 |
-
inActiveStrokeColor={Theme[themeTypeContext].border_color}
|
212 |
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
|
220 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
221 |
}}
|
222 |
-
|
223 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
224 |
|
225 |
-
|
226 |
-
|
227 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
228 |
|
229 |
-
|
230 |
-
|
231 |
|
232 |
-
|
233 |
-
|
234 |
-
setChapterToDownload(chapter_to_download)
|
235 |
|
236 |
-
|
237 |
-
|
238 |
-
setDownloadProgress(download_progress)
|
239 |
|
240 |
-
|
241 |
-
|
242 |
-
|
243 |
-
|
244 |
-
|
245 |
-
|
|
|
246 |
}</>
|
247 |
-
|
248 |
-
|
249 |
-
|
250 |
-
|
251 |
-
|
252 |
-
|
253 |
-
|
254 |
-
|
255 |
-
|
256 |
|
257 |
-
|
258 |
-
|
259 |
-
|
260 |
-
|
261 |
-
|
262 |
-
|
263 |
-
|
264 |
-
|
265 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
266 |
}}
|
267 |
-
|
268 |
-
|
269 |
-
|
270 |
}}
|
271 |
-
|
272 |
-
|
|
|
|
|
|
|
273 |
}</>
|
274 |
-
|
275 |
-
|
276 |
-
|
277 |
-
? <TouchableRipple
|
278 |
-
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
279 |
-
style={{
|
280 |
-
borderRadius:5,
|
281 |
-
borderWidth:0,
|
282 |
-
padding:5,
|
283 |
-
}}
|
284 |
-
|
285 |
-
onPress={()=>{
|
286 |
-
Request_Download(chapter)
|
287 |
-
}}
|
288 |
-
>
|
289 |
-
<Icon source={"cloud-download"} size={((Dimensions.width+Dimensions.height)/2)*0.04} color={Theme[themeTypeContext].icon_color}/>
|
290 |
-
|
291 |
-
</TouchableRipple>
|
292 |
-
:<></>
|
293 |
-
}</>
|
294 |
-
</>
|
295 |
}</>
|
296 |
-
: <Icon source={"wifi-off"} size={((Dimensions.width+Dimensions.height)/2)*0.0425} color={Theme[themeTypeContext].icon_color}/>
|
297 |
}
|
298 |
</View>}</>
|
299 |
}
|
|
|
12 |
import { View, AnimatePresence } from 'moti';
|
13 |
import * as Clipboard from 'expo-clipboard';
|
14 |
import NetInfo from "@react-native-community/netinfo";
|
15 |
+
import dayjs from 'dayjs'; import utc from 'dayjs/plugin/utc'; dayjs.extend(utc);
|
16 |
|
17 |
|
18 |
import Theme from '@/constants/theme';
|
|
|
37 |
chapter,
|
38 |
signal,
|
39 |
isDownloading,
|
40 |
+
chapter_requested,
|
41 |
+
download_progress,
|
42 |
+
chapter_to_download,
|
43 |
+
chapter_queue,
|
|
|
|
|
|
|
|
|
44 |
|
45 |
}:any) => {
|
46 |
const {showMenuContext, setShowMenuContext}:any = useContext(CONTEXT)
|
|
|
54 |
|
55 |
const [styles, setStyles]:any = useState("")
|
56 |
const [is_saved, set_is_saved] = useState(false)
|
57 |
+
const [isLoading, setIsLoading] = useState(true);
|
58 |
const [is_net_connected, set_is_net_connected]:any = useState(false)
|
59 |
|
60 |
+
|
61 |
+
const [chapterQueue, setChapterQueue]:any = useState({})
|
62 |
+
const [chapterRequested, setChapterRequested]:any = useState({})
|
63 |
+
const [chapterToDownload, setChapterToDownload]:any = useState({})
|
64 |
+
const [downloadProgress, setDownloadProgress]:any = useState({[chapter.id]:{progress:0,total:100}})
|
65 |
+
|
66 |
+
|
67 |
+
const chapter_status_interval = useRef<any>(null)
|
68 |
+
const is_running = useRef<boolean>(false)
|
69 |
+
|
70 |
+
useEffect(()=>{
|
71 |
+
// console.log("CTD", chapterToDownload)
|
72 |
+
},[chapterToDownload])
|
73 |
+
|
74 |
+
useEffect(()=>{
|
75 |
+
// console.log("CR", chapterRequested)
|
76 |
+
},[chapterRequested])
|
77 |
+
|
78 |
+
useFocusEffect(useCallback(() => {
|
79 |
+
clearInterval(chapter_status_interval.current)
|
80 |
+
chapter_status_interval.current = setInterval(() => {
|
81 |
+
if (is_running.current) return
|
82 |
+
is_running.current = true
|
83 |
+
|
84 |
+
// current -> Checking for chapter requested
|
85 |
+
if (chapter_requested.current.hasOwnProperty(chapter.id) && !chapterRequested.hasOwnProperty(chapter.id)) {
|
86 |
+
setChapterRequested({[chapter.id]:chapter_requested.current[chapter.id]})
|
87 |
+
}else if (
|
88 |
+
chapter_requested.current.hasOwnProperty(chapter.id)
|
89 |
+
&& chapter_requested.current[chapter.id]?.state !== chapterRequested[chapter.id]?.state
|
90 |
+
) {
|
91 |
+
setChapterRequested({[chapter.id]:chapter_requested.current[chapter.id]})
|
92 |
+
}else if (!chapter_requested.current.hasOwnProperty(chapter.id) && chapterRequested.hasOwnProperty(chapter.id)){
|
93 |
+
setChapterRequested({})
|
94 |
+
}
|
95 |
+
|
96 |
+
// current -> Checking for chapter to download
|
97 |
+
if (chapter_to_download.current.hasOwnProperty(chapter.id) && !chapterToDownload.hasOwnProperty(chapter.id)) {
|
98 |
+
setChapterToDownload({[chapter.id]:chapter_to_download.current[chapter.id]})
|
99 |
+
}else if (
|
100 |
+
chapter_to_download.current.hasOwnProperty(chapter.id)
|
101 |
+
&& chapter_to_download.current[chapter.id]?.state !== chapterToDownload[chapter.id]?.state
|
102 |
+
) {
|
103 |
+
setChapterToDownload({[chapter.id]:chapter_to_download.current[chapter.id]})
|
104 |
+
}else if (!chapter_to_download.current.hasOwnProperty(chapter.id) && chapterToDownload.hasOwnProperty(chapter.id)){
|
105 |
+
setChapterToDownload({})
|
106 |
+
}
|
107 |
+
|
108 |
+
// current -> Check download progress
|
109 |
+
if (download_progress.current.hasOwnProperty(chapter.id) && !downloadProgress.hasOwnProperty(chapter.id)) {
|
110 |
+
setDownloadProgress({[chapter.id]:download_progress.current[chapter.id]})
|
111 |
+
}else if (
|
112 |
+
download_progress.current.hasOwnProperty(chapter.id)
|
113 |
+
&& (
|
114 |
+
download_progress.current[chapter.id]?.progress !== downloadProgress[chapter.id]?.progress
|
115 |
+
|| download_progress.current[chapter.id]?.total !== downloadProgress[chapter.id]?.total
|
116 |
+
)
|
117 |
+
) {
|
118 |
+
setDownloadProgress({[chapter.id]:download_progress.current[chapter.id]})
|
119 |
+
}else if (!download_progress.current.hasOwnProperty(chapter.id) && downloadProgress.hasOwnProperty(chapter.id)){
|
120 |
+
setDownloadProgress({})
|
121 |
+
}
|
122 |
+
|
123 |
+
// current -> Check chapter Queue
|
124 |
+
if (chapter_queue.current?.queue?.hasOwnProperty(`${SOURCE}-${ID}-${chapter.idx}`)) {
|
125 |
+
setChapterQueue(chapter_queue.current)
|
126 |
+
}else if (chapterQueue.queue?.hasOwnProperty(`${SOURCE}-${ID}-${chapter.idx}`)){
|
127 |
+
setChapterQueue({})
|
128 |
+
}
|
129 |
+
|
130 |
+
is_running.current = false
|
131 |
+
},3000)
|
132 |
+
return () => clearInterval(chapter_status_interval.current)
|
133 |
+
},[chapterQueue,chapterRequested,chapterToDownload, downloadProgress]))
|
134 |
+
|
135 |
+
|
136 |
useEffect(() => {(async () => {
|
137 |
+
setIsLoading(true)
|
138 |
const net_info = await NetInfo.fetch()
|
139 |
set_is_net_connected(net_info.isConnected)
|
140 |
|
|
|
142 |
const stored_chapter = await ChapterStorage.get(`${SOURCE}-${ID}`,chapter.id)
|
143 |
if (stored_chapter?.data_state === "completed") set_is_saved(true)
|
144 |
else set_is_saved(false)
|
145 |
+
setIsLoading(false)
|
146 |
})()}, [])
|
147 |
|
148 |
useEffect(()=>{(async () => {
|
149 |
+
setIsLoading(true)
|
150 |
const stored_chapter = await ChapterStorage.get(`${SOURCE}-${ID}`,chapter.id)
|
151 |
if (stored_chapter?.data_state === "completed") set_is_saved(true)
|
152 |
else set_is_saved(false)
|
153 |
+
setIsLoading(false)
|
154 |
})()},[page,sort])
|
155 |
|
156 |
const Request_Download = async (CHAPTER:any) => {
|
|
|
157 |
const stored_comic = await ComicStorage.getByID(SOURCE,ID)
|
158 |
if (stored_comic) {
|
159 |
setWidgetContext({state:true,component:<RequestChapterWidget
|
160 |
SOURCE={SOURCE}
|
161 |
ID={ID}
|
162 |
CHAPTER={CHAPTER}
|
163 |
+
chapter_requested={chapter_requested}
|
164 |
+
get_requested_info={() => get_requested_info(setShowCloudflareTurnstileContext, chapter_requested, chapter_to_download, signal, SOURCE, ID)}
|
|
|
|
|
|
|
165 |
/>})
|
166 |
}
|
167 |
else{
|
|
|
208 |
if (stored_chapter?.data_state === "completed") {
|
209 |
const stored_comic = await ComicStorage.getByID(SOURCE,ID)
|
210 |
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})
|
211 |
+
|
212 |
+
router.navigate(`/read/${SOURCE}/${ID}/${chapter.idx}/`)
|
213 |
}else{
|
214 |
Toast.show({
|
215 |
type: 'error',
|
|
|
233 |
>
|
234 |
{chapter.title}
|
235 |
</Button>
|
236 |
+
{isLoading
|
237 |
+
? (<ActivityIndicator animating={true} color={Theme[themeTypeContext].icon_color} />)
|
238 |
+
|
239 |
+
: <>{is_net_connected || is_saved
|
240 |
+
? <>{is_saved
|
241 |
+
? <Icon source={"content-save-check"} size={((Dimensions.width+Dimensions.height)/2)*0.0425} color={Theme[themeTypeContext].icon_color}/>
|
242 |
+
: <>
|
243 |
+
<>{chapterRequested[chapter.id]?.state === "queue" && !chapterQueue?.queue?.hasOwnProperty(`${SOURCE}-${ID}-${chapter.idx}`)
|
244 |
+
&& <ActivityIndicator animating={true} color={"#7df9ff"} />
|
245 |
+
}</>
|
246 |
+
|
247 |
|
248 |
+
<>{chapterRequested[chapter.id]?.state === "unkown" && !chapterQueue.queue?.hasOwnProperty(`${SOURCE}-${ID}-${chapter.idx}`)
|
249 |
+
&& <TouchableRipple
|
250 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
251 |
+
style={{
|
252 |
+
borderRadius:5,
|
253 |
+
borderWidth:0,
|
254 |
+
padding:5,
|
255 |
+
}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
256 |
|
257 |
+
onPress={()=>{
|
258 |
+
Toast.show({
|
259 |
+
type: 'error',
|
260 |
+
text1: '❓Request not found in server.',
|
261 |
+
text2: "You request this chapter but the server doesn't have this in queue.\nTry request again.",
|
262 |
+
|
263 |
+
position: "bottom",
|
264 |
+
visibilityTime: 12000,
|
265 |
+
text1Style:{
|
266 |
+
fontFamily:"roboto-bold",
|
267 |
+
fontSize:((Dimensions.width+Dimensions.height)/2)*0.025
|
268 |
+
},
|
269 |
+
text2Style:{
|
270 |
+
fontFamily:"roboto-medium",
|
271 |
+
fontSize:((Dimensions.width+Dimensions.height)/2)*0.0185,
|
272 |
+
|
273 |
+
},
|
274 |
+
});
|
275 |
+
Request_Download(chapter)
|
276 |
}}
|
277 |
+
>
|
278 |
+
<Icon source={"alert-circle"} size={((Dimensions.width+Dimensions.height)/2)*0.04} color={"red"}/>
|
279 |
+
|
280 |
+
</TouchableRipple>
|
281 |
+
}</>
|
282 |
+
|
283 |
+
<>{chapterRequested[chapter.id]?.state === "ready"
|
284 |
+
&& <>{chapterToDownload[chapter.id]?.state === "downloading"
|
285 |
+
? <CircularProgress
|
286 |
+
value={downloadProgress[chapter.id].progress}
|
287 |
+
maxValue={downloadProgress[chapter.id].total}
|
288 |
+
radius={((Dimensions.width+Dimensions.height)/2)*0.0225}
|
289 |
+
inActiveStrokeColor={Theme[themeTypeContext].border_color}
|
290 |
|
291 |
+
showProgressValue={false}
|
292 |
+
title={"📥"}
|
293 |
+
titleStyle={{
|
294 |
+
pointerEvents:"none",
|
295 |
+
color:Theme[themeTypeContext].text_color,
|
296 |
+
fontSize:((Dimensions.width+Dimensions.height)/2)*0.025,
|
297 |
+
fontFamily:"roboto-medium",
|
298 |
+
textAlign:"center",
|
299 |
+
}}
|
300 |
+
onAnimationComplete={async ()=>{
|
301 |
+
if (downloadProgress[chapter.id].progress !== downloadProgress[chapter.id].total) return
|
302 |
+
|
303 |
+
const stored_chapter_requested = (await ComicStorage.getByID(SOURCE,ID)).chapter_requested
|
304 |
+
const new_chapter_requested = stored_chapter_requested.filter((item:any) => item.chapter_id !== chapter.id);
|
305 |
+
await ComicStorage.updateChapterQueue(SOURCE,ID,new_chapter_requested)
|
306 |
|
307 |
+
delete chapter_requested.current[chapter.id]
|
308 |
+
setChapterRequested({})
|
309 |
|
310 |
+
delete chapter_to_download.current[chapter.id]
|
311 |
+
setChapterToDownload({})
|
|
|
312 |
|
313 |
+
delete download_progress[chapter.id]
|
314 |
+
setDownloadProgress({})
|
|
|
315 |
|
316 |
+
set_is_saved(true)
|
317 |
+
isDownloading.current = false
|
318 |
+
console.log("DONE DOWNLOADING!")
|
319 |
+
}}
|
320 |
+
/>
|
321 |
+
: <ActivityIndicator animating={true} color={"green"} />
|
322 |
+
}</>
|
323 |
}</>
|
324 |
+
|
325 |
+
<>{chapterQueue.queue?.hasOwnProperty(`${SOURCE}-${ID}-${chapter.idx}`) && !(chapterRequested[chapter.id]?.state === "ready")
|
326 |
+
&& <>{chapterQueue.queue[`${SOURCE}-${ID}-${chapter.idx}`]
|
327 |
+
? <CircularProgress
|
328 |
+
value={100 - (((chapterQueue.queue[`${SOURCE}-${ID}-${chapter.idx}`])*100)/chapterQueue.max_queue)}
|
329 |
+
maxValue={100}
|
330 |
+
radius={((Dimensions.width+Dimensions.height)/2)*0.0225}
|
331 |
+
inActiveStrokeColor={Theme[themeTypeContext].border_color}
|
332 |
+
|
333 |
|
334 |
+
showProgressValue={false}
|
335 |
+
title={chapterQueue.queue[`${SOURCE}-${ID}-${chapter.idx}`]}
|
336 |
+
titleStyle={{
|
337 |
+
pointerEvents:"none",
|
338 |
+
color:Theme[themeTypeContext].text_color,
|
339 |
+
fontSize:((Dimensions.width+Dimensions.height)/2)*0.025,
|
340 |
+
fontFamily:"roboto-medium",
|
341 |
+
textAlign:"center",
|
342 |
+
}}
|
343 |
+
onAnimationComplete={()=>{
|
344 |
+
get_requested_info(setShowCloudflareTurnstileContext, setChapterRequested, chapter_to_download, signal, SOURCE, ID)
|
345 |
+
}}
|
346 |
+
/>
|
347 |
+
: <ActivityIndicator animating={true} />
|
348 |
+
}</>
|
349 |
+
}</>
|
350 |
+
|
351 |
+
<>{!chapterRequested.hasOwnProperty(chapter.id) && !chapterQueue.queue?.hasOwnProperty(`${SOURCE}-${ID}-${chapter.idx}`)
|
352 |
+
? <TouchableRipple
|
353 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
354 |
+
style={{
|
355 |
+
borderRadius:5,
|
356 |
+
borderWidth:0,
|
357 |
+
padding:5,
|
358 |
}}
|
359 |
+
|
360 |
+
onPress={()=>{
|
361 |
+
Request_Download(chapter)
|
362 |
}}
|
363 |
+
>
|
364 |
+
<Icon source={"cloud-download"} size={((Dimensions.width+Dimensions.height)/2)*0.04} color={Theme[themeTypeContext].icon_color}/>
|
365 |
+
|
366 |
+
</TouchableRipple>
|
367 |
+
:<></>
|
368 |
}</>
|
369 |
+
</>
|
370 |
+
}</>
|
371 |
+
: <Icon source={"wifi-off"} size={((Dimensions.width+Dimensions.height)/2)*0.0425} color={Theme[themeTypeContext].icon_color}/>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
372 |
}</>
|
|
|
373 |
}
|
374 |
</View>}</>
|
375 |
}
|
frontend/app/view/componenets/widgets/bookmark.tsx
CHANGED
@@ -1,5 +1,5 @@
|
|
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';
|
@@ -17,9 +17,10 @@ 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[];
|
@@ -27,10 +28,11 @@ interface BookmarkWidgetProps {
|
|
27 |
}
|
28 |
|
29 |
const BookmarkWidget: React.FC<BookmarkWidgetProps> = ({
|
|
|
30 |
onRefresh,
|
31 |
SOURCE,
|
32 |
ID,
|
33 |
-
CONTENT
|
34 |
}) => {
|
35 |
const Dimensions = useWindowDimensions();
|
36 |
|
@@ -58,206 +60,207 @@ const BookmarkWidget: React.FC<BookmarkWidgetProps> = ({
|
|
58 |
const controller = new AbortController();
|
59 |
const signal = controller.signal;
|
60 |
|
61 |
-
const RenderTag =
|
62 |
-
|
63 |
-
|
64 |
-
|
65 |
-
|
66 |
-
|
67 |
-
|
68 |
-
|
69 |
-
|
70 |
-
|
71 |
-
|
72 |
-
|
73 |
-
|
74 |
-
|
75 |
-
|
76 |
-
|
77 |
-
|
78 |
-
|
79 |
-
|
80 |
-
|
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:"
|
|
|
|
|
|
|
|
|
98 |
height:"auto",
|
99 |
-
|
100 |
}}
|
101 |
>
|
102 |
-
|
103 |
-
<TouchableRipple
|
104 |
-
|
105 |
-
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
106 |
style={{
|
107 |
-
|
108 |
-
|
109 |
-
|
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 |
-
|
176 |
-
|
177 |
-
|
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 |
-
|
191 |
-
|
192 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
193 |
}}
|
194 |
>
|
195 |
|
196 |
-
<Icon source={"
|
197 |
</TouchableRipple>
|
198 |
-
|
199 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
200 |
style={{
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
padding:5,
|
205 |
}}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
206 |
|
207 |
-
|
208 |
-
|
|
|
|
|
|
|
|
|
209 |
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
|
|
|
|
|
|
|
|
|
|
|
215 |
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
|
220 |
-
|
221 |
-
|
222 |
-
|
|
|
223 |
|
224 |
-
|
225 |
-
|
226 |
-
|
227 |
-
|
228 |
-
|
229 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
230 |
}
|
231 |
-
|
232 |
-
setManageBookmark({...manageBookmark,edit:""})
|
233 |
-
setEditTag("")
|
234 |
}
|
235 |
-
|
236 |
-
}
|
237 |
-
|
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 |
-
|
248 |
|
249 |
-
|
250 |
|
|
|
|
|
|
|
|
|
251 |
|
252 |
-
|
253 |
-
|
254 |
-
|
255 |
-
|
256 |
-
|
257 |
-
}
|
258 |
-
</>)
|
259 |
-
}
|
260 |
-
|
261 |
|
262 |
|
263 |
const load_bookmark = async ()=>{
|
@@ -283,7 +286,6 @@ const BookmarkWidget: React.FC<BookmarkWidgetProps> = ({
|
|
283 |
}
|
284 |
|
285 |
useEffect(()=>{
|
286 |
-
console.log(BOOKMARK_DATA)
|
287 |
SET_MIGRATE_BOOKMARK_DATA([{label:"None",value:""},...BOOKMARK_DATA])
|
288 |
},[BOOKMARK_DATA])
|
289 |
|
@@ -298,8 +300,8 @@ const BookmarkWidget: React.FC<BookmarkWidgetProps> = ({
|
|
298 |
style={{
|
299 |
zIndex:10,
|
300 |
backgroundColor:Theme[themeTypeContext].background_color,
|
301 |
-
|
302 |
-
|
303 |
|
304 |
borderColor:Theme[themeTypeContext].border_color,
|
305 |
borderWidth:2,
|
@@ -351,7 +353,7 @@ const BookmarkWidget: React.FC<BookmarkWidgetProps> = ({
|
|
351 |
theme_type={themeTypeContext}
|
352 |
Dimensions={Dimensions}
|
353 |
|
354 |
-
label='Add to
|
355 |
data={BOOKMARK_DATA}
|
356 |
value={bookmark}
|
357 |
onChange={(async (item:any) => {
|
@@ -420,10 +422,9 @@ const BookmarkWidget: React.FC<BookmarkWidgetProps> = ({
|
|
420 |
const stored_comic = await ComicStorage.getByID(SOURCE,CONTENT.id)
|
421 |
if (stored_comic) await ComicStorage.replaceTag(SOURCE, CONTENT.id, bookmark)
|
422 |
else {
|
423 |
-
|
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,
|
@@ -457,7 +458,7 @@ const BookmarkWidget: React.FC<BookmarkWidgetProps> = ({
|
|
457 |
style={{flex:1}}
|
458 |
>
|
459 |
<TextInput mode="outlined" label="Search" textColor={Theme[themeTypeContext].text_color}
|
460 |
-
|
461 |
style={{
|
462 |
|
463 |
backgroundColor:Theme[themeTypeContext].background_color,
|
@@ -491,10 +492,14 @@ const BookmarkWidget: React.FC<BookmarkWidgetProps> = ({
|
|
491 |
|
492 |
}}
|
493 |
>
|
494 |
-
<>{BOOKMARK_DATA.map((item:any) =>
|
495 |
-
|
496 |
-
|
497 |
-
|
|
|
|
|
|
|
|
|
498 |
</ScrollView>
|
499 |
</View>
|
500 |
</>
|
@@ -505,7 +510,7 @@ const BookmarkWidget: React.FC<BookmarkWidgetProps> = ({
|
|
505 |
color:Theme[themeTypeContext].text_color,
|
506 |
fontSize:(Dimensions.width+Dimensions.height)/2*0.045,
|
507 |
fontFamily:"roboto-bold",
|
508 |
-
}}>No
|
509 |
</>
|
510 |
|
511 |
}</>
|
@@ -530,7 +535,7 @@ const BookmarkWidget: React.FC<BookmarkWidgetProps> = ({
|
|
530 |
setCreateTag({state:true,title:""})
|
531 |
setShowMenuOption({...showMenuOption,state:false,id:""})
|
532 |
})}
|
533 |
-
>+ Create
|
534 |
<Button mode='outlined'
|
535 |
labelStyle={{
|
536 |
color:Theme[themeTypeContext].text_color,
|
@@ -564,7 +569,7 @@ const BookmarkWidget: React.FC<BookmarkWidgetProps> = ({
|
|
564 |
gap:12,
|
565 |
}}
|
566 |
>
|
567 |
-
<TextInput mode="outlined" label="Create
|
568 |
placeholder="Bookmark Tag"
|
569 |
|
570 |
right={<TextInput.Affix text={`| Max: 72`} />}
|
@@ -738,6 +743,8 @@ const BookmarkWidget: React.FC<BookmarkWidgetProps> = ({
|
|
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}/`
|
@@ -745,10 +752,10 @@ const BookmarkWidget: React.FC<BookmarkWidgetProps> = ({
|
|
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 |
</>
|
@@ -809,12 +816,12 @@ const BookmarkWidget: React.FC<BookmarkWidgetProps> = ({
|
|
809 |
|
810 |
}}
|
811 |
>
|
812 |
-
<Icon source={"pencil"} size={((Dimensions.width+Dimensions.height)/2)*0.025} color={"
|
813 |
<View>
|
814 |
<Text selectable={false}
|
815 |
style={{
|
816 |
textAlign:"center",
|
817 |
-
color:"
|
818 |
fontFamily:"roboto-medium",
|
819 |
fontSize:(Dimensions.width+Dimensions.height)/2*0.02
|
820 |
}}
|
@@ -881,13 +888,14 @@ const BookmarkWidget: React.FC<BookmarkWidgetProps> = ({
|
|
881 |
display:"flex",
|
882 |
justifyContent:"center",
|
883 |
alignItems:"center",
|
|
|
884 |
}}
|
885 |
>
|
886 |
<View
|
887 |
style={{
|
888 |
backgroundColor:Theme[themeTypeContext].background_color,
|
889 |
-
|
890 |
-
|
891 |
height:"auto",
|
892 |
|
893 |
borderColor:Theme[themeTypeContext].border_color,
|
|
|
1 |
|
2 |
+
import React, { useEffect, useState, useCallback, useContext, useRef, Fragment, memo } 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';
|
|
|
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 |
+
import ChapterDataStorage from '@/constants/module/storages/chapter_data_storage';
|
21 |
|
22 |
interface BookmarkWidgetProps {
|
23 |
+
setIsLoading: any;
|
24 |
onRefresh: any;
|
25 |
SOURCE: string | string[];
|
26 |
ID: string | string[];
|
|
|
28 |
}
|
29 |
|
30 |
const BookmarkWidget: React.FC<BookmarkWidgetProps> = ({
|
31 |
+
setIsLoading,
|
32 |
onRefresh,
|
33 |
SOURCE,
|
34 |
ID,
|
35 |
+
CONTENT,
|
36 |
}) => {
|
37 |
const Dimensions = useWindowDimensions();
|
38 |
|
|
|
60 |
const controller = new AbortController();
|
61 |
const signal = controller.signal;
|
62 |
|
63 |
+
const RenderTag = useCallback(({item}:any) =>{
|
64 |
+
const [editTag, setEditTag]:any = useState(item.value)
|
65 |
+
useEffect(()=>{
|
66 |
+
},[manageBookmark])
|
67 |
+
|
68 |
+
return (<>
|
69 |
+
{item.value.includes(searchTag) &&
|
70 |
+
(
|
71 |
+
<View
|
72 |
+
style={{
|
73 |
+
display:"flex",
|
74 |
+
flexDirection:"row",
|
75 |
+
alignItems:"center",
|
76 |
+
justifyContent:"space-between",
|
77 |
+
gap:8,
|
78 |
+
zIndex:10,
|
79 |
+
}}
|
80 |
+
>
|
81 |
+
<>{manageBookmark.edit !== item.value && manageBookmark.delete !== item.value &&
|
82 |
+
(<View
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
83 |
style={{
|
84 |
+
width:"100%",
|
85 |
+
display:"flex",
|
86 |
+
flexDirection:"row",
|
87 |
+
justifyContent:"space-between",
|
88 |
+
alignItems:"center",
|
89 |
height:"auto",
|
90 |
+
gap:18,
|
91 |
}}
|
92 |
>
|
93 |
+
<Text
|
|
|
|
|
|
|
94 |
style={{
|
95 |
+
color:"white",
|
96 |
+
fontFamily:"roboto-medium",
|
97 |
+
fontSize:(Dimensions.width+Dimensions.height)/2*0.025
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
98 |
}}
|
99 |
+
>{item.label}</Text>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
100 |
<View
|
101 |
style={{
|
102 |
+
width:"auto",
|
103 |
+
height:"auto",
|
104 |
+
|
105 |
}}
|
106 |
>
|
107 |
+
|
108 |
<TouchableRipple
|
109 |
+
|
110 |
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
111 |
style={{
|
112 |
borderRadius:5,
|
113 |
borderWidth:0,
|
114 |
backgroundColor: "transparent",
|
115 |
padding:5,
|
116 |
+
|
117 |
}}
|
118 |
|
119 |
+
onPress={(event)=>{
|
120 |
+
if (manageBookmark.edit){
|
121 |
+
setManageBookmark({...manageBookmark,edit:""})
|
122 |
+
setEditTag("")
|
123 |
+
}
|
124 |
+
|
125 |
+
const x = event.nativeEvent.pageX
|
126 |
+
const y = event.nativeEvent.pageY
|
127 |
+
|
128 |
+
setShowMenuOption({
|
129 |
+
...showMenuOption,
|
130 |
+
state: showMenuOption.id === item.value ? false : true,
|
131 |
+
positions:[y+((Dimensions.width+Dimensions.height)/2)*0.0225,0,x-((Dimensions.width+Dimensions.height)/2)*0.18,0],
|
132 |
+
id:showMenuOption.id === item.value ? "" : item.value,
|
133 |
+
})
|
134 |
+
|
135 |
+
|
136 |
+
|
137 |
}}
|
138 |
>
|
139 |
|
140 |
+
<Icon source={"dots-vertical"} size={((Dimensions.width+Dimensions.height)/2)*0.035} color={Theme[themeTypeContext].icon_color}/>
|
141 |
</TouchableRipple>
|
142 |
+
</View>
|
143 |
+
</View>)
|
144 |
+
}</>
|
145 |
+
<>{manageBookmark.edit === item.value &&
|
146 |
+
(<View
|
147 |
+
style={{
|
148 |
+
display:"flex",
|
149 |
+
flexDirection:"row",
|
150 |
+
justifyContent:"space-between",
|
151 |
+
alignItems:"center",
|
152 |
+
width:"100%",
|
153 |
+
height:"auto",
|
154 |
+
gap:12,
|
155 |
+
padding:12,
|
156 |
+
}}
|
157 |
+
>
|
158 |
+
<View
|
159 |
+
style={{flex:1}}
|
160 |
+
>
|
161 |
+
<TextInput mode="outlined" label="Edit" textColor={Theme[themeTypeContext].text_color} maxLength={72}
|
162 |
+
autoFocus={true}
|
163 |
+
right={<TextInput.Affix text={`| Max: 72`} />}
|
164 |
+
style={{
|
165 |
+
backgroundColor:Theme[themeTypeContext].background_color,
|
166 |
+
borderColor:Theme[themeTypeContext].border_color,
|
167 |
+
|
168 |
+
}}
|
169 |
+
outlineColor={Theme[themeTypeContext].text_input_border_color}
|
170 |
+
value={editTag}
|
171 |
+
onChangeText={(text)=>{
|
172 |
+
setEditTag(text)
|
173 |
+
}}
|
174 |
+
/>
|
175 |
+
</View>
|
176 |
+
<View
|
177 |
style={{
|
178 |
+
display:"flex",
|
179 |
+
flexDirection:"row",
|
180 |
+
gap:8,
|
|
|
181 |
}}
|
182 |
+
>
|
183 |
+
<TouchableRipple
|
184 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
185 |
+
style={{
|
186 |
+
borderRadius:5,
|
187 |
+
borderWidth:0,
|
188 |
+
backgroundColor: "transparent",
|
189 |
+
padding:5,
|
190 |
+
}}
|
191 |
|
192 |
+
onPress={()=>{
|
193 |
+
setManageBookmark({...manageBookmark,edit:""})
|
194 |
+
setEditTag("")
|
195 |
+
setShowMenuOption({...showMenuOption,state:false,id:""})
|
196 |
+
}}
|
197 |
+
>
|
198 |
|
199 |
+
<Icon source={"close"} size={((Dimensions.width+Dimensions.height)/2)*0.035} color={"red"}/>
|
200 |
+
</TouchableRipple>
|
201 |
+
<TouchableRipple
|
202 |
+
rippleColor={Theme[themeTypeContext].ripple_color_outlined}
|
203 |
+
style={{
|
204 |
+
borderRadius:5,
|
205 |
+
borderWidth:0,
|
206 |
+
backgroundColor: "transparent",
|
207 |
+
padding:5,
|
208 |
+
}}
|
209 |
|
210 |
+
onPress={async ()=>{
|
211 |
+
const stored_bookmark = await Storage.get("bookmark");
|
212 |
+
|
213 |
+
const index = stored_bookmark.findIndex((item:string) => item === manageBookmark.edit);
|
214 |
+
|
215 |
+
if (index !== -1){
|
216 |
+
stored_bookmark[index] = editTag;
|
217 |
+
await Storage.store("bookmark", stored_bookmark)
|
218 |
|
219 |
+
const stored_comics:any = await ComicStorage.getByTag(manageBookmark.edit)
|
220 |
+
for (const item of stored_comics){
|
221 |
+
await ComicStorage.replaceTag(item.source, item.id, editTag)
|
222 |
+
}
|
223 |
+
if ((manageBookmark.edit === defaultTag) && (editTag !== defaultTag)) {
|
224 |
+
onRefresh();
|
225 |
+
setWidgetContext({state:false,component:<></>});
|
226 |
+
|
227 |
+
}else{
|
228 |
+
|
229 |
+
const index = BOOKMARK_DATA.findIndex((item:any) => item.value === manageBookmark.edit);
|
230 |
+
if (index !== -1){
|
231 |
+
BOOKMARK_DATA[index].label = editTag
|
232 |
+
BOOKMARK_DATA[index].value = editTag
|
233 |
+
}
|
234 |
+
SET_BOOKMARK_DATA(BOOKMARK_DATA)
|
235 |
+
setManageBookmark({...manageBookmark,edit:""})
|
236 |
+
setEditTag("")
|
237 |
}
|
238 |
+
|
|
|
|
|
239 |
}
|
240 |
+
setShowMenuOption({...showMenuOption,state:false,id:""})
|
241 |
+
}}
|
242 |
+
>
|
243 |
+
|
244 |
+
<Icon source={"check"} size={((Dimensions.width+Dimensions.height)/2)*0.035} color={"green"}/>
|
245 |
+
</TouchableRipple>
|
246 |
+
</View>
|
247 |
+
|
248 |
|
|
|
|
|
|
|
|
|
|
|
249 |
|
250 |
+
</View>)
|
251 |
|
252 |
+
}</>
|
253 |
|
254 |
+
|
255 |
+
|
256 |
+
|
257 |
+
</View>
|
258 |
|
259 |
+
)
|
260 |
+
}
|
261 |
+
</>)
|
262 |
+
}
|
263 |
+
,[Theme,themeTypeContext,manageBookmark,searchTag,removeTag,createTag,defaultTag,bookmark,showMenuOption,MIGRATE_BOOKMARK_DATA,BOOKMARK_DATA])
|
|
|
|
|
|
|
|
|
264 |
|
265 |
|
266 |
const load_bookmark = async ()=>{
|
|
|
286 |
}
|
287 |
|
288 |
useEffect(()=>{
|
|
|
289 |
SET_MIGRATE_BOOKMARK_DATA([{label:"None",value:""},...BOOKMARK_DATA])
|
290 |
},[BOOKMARK_DATA])
|
291 |
|
|
|
300 |
style={{
|
301 |
zIndex:10,
|
302 |
backgroundColor:Theme[themeTypeContext].background_color,
|
303 |
+
maxWidth:500,
|
304 |
+
width:"100%",
|
305 |
|
306 |
borderColor:Theme[themeTypeContext].border_color,
|
307 |
borderWidth:2,
|
|
|
353 |
theme_type={themeTypeContext}
|
354 |
Dimensions={Dimensions}
|
355 |
|
356 |
+
label='Add to tag'
|
357 |
data={BOOKMARK_DATA}
|
358 |
value={bookmark}
|
359 |
onChange={(async (item:any) => {
|
|
|
422 |
const stored_comic = await ComicStorage.getByID(SOURCE,CONTENT.id)
|
423 |
if (stored_comic) await ComicStorage.replaceTag(SOURCE, CONTENT.id, bookmark)
|
424 |
else {
|
425 |
+
await store_comic_cover(setShowCloudflareTurnstileContext,signal,SOURCE,ID,CONTENT)
|
426 |
|
427 |
await ComicStorage.store(SOURCE,CONTENT.id, bookmark, {
|
|
|
428 |
title:CONTENT.title,
|
429 |
author:CONTENT.author,
|
430 |
category:CONTENT.category,
|
|
|
458 |
style={{flex:1}}
|
459 |
>
|
460 |
<TextInput mode="outlined" label="Search" textColor={Theme[themeTypeContext].text_color}
|
461 |
+
|
462 |
style={{
|
463 |
|
464 |
backgroundColor:Theme[themeTypeContext].background_color,
|
|
|
492 |
|
493 |
}}
|
494 |
>
|
495 |
+
<>{BOOKMARK_DATA.map((item:any) =>
|
496 |
+
(
|
497 |
+
<View key={item.value}>
|
498 |
+
<RenderTag item={item}/>
|
499 |
+
<View style={{width:"100%",height:2,backgroundColor:Theme[themeTypeContext].border_color}}/>
|
500 |
+
</View>
|
501 |
+
)
|
502 |
+
)}</>
|
503 |
</ScrollView>
|
504 |
</View>
|
505 |
</>
|
|
|
510 |
color:Theme[themeTypeContext].text_color,
|
511 |
fontSize:(Dimensions.width+Dimensions.height)/2*0.045,
|
512 |
fontFamily:"roboto-bold",
|
513 |
+
}}>No tag found</Text>
|
514 |
</>
|
515 |
|
516 |
}</>
|
|
|
535 |
setCreateTag({state:true,title:""})
|
536 |
setShowMenuOption({...showMenuOption,state:false,id:""})
|
537 |
})}
|
538 |
+
>+ Create</Button>
|
539 |
<Button mode='outlined'
|
540 |
labelStyle={{
|
541 |
color:Theme[themeTypeContext].text_color,
|
|
|
569 |
gap:12,
|
570 |
}}
|
571 |
>
|
572 |
+
<TextInput mode="outlined" label="Create Tag" textColor={Theme[themeTypeContext].text_color} maxLength={72}
|
573 |
placeholder="Bookmark Tag"
|
574 |
|
575 |
right={<TextInput.Affix text={`| Max: 72`} />}
|
|
|
743 |
}}
|
744 |
style={{backgroundColor:"red",borderRadius:5}}
|
745 |
onPress={(async ()=>{
|
746 |
+
setWidgetContext({state:false,component:<></>})
|
747 |
+
setIsLoading(true)
|
748 |
setRemoveTag({...removeTag,removing:false})
|
749 |
if (Platform.OS !== "web"){
|
750 |
const comic_dir = FileSystem.documentDirectory + "ComicMTL/" + `${SOURCE}/` + `${ID}/`
|
|
|
752 |
}
|
753 |
|
754 |
await ChapterStorage.drop(`${SOURCE}-${CONTENT.id}`)
|
755 |
+
await ChapterDataStorage.removeByComicID(CONTENT.id)
|
756 |
await ComicStorage.removeByID(SOURCE,CONTENT.id)
|
757 |
+
|
758 |
onRefresh()
|
|
|
759 |
})}
|
760 |
>Yes</Button>
|
761 |
</>
|
|
|
816 |
|
817 |
}}
|
818 |
>
|
819 |
+
<Icon source={"pencil"} size={((Dimensions.width+Dimensions.height)/2)*0.025} color={"cyan"}/>
|
820 |
<View>
|
821 |
<Text selectable={false}
|
822 |
style={{
|
823 |
textAlign:"center",
|
824 |
+
color:"cyan",
|
825 |
fontFamily:"roboto-medium",
|
826 |
fontSize:(Dimensions.width+Dimensions.height)/2*0.02
|
827 |
}}
|
|
|
888 |
display:"flex",
|
889 |
justifyContent:"center",
|
890 |
alignItems:"center",
|
891 |
+
padding:15,
|
892 |
}}
|
893 |
>
|
894 |
<View
|
895 |
style={{
|
896 |
backgroundColor:Theme[themeTypeContext].background_color,
|
897 |
+
maxWidth:500,
|
898 |
+
width:"100%",
|
899 |
height:"auto",
|
900 |
|
901 |
borderColor:Theme[themeTypeContext].border_color,
|
frontend/app/view/componenets/widgets/request_chapter.tsx
CHANGED
@@ -23,21 +23,15 @@ interface RequestChapterWidgetProps {
|
|
23 |
SOURCE: string | string[];
|
24 |
ID: string | string[];
|
25 |
CHAPTER: any;
|
26 |
-
|
27 |
-
setChapterQueue: any;
|
28 |
-
chapterRequested: any;
|
29 |
-
setChapterRequested: any;
|
30 |
get_requested_info: any;
|
31 |
-
|
32 |
|
33 |
const RequestChapterWidget: React.FC<RequestChapterWidgetProps> = ({
|
34 |
SOURCE,
|
35 |
ID,
|
36 |
CHAPTER,
|
37 |
-
|
38 |
-
setChapterQueue,
|
39 |
-
chapterRequested,
|
40 |
-
setChapterRequested,
|
41 |
get_requested_info
|
42 |
}) => {
|
43 |
const Dimensions = useWindowDimensions();
|
@@ -102,7 +96,7 @@ const RequestChapterWidget: React.FC<RequestChapterWidgetProps> = ({
|
|
102 |
theme_type={themeTypeContext}
|
103 |
Dimensions={Dimensions}
|
104 |
|
105 |
-
label='
|
106 |
data={[
|
107 |
{
|
108 |
label: "Enable",
|
@@ -122,7 +116,7 @@ const RequestChapterWidget: React.FC<RequestChapterWidgetProps> = ({
|
|
122 |
theme_type={themeTypeContext}
|
123 |
Dimensions={Dimensions}
|
124 |
|
125 |
-
label='Translation'
|
126 |
data={[
|
127 |
{
|
128 |
label: "Enable",
|
@@ -219,9 +213,7 @@ const RequestChapterWidget: React.FC<RequestChapterWidgetProps> = ({
|
|
219 |
style={{backgroundColor:"green",borderRadius:5}}
|
220 |
onPress={(async ()=>{
|
221 |
setIsRequesting(true)
|
222 |
-
|
223 |
-
new_queue[CHAPTER.id] = "queue"
|
224 |
-
setChapterRequested({...chapterRequested,...new_queue})
|
225 |
|
226 |
const API_BASE = await Storage.get("IN_USE_API_BASE")
|
227 |
const stored_socket_info = await Storage.get("SOCKET_INFO")
|
|
|
23 |
SOURCE: string | string[];
|
24 |
ID: string | string[];
|
25 |
CHAPTER: any;
|
26 |
+
chapter_requested: any;
|
|
|
|
|
|
|
27 |
get_requested_info: any;
|
28 |
+
}
|
29 |
|
30 |
const RequestChapterWidget: React.FC<RequestChapterWidgetProps> = ({
|
31 |
SOURCE,
|
32 |
ID,
|
33 |
CHAPTER,
|
34 |
+
chapter_requested,
|
|
|
|
|
|
|
35 |
get_requested_info
|
36 |
}) => {
|
37 |
const Dimensions = useWindowDimensions();
|
|
|
96 |
theme_type={themeTypeContext}
|
97 |
Dimensions={Dimensions}
|
98 |
|
99 |
+
label='Colorization'
|
100 |
data={[
|
101 |
{
|
102 |
label: "Enable",
|
|
|
116 |
theme_type={themeTypeContext}
|
117 |
Dimensions={Dimensions}
|
118 |
|
119 |
+
label='Translation (Beta)'
|
120 |
data={[
|
121 |
{
|
122 |
label: "Enable",
|
|
|
213 |
style={{backgroundColor:"green",borderRadius:5}}
|
214 |
onPress={(async ()=>{
|
215 |
setIsRequesting(true)
|
216 |
+
chapter_requested.current[CHAPTER.id] = {state: "queue"}
|
|
|
|
|
217 |
|
218 |
const API_BASE = await Storage.get("IN_USE_API_BASE")
|
219 |
const stored_socket_info = await Storage.get("SOCKET_INFO")
|