Compare commits

..

104 Commits

Author SHA1 Message Date
Rajarshee Chatterjee da18cd9ade chore: Update translations (#244)
Lint / ESLint (push) Has been cancelled
* New translations strings.json (Turkish)

* New translations strings.json (Portuguese, Brazilian)
2022-05-04 07:18:32 +05:30
Rajarshee Chatterjee 802aa6d71a fix(source): MyLoveNovel (Closes LNReader/lnreader-sources#217) 2022-05-04 07:03:51 +05:30
Rajarshee Chatterjee 3fbca6c14d fix: Links not working in Render HTML (Closes #245) 2022-05-04 06:56:30 +05:30
Rajarshee Chatterjee 5a07255b95 fix: Tracker Button showing when Tracker is not available 2022-05-04 06:56:06 +05:30
Rajarshee Chatterjee 9ed1d5c8b6 Release v1.1.9 2022-05-03 18:30:03 +05:30
Rajarshee Chatterjee f07a4845b8 fix: Browse Screen Search Message 2022-05-03 17:51:37 +05:30
Rajarshee Chatterjee 922ffbe7b0 fix: Tracker Button not Showing 2022-05-03 17:50:14 +05:30
Rajarshee Chatterjee d9b40589b7 fix: Backups (Closes #109, Closes #180)
Lint / ESLint (push) Has been cancelled
2022-05-03 17:23:02 +05:30
Rajarshee Chatterjee aee3126e6c feat(source): Add MTNovel (Closes LNReader/lnreader-sources#197) 2022-05-03 12:55:58 +05:30
Rajarshee Chatterjee ae16cc70f5 fix: Build Workflow 2022-05-03 12:26:14 +05:30
Rajarshee Chatterjee bc3cbd7d7b fix(source): NovelRingan: Fix Cover (Closes LNReader/lnreader-sources#209) 2022-05-03 11:56:09 +05:30
Rajarshee Chatterjee c463a99e9a chore: Remove Duplicate Job 2022-05-03 11:46:49 +05:30
Rajarshee Chatterjee 1c2dce3a44 fix(source): LightNovelReader & ReadLightNovel: Fix Search and Description (Closes LNReader/lnreader-sources#212, Closes LNReader/lnreader-sources#214) 2022-05-03 11:29:36 +05:30
Rajarshee Chatterjee d484380734 fix(source): SakuraNovel: Fix Chapter Selector (Closes LNReader/lnreader-sources#211) 2022-05-03 11:23:24 +05:30
Rajarshee Chatterjee 7f2d3ac9b7 fix(source): MeioNovel: Add total pages (Closes LNReader/lnreader-sources#208) 2022-05-03 11:15:42 +05:30
Rajarshee Chatterjee 410ff3d727 chore(deps): Uninstall unused dependecies 2022-05-03 11:07:44 +05:30
dependabot[bot] b8824a9bd3 chore(deps): bump async from 2.6.3 to 2.6.4 (#242)
Bumps [async](https://github.com/caolan/async) from 2.6.3 to 2.6.4.
- [Release notes](https://github.com/caolan/async/releases)
- [Changelog](https://github.com/caolan/async/blob/v2.6.4/CHANGELOG.md)
- [Commits](https://github.com/caolan/async/compare/v2.6.3...v2.6.4)

---
updated-dependencies:
- dependency-name: async
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-29 22:03:21 +05:30
Rider21 74be58ee81 feat(source): Add Genre, Author and Status in Russian Sources (#239) 2022-04-29 08:12:03 +05:30
Rider21 6ef45e8b39 fix(source): Jaomix (#237)
* oops

* hm
2022-04-25 08:24:25 +05:30
Rajarshee Chatterjee f42f316748 feat: Add translation project link in About 2022-04-24 19:35:36 +05:30
Rajarshee Chatterjee b9052e6d38 fix(source): Novel Updates 2022-04-24 19:33:16 +05:30
Rajarshee Chatterjee acfb73c424 fix: Add cover placeholder color 2022-04-24 19:11:19 +05:30
Rajarshee Chatterjee 1c3ea20aaf chore: Update translations (#225)
* New translations strings.json (Russian)

* New translations strings.json (Korean)

* New translations strings.json (Vietnamese)

* New translations strings.json (Chinese Traditional)

* New translations strings.json (Ukrainian)

* New translations strings.json (Turkish)

* New translations strings.json (Swedish)

* New translations strings.json (Serbian (Cyrillic))

* New translations strings.json (Albanian)

* New translations strings.json (Portuguese)

* New translations strings.json (Polish)

* New translations strings.json (Norwegian)

* New translations strings.json (Dutch)

* New translations strings.json (Japanese)

* New translations strings.json (Romanian)

* New translations strings.json (Italian)

* New translations strings.json (Hungarian)

* New translations strings.json (Hebrew)

* New translations strings.json (Finnish)

* New translations strings.json (Greek)

* New translations strings.json (German)

* New translations strings.json (Danish)

* New translations strings.json (Czech)

* New translations strings.json (Catalan)

* New translations strings.json (Arabic)

* New translations strings.json (Afrikaans)

* New translations strings.json (Spanish)

* New translations strings.json (French)

* New translations strings.json (Ukrainian)

* New translations strings.json (Turkish)
2022-04-24 17:42:17 +05:30
Rajarshee Chatterjee 815172b91d style: Run Prettier 2022-04-24 17:39:48 +05:30
Rider21 2d85fa0938 feat: New Sources (#236)
* jaomix

* Rulate

* РанобэРФ

* corrected covers
2022-04-24 17:35:10 +05:30
Rajarshee Chatterjee 5e2152ad0d fix: Bookmark icon not visible (Closes #227) 2022-04-17 09:19:58 +05:30
dependabot[bot] 0388ec4f7e chore(deps): bump urijs from 1.19.10 to 1.19.11 (#228)
Bumps [urijs](https://github.com/medialize/URI.js) from 1.19.10 to 1.19.11.
- [Release notes](https://github.com/medialize/URI.js/releases)
- [Changelog](https://github.com/medialize/URI.js/blob/gh-pages/CHANGELOG.md)
- [Commits](https://github.com/medialize/URI.js/compare/v1.19.10...v1.19.11)

---
updated-dependencies:
- dependency-name: urijs
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-04-14 19:16:03 +05:30
Rajarshee Chatterjee 6f10966714 fix: Only show pinned sources not working (Closes #226) 2022-04-11 09:56:46 +05:30
Rajarshee Chatterjee d2c0a56c35 chore: Add Strings to Translations 2022-04-11 09:44:40 +05:30
Rajarshee Chatterjee 3dbb835045 chore: Add Strings to Translations 2022-04-10 21:44:40 +05:30
Rajarshee Chatterjee 38c062c195 docs: Add Translated and Preview Badge 2022-04-10 16:36:09 +05:30
Rajarshee Chatterjee 969c17e68d fix: Global Search and Migration 2022-04-10 11:26:15 +05:30
Rajarshee Chatterjee c3a6eb372f style: Run Prettier 2022-04-09 19:51:10 +05:30
Rider21 09bcbd58f5 feat(source): Add Renovels (#224) 2022-04-09 19:49:27 +05:30
Rajarshee Chatterjee fd642c9035 chore: npm Audit FIxes 2022-04-09 16:26:44 +05:30
Rajarshee Chatterjee 642b8a855d Minor cleanup 2022-04-09 15:41:55 +05:30
Rajarshee Chatterjee 54a6ec64bf refactor: Browse & Browse Settings Screen 2022-04-09 11:43:54 +05:30
Rajarshee Chatterjee b97a3cf16d fix(source): Fix HakoLightNovelSearch 2022-04-09 08:24:51 +05:30
Rajarshee Chatterjee ae8c858ae2 Merge branch 'main' of https://github.com/LNReader/lnreader into main 2022-04-08 22:25:27 +05:30
Rajarshee Chatterjee 9f4b849d34 chore: Update translations (#206)
* New translations strings.json (Romanian)

* New translations strings.json (Dutch)

* New translations strings.json (Vietnamese)

* New translations strings.json (Chinese Traditional)

* New translations strings.json (Ukrainian)

* New translations strings.json (Turkish)

* New translations strings.json (Swedish)

* New translations strings.json (Serbian (Cyrillic))

* New translations strings.json (Albanian)

* New translations strings.json (Russian)

* New translations strings.json (Portuguese)

* New translations strings.json (Polish)

* New translations strings.json (Norwegian)

* New translations strings.json (Korean)

* New translations strings.json (French)

* New translations strings.json (Japanese)

* New translations strings.json (Italian)

* New translations strings.json (Hungarian)

* New translations strings.json (Hebrew)

* New translations strings.json (Finnish)

* New translations strings.json (Greek)

* New translations strings.json (German)

* New translations strings.json (Danish)

* New translations strings.json (Czech)

* New translations strings.json (Catalan)

* New translations strings.json (Arabic)

* New translations strings.json (Afrikaans)

* New translations strings.json (Spanish)

* New translations strings.json (Turkish)

* New translations strings.json (Arabic)

* New translations strings.json (Russian)

* New translations strings.json (Russian)

* New translations strings.json (Korean)

* New translations strings.json (Vietnamese)

* New translations strings.json (Chinese Traditional)

* New translations strings.json (Ukrainian)

* New translations strings.json (Turkish)

* New translations strings.json (Swedish)

* New translations strings.json (Serbian (Cyrillic))

* New translations strings.json (Albanian)

* New translations strings.json (Portuguese)

* New translations strings.json (Polish)

* New translations strings.json (Norwegian)

* New translations strings.json (Dutch)

* New translations strings.json (Japanese)

* New translations strings.json (Romanian)

* New translations strings.json (Italian)

* New translations strings.json (Hungarian)

* New translations strings.json (Hebrew)

* New translations strings.json (Finnish)

* New translations strings.json (Greek)

* New translations strings.json (German)

* New translations strings.json (Danish)

* New translations strings.json (Czech)

* New translations strings.json (Catalan)

* New translations strings.json (Arabic)

* New translations strings.json (Afrikaans)

* New translations strings.json (Spanish)

* New translations strings.json (French)
2022-04-08 22:24:25 +05:30
Rajarshee Chatterjee be82f3d9ae feat: Add Script to generate String Types 2022-04-08 20:59:15 +05:30
Rajarshee Chatterjee 027f4420f6 feat(source): HakoLightNovel (Closes LNReader/lnreader-sources#167) 2022-04-08 20:36:28 +05:30
Rajarshee Chatterjee e7b8edf8f1 feat: Add Script to generate Source File Template from JSON 2022-04-08 19:35:50 +05:30
Rajarshee Chatterjee 9266b57560 feat(source): Add LightNovelReader (Closes LNReader/lnreader-sources#138) 2022-04-08 19:24:07 +05:30
Rajarshee Chatterjee 4214caa87c feat(source): Add NovelsCafe (Closes LNReader/lnreader-sources#147) 2022-04-08 18:24:57 +05:30
Rajarshee Chatterjee 766c7b565e feat(source): Add FreeNovelUpdates (Closes LNReader/lnreader-sources#184) 2022-04-08 17:40:15 +05:30
Rajarshee Chatterjee 06d66c15d4 feat(source): Add NovelOwl (LNReader/lnreader-sources#194) 2022-04-08 12:07:56 +05:30
Rajarshee Chatterjee e2d58d1587 feat: Add Script to Generate Madara Sources 2022-04-08 12:00:35 +05:30
Rajarshee Chatterjee 1f86979165 feat: Add husky and lint-staged 2022-04-08 08:19:52 +05:30
Rajarshee Chatterjee 951690d49a fix: Last update time (Closes #214) 2022-04-08 07:37:45 +05:30
M0RGaming f4be4aa4e1 Fixed Last Read Crash (#216) 2022-04-05 07:31:22 +05:30
Rajarshee Chatterjee 1adb5a9ce3 Minor fixes 2022-04-02 08:49:23 +05:30
dependabot[bot] fdef214dcf Bump plist from 3.0.4 to 3.0.5 (#211)
Bumps [plist](https://github.com/TooTallNate/node-plist) from 3.0.4 to 3.0.5.
- [Release notes](https://github.com/TooTallNate/node-plist/releases)
- [Changelog](https://github.com/TooTallNate/plist.js/blob/master/History.md)
- [Commits](https://github.com/TooTallNate/node-plist/commits)

---
updated-dependencies:
- dependency-name: plist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-31 21:35:27 +05:30
dependabot[bot] e99bf2a430 Bump ansi-regex from 4.1.0 to 4.1.1 (#210)
Bumps [ansi-regex](https://github.com/chalk/ansi-regex) from 4.1.0 to 4.1.1.
- [Release notes](https://github.com/chalk/ansi-regex/releases)
- [Commits](https://github.com/chalk/ansi-regex/compare/v4.1.0...v4.1.1)

---
updated-dependencies:
- dependency-name: ansi-regex
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-31 21:35:19 +05:30
dependabot[bot] 471e7f56e7 Bump minimist from 1.2.5 to 1.2.6 (#209)
Bumps [minimist](https://github.com/substack/minimist) from 1.2.5 to 1.2.6.
- [Release notes](https://github.com/substack/minimist/releases)
- [Commits](https://github.com/substack/minimist/compare/1.2.5...1.2.6)

---
updated-dependencies:
- dependency-name: minimist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-31 21:32:51 +05:30
Rajarshee Chatterjee 1f527a653d chore: Add string keys 2022-03-19 17:54:37 +05:30
Rajarshee Chatterjee c4240e6c1a feat: Add build release script 2022-03-19 17:10:08 +05:30
Rajarshee Chatterjee 0bc936becf Crowdin translations (#205)
* New translations strings.json (French)

* New translations strings.json (Turkish)

* New translations strings.json (German)
2022-03-19 17:02:19 +05:30
Rajarshee Chatterjee f4643b0a4e Crowdin translations (#204)
* New translations strings.json (Portuguese)

* New translations strings.json (Portuguese, Brazilian)

* New translations strings.json (Portuguese)

* New translations strings.json (Albanian)

* New translations strings.json (Romanian)

* New translations strings.json (Dutch)

* New translations strings.json (Portuguese, Brazilian)

* New translations strings.json (Vietnamese)

* New translations strings.json (Chinese Traditional)

* New translations strings.json (Ukrainian)

* New translations strings.json (Turkish)

* New translations strings.json (Swedish)

* New translations strings.json (Serbian (Cyrillic))

* New translations strings.json (Russian)

* New translations strings.json (Polish)

* New translations strings.json (Norwegian)

* New translations strings.json (Korean)

* New translations strings.json (French)

* New translations strings.json (Japanese)

* New translations strings.json (Italian)

* New translations strings.json (Hungarian)

* New translations strings.json (Hebrew)

* New translations strings.json (Finnish)

* New translations strings.json (Greek)

* New translations strings.json (German)

* New translations strings.json (Danish)

* New translations strings.json (Czech)

* New translations strings.json (Catalan)

* New translations strings.json (Arabic)

* New translations strings.json (Afrikaans)

* New translations strings.json (Spanish)

* New translations strings.json (Albanian)
2022-03-19 12:57:23 +05:30
Rajarshee Chatterjee ac42c025c2 chore: Update Crowdin configuration file 2022-03-19 12:56:32 +05:30
Rajarshee Chatterjee 20cfc960f8 chore: Add string keys 2022-03-19 12:42:47 +05:30
Rajarshee Chatterjee de357c3822 Update Crowdin configuration file 2022-03-19 12:28:00 +05:30
Rajarshee Chatterjee 5e73b32b97 Crowdin translations (#203)
* New translations strings.json (Romanian)

* New translations strings.json (Danish)

* New translations strings.json (Vietnamese)

* New translations strings.json (Portuguese, Brazilian)

* New translations strings.json (Romanian)

* New translations strings.json (French)

* New translations strings.json (Afrikaans)

* New translations strings.json (Arabic)

* New translations strings.json (Catalan)

* New translations strings.json (Czech)

* New translations strings.json (German)

* New translations strings.json (Chinese Traditional)

* New translations strings.json (Greek)

* New translations strings.json (Finnish)

* New translations strings.json (Hebrew)

* New translations strings.json (Hungarian)

* New translations strings.json (Italian)

* New translations strings.json (Japanese)

* New translations strings.json (Korean)

* New translations strings.json (Dutch)

* New translations strings.json (Norwegian)

* New translations strings.json (Polish)

* New translations strings.json (French)

* New translations strings.json (Hungarian)

* New translations strings.json (Spanish)

* New translations strings.json (Afrikaans)

* New translations strings.json (Arabic)

* New translations strings.json (Catalan)

* New translations strings.json (Czech)

* New translations strings.json (Danish)

* New translations strings.json (German)

* New translations strings.json (Greek)

* New translations strings.json (Finnish)

* New translations strings.json (Hebrew)

* New translations strings.json (Italian)

* New translations strings.json (Ukrainian)

* New translations strings.json (Japanese)

* New translations strings.json (Korean)

* New translations strings.json (Dutch)

* New translations strings.json (Norwegian)

* New translations strings.json (Polish)

* New translations strings.json (Russian)

* New translations strings.json (Serbian (Cyrillic))

* New translations strings.json (Swedish)

* New translations strings.json (Turkish)

* New translations strings.json (Portuguese)

* New translations strings.json (Russian)

* New translations strings.json (Russian)

* New translations strings.json (Italian)

* New translations strings.json (Japanese)

* New translations strings.json (Korean)

* New translations strings.json (Dutch)

* New translations strings.json (Norwegian)

* New translations strings.json (Polish)

* New translations strings.json (Portuguese)

* New translations strings.json (Serbian (Cyrillic))

* New translations strings.json (Hebrew)

* New translations strings.json (Swedish)

* New translations strings.json (Turkish)

* New translations strings.json (Ukrainian)

* New translations strings.json (Chinese Simplified)

* New translations strings.json (English)

* New translations strings.json (Vietnamese)

* New translations strings.json (Hungarian)

* New translations strings.json (Finnish)

* New translations strings.json (Serbian (Cyrillic))

* New translations strings.json (Portuguese, Brazilian)

* New translations strings.json (Swedish)

* New translations strings.json (Turkish)

* New translations strings.json (Ukrainian)

* New translations strings.json (Chinese Simplified)

* New translations strings.json (English)

* New translations strings.json (Vietnamese)

* New translations strings.json (Romanian)

* New translations strings.json (Greek)

* New translations strings.json (French)

* New translations strings.json (Spanish)

* New translations strings.json (Afrikaans)

* New translations strings.json (Arabic)

* New translations strings.json (Catalan)

* New translations strings.json (Czech)

* New translations strings.json (Danish)

* New translations strings.json (German)

* New translations strings.json (Portuguese, Brazilian)
2022-03-19 12:18:13 +05:30
Rajarshee Chatterjee d289b0ea17 Merge branch 'main' of https://github.com/LNReader/lnreader into main 2022-03-19 12:16:52 +05:30
Rajarshee Chatterjee 20d3a786b6 fix: Remove ununsed variable 2022-03-19 12:16:43 +05:30
Rajarshee Chatterjee ab2a17b41e Update Crowdin configuration file 2022-03-19 12:00:41 +05:30
Rajarshee Chatterjee e76bc77128 chore: Add string keys 2022-03-19 11:48:33 +05:30
Rajarshee Chatterjee 783d1e1873 Refactor 2022-03-19 09:38:35 +05:30
Rajarshee Chatterjee c307781cf9 feat(source): Add Türkçe Light Novels (Closes LNReader/lnreader-sources#189) 2022-03-18 08:31:09 +05:30
Rajarshee Chatterjee 10fb3ec347 Minor fixes 2022-03-16 06:37:40 +05:30
Rajarshee Chatterjee 71eab75048 feat(source): Add ReadFreeNovel (Closes LNReader/lnreader-sources#173) 2022-03-16 06:36:17 +05:30
Rajarshee Chatterjee d6b24a756d refactor: Reader 2022-03-13 16:31:16 +05:30
Rajarshee Chatterjee a779cc12d0 refactor: UpdatesScreen 2022-03-08 10:14:19 +05:30
Rajarshee Chatterjee 5c64314d7d feat (source): Add AllNovelFull (Closes LNReader/lnreader-sources#176) 2022-03-06 18:58:04 +05:30
Rajarshee Chatterjee 0b41728789 fix (source): Fix TeamXNovel 2022-03-06 18:57:01 +05:30
Rajarshee Chatterjee 388052b59d Merge branch 'main' of https://github.com/LNReader/lnreader into main 2022-03-06 18:09:05 +05:30
hani19998 edcb661b86 feat(source): Add TeamXNovel (Closes LNReader/lnreader-sources#175)
* Update MadaraGenerator.js

TeamXNovel

* Update sourceManager.ts

TeamXNovel

* Update sources.json

TeamXNovel
2022-03-06 18:06:52 +05:30
Rajarshee Chatterjee a248c9e961 fix: background color 2022-03-06 17:41:48 +05:30
dependabot[bot] 0e49e873cd Bump plist from 3.0.2 to 3.0.4 (#187)
Bumps [plist](https://github.com/TooTallNate/node-plist) from 3.0.2 to 3.0.4.
- [Release notes](https://github.com/TooTallNate/node-plist/releases)
- [Changelog](https://github.com/TooTallNate/plist.js/blob/master/History.md)
- [Commits](https://github.com/TooTallNate/node-plist/commits)

---
updated-dependencies:
- dependency-name: plist
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-06 13:51:03 +05:30
dependabot[bot] 9137d09743 Bump url-parse from 1.5.4 to 1.5.10 (#181)
Bumps [url-parse](https://github.com/unshiftio/url-parse) from 1.5.4 to 1.5.10.
- [Release notes](https://github.com/unshiftio/url-parse/releases)
- [Commits](https://github.com/unshiftio/url-parse/compare/1.5.4...1.5.10)

---
updated-dependencies:
- dependency-name: url-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-03-06 13:50:57 +05:30
Rajarshee Chatterjee 4d2b275a27 fix: lint errors 2022-03-06 13:49:10 +05:30
Rajarshee Chatterjee c06c80c40f refactor: ReaderScreen 2022-03-06 13:35:49 +05:30
Rajarshee Chatterjee e258d5c1a9 refactor: HistoryScreen & Settings 2022-03-06 13:00:56 +05:30
Rajarshee Chatterjee de94340616 refactor 2022-03-06 10:11:09 +05:30
Rajarshee Chatterjee 8e1820213f refactor: sources 2022-03-06 09:59:02 +05:30
Rajarshee Chatterjee 36b9e2cf69 style: run prettier 2022-03-06 09:27:27 +05:30
Rajarshee Chatterjee 04928e8cdc style: run prettier 2022-03-06 09:20:04 +05:30
Rajarshee Chatterjee 69e269216a feat: Translations (Closes #190) 2022-03-06 09:17:00 +05:30
Rajarshee Chatterjee 5f3959d62d Add Novel4Up (Closes LNReader/lnreader-sources#162) 2022-02-05 11:22:34 +05:30
Rajarshee Chatterjee 2c02437e62 ReaperScans: Fix novelUrl (Closes LNReader/lnreader-sources#166) 2022-02-05 11:16:23 +05:30
Rajarshee Chatterjee 4255e5d060 Fix workflow 2022-02-04 22:29:58 +05:30
dependabot[bot] a1d32a9e1d Bump shelljs from 0.8.4 to 0.8.5 (#158)
Bumps [shelljs](https://github.com/shelljs/shelljs) from 0.8.4 to 0.8.5.
- [Release notes](https://github.com/shelljs/shelljs/releases)
- [Changelog](https://github.com/shelljs/shelljs/blob/master/CHANGELOG.md)
- [Commits](https://github.com/shelljs/shelljs/compare/v0.8.4...v0.8.5)

---
updated-dependencies:
- dependency-name: shelljs
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-04 22:13:16 +05:30
dependabot[bot] 93a7a00735 Bump path-parse from 1.0.6 to 1.0.7 (#157)
Bumps [path-parse](https://github.com/jbgutierrez/path-parse) from 1.0.6 to 1.0.7.
- [Release notes](https://github.com/jbgutierrez/path-parse/releases)
- [Commits](https://github.com/jbgutierrez/path-parse/commits/v1.0.7)

---
updated-dependencies:
- dependency-name: path-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-04 22:13:03 +05:30
dependabot[bot] cb875b5d71 Bump url-parse from 1.5.1 to 1.5.4 (#156)
Bumps [url-parse](https://github.com/unshiftio/url-parse) from 1.5.1 to 1.5.4.
- [Release notes](https://github.com/unshiftio/url-parse/releases)
- [Commits](https://github.com/unshiftio/url-parse/compare/1.5.1...1.5.4)

---
updated-dependencies:
- dependency-name: url-parse
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-04 22:12:50 +05:30
dependabot[bot] ce0fa93959 Bump tmpl from 1.0.4 to 1.0.5 (#155)
Bumps [tmpl](https://github.com/daaku/nodejs-tmpl) from 1.0.4 to 1.0.5.
- [Release notes](https://github.com/daaku/nodejs-tmpl/releases)
- [Commits](https://github.com/daaku/nodejs-tmpl/commits/v1.0.5)

---
updated-dependencies:
- dependency-name: tmpl
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-02-04 22:12:34 +05:30
Rajarshee Chatterjee 1537b67f12 Allow inline styles & run prettier 2022-02-04 22:11:55 +05:30
Rajarshee Chatterjee 3a1921b7b1 Add TypeScript & Redux Toolkit 2022-02-04 21:53:22 +05:30
Rajarshee Chatterjee 87375bc005 Fix typo (Closes #154) 2022-02-04 21:12:44 +05:30
Rajarshee Chatterjee 13f4b08b7e Refactor ReaderScreen 2022-01-14 17:04:33 +05:30
Rajarshee Chatterjee 4321b892ad Divine Dao Library: Add chapter name 2022-01-13 22:10:05 +05:30
Rajarshee Chatterjee b258f9cf13 Fix LightNovelPub (Closes LNReader/lnreader-sources#133) 2022-01-07 11:01:59 +05:30
Rajarshee Chatterjee 02eb632484 Fix ReadNovelFull (Closes LNReader/lnreader-sources#150) 2022-01-07 10:11:51 +05:30
Rajarshee Chatterjee f6faaea0db Add OpenDyslexic font (Closes #137) 2022-01-07 09:59:51 +05:30
Rajarshee Chatterjee 046b020f26 Release v1.1.8 2021-12-31 21:41:01 +05:30
372 changed files with 17924 additions and 4742 deletions
+15 -4
View File
@@ -1,8 +1,19 @@
module.exports = {
root: true,
extends: '@react-native-community',
rules: {
'react-native/no-inline-styles': 'off',
'no-console': 'warn',
},
parser: '@typescript-eslint/parser',
plugins: ['@typescript-eslint'],
overrides: [
{
files: ['*.js', '*.jsx', '*.ts', '*.tsx'],
rules: {
'@typescript-eslint/no-shadow': ['warn'],
'no-shadow': 'off',
'no-undef': 'off',
'react-native/no-inline-styles': 'warn',
'no-console': 'warn',
'react-hooks/exhaustive-deps': 'warn',
},
},
],
};
+28 -38
View File
@@ -1,45 +1,35 @@
name: CI
on:
push:
branches:
- main
push:
branches:
- main
jobs:
install-and-test:
name: Install dependencies
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install npm dependencies
run: |
npm install
build-android:
name: Build app
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Dependencies
run: |
npm install
build-android:
name: Build app
needs: install-and-test
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Dependencies
run: |
npm install
- name: Cache Gradle Wrapper
uses: actions/cache@v2
with:
path: ~/.gradle/wrapper
key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }}
- name: Cache Gradle Wrapper
uses: actions/cache@v2
with:
path: ~/.gradle/wrapper
key: ${{ runner.os }}-gradle-wrapper-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }}
- name: Cache Gradle Dependencies
uses: actions/cache@v2
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-caches-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-caches-
- name: Cache Gradle Dependencies
uses: actions/cache@v2
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-caches-${{ hashFiles('gradle/wrapper/gradle-wrapper.properties') }}
restore-keys: |
${{ runner.os }}-gradle-caches-
- name: Make Gradlew Executable
run: cd android && chmod +x ./gradlew
- name: Make Gradlew Executable
run: cd android && chmod +x ./gradlew
- name: Build Android Release
run: |
cd android && ./gradlew assembleRelease --no-daemon
- name: Build Android Release
run: |
cd android && ./gradlew assembleRelease --no-daemon
+5 -5
View File
@@ -5,8 +5,8 @@ jobs:
name: ESLint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install modules
run: npm i
- name: Run ESLint
run: npm run lint
- uses: actions/checkout@v2
- name: Install modules
run: npm i
- name: Run ESLint
run: npm run lint
+2 -1
View File
@@ -72,4 +72,5 @@ web-build/
# @end expo-cli
.vscode/
.vscode/
src/sources/en/generators/
+1
View File
@@ -0,0 +1 @@
_
+4
View File
@@ -0,0 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npx lint-staged
+5 -1
View File
@@ -1,6 +1,10 @@
module.exports = {
bracketSpacing: false,
tabWidth: 2,
bracketSpacing: true,
useTabs: false,
bracketSameLine: false,
singleQuote: true,
trailingComma: 'all',
arrowParens: 'avoid',
quoteProps: 'preserve',
};
+9 -17
View File
@@ -1,35 +1,27 @@
import {enableFreeze} from 'react-native-screens';
import { enableFreeze } from 'react-native-screens';
enableFreeze(true);
import React, {useEffect} from 'react';
import {StatusBar} from 'react-native';
import React, { useEffect } from 'react';
import { StatusBar } from 'react-native';
import {useFonts} from 'expo-font';
import {Provider as PaperProvider} from 'react-native-paper';
import {Provider} from 'react-redux';
import {persistor, store} from './src/redux/store';
import {PersistGate} from 'redux-persist/lib/integration/react';
import { Provider as PaperProvider } from 'react-native-paper';
import { Provider } from 'react-redux';
import { persistor, store } from './src/redux/store';
import { PersistGate } from 'redux-persist/lib/integration/react';
import * as SplashScreen from 'expo-splash-screen';
import Main from './src/navigators/Main';
import {createDB} from './src/database/DBHelper';
import {fonts} from './src/theme/fonts';
import {SafeAreaProvider} from 'react-native-safe-area-context';
import { createDB } from './src/database/db';
import { SafeAreaProvider } from 'react-native-safe-area-context';
const App = () => {
const [loaded] = useFonts(fonts);
useEffect(() => {
SplashScreen.preventAutoHideAsync();
createDB();
}, []);
if (!loaded) {
return null;
}
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
+11 -11
View File
@@ -17,23 +17,23 @@ diverse, inclusive, and healthy community.
Examples of behavior that contributes to a positive environment for our
community include:
* Demonstrating empathy and kindness toward other people
* Being respectful of differing opinions, viewpoints, and experiences
* Giving and gracefully accepting constructive feedback
* Accepting responsibility and apologizing to those affected by our mistakes,
- Demonstrating empathy and kindness toward other people
- Being respectful of differing opinions, viewpoints, and experiences
- Giving and gracefully accepting constructive feedback
- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
* Focusing on what is best not just for us as individuals, but for the
- Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
* The use of sexualized language or imagery, and sexual attention or
- The use of sexualized language or imagery, and sexual attention or
advances of any kind
* Trolling, insulting or derogatory comments, and personal or political attacks
* Public or private harassment
* Publishing others' private information, such as a physical or email
- Trolling, insulting or derogatory comments, and personal or political attacks
- Public or private harassment
- Publishing others' private information, such as a physical or email
address, without their explicit permission
* Other conduct which could reasonably be considered inappropriate in a
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
@@ -106,7 +106,7 @@ Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
standards, including sustained inappropriate behavior, harassment of an
standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
-1
View File
@@ -2,7 +2,6 @@
Contributions are welcome and are greatly appreciated!
## Setting up your environment
After forking to your own github org or account, do the following steps to get started:
+7
View File
@@ -17,6 +17,10 @@
<a href="https://github.com/LNReader/lnreader/releases/latest">
<img alt="GitHub release (latest by date)" src="https://img.shields.io/github/v/release/LNReader/lnreader">
</a>
<a href="https://github.com/LNReader/lnreader-preview/releases/latest">
<img alt="GitHub release (latest SemVer)" src="https://img.shields.io/github/v/release/LNReader/lnreader-preview?color=red&label=preview">
</a>
<a title="Crowdin" target="_blank" href="https://crowdin.com/project/lnreader"><img src="https://badges.crowdin.net/lnreader/localized.svg"></a>
<a href="https://github.com/LNReader/lnreader/blob/main/LICENSE">
<img alt="GitHub" src="https://img.shields.io/github/license/LNReader/lnreader">
</a>
@@ -30,12 +34,15 @@
</p>
## Download
Get the app from our [releases page](https://github.com/rajarsheechatterjee/LNReader/releases).
## Sources
LNReader does not have any affiliation with the content providers available.
Source requests should be created at [LNReader/lnreader-sources](https://github.com/LNReader/lnreader-sources).
## License
[MIT ](https://github.com/rajarsheechatterjee/lnreader/blob/main/LICENSE)
+1 -1
View File
@@ -136,7 +136,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.1.7"
versionName "1.1.9"
}
splits {
abi {
@@ -17,6 +17,7 @@ public class BasePackageList {
new expo.modules.imageloader.ImageLoaderPackage(),
new expo.modules.keepawake.KeepAwakePackage(),
new expo.modules.lineargradient.LinearGradientPackage(),
new expo.modules.localization.LocalizationPackage(),
new expo.modules.notifications.NotificationsPackage(),
new expo.modules.permissions.PermissionsPackage(),
new expo.modules.splashscreen.SplashScreenPackage(),
+4
View File
@@ -0,0 +1,4 @@
pull_request_title: 'chore: Update translations'
files:
- source: /strings/languages/en/strings.json
translation: /strings/languages/%two_letters_code%/strings.json
+1 -1
View File
@@ -1,5 +1,5 @@
import 'react-native-gesture-handler';
import {registerRootComponent} from 'expo';
import { registerRootComponent } from 'expo';
import App from './App';
+4
View File
@@ -0,0 +1,4 @@
module.exports = {
preset: 'react-native',
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
};
+1 -1
View File
@@ -1,4 +1,4 @@
// Learn more https://docs.expo.io/guides/customizing-metro
const {getDefaultConfig} = require('expo/metro-config');
const { getDefaultConfig } = require('expo/metro-config');
module.exports = getDefaultConfig(__dirname);
+5525 -745
View File
File diff suppressed because it is too large Load Diff
+41 -8
View File
@@ -1,19 +1,30 @@
{
"name": "lnreader",
"version": "1.1.9",
"private": true,
"scripts": {
"android": "react-native run-android",
"ios": "react-native run-ios",
"start": "react-native start",
"lint": "eslint ."
"lint": "eslint . --ext .js,.jsx,.ts,.tsx",
"prettier": "prettier --write \"./src/**/*.{js,jsx,ts,tsx}\"",
"buildRelease": "cd android && gradlew clean && gradlew assembleRelease",
"sources:madara": "node scripts/generateMadaraSource.cjs && prettier --write {src/sources/multisrc/madara/MadaraGenerator.ts,src/sources/sources.json}",
"source:generator": "node scripts/generateSource.cjs",
"strings": "node scripts/stringTypes.cjs",
"prepare": "husky install"
},
"dependencies": {
"@react-native-async-storage/async-storage": "^1.15.11",
"@react-native-community/masked-view": "^0.1.11",
"@react-native-community/netinfo": "^7.1.1",
"@react-native-community/slider": "^4.1.12",
"@react-navigation/bottom-tabs": "^6.0.9",
"@react-navigation/material-bottom-tabs": "^6.0.9",
"@react-navigation/native": "^6.0.6",
"@react-navigation/stack": "^6.0.11",
"@reduxjs/toolkit": "^1.7.2",
"@types/react-native-vector-icons": "^6.4.10",
"cheerio": "^1.0.0-rc.10",
"expo": "^41.0.1",
"expo-document-picker": "^9.2.4",
"expo-file-system": "~11.0.2",
@@ -22,11 +33,14 @@
"expo-keep-awake": "~9.1.2",
"expo-linear-gradient": "~9.1.0",
"expo-linking": "~2.2.3",
"expo-localization": "~10.1.0",
"expo-notifications": "~0.11.6",
"expo-splash-screen": "^0.11.4",
"expo-sqlite": "~9.1.0",
"expo-updates": "~0.5.4",
"expo-web-browser": "~9.1.0",
"i18n-js": "^3.8.0",
"lodash": "^4.17.21",
"moment": "^2.29.1",
"prop-types": "^15.7.2",
"qs": "^6.10.1",
@@ -34,7 +48,6 @@
"react-dom": "16.13.1",
"react-native": "^0.64.3",
"react-native-background-actions": "^2.6.5",
"react-native-cheerio": "^1.0.0-rc.4",
"react-native-device-info": "^8.4.7",
"react-native-easing-gradient": "^1.0.0",
"react-native-fast-image": "^8.5.11",
@@ -42,8 +55,10 @@
"react-native-pager-view": "^5.4.9",
"react-native-paper": "^4.10.1",
"react-native-reanimated": "^2.2.4",
"react-native-render-html": "^6.3.4",
"react-native-safe-area-context": "^3.2.0",
"react-native-screens": "^3.9.0",
"react-native-skeleton-placeholder": "^5.0.0",
"react-native-swipe-gestures": "^1.0.5",
"react-native-tab-view": "^3.1.1",
"react-native-tts": "^4.1.0",
@@ -55,15 +70,33 @@
"redux": "^4.1.2",
"redux-persist": "^6.0.0",
"redux-thunk": "^2.4.0",
"rn-sliding-up-panel": "^2.4.4"
"rn-sliding-up-panel": "^2.4.4",
"sanitize-html": "^2.7.0"
},
"devDependencies": {
"@babel/core": "^7.16.0",
"@react-native-community/eslint-config": "^3.0.1",
"@types/i18n-js": "^3.8.2",
"@types/jest": "^27.4.0",
"@types/lodash": "^4.14.179",
"@types/react-native": "^0.66.15",
"@types/react-test-renderer": "^17.0.1",
"@types/sanitize-html": "^2.6.2",
"@typescript-eslint/eslint-plugin": "^5.10.2",
"@typescript-eslint/parser": "^5.10.2",
"eslint": "^7.32.0",
"eslint-plugin-react-native": "^3.11.0"
"eslint-plugin-react-native": "^3.11.0",
"husky": "^7.0.4",
"lint-staged": "^12.3.7",
"typescript": "^4.5.5"
},
"private": true,
"name": "lnreader",
"version": "1.1.6"
"resolutions": {
"@types/react": "^17"
},
"lint-staged": {
"*.{js,jsx,ts,tsx}": [
"eslint --fix",
"prettier --check"
]
}
}
+7
View File
@@ -0,0 +1,7 @@
module.exports = {
project: {
ios: {},
android: {},
},
assets: ['./assets/fonts/'],
};
+60
View File
@@ -0,0 +1,60 @@
const path = require('path');
const fs = require('fs');
const MadaraSources = require('../src/sources/multisrc/madara/MadaraSources.json');
const AllSources = require('../src/sources/sources.json');
const getScraperName = scraperName => {
let name = scraperName.replace(/\./g, 'Dot').replace('1st', 'First');
return name;
};
let content =
"/**\n * This file is auto-generated\n */\n\nimport MadaraScraper from './MadaraScraper';\n\n";
MadaraSources.forEach(source => {
content =
content +
`export const ${getScraperName(
source.sourceName,
)}Scraper = new MadaraScraper(${source.sourceId}, "${source.baseUrl}", "${
source.sourceName
}", ${source.options ? JSON.stringify(source.options) : ''});\n\n`;
});
fs.writeFile(
path.resolve(process.cwd(), 'src/sources/multisrc/madara/MadaraGenerator.ts'),
content,
error => {
if (error) {
console.log(error);
}
},
);
MadaraSources.forEach(madaraSource => {
if (!AllSources.some(source => source.sourceId === madaraSource.sourceId)) {
let allSourcesContent = AllSources;
allSourcesContent.push({
sourceId: madaraSource.sourceId,
sourceName: madaraSource.sourceName,
icon: `https://github.com/LNReader/lnreader-sources/blob/main/multisrc/madara/icons/${madaraSource.sourceName.toLowerCase()}.png?raw=true`,
url: madaraSource.baseUrl,
lang: madaraSource.options.lang || 'English',
});
allSourcesContent = JSON.stringify(allSourcesContent);
fs.writeFile(
path.resolve(process.cwd(), 'src/sources/sources.json'),
allSourcesContent,
error => {
if (error) {
console.log(error);
}
},
);
}
});
+328
View File
@@ -0,0 +1,328 @@
/**
* This script can be used to generate sources
*/
const fs = require('fs');
const path = require('path');
const prettier = require('prettier');
const prettierConfig = require('../.prettierrc');
const AllSources = require('../src/sources/sources.json');
const getLanguageCode = language => {
const codes = {
English: 'en',
Arabic: 'ar',
French: 'fr',
Turkish: 'tr',
Chinese: 'cn',
Japanese: 'jp',
Vietnamese: 'vi',
};
return codes[language];
};
try {
if (process.argv[2]) {
const source = JSON.parse(
fs.readFileSync(path.resolve(process.cwd(), process.argv[2]), 'utf8'),
);
const isDuplicate = AllSources.some(
item => item.sourceId === source.sourceId,
);
const content = `
import * as cheerio from 'cheerio';
import { isUrlAbsolute } from '../../utils/isAbsoluteUrl';
${
source?.options?.imports?.toaster
? "import { showToast } from '../../hooks/showToast'"
: ''
};
import {
SourceChapter,
SourceChapterItem,
SourceNovel,
SourceNovelItem,
} from '../types';
const sourceId = ${source.sourceId};
const sourceName = "${source.sourceName}";
const baseUrl = "${source.baseUrl}";
${
!source.popularNovels.url.includes('${page}')
? '// eslint-disable-next-line @typescript-eslint/no-unused-vars'
: ''
}
const popularNovels = async (page: number) => {
const totalPages = ${source.popularNovels.totalPages};
const url = \`${source.popularNovels.url}\`;
const result = await fetch(url);
const body = await result.text();
const loadedCheerio = cheerio.load(body);
const novels: SourceNovelItem[] = [];
loadedCheerio('${
source.popularNovels.selectors.novelList
}').each(function () {
let novelUrl = loadedCheerio(this).find('${
source.popularNovels.selectors.novelUrl
}').attr('href');
if (novelUrl && !isUrlAbsolute(novelUrl)) {
novelUrl = baseUrl + novelUrl;
}
if (novelUrl) {
const novelName = loadedCheerio(this).find('${
source.popularNovels.selectors.novelName
}').text().trim();
let novelCover = loadedCheerio(this).find('${
source.popularNovels.selectors.novelCover
}').attr('src');
if (novelCover && !isUrlAbsolute(novelCover)) {
novelCover = baseUrl + novelCover;
}
const novel = {
sourceId,
novelUrl,
novelName,
novelCover,
};
novels.push(novel);
}
});
return { totalPages, novels };
};
const parseNovelAndChapters = async (novelUrl: string) => {
const url = novelUrl;
const result = await fetch(url);
const body = await result.text();
let loadedCheerio = cheerio.load(body);
const novel: SourceNovel = {
sourceId,
sourceName,
url: novelUrl,
novelUrl,
chapters: [],
};
novel.novelName = loadedCheerio('${
source.novelAndChapters.selectors.novelName
}').text();
let novelCover = loadedCheerio('${
source.novelAndChapters.selectors.novelCover
}').attr('src');
novel.novelCover = novelCover
? isUrlAbsolute(novelCover)
? novelCover
: baseUrl + novelCover
: undefined;
novel.summary = loadedCheerio('${
source.novelAndChapters.selectors.summary
}').text().trim();
novel.author = loadedCheerio('${
source.novelAndChapters.selectors.author
}').text().trim();
novel.genre = loadedCheerio('${
source.novelAndChapters.selectors.genre
}').text().trim()${source?.options?.status?.replace || ''};
novel.status = loadedCheerio('${
source.novelAndChapters.selectors.status
}').text().trim();
loadedCheerio('${
source.novelAndChapters.selectors.chapterList
}').each(function () {
let chapterUrl = loadedCheerio(this).find('a').attr('href');
if (chapterUrl && !isUrlAbsolute(chapterUrl)) {
chapterUrl = baseUrl + chapterUrl;
}
if (chapterUrl) {
const chapterName = loadedCheerio(this).find('${
source.novelAndChapters.selectors.chapterName
}').text().trim();
const releaseDate = ${
source.novelAndChapters.selectors.releaseDate
? `loadedCheerio(this).find('${source.novelAndChapters.selectors.releaseDate}').text();`
: null
}
const chapter: SourceChapterItem = {
chapterName,
releaseDate,
chapterUrl,
};
novel.chapters?.push(chapter);
}
});
return novel;
};
const parseChapter = async (novelUrl: string, chapterUrl: string) => {
const url = chapterUrl;
const result = await fetch(url);
const body = await result.text();
const loadedCheerio = cheerio.load(body);
const chapterName = loadedCheerio('${
source.chapter.selectors.chapterName
}').text();
const chapterText = loadedCheerio('${
source.chapter.selectors.chapterText
}').html() || '';
const chapter: SourceChapter = {
sourceId,
novelUrl,
chapterUrl,
chapterName,
chapterText,
};
return chapter;
};
${
!source.search.url.includes('${searchTerm}')
? '// eslint-disable-next-line @typescript-eslint/no-unused-vars'
: ''
}
const searchNovels = async (searchTerm: string) => {
${
source.search
? `
const url = \`${source.search.url}\`;
const result = await fetch(url);
const body = await result.text();
const loadedCheerio = cheerio.load(body);
const novels: SourceNovelItem[] = [];
loadedCheerio('${source.search.selectors.novelList}').each(function () {
let novelUrl = loadedCheerio(this).find('${source.search.selectors.novelUrl}').attr('href');
if (novelUrl && !isUrlAbsolute(novelUrl)) {
novelUrl = baseUrl + novelUrl;
}
if (novelUrl) {
const novelName = loadedCheerio(this).find('${source.search.selectors.novelName}').text();
let novelCover = loadedCheerio(this).find('${source.search.selectors.novelCover}').attr('src');
if (novelCover && !isUrlAbsolute(novelCover)) {
novelCover = baseUrl + novelCover;
}
novels.push({
sourceId,
novelUrl,
novelName,
novelCover,
});
}
});
return novels;`
: `
showToast('Search is not available in this source')
return [];
`
}
};
const ${source.sourceName}Scraper = {
popularNovels,
parseNovelAndChapters,
parseChapter,
searchNovels,
};
export default ${source.sourceName}Scraper;
`;
const formatContent = prettier.format(content, {
parser: 'typescript',
...prettierConfig,
});
fs.writeFile(
path.resolve(
process.cwd(),
`src/sources/${getLanguageCode(source.lang)}/${source.sourceName}.ts`,
),
formatContent,
error => {
if (error) {
console.log(error);
}
},
);
if (!isDuplicate) {
let allSourcesContent = AllSources;
allSourcesContent.push({
sourceId: source.sourceId,
sourceName: source.sourceName,
icon: `https://github.com/LNReader/lnreader-sources/blob/main/src/${getLanguageCode(
source.lang,
)}/${source.sourceName.toLowerCase()}/icon.png?raw=true`,
url: source.baseUrl,
lang: source.lang || 'English',
});
allSourcesContent = prettier.format(JSON.stringify(allSourcesContent), {
parser: 'json',
...prettierConfig,
});
fs.writeFile(
path.resolve(process.cwd(), 'src/sources/sources.json'),
allSourcesContent,
error => {
if (error) {
console.log(error);
}
},
);
} else {
console.log('Source with same id already exists');
}
} else {
console.log('Template path not provided');
}
} catch (e) {
console.log(e);
}
+44
View File
@@ -0,0 +1,44 @@
const fs = require('fs');
const path = require('path');
const prettier = require('prettier');
const prettierConfig = require('../.prettierrc');
const flatten = (obj, target, prefix) => {
(target = target || {}), (prefix = prefix || '');
Object.keys(obj).forEach(function (key) {
if (typeof obj[key] === 'object' && obj[key] !== null) {
flatten(obj[key], target, prefix + key + '.');
} else {
return (target[prefix + key] = 'string');
}
});
return target;
};
const strings = fs.readFileSync(
path.resolve(process.cwd(), 'strings/languages/en/strings.json'),
'utf8',
);
const stringTypes = `/**\n * This file is auto-generated\n */\n\n
export interface StringMap ${JSON.stringify(flatten(JSON.parse(strings)))}
`;
const formatContent = prettier.format(stringTypes, {
parser: 'typescript',
...prettierConfig,
});
fs.writeFile(
path.resolve(process.cwd(), 'strings/types/index.ts'),
formatContent,
err => {
if (err) {
console.log(err);
}
},
);
+4 -4
View File
@@ -1,15 +1,15 @@
import React from 'react';
import {Dimensions, StyleSheet, View} from 'react-native';
import { Dimensions, StyleSheet, View } from 'react-native';
import {IconButton} from 'react-native-paper';
import { IconButton } from 'react-native-paper';
export const Actionbar = ({active, actions, theme, style}) => {
export const Actionbar = ({ active, actions, theme, style }) => {
if (active) {
return (
<View
style={[
styles.actionbarContainer,
{backgroundColor: theme.colorPrimary},
{ backgroundColor: theme.colorPrimary },
style,
]}
>
+6 -6
View File
@@ -1,22 +1,22 @@
import React from 'react';
import PropTypes from 'prop-types';
import {Appbar as MaterialAppbar} from 'react-native-paper';
import { Appbar as MaterialAppbar } from 'react-native-paper';
import {useTheme} from '../hooks/reduxHooks';
import {ViewPropTypes} from 'react-native';
import { useTheme } from '../hooks/reduxHooks';
import { ViewPropTypes } from 'react-native';
export const Appbar = ({title, onBackAction, style, children}) => {
export const Appbar = ({ title, onBackAction, style, children }) => {
const theme = useTheme();
return (
<MaterialAppbar.Header
style={[{backgroundColor: theme.colorPrimary}, style]}
style={[{ backgroundColor: theme.colorPrimary }, style]}
>
{onBackAction && <MaterialAppbar.BackAction onPress={onBackAction} />}
<MaterialAppbar.Content
title={title}
titleStyle={{color: theme.textColorPrimary}}
titleStyle={{ color: theme.textColorPrimary }}
/>
{children}
</MaterialAppbar.Header>
+26
View File
@@ -0,0 +1,26 @@
import React from 'react';
import { StatusBar } from 'react-native';
import { Appbar as PaperAppbar } from 'react-native-paper';
import { ThemeTypeV1 } from '../../theme/v1/theme/types';
interface AppbarProps {
title: string;
handleGoBack: () => void;
theme: ThemeTypeV1;
}
const Appbar: React.FC<AppbarProps> = ({ title, handleGoBack, theme }) => (
<PaperAppbar.Header
style={{ backgroundColor: theme.colorPrimary }}
statusBarHeight={StatusBar.currentHeight}
>
{handleGoBack && <PaperAppbar.BackAction onPress={handleGoBack} />}
<PaperAppbar.Content
title={title}
titleStyle={{ color: theme.textColorPrimary }}
/>
</PaperAppbar.Header>
);
export default Appbar;
+2 -2
View File
@@ -1,7 +1,7 @@
import React from 'react';
import {View} from 'react-native';
import { View } from 'react-native';
const BottomSheetHandle = ({theme}) => (
const BottomSheetHandle = ({ theme }) => (
<View
style={{
backgroundColor: `${theme.textColorPrimary}66`,
+87
View File
@@ -0,0 +1,87 @@
import { Pressable, StyleSheet, Text, View } from 'react-native';
import React from 'react';
import { ThemeTypeV1 } from '../../theme/v1/theme/types';
export enum ButtonVariation {
DEFAULT = 'default',
OUTLINED = 'outlined',
CLEAR = 'clear',
}
interface Props {
variation?: ButtonVariation;
title: string;
textColor?: string;
onPress?: () => void;
margin?: number;
theme: ThemeTypeV1;
}
const Button: React.FC<Props> = ({
variation = ButtonVariation.DEFAULT,
title,
textColor,
margin = 0,
onPress,
theme,
}) => {
return (
<View
style={[
styles.button,
{ margin },
variation === 'outlined'
? {
...styles.outlined,
borderColor: theme.textColorHint,
}
: variation !== 'clear' && {
backgroundColor: theme.colorAccent,
},
]}
>
<Pressable
style={styles.pressable}
onPress={onPress}
android_ripple={{ color: theme.rippleColor }}
>
<Text
style={{
color: textColor
? textColor
: variation === 'outlined'
? theme.colorAccent
: variation === 'default'
? theme.colorButtonText
: theme.textColorPrimary,
}}
numberOfLines={1}
>
{title}
</Text>
</Pressable>
</View>
);
};
export default Button;
const styles = StyleSheet.create({
button: {
minHeight: 40,
justifyContent: 'center',
borderRadius: 50,
overflow: 'hidden',
},
pressable: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingVertical: 8,
paddingHorizontal: 24,
},
outlined: {
borderWidth: 1,
},
});
+11 -9
View File
@@ -1,12 +1,12 @@
import React from 'react';
import {Pressable, StyleSheet, Text} from 'react-native';
import {Checkbox as PaperCheckbox} from 'react-native-paper';
import { Pressable, StyleSheet, Text } from 'react-native';
import { Checkbox as PaperCheckbox } from 'react-native-paper';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
export const Checkbox = ({label, status, onPress, disabled, theme}) => (
export const Checkbox = ({ label, status, onPress, disabled, theme }) => (
<Pressable
android_ripple={{color: theme.rippleColor}}
android_ripple={{ color: theme.rippleColor }}
style={styles.pressable}
onPress={onPress}
disabled={disabled}
@@ -24,14 +24,16 @@ export const Checkbox = ({label, status, onPress, disabled, theme}) => (
uncheckedColor={theme.textColorSecondary}
disabled={disabled}
/>
<Text style={{color: theme.textColorPrimary, marginLeft: 12}}>{label}</Text>
<Text style={{ color: theme.textColorPrimary, marginLeft: 12 }}>
{label}
</Text>
</Pressable>
);
export const SortItem = ({label, status, onPress, theme}) => (
export const SortItem = ({ label, status, onPress, theme }) => (
<Pressable
android_ripple={{color: theme.rippleColor}}
style={[styles.pressable, {paddingVertical: 16, paddingLeft: 64}]}
android_ripple={{ color: theme.rippleColor }}
style={[styles.pressable, { paddingVertical: 16, paddingLeft: 64 }]}
onPress={onPress}
>
{status && (
@@ -42,7 +44,7 @@ export const SortItem = ({label, status, onPress, theme}) => (
style={styles.icon}
/>
)}
<Text style={{color: theme.textColorPrimary}}>{label}</Text>
<Text style={{ color: theme.textColorPrimary }}>{label}</Text>
</Pressable>
);
-49
View File
@@ -1,49 +0,0 @@
import React from 'react';
import {View, Text, Pressable, StyleSheet} from 'react-native';
import PropTypes from 'prop-types';
export const Chip = ({label, onPress, theme}) => {
return (
<View style={[styles.container, {backgroundColor: theme.colorPrimary}]}>
<Pressable
style={styles.content}
onPress={onPress}
android_ripple={{color: theme.rippleColor}}
>
<Text style={[styles.label, {color: theme.textColorSecondary}]}>
{label}
</Text>
</Pressable>
</View>
);
};
Chip.propTypes = {
label: PropTypes.string,
onPress: PropTypes.func,
theme: PropTypes.object.isRequired,
};
const styles = StyleSheet.create({
container: {
borderRadius: 8,
overflow: 'hidden',
alignSelf: 'flex-start',
justifyContent: 'center',
alignItems: 'center',
height: 32,
marginHorizontal: 4,
elevation: 1,
},
content: {
flex: 1,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: 16,
},
label: {
fontSize: 14,
},
});
+42
View File
@@ -0,0 +1,42 @@
import React from 'react';
import { Pressable, StyleSheet, Text, View } from 'react-native';
import { ThemeTypeV1 } from '../../theme/v1/theme/types';
interface ChipProps {
label: string;
theme: ThemeTypeV1;
}
const Chip: React.FC<ChipProps> = ({ label, theme }) => (
<View style={[styles.chipContainer, { backgroundColor: theme.colorPrimary }]}>
<Pressable
android_ripple={{ color: theme.rippleColor }}
style={styles.pressable}
>
<Text style={[styles.label, { color: theme.textColorSecondary }]}>
{label}
</Text>
</Pressable>
</View>
);
export default Chip;
const styles = StyleSheet.create({
chipContainer: {
overflow: 'hidden',
borderRadius: 8,
height: 28,
marginRight: 8,
},
pressable: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: 16,
},
label: {
fontSize: 12,
},
});
+8 -8
View File
@@ -1,7 +1,7 @@
import React, {useState} from 'react';
import {FlatList, Pressable, StyleSheet, Text, View} from 'react-native';
import React, { useState } from 'react';
import { FlatList, Pressable, StyleSheet, Text, View } from 'react-native';
import {Modal, Portal, TextInput} from 'react-native-paper';
import { Modal, Portal, TextInput } from 'react-native-paper';
const ColorPickerModal = ({
theme,
@@ -73,19 +73,19 @@ const ColorPickerModal = ({
onDismiss={onDismiss}
contentContainerStyle={[
styles.modalContainer,
{backgroundColor: theme.colorPrimary},
{ backgroundColor: theme.colorPrimary },
]}
>
<Text style={[styles.modalTitle, {color: theme.textColorPrimary}]}>
<Text style={[styles.modalTitle, { color: theme.textColorPrimary }]}>
{title}
</Text>
{showAccentColors && (
<FlatList
contentContainerStyle={{marginBottom: 8}}
contentContainerStyle={{ marginBottom: 8 }}
data={accentColors}
numColumns={4}
keyExtractor={item => item}
renderItem={({item}) => (
renderItem={({ item }) => (
<View
style={{
borderRadius: 4,
@@ -98,7 +98,7 @@ const ColorPickerModal = ({
}}
>
<Pressable
style={{flex: 1}}
style={{ flex: 1 }}
android_ripple={{
color: 'rgba(0,0,0,0.12)',
}}
+6 -6
View File
@@ -1,9 +1,9 @@
import React from 'react';
import {View, StyleSheet} from 'react-native';
import {useSafeAreaInsets} from 'react-native-safe-area-context';
import { View, StyleSheet } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
const ScreenContainer = ({children, theme}) => {
const {bottom} = useSafeAreaInsets();
const ScreenContainer = ({ children, theme }) => {
const { bottom } = useSafeAreaInsets();
return (
<View
@@ -18,11 +18,11 @@ const ScreenContainer = ({children, theme}) => {
);
};
const Row = ({children, style}) => (
const Row = ({ children, style }) => (
<View style={[styles.row, style]}>{children}</View>
);
export {ScreenContainer, Row};
export { ScreenContainer, Row };
const styles = StyleSheet.create({
row: {
+11 -11
View File
@@ -1,8 +1,8 @@
/* eslint-disable no-bitwise */
import PropTypes from 'prop-types';
import React, {PureComponent, Children} from 'react';
import {View, Animated, StyleSheet} from 'react-native';
import React, { PureComponent, Children } from 'react';
import { View, Animated, StyleSheet } from 'react-native';
export default class FadeView extends PureComponent {
static defaultProps = {
@@ -30,7 +30,7 @@ export default class FadeView extends PureComponent {
this.renderChild = this.renderChild.bind(this);
this.mounted = false;
let {active} = this.props;
let { active } = this.props;
this.state = {
progress: new Animated.Value(Number(active)),
@@ -46,11 +46,11 @@ export default class FadeView extends PureComponent {
this.mounted = false;
}
UNSAFE_componentWillReceiveProps({active, animationDuration}) {
let {progress} = this.state;
UNSAFE_componentWillReceiveProps({ active, animationDuration }) {
let { progress } = this.state;
if (active ^ this.props.active) {
this.setState({animating: true});
this.setState({ animating: true });
Animated.timing(progress, {
toValue: Number(active),
@@ -58,15 +58,15 @@ export default class FadeView extends PureComponent {
useNativeDriver: true,
}).start(() => {
if (this.mounted) {
this.setState({animating: false});
this.setState({ animating: false });
}
});
}
}
renderChild(child, index) {
let {active, removeHiddenSubviews} = this.props;
let {animating, progress} = this.state;
let { active, removeHiddenSubviews } = this.props;
let { animating, progress } = this.state;
let opacity = progress.interpolate({
inputRange: [0, 1],
@@ -85,7 +85,7 @@ export default class FadeView extends PureComponent {
return (
<Animated.View
style={[styles.container, {opacity}]}
style={[styles.container, { opacity }]}
pointerEvents={pointerEvents}
>
{child}
@@ -94,7 +94,7 @@ export default class FadeView extends PureComponent {
}
render() {
let {children, ...props} = this.props;
let { children, ...props } = this.props;
return <View {...props}>{Children.map(children, this.renderChild)}</View>;
}
+4 -4
View File
@@ -1,8 +1,8 @@
import React from 'react';
import {Pressable, View} from 'react-native';
import { Pressable, View } from 'react-native';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
export const ToggleButton = ({icon, selected, theme, color, onPress}) => (
export const ToggleButton = ({ icon, selected, theme, color, onPress }) => (
<View
style={{
borderRadius: 8,
@@ -11,7 +11,7 @@ export const ToggleButton = ({icon, selected, theme, color, onPress}) => (
}}
>
<Pressable
android_ripple={{color: theme.rippleColor}}
android_ripple={{ color: theme.rippleColor }}
style={{
padding: 10,
alignItems: 'center',
@@ -46,7 +46,7 @@ export const ToggleColorButton = ({
}}
>
<Pressable
android_ripple={{color: textColor}}
android_ripple={{ color: textColor }}
style={{
flex: 1,
padding: 10,
+28
View File
@@ -0,0 +1,28 @@
import React, { ReactNode } from 'react';
import { StyleSheet, View } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
interface Props {
children: ReactNode;
}
const Container: React.FC<Props> = ({ children }) => {
const { bottom } = useSafeAreaInsets();
const containerStyle = [
styles.container,
{
paddingBottom: bottom,
},
];
return <View style={containerStyle}>{children}</View>;
};
export default Container;
const styles = StyleSheet.create({
container: {
flex: 1,
},
});
+2 -2
View File
@@ -1,8 +1,8 @@
/* eslint-disable no-unused-vars */
import React from 'react';
import {StyleSheet} from 'react-native';
import { StyleSheet } from 'react-native';
export const Dialog = ({visible, theme}) => {};
export const Dialog = ({ visible, theme }) => {};
const styles = StyleSheet.create({});
+7 -5
View File
@@ -1,8 +1,8 @@
import React from 'react';
import {StyleSheet, Text, View} from 'react-native';
import {useSelector} from 'react-redux';
import { StyleSheet, Text, View } from 'react-native';
import { useSelector } from 'react-redux';
const EmptyView = ({icon, description, style, children, iconStyle}) => {
const EmptyView = ({ icon, description, style, children, iconStyle }) => {
const theme = useSelector(state => state.settingsReducer.theme);
return (
@@ -10,14 +10,16 @@ const EmptyView = ({icon, description, style, children, iconStyle}) => {
<Text
style={[
styles.emptyViewIcon,
{color: theme.textColorHint},
{ color: theme.textColorHint },
style,
iconStyle,
]}
>
{icon}
</Text>
<Text style={[styles.emptyViewText, {color: theme.textColorHint}, style]}>
<Text
style={[styles.emptyViewText, { color: theme.textColorHint }, style]}
>
{description}
</Text>
{children}
+38
View File
@@ -0,0 +1,38 @@
import React from 'react';
import { StyleSheet, View, Text } from 'react-native';
import { ThemeTypeV1 } from '../../theme/v1/theme/types';
interface EmptyViewProps {
icon?: string;
description: string;
theme: ThemeTypeV1;
}
const EmptyView: React.FC<EmptyViewProps> = ({ icon, description, theme }) => (
<View style={styles.container}>
{icon ? (
<Text style={[styles.icon, { color: theme.textColorHint }]}>{icon}</Text>
) : null}
<Text style={[styles.text, { color: theme.textColorHint }]}>
{description}
</Text>
</View>
);
export default EmptyView;
const styles = StyleSheet.create({
container: {
flex: 1,
alignItems: 'center',
justifyContent: 'center',
},
icon: {
fontSize: 40,
fontWeight: 'bold',
},
text: {
marginTop: 16,
},
});
@@ -0,0 +1,39 @@
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { Container } from '..';
import { ThemeTypeV1 } from '../../theme/v1/theme/types';
interface ErrorScreenProps {
error?: string;
theme: ThemeTypeV1;
}
const ErrorScreen: React.FC<ErrorScreenProps> = ({ error, theme }) => {
return (
<Container>
<View style={styles.container}>
<Text style={[styles.icon, { color: theme.textColorHint }]}>ಥ_ಥ</Text>
<Text style={[styles.error, { color: theme.textColorHint }]}>
{error}
</Text>
</View>
</Container>
);
};
export default ErrorScreen;
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
icon: {
fontSize: 44,
},
error: {
marginTop: 16,
},
});
+7 -7
View File
@@ -1,26 +1,26 @@
import React from 'react';
import {Text, View} from 'react-native';
import { Text, View } from 'react-native';
import {IconButton} from 'react-native-paper';
import { IconButton } from 'react-native-paper';
// import {Row} from './Common';
import EmptyView from './EmptyView';
const ErrorView = ({theme, onRetry, error, openWebView}) => (
<View style={{flex: 1, justifyContent: 'center'}}>
const ErrorView = ({ theme, onRetry, error, openWebView }) => (
<View style={{ flex: 1, justifyContent: 'center' }}>
<EmptyView
icon="(; ̄Д ̄)"
description={error}
style={{color: theme.textColorSecondary}}
style={{ color: theme.textColorSecondary }}
>
<IconButton
icon="reload"
size={25}
style={{margin: 0, marginTop: 24}}
style={{ margin: 0, marginTop: 24 }}
color={theme.text}
onPress={onRetry}
/>
<Text style={{color: theme.textColorPrimary}}>Retry</Text>
<Text style={{ color: theme.textColorPrimary }}>Retry</Text>
</EmptyView>
</View>
);
+8 -8
View File
@@ -1,19 +1,19 @@
import React from 'react';
import {Pressable, StyleSheet, Text, View} from 'react-native';
import {IconButton} from 'react-native-paper';
import { Pressable, StyleSheet, Text, View } from 'react-native';
import { IconButton } from 'react-native-paper';
// const icons = ['(・o・;)', 'Σ(ಠ_ಠ)', 'ಥ_ಥ', '(˘・_・˘)', '(; ̄Д ̄)', '(・Д・。'];
export const ErrorView = ({errorName, actions, theme}) => (
<View style={{flex: 1, justifyContent: 'center', alignItems: 'center'}}>
<Text style={[styles.emptyViewIcon, {color: theme.textColorHint}]}>
export const ErrorView = ({ errorName, actions, theme }) => (
<View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
<Text style={[styles.emptyViewIcon, { color: theme.textColorHint }]}>
{/* {icons[Math.floor(Math.random() * 5)]} */}
ಥ_ಥ
</Text>
<Text style={[styles.emptyViewText, {color: theme.textColorHint}]}>
<Text style={[styles.emptyViewText, { color: theme.textColorHint }]}>
{errorName}
</Text>
<View style={{flexDirection: 'row'}}>
<View style={{ flexDirection: 'row' }}>
{actions.map(action => (
<View
style={{
@@ -40,7 +40,7 @@ export const ErrorView = ({errorName, actions, theme}) => (
icon={action.icon}
color={theme.textColorHint}
size={24}
style={{margin: 0}}
style={{ margin: 0 }}
/>
<Text
style={{
+2 -2
View File
@@ -1,5 +1,5 @@
import React from 'react';
import {Pressable, StyleSheet, ViewPropTypes} from 'react-native';
import { Pressable, StyleSheet, ViewPropTypes } from 'react-native';
import PropTypes from 'prop-types';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
@@ -14,7 +14,7 @@ export const IconButton = ({
}) => (
<Pressable
style={[styles.container, containerStyle]}
android_ripple={{color: theme.rippleColor, borderless: true, radius: 20}}
android_ripple={{ color: theme.rippleColor, borderless: true, radius: 20 }}
onPress={onPress}
>
<MaterialCommunityIcons name={icon} size={size} color={color} />
@@ -0,0 +1,54 @@
import React from 'react';
import { Pressable, StyleSheet, View, ViewStyle } from 'react-native';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import { ThemeTypeV1 } from '../../theme/v1/theme/types';
type Props = {
name: string;
color?: string;
size?: number;
disabled?: boolean;
padding?: number;
onPress?: () => void;
theme: ThemeTypeV1;
style?: ViewStyle;
};
const IconButton: React.FC<Props> = ({
name,
color,
size = 24,
padding = 8,
onPress,
disabled,
theme,
style,
}) => (
<View style={[styles.container, style]}>
<Pressable
style={[styles.pressable, { padding }]}
onPress={onPress}
disabled={disabled}
android_ripple={{ color: theme.rippleColor }}
>
<MaterialCommunityIcons
name={name}
size={size}
color={disabled ? theme.textColorHint : color || theme.textColorPrimary}
/>
</Pressable>
</View>
);
export default IconButton;
const styles = StyleSheet.create({
container: {
borderRadius: 50,
overflow: 'hidden',
},
pressable: {
padding: 8,
},
});
+21 -19
View File
@@ -1,16 +1,16 @@
import React from 'react';
import {Pressable, StyleSheet, Text, View} from 'react-native';
import { Pressable, StyleSheet, Text, View } from 'react-native';
import {List as PaperList, Divider as PaperDivider} from 'react-native-paper';
import { List as PaperList, Divider as PaperDivider } from 'react-native-paper';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
const ListSection = ({children}) => (
const ListSection = ({ children }) => (
<PaperList.Section style={styles.listSection}>{children}</PaperList.Section>
);
const ListSubHeader = ({children, theme}) => (
const ListSubHeader = ({ children, theme }) => (
<PaperList.Subheader
style={[styles.listSubHeader, {color: theme.colorAccent}]}
style={[styles.listSubHeader, { color: theme.colorAccent }]}
>
{children}
</PaperList.Subheader>
@@ -30,12 +30,12 @@ const ListItem = ({
<PaperList.Item
title={title}
style={style}
titleStyle={[{color: theme.textColorPrimary}, titleStyle]}
titleStyle={[{ color: theme.textColorPrimary }, titleStyle]}
description={description}
descriptionStyle={{color: theme.textColorSecondary}}
descriptionStyle={{ color: theme.textColorSecondary }}
descriptionNumberOfLines={1}
left={() => (
<View style={{justifyContent: 'center'}}>
<View style={{ justifyContent: 'center' }}>
{icon && (
<PaperList.Icon
color={theme.colorAccent}
@@ -53,7 +53,7 @@ const ListItem = ({
name={right}
color={iconColor || '#47a84a'}
size={23}
style={{marginRight: 16, textAlignVertical: 'center'}}
style={{ marginRight: 16, textAlignVertical: 'center' }}
/>
)
}
@@ -62,14 +62,14 @@ const ListItem = ({
/>
);
const Divider = ({theme}) => (
<PaperDivider style={{backgroundColor: theme.dividerColor, height: 1}} />
const Divider = ({ theme }) => (
<PaperDivider style={{ backgroundColor: theme.dividerColor, height: 1 }} />
);
const InfoItem = ({title, icon, theme}) => (
const InfoItem = ({ title, icon, theme }) => (
<PaperList.Item
title={title}
titleStyle={{color: theme.textColorSecondary, fontSize: 14}}
titleStyle={{ color: theme.textColorSecondary, fontSize: 14 }}
titleNumberOfLines={5}
left={() =>
icon && <PaperList.Icon color={theme.textColorSecondary} icon={icon} />
@@ -77,7 +77,7 @@ const InfoItem = ({title, icon, theme}) => (
/>
);
const ColorItem = ({title, description, theme, onPress}) => (
const ColorItem = ({ title, description, theme, onPress }) => (
<Pressable
style={{
padding: 16,
@@ -85,12 +85,14 @@ const ColorItem = ({title, description, theme, onPress}) => (
justifyContent: 'space-between',
alignItems: 'center',
}}
android_ripple={{color: theme.rippleColor}}
android_ripple={{ color: theme.rippleColor }}
onPress={onPress}
>
<View>
<Text style={{color: theme.textColorPrimary, fontSize: 16}}>{title}</Text>
<Text style={{color: theme.textColorSecondary}}>{description}</Text>
<Text style={{ color: theme.textColorPrimary, fontSize: 16 }}>
{title}
</Text>
<Text style={{ color: theme.textColorSecondary }}>{description}</Text>
</View>
<View
style={{
@@ -104,8 +106,8 @@ const ColorItem = ({title, description, theme, onPress}) => (
</Pressable>
);
const Icon = ({icon, theme}) => (
<PaperList.Icon color={theme.colorAccent} icon={icon} style={{margin: 0}} />
const Icon = ({ icon, theme }) => (
<PaperList.Icon color={theme.colorAccent} icon={icon} style={{ margin: 0 }} />
);
export const List = {
+109
View File
@@ -0,0 +1,109 @@
import React, { ReactNode } from 'react';
import { StyleSheet, View } from 'react-native';
import { List as PaperList, Divider as PaperDivider } from 'react-native-paper';
import { ThemeTypeV1 } from '../../theme/v1/theme/types';
interface ListItemProps {
title: string;
description?: string | null;
icon?: string;
onPress?: () => void;
theme: ThemeTypeV1;
}
const Section = ({ children }: { children: ReactNode }) => (
<PaperList.Section style={styles.listSection}>{children}</PaperList.Section>
);
const SubHeader = ({
children,
theme,
}: {
children: ReactNode;
theme: ThemeTypeV1;
}) => (
<PaperList.Subheader style={{ color: theme.colorAccent }}>
{children}
</PaperList.Subheader>
);
const Item: React.FC<ListItemProps> = ({
title,
description,
icon,
onPress,
theme,
}) => (
<PaperList.Item
title={title}
titleStyle={{ color: theme.textColorPrimary }}
description={description}
descriptionStyle={{ color: theme.textColorSecondary }}
left={() => (
<View style={styles.iconContainer}>
{icon && (
<PaperList.Icon
color={theme.colorAccent}
icon={icon}
style={styles.icon}
/>
)}
</View>
)}
onPress={onPress}
rippleColor={theme.rippleColor}
/>
);
const Divider = ({ theme }: { theme: ThemeTypeV1 }) => (
<PaperDivider
style={[styles.divider, { backgroundColor: theme.dividerColor }]}
/>
);
const InfoItem = ({
title,
icon,
theme,
}: {
title: string;
icon: string;
theme: ThemeTypeV1;
}) => (
<PaperList.Item
title={title}
titleStyle={[{ color: theme.textColorSecondary }, styles.infoItem]}
titleNumberOfLines={5}
left={() =>
icon && <PaperList.Icon color={theme.textColorSecondary} icon={icon} />
}
/>
);
export default {
Section,
SubHeader,
Item,
Divider,
InfoItem,
};
const styles = StyleSheet.create({
listSection: {
flex: 1,
marginVertical: 0,
},
divider: {
height: 1,
},
iconContainer: {
justifyContent: 'center',
},
icon: {
marginVertical: 0,
},
infoItem: {
fontSize: 14,
},
});
+9 -12
View File
@@ -1,8 +1,9 @@
import React from 'react';
import {StyleSheet, View, Text, Pressable} from 'react-native';
import { StyleSheet, View, Text, Pressable } from 'react-native';
import FastImage from 'react-native-fast-image';
import {defaultCoverUri} from '../sources/helpers/constants';
import { defaultCoverUri } from '../sources/helpers/constants';
import { coverPlaceholderColor } from '../theme/colors';
const ListView = ({
item,
@@ -14,29 +15,24 @@ const ListView = ({
isSelected,
onLongPress,
}) => {
const uri =
item.novelCover && !item.novelCover.startsWith('/')
? item.novelCover
: defaultCoverUri;
return (
<Pressable
android_ripple={{color: theme.rippleColor}}
android_ripple={{ color: theme.rippleColor }}
style={[
styles.listView,
isSelected && {backgroundColor: theme.rippleColor},
isSelected && { backgroundColor: theme.rippleColor },
]}
onPress={onPress}
onLongPress={onLongPress}
>
<FastImage
source={{
uri,
uri: item.novelCover,
}}
style={[styles.extensionIcon, inLibraryBadge && {opacity: 0.5}]}
style={[styles.extensionIcon, inLibraryBadge && { opacity: 0.5 }]}
/>
<Text
style={[{color: theme.textColorPrimary}, styles.novelName]}
style={[{ color: theme.textColorPrimary }, styles.novelName]}
numberOfLines={1}
>
{item.novelName}
@@ -64,6 +60,7 @@ const styles = StyleSheet.create({
width: 40,
height: 40,
borderRadius: 4,
backgroundColor: coverPlaceholderColor,
},
novelName: {
flex: 1,
@@ -1,8 +1,8 @@
import React from 'react';
import {ActivityIndicator, View} from 'react-native';
import { ActivityIndicator, View } from 'react-native';
export const LoadingScreen = ({theme}) => (
<View style={{flex: 1, justifyContent: 'center'}}>
export const LoadingScreen = ({ theme }) => (
<View style={{ flex: 1, justifyContent: 'center' }}>
<ActivityIndicator size={50} color={theme.colorAccent} />
</View>
);
@@ -0,0 +1,25 @@
import React from 'react';
import { StyleSheet, ActivityIndicator } from 'react-native';
import { Container } from '..';
import { ThemeTypeV1 } from '../../theme/v1/theme/types';
const LoadingScreen: React.FC<{ theme: ThemeTypeV1 }> = ({ theme }) => (
<Container>
<ActivityIndicator
size={50}
color={theme.colorAccent}
style={styles.indicator}
/>
</Container>
);
export default LoadingScreen;
const styles = StyleSheet.create({
indicator: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
});
+10 -10
View File
@@ -1,10 +1,10 @@
import React, {useState} from 'react';
import {StyleSheet, View, Text} from 'react-native';
import {Portal, Modal, Button} from 'react-native-paper';
import React, { useState } from 'react';
import { StyleSheet, View, Text } from 'react-native';
import { Portal, Modal, Button } from 'react-native-paper';
import * as Linking from 'expo-linking';
import {useTheme} from '../hooks/reduxHooks';
import { useTheme } from '../hooks/reduxHooks';
const NewUpdateDialog = ({newVersion}) => {
const NewUpdateDialog = ({ newVersion }) => {
const [newUpdateDialog, showNewUpdateDialog] = useState(true);
const theme = useTheme();
@@ -16,13 +16,13 @@ const NewUpdateDialog = ({newVersion}) => {
onDismiss={() => showNewUpdateDialog(false)}
contentContainerStyle={[
styles.containerStyle,
{backgroundColor: theme.colorPrimaryDark},
{ backgroundColor: theme.colorPrimaryDark },
]}
>
<Text style={[styles.modalHeader, {color: theme.textColorPrimary}]}>
<Text style={[styles.modalHeader, { color: theme.textColorPrimary }]}>
{`New update available ${newVersion.tag_name}`}
</Text>
<Text style={{color: theme.textColorSecondary, fontSize: 16}}>
<Text style={{ color: theme.textColorSecondary, fontSize: 16 }}>
{newVersion.body}
</Text>
<View
@@ -38,7 +38,7 @@ const NewUpdateDialog = ({newVersion}) => {
letterSpacing: 0,
textTransform: 'none',
}}
theme={{colors: {primary: theme.colorAccent}}}
theme={{ colors: { primary: theme.colorAccent } }}
onPress={() => showNewUpdateDialog(false)}
>
Cancel
@@ -49,7 +49,7 @@ const NewUpdateDialog = ({newVersion}) => {
letterSpacing: 0,
textTransform: 'none',
}}
theme={{colors: {primary: theme.colorAccent}}}
theme={{ colors: { primary: theme.colorAccent } }}
onPress={() => Linking.openURL(newVersion.downloadUrl)}
>
Install
+22 -20
View File
@@ -1,4 +1,4 @@
import React, {memo, useCallback} from 'react';
import React, { memo, useCallback } from 'react';
import {
StyleSheet,
View,
@@ -7,14 +7,15 @@ import {
Pressable,
} from 'react-native';
import {LinearGradient} from 'expo-linear-gradient';
import { LinearGradient } from 'expo-linear-gradient';
import FastImage from 'react-native-fast-image';
import ListView from './ListView';
import {useDeviceOrientation} from '../services/utils/helpers';
import {useSettings} from '../hooks/reduxHooks';
import {defaultCoverUri} from '../sources/helpers/constants';
import { useDeviceOrientation } from '../services/utils/helpers';
import { useSettings } from '../hooks/reduxHooks';
import { defaultCoverUri } from '../sources/helpers/constants';
import { coverPlaceholderColor } from '../theme/colors';
const NovelCover = ({
item,
@@ -25,7 +26,7 @@ const NovelCover = ({
onLongPress,
selectedNovels,
}) => {
const {displayMode, novelsPerRow, showDownloadBadges, showUnreadBadges} =
const { displayMode, novelsPerRow, showDownloadBadges, showUnreadBadges } =
useSettings();
const window = useWindowDimensions();
@@ -43,10 +44,7 @@ const NovelCover = ({
const selectNovel = () => onLongPress && onLongPress(item.novelId);
const uri =
item.novelCover && !item.novelCover.startsWith('/')
? item.novelCover
: defaultCoverUri;
const uri = item.novelCover;
return displayMode !== 2 ? (
<View
@@ -64,7 +62,7 @@ const NovelCover = ({
]}
>
<Pressable
android_ripple={{color: theme.colorAccent}}
android_ripple={{ color: theme.colorAccent }}
style={styles.opac}
onPress={
selectedNovels && selectedNovels.length > 0 ? selectNovel : onPress
@@ -90,10 +88,14 @@ const NovelCover = ({
)}
</View>
<FastImage
source={{uri}}
source={{ uri }}
style={[
{height: getHeight(), borderRadius: 4},
libraryStatus && {opacity: 0.5},
{
height: getHeight(),
borderRadius: 4,
backgroundColor: coverPlaceholderColor,
},
libraryStatus && { opacity: 0.5 },
]}
/>
<View style={styles.compactTitleContainer}>
@@ -141,7 +143,7 @@ const NovelCover = ({
export default memo(NovelCover);
const ComfortableTitle = ({theme, novelName}) => (
const ComfortableTitle = ({ theme, novelName }) => (
<Text
numberOfLines={2}
style={[
@@ -156,7 +158,7 @@ const ComfortableTitle = ({theme, novelName}) => (
</Text>
);
const CompactTitle = ({novelName}) => (
const CompactTitle = ({ novelName }) => (
<View style={styles.titleContainer}>
<LinearGradient
colors={['transparent', 'rgba(0,0,0,0.7)']}
@@ -169,7 +171,7 @@ const CompactTitle = ({novelName}) => (
{
color: 'rgba(255,255,255,1)',
textShadowColor: 'rgba(0, 0, 0, 0.75)',
textShadowOffset: {width: -1, height: 1},
textShadowOffset: { width: -1, height: 1 },
textShadowRadius: 10,
},
]}
@@ -180,7 +182,7 @@ const CompactTitle = ({novelName}) => (
</View>
);
const InLibraryBadge = ({theme}) => (
const InLibraryBadge = ({ theme }) => (
<Text
style={[
styles.inLibraryBadge,
@@ -305,8 +307,8 @@ const styles = StyleSheet.create({
badgeContainer: {
position: 'absolute',
zIndex: 1,
top: 8,
left: 8,
top: 10,
left: 10,
flexDirection: 'row',
},
});
+180
View File
@@ -0,0 +1,180 @@
import React from 'react';
import {
Pressable,
PressableProps,
StyleSheet,
Text,
View,
} from 'react-native';
import FastImage from 'react-native-fast-image';
import { useAppearanceSettings, useLibrarySettings } from '../../redux/hooks';
import { LibraryNovelInfo, NovelInfo } from '../../database/types';
import { SourceNovelItem } from '../../sources/types';
import { ThemeType } from '../../theme/types';
import {
UnreadBadge,
DownloadsBadge,
InLibraryBadge,
} from './NovelCoverBadges';
import { DisplayModes } from '../../redux/settings/settingsSlice';
import { LinearGradient } from 'expo-linear-gradient';
import { useDeviceOrientation, useNovelCoverHeight } from '../../hooks';
import { DeviceOrientation } from '../../hooks/useDeviceOrientation';
interface Props extends PressableProps {
item: LibraryNovelInfo | SourceNovelItem | NovelInfo;
isSelected: boolean;
theme: ThemeType;
showInLibraryBadge?: boolean;
}
const NovelItem: React.FC<Props> = props => {
const { item, showInLibraryBadge = false, theme, isSelected = false } = props;
const { displayMode } = useAppearanceSettings();
const { novelsPerRowLandscape, novelsPerRowPotrait } =
useAppearanceSettings();
const { showUnreadBadge, showDownloadsBadge } = useLibrarySettings();
const orientation = useDeviceOrientation();
const coverHeight = useNovelCoverHeight();
const coverSelectedStyle = {
backgroundColor: theme.primary,
opacity: 0.8,
};
return (
<View
style={[
{
flex:
1 /
(orientation === DeviceOrientation.POTRAIT
? novelsPerRowPotrait
: novelsPerRowLandscape),
},
styles.container,
isSelected && coverSelectedStyle,
]}
>
<Pressable
style={styles.pressable}
android_ripple={{ color: theme.rippleColor }}
{...props}
>
<View style={styles.badgeContainer}>
{'chaptersDownloaded' in item &&
item.chaptersDownloaded &&
showDownloadsBadge ? (
<DownloadsBadge
unreadCount={item.chaptersUnread}
downloadCount={item.chaptersDownloaded}
showUnreadBadge={showUnreadBadge}
theme={theme}
/>
) : null}
{'chaptersUnread' in item &&
item.chaptersUnread &&
showUnreadBadge ? (
<UnreadBadge
unreadCount={item.chaptersUnread}
downloadCount={item.chaptersDownloaded}
showDownloadsBadge={showDownloadsBadge}
theme={theme}
/>
) : null}
</View>
{showInLibraryBadge ? <InLibraryBadge theme={theme} /> : null}
<FastImage
source={{ uri: item.novelCover }}
style={[
{ height: coverHeight },
styles.image,
showInLibraryBadge && styles.inLibrary,
]}
>
{displayMode === DisplayModes.Compact ? (
<CompactTitle name={item.novelName} />
) : null}
</FastImage>
{displayMode === DisplayModes.Comfortable ? (
<Text
numberOfLines={2}
style={[{ color: theme.textColorPrimary }, styles.title]}
>
{item.novelName}
</Text>
) : null}
</Pressable>
</View>
);
};
export default NovelItem;
const CompactTitle = ({ name }: { name: string }) => (
<View style={styles.compactTitleContainer}>
<LinearGradient
colors={['transparent', 'rgba(0,0,0,0.7)']}
style={styles.linearGradient}
>
<Text numberOfLines={2} style={[styles.title, styles.compactTitle]}>
{name}
</Text>
</LinearGradient>
</View>
);
const styles = StyleSheet.create({
container: {
overflow: 'hidden',
borderRadius: 6,
margin: 2,
},
pressable: {
flex: 1,
padding: 4,
},
badgeContainer: {
position: 'absolute',
zIndex: 1,
top: 12,
left: 12,
flexDirection: 'row',
},
image: {
borderRadius: 4,
},
inLibrary: {
opacity: 0.5,
},
title: {
padding: 4,
fontSize: 12,
},
compactTitleContainer: {
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
flex: 1,
borderRadius: 4,
},
linearGradient: {
borderRadius: 4,
padding: 4,
paddingTop: 16,
},
compactTitle: {
color: 'rgba(255,255,255,1)',
textShadowColor: 'rgba(0, 0, 0, 0.75)',
textShadowOffset: {
width: -1,
height: 1,
},
textShadowRadius: 10,
},
});
@@ -0,0 +1,104 @@
import React from 'react';
import { StyleSheet, Text } from 'react-native';
import { ThemeType } from '../../theme/types';
interface UnreadBadgeProps {
unreadCount: number;
downloadCount: number;
showDownloadsBadge: boolean;
theme: ThemeType;
}
const UnreadBadge: React.FC<UnreadBadgeProps> = ({
unreadCount,
downloadCount,
showDownloadsBadge,
theme,
}) => (
<Text
style={[
styles.unreadBadge,
(!downloadCount || !showDownloadsBadge) && styles.borderLeftRadius,
{ backgroundColor: theme.primary, color: theme.onPrimary },
]}
>
{unreadCount}
</Text>
);
interface DownloadsBadgeProps {
unreadCount: number;
downloadCount: number;
showUnreadBadge: boolean;
theme: ThemeType;
}
const DownloadsBadge: React.FC<DownloadsBadgeProps> = ({
unreadCount,
downloadCount,
showUnreadBadge,
theme,
}) => (
<Text
style={[
styles.downloadBadge,
(!unreadCount || !showUnreadBadge) && styles.borderRightRadius,
{ backgroundColor: theme.tertiary, color: theme.onTertiary },
]}
>
{downloadCount}
</Text>
);
interface InLibraryBadgeProps {
theme: ThemeType;
}
const InLibraryBadge: React.FC<InLibraryBadgeProps> = ({ theme }) => (
<Text
style={[
styles.inLibraryBadge,
{ backgroundColor: theme.primary, color: theme.onPrimary },
]}
>
In library
</Text>
);
export { UnreadBadge, DownloadsBadge, InLibraryBadge };
const styles = StyleSheet.create({
borderLeftRadius: {
borderTopLeftRadius: 4,
borderBottomLeftRadius: 4,
},
borderRightRadius: {
borderTopRightRadius: 4,
borderBottomRightRadius: 4,
},
unreadBadge: {
borderTopRightRadius: 4,
borderBottomRightRadius: 4,
paddingVertical: 2,
paddingHorizontal: 4,
fontSize: 12,
},
downloadBadge: {
borderTopLeftRadius: 4,
borderBottomLeftRadius: 4,
paddingVertical: 2,
paddingHorizontal: 4,
fontSize: 12,
},
inLibraryBadge: {
position: 'absolute',
top: 12,
right: 12,
borderRadius: 4,
paddingVertical: 2,
paddingHorizontal: 4,
fontSize: 12,
zIndex: 2,
},
});
+6 -6
View File
@@ -1,8 +1,8 @@
import React, {useCallback} from 'react';
import {StyleSheet, FlatList} from 'react-native';
import React, { useCallback } from 'react';
import { StyleSheet, FlatList } from 'react-native';
import {useSettings} from '../hooks/reduxHooks';
import {useDeviceOrientation} from '../services/utils/helpers';
import { useSettings } from '../hooks/reduxHooks';
import { useDeviceOrientation } from '../services/utils/helpers';
const NovelList = ({
data,
@@ -13,7 +13,7 @@ const NovelList = ({
ListEmptyComponent,
ListFooterComponent,
}) => {
const {displayMode, novelsPerRow} = useSettings();
const { displayMode, novelsPerRow } = useSettings();
const orientation = useDeviceOrientation();
@@ -35,7 +35,7 @@ const NovelList = ({
<FlatList
contentContainerStyle={[
styles.flatListCont,
displayMode !== 2 && {paddingHorizontal: 4},
displayMode !== 2 && { paddingHorizontal: 4 },
]}
numColumns={getNovelsPerRow()}
key={[orientation, getNovelsPerRow()]}
+40
View File
@@ -0,0 +1,40 @@
import React from 'react';
import { FlatList, FlatListProps, StyleSheet } from 'react-native';
import { useAppearanceSettings } from '../../redux/hooks';
import { useDeviceOrientation } from '../../hooks';
import { LibraryNovelInfo, NovelInfo } from '../../database/types';
import { SourceNovelItem } from '../../sources/types';
import { DeviceOrientation } from '../../hooks/useDeviceOrientation';
const NovelList: React.FC<
FlatListProps<SourceNovelItem | NovelInfo | LibraryNovelInfo>
> = props => {
const orientation = useDeviceOrientation();
const { novelsPerRowPotrait, novelsPerRowLandscape } =
useAppearanceSettings();
return (
<FlatList
contentContainerStyle={styles.container}
numColumns={
orientation === DeviceOrientation.LANDSCAPE
? novelsPerRowLandscape
: novelsPerRowPotrait
}
key={[orientation, novelsPerRowPotrait] as any}
{...props}
/>
);
};
export default NovelList;
const styles = StyleSheet.create({
container: {
flexGrow: 1,
padding: 4,
},
});
+5 -5
View File
@@ -1,14 +1,14 @@
import React from 'react';
import {StyleSheet, View, Text} from 'react-native';
import {RadioButton as MaterialRadioButton} from 'react-native-paper';
import { StyleSheet, View, Text } from 'react-native';
import { RadioButton as MaterialRadioButton } from 'react-native-paper';
export const RadioButtonGroup = ({children, value, onValueChange}) => (
export const RadioButtonGroup = ({ children, value, onValueChange }) => (
<MaterialRadioButton.Group onValueChange={onValueChange} value={value}>
{children}
</MaterialRadioButton.Group>
);
export const RadioButton = ({value, label, theme, labelStyle}) => (
export const RadioButton = ({ value, label, theme, labelStyle }) => (
<View style={styles.radioButtonContainer}>
<MaterialRadioButton
value={value}
@@ -18,7 +18,7 @@ export const RadioButton = ({value, label, theme, labelStyle}) => (
<Text
style={[
styles.radioButtonLabel,
{color: theme.textColorPrimary},
{ color: theme.textColorPrimary },
labelStyle,
]}
>
+6 -4
View File
@@ -1,6 +1,6 @@
import React from 'react';
import {Pressable, StyleSheet, Text} from 'react-native';
import {RadioButton as PaperRadioButton} from 'react-native-paper';
import { Pressable, StyleSheet, Text } from 'react-native';
import { RadioButton as PaperRadioButton } from 'react-native-paper';
export const RadioButton = ({
label,
@@ -11,7 +11,7 @@ export const RadioButton = ({
theme,
}) => (
<Pressable
android_ripple={{color: theme.rippleColor}}
android_ripple={{ color: theme.rippleColor }}
style={[styles.pressable, style]}
onPress={onPress}
>
@@ -21,7 +21,9 @@ export const RadioButton = ({
color={theme.colorAccent}
uncheckedColor={theme.textColorSecondary}
/>
<Text style={[labelStyle, {color: theme.textColorPrimary, marginLeft: 12}]}>
<Text
style={[labelStyle, { color: theme.textColorPrimary, marginLeft: 12 }]}
>
{label}
</Text>
</Pressable>
+20 -10
View File
@@ -1,8 +1,14 @@
import React, {useRef} from 'react';
import {StyleSheet, View, TextInput, StatusBar, Pressable} from 'react-native';
import React, { useRef } from 'react';
import {
StyleSheet,
View,
TextInput,
StatusBar,
Pressable,
} from 'react-native';
import {Row} from '../Common';
import {IconButton} from '../IconButton/IconButton';
import { Row } from '../Common';
import { IconButton } from '../IconButton/IconButton';
export const Searchbar = ({
theme,
@@ -21,22 +27,26 @@ export const Searchbar = ({
<View style={styles.searchBar}>
<Pressable
onPress={() => searchRef.current.focus()}
style={[styles.pressable, {backgroundColor: theme.searchBarColor}]}
android_ripple={{color: theme.rippleColor}}
style={[styles.pressable, { backgroundColor: theme.searchBarColor }]}
android_ripple={{ color: theme.rippleColor }}
>
<Row style={{flex: 1}}>
<Row style={{ flex: 1 }}>
{backAction && (
<IconButton
icon={backAction}
color={theme.textColorPrimary}
onPress={onBackAction}
theme={theme}
containerStyle={{marginLeft: 0}}
containerStyle={{ marginLeft: 0 }}
/>
)}
<TextInput
ref={searchRef}
style={{fontSize: 16, color: theme.textColorPrimary, marginLeft: 4}}
style={{
fontSize: 16,
color: theme.textColorPrimary,
marginLeft: 4,
}}
placeholder={placeholder}
placeholderTextColor={theme.textColorSecondary}
onChangeText={onChangeText}
@@ -60,7 +70,7 @@ export const Searchbar = ({
color={action.color ?? theme.textColorPrimary}
onPress={action.onPress}
theme={theme}
containerStyle={{marginRight: 0, marginLeft: 16}}
containerStyle={{ marginRight: 0, marginLeft: 16 }}
/>
))}
</Pressable>
+130
View File
@@ -0,0 +1,130 @@
import React, { useRef } from 'react';
import { Pressable, StyleSheet, View, TextInput } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { IconButtonV2 } from '..';
import { ThemeTypeV1 } from '../../theme/v1/theme/types';
interface RightIcon {
iconName: string;
color?: string;
onPress: () => void;
}
interface SearcbarProps {
searchText: string;
placeholder: string;
onChangeText?: (text: string) => void;
onSubmitEditing?: () => void;
leftIcon: string;
rightIcons?: RightIcon[];
handleBackAction?: () => void;
clearSearchbar: () => void;
onLeftIconPress?: () => void;
theme: ThemeTypeV1;
}
const Searchbar: React.FC<SearcbarProps> = ({
searchText,
placeholder,
onChangeText,
onSubmitEditing,
leftIcon,
rightIcons,
handleBackAction,
clearSearchbar,
onLeftIconPress,
theme,
}) => {
const searchbarRef = useRef<any>(null);
const focusSearchbar = () => searchbarRef.current.focus();
const { top } = useSafeAreaInsets();
const marginTop = top + 8;
return (
<View
style={[
styles.searchbarContainer,
{ marginTop, backgroundColor: theme.searchBarColor },
]}
>
<Pressable
onPress={focusSearchbar}
android_ripple={{ color: theme.rippleColor }}
style={[styles.searchbar]}
>
<IconButtonV2
name={handleBackAction ? 'arrow-left' : leftIcon}
color={theme.textColorPrimary}
onPress={() => {
if (handleBackAction) {
handleBackAction();
} else if (onLeftIconPress) {
onLeftIconPress();
}
}}
theme={theme}
/>
<TextInput
ref={searchbarRef}
style={[styles.textInput, { color: theme.textColorPrimary }]}
placeholder={placeholder}
placeholderTextColor={theme.textColorSecondary}
onChangeText={onChangeText}
onSubmitEditing={onSubmitEditing}
defaultValue={searchText}
/>
{searchText !== '' ? (
<IconButtonV2
name="close"
color={theme.textColorPrimary}
onPress={clearSearchbar}
theme={theme}
/>
) : null}
{rightIcons?.map((icon, index) => (
<IconButtonV2
key={index}
name={icon.iconName}
color={icon.color || theme.textColorPrimary}
onPress={icon.onPress}
theme={theme}
/>
))}
</Pressable>
</View>
);
};
export default Searchbar;
const styles = StyleSheet.create({
searchbarContainer: {
marginHorizontal: 16,
marginBottom: 16,
minHeight: 46,
borderRadius: 50,
overflow: 'hidden',
zIndex: 1,
},
searchbar: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
paddingHorizontal: 8,
},
textInput: {
flex: 1,
fontSize: 16,
marginHorizontal: 8,
},
icon: {
marginHorizontal: 8,
},
searchIconContainer: {
borderRadius: 50,
overflow: 'hidden',
},
});
+14 -10
View File
@@ -1,19 +1,23 @@
import React from 'react';
import {Pressable, StyleSheet, View, Text} from 'react-native';
import { Pressable, StyleSheet, View, Text } from 'react-native';
import {Switch, List} from 'react-native-paper';
import { Switch, List } from 'react-native-paper';
const SwitchSetting = ({label, description, onPress, theme, icon, value}) => (
const SwitchSetting = ({ label, description, onPress, theme, icon, value }) => (
<Pressable
android_ripple={{color: theme.rippleColor}}
style={[styles.container, {paddingVertical: description ? 16 : 12}]}
android_ripple={{ color: theme.rippleColor }}
style={[styles.container, { paddingVertical: description ? 16 : 12 }]}
onPress={onPress}
>
<View style={{flexDirection: 'row', flex: 1}}>
<View style={{ flexDirection: 'row', flex: 1 }}>
{icon && (
<List.Icon color={theme.colorAccent} icon={icon} style={{margin: 0}} />
<List.Icon
color={theme.colorAccent}
icon={icon}
style={{ margin: 0 }}
/>
)}
<View style={[icon && {marginLeft: 16}]}>
<View style={[icon && { marginLeft: 16 }]}>
<Text
style={{
color: theme.textColorPrimary,
@@ -25,7 +29,7 @@ const SwitchSetting = ({label, description, onPress, theme, icon, value}) => (
{label}
</Text>
{description && (
<Text style={{color: theme.textColorSecondary}}>{description}</Text>
<Text style={{ color: theme.textColorSecondary }}>{description}</Text>
)}
</View>
</View>
@@ -33,7 +37,7 @@ const SwitchSetting = ({label, description, onPress, theme, icon, value}) => (
value={value}
onValueChange={onPress}
color={theme.colorAccent}
style={{marginLeft: 8}}
style={{ marginLeft: 8 }}
/>
</Pressable>
);
+63
View File
@@ -0,0 +1,63 @@
import React from 'react';
import { Pressable, StyleSheet, View, Text } from 'react-native';
import { Switch } from 'react-native-paper';
import { ThemeTypeV1 } from '../../theme/v1/theme/types';
interface SwitchItemProps {
value: boolean;
label: string;
description?: string;
onPress: () => void;
theme: ThemeTypeV1;
}
const SwitchItem: React.FC<SwitchItemProps> = ({
label,
description,
onPress,
theme,
value,
}) => (
<Pressable
android_ripple={{ color: theme.rippleColor }}
style={[styles.container]}
onPress={onPress}
>
<View style={styles.labelContainer}>
<Text style={[{ color: theme.textColorPrimary }, styles.label]}>
{label}
</Text>
{description ? (
<Text style={{ color: theme.textColorSecondary }}>{description}</Text>
) : null}
</View>
<Switch
value={value}
onValueChange={onPress}
color={theme.colorAccent}
style={styles.switch}
/>
</Pressable>
);
export default SwitchItem;
const styles = StyleSheet.create({
container: {
flexDirection: 'row',
justifyContent: 'space-between',
paddingHorizontal: 16,
paddingVertical: 12,
},
labelContainer: {
flex: 1,
},
label: {
fontSize: 16,
textAlignVertical: 'center',
},
switch: {
marginLeft: 8,
},
});
+6 -6
View File
@@ -1,8 +1,8 @@
import React from 'react';
import {View, Text, /* StyleSheet, */ Pressable} from 'react-native';
import { View, Text, /* StyleSheet, */ Pressable } from 'react-native';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
export const ThemePicker = ({theme, currentTheme, onPress}) => (
export const ThemePicker = ({ theme, currentTheme, onPress }) => (
<View
style={{
justifyContent: 'center',
@@ -25,7 +25,7 @@ export const ThemePicker = ({theme, currentTheme, onPress}) => (
elevation: 1,
}}
>
<Pressable style={{flex: 1}} onPress={onPress}>
<Pressable style={{ flex: 1 }} onPress={onPress}>
{currentTheme.id === theme.id && (
<MaterialCommunityIcons
name="check"
@@ -60,7 +60,7 @@ export const ThemePicker = ({theme, currentTheme, onPress}) => (
}}
/>
</View>
<View style={{padding: 8}}>
<View style={{ padding: 8 }}>
<View
style={{
height: 18,
@@ -68,7 +68,7 @@ export const ThemePicker = ({theme, currentTheme, onPress}) => (
borderRadius: 4,
}}
/>
<View style={{paddingVertical: 4, flexDirection: 'row'}}>
<View style={{ paddingVertical: 4, flexDirection: 'row' }}>
<View
style={{
height: 10,
@@ -87,7 +87,7 @@ export const ThemePicker = ({theme, currentTheme, onPress}) => (
}}
/>
</View>
<View style={{flexDirection: 'row'}}>
<View style={{ flexDirection: 'row' }}>
<View
style={{
height: 10,
+3 -3
View File
@@ -1,8 +1,8 @@
import React from 'react';
import PropTypes from 'prop-types';
import {Text as NativeText, TextPropTypes} from 'react-native';
import { Text as NativeText, TextPropTypes } from 'react-native';
const Text = ({type, children, style, theme}) => {
const Text = ({ type, children, style, theme }) => {
const color =
type === 'primary'
? theme.textColorPrimary
@@ -10,7 +10,7 @@ const Text = ({type, children, style, theme}) => {
? theme.textColorSecondary
: theme.textColorHint;
return <NativeText style={[style, {color}]}>{children}</NativeText>;
return <NativeText style={[style, { color }]}>{children}</NativeText>;
};
Text.propType = {
+12
View File
@@ -0,0 +1,12 @@
export { default as IconButtonV2 } from './IconButtonV2/IconButtonV2';
export { default as SearchbarV2 } from './SearchbarV2/SearchbarV2';
export { default as LoadingScreenV2 } from './LoadingScreenV2/LoadingScreenV2';
export { default as Container } from './Container/Container';
export { default as ErrorScreenV2 } from './ErrorScreenV2/ErrorScreenV2';
export { default as EmptyView } from './EmptyView/EmptyView';
export { default as NovelList } from './NovelList/NovelList';
export { default as Chip } from './Chip/Chip';
export { default as Button } from './Button/Button';
export { default as Appbar } from './Appbar/Appbar';
export { default as SwitchItem } from './SwitchItem/SwitchItem';
export { default as List } from './List/List';
@@ -13,15 +13,8 @@ import {
createHistoryTableQuery,
createChapterIdIndexQuery,
} from './tables/HistoryTable';
import {createDownloadTableQuery} from './tables/DownloadTable';
import {
createUpdatesTableQuery,
// updatesSeedDataQuery,
} from './tables/UpdateTable';
/**
* Database Version = 2
*/
import { createDownloadTableQuery } from './tables/DownloadTable';
import { createUpdatesTableQuery } from './tables/UpdateTable';
const dbName = 'lnreader.db';
@@ -1,5 +1,9 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import * as SQLite from 'expo-sqlite';
import {getSource} from '../../sources/sources';
import { showToast } from '../../hooks/showToast';
import { sourceManager } from '../../sources/sourceManager';
import { ChapterItem } from '../types';
const db = SQLite.openDatabase('lnreader.db');
const insertChaptersQuery =
@@ -47,7 +51,7 @@ export const getChapters = (novelId, sort, filter) => {
tx.executeSql(
getChaptersQuery(sort, filter),
[novelId],
(txObj, {rows: {_array}}) => {
(txObj, { rows: { _array } }) => {
resolve(_array);
},
(txObj, error) => {
@@ -77,35 +81,55 @@ export const getChapterFromDB = async chapterId => {
);
};
export const getNextChapterFromDB = async (novelId, chapterId) => {
const getPrevChapterQuery = `
SELECT
*
FROM
chapters
WHERE
novelId = ?
AND
chapterId < ?
`;
export const getPrevChapter = (
novelId: number,
chapterId: number,
): Promise<ChapterItem> => {
return new Promise((resolve, reject) =>
db.transaction(tx => {
tx.executeSql(
'SELECT * FROM chapters WHERE novelId = ? AND chapterId > ?',
getPrevChapterQuery,
[novelId, chapterId],
(txObj, results) => {
resolve(results.rows.item(0));
},
(txObj, error) => {
// console.log('Error ', error)
},
(txObj, results) => resolve(results.rows.item(results.rows.length - 1)),
(txObj, error) => showToast(error.message),
);
}),
);
};
export const getPrevChapterFromDB = async (novelId, chapterId) => {
const getNextChapterQuery = `
SELECT
*
FROM
chapters
WHERE
novelId = ?
AND
chapterId > ?
`;
export const getNextChapter = (
novelId: number,
chapterId: number,
): Promise<ChapterItem> => {
return new Promise((resolve, reject) =>
db.transaction(tx => {
tx.executeSql(
'SELECT * FROM chapters WHERE novelId = ? AND chapterId < ?',
getNextChapterQuery,
[novelId, chapterId],
(txObj, results) => {
resolve(results.rows.item(results.rows.length - 1));
},
(txObj, error) => {
// console.log('Error ', error)
},
(txObj, results) => resolve(results.rows.item(0)),
(txObj, error) => showToast(error.message),
);
}),
);
@@ -208,7 +232,7 @@ export const downloadChapter = async (
chapterUrl,
chapterId,
) => {
const source = getSource(sourceId);
const source = sourceManager(sourceId);
const chapter = await source.parseChapter(novelUrl, chapterUrl);
@@ -270,7 +294,7 @@ export const getLastReadChapter = async novelId => {
tx.executeSql(
getLastReadChapterQuery,
[novelId],
(txObj, {rows}) => resolve(rows.item(0)),
(txObj, { rows }) => resolve(rows.item(0)),
(txObj, error) => {
// console.log('Error ', error)
},
@@ -340,7 +364,7 @@ export const getDownloadedChapters = () => {
tx.executeSql(
getDownloadedChaptersQuery,
null,
(txObj, {rows: {_array}}) => {
(txObj, { rows: { _array } }) => {
resolve(_array);
},
(txObj, error) => {
@@ -357,3 +381,19 @@ export const deleteDownloads = async () => {
tx.executeSql('DELETE FROM downloads; VACCUM;');
});
};
export const updateChapterDownloadedQuery = `
UPDATE
chapters
SET
downloaded = 1
WHERE
chapterId = ?`;
export const updateChapterDeletedQuery = `
UPDATE
chapters
SET
downloaded = 0
WHERE
chapterId = ?`;
+85
View File
@@ -0,0 +1,85 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import * as SQLite from 'expo-sqlite';
import { showToast } from '../../hooks/showToast';
import { sourceManager } from '../../sources/sourceManager';
import { DownloadedChapter } from '../types';
import {
updateChapterDeletedQuery,
updateChapterDownloadedQuery,
} from './ChapterQueries';
const db = SQLite.openDatabase('lnreader.db');
const downloadChapterQuery = `
INSERT INTO
downloads (downloadChapterId, chapterName, chapterText)
VALUES
(?, ?, ?)
`;
export const fetchAndInsertChapterInDb = async (
sourceId: number,
novelUrl: string,
chapterId: number,
chapterUrl: string,
) => {
const chapter = await sourceManager(sourceId).parseChapter(
novelUrl,
chapterUrl,
);
db.transaction(tx => {
tx.executeSql(updateChapterDownloadedQuery, [chapterId]);
tx.executeSql(
downloadChapterQuery,
[chapterId, chapter.chapterName, chapter.chapterText],
(txObj, res) => {},
(_txObj, error) => showToast(error),
);
});
};
const deleteChapterFromDbQuery = `
DELETE FROM
downloads
WHERE
downloadChapterId = ?
`;
export const deleteChapterFromDb = (chapterId: number) => {
db.transaction(tx => {
tx.executeSql(updateChapterDeletedQuery, [chapterId]);
tx.executeSql(
deleteChapterFromDbQuery,
[chapterId],
(txObj, res) => {},
(_txObj, error) => showToast(error.message),
);
});
};
const getChapterFromDbQuery = `
SELECT
*
FROM
downloads
WHERE
downloadChapterId = ?
`;
export const getChapterFromDb = async (
chapterId: number,
): Promise<DownloadedChapter> => {
return new Promise((resolve, reject) =>
db.transaction(tx => {
tx.executeSql(
getChapterFromDbQuery,
[chapterId],
(txObj, results) => resolve(results.rows.item(0)),
(txObj, error) => reject(error),
);
}),
);
};
+3 -3
View File
@@ -1,7 +1,7 @@
import * as SQLite from 'expo-sqlite';
const db = SQLite.openDatabase('lnreader.db');
import {showToast} from '../../hooks/showToast';
import { showToast } from '../../hooks/showToast';
const getHistoryQuery = `
SELECT history.*, chapters.*, novels.*
@@ -21,7 +21,7 @@ export const getHistoryFromDb = async () => {
tx.executeSql(
getHistoryQuery,
null,
(txObj, {rows: {_array}}) => {
(txObj, { rows: { _array } }) => {
resolve(_array);
},
(txObj, error) => {
@@ -48,7 +48,7 @@ export const insertHistory = async (novelId, chapterId) => {
});
};
export const deleteHistory = async historyId => {
export const deleteChapterHistory = async historyId => {
db.transaction(tx => {
tx.executeSql(
'DELETE FROM history WHERE historyId = ?',
+2 -2
View File
@@ -28,7 +28,7 @@ export const getLibrary = (sort, filter) => {
tx.executeSql(
getLibraryQuery(sort, filter),
null,
(txObj, {rows: {_array}}) => {
(txObj, { rows: { _array } }) => {
// console.log(_array);
resolve(_array);
},
@@ -69,7 +69,7 @@ export const searchLibrary = (searchText, sort, filter) => {
tx.executeSql(
searchLibraryQuery(searchText, sort, filter),
null,
(txObj, {rows: {_array}}) => resolve(_array),
(txObj, { rows: { _array } }) => resolve(_array),
(txObj, error) => {
// console.log('Error ', error);
},
+20 -15
View File
@@ -4,10 +4,10 @@ const db = SQLite.openDatabase('lnreader.db');
import BackgroundService from 'react-native-background-actions';
import * as DocumentPicker from 'expo-document-picker';
import {fetchChapters, fetchNovel} from '../../services/Source/source';
import {insertChapters} from './ChapterQueries';
import { fetchChapters, fetchNovel } from '../../services/Source/source';
import { insertChapters } from './ChapterQueries';
import {showToast} from '../../hooks/showToast';
import { showToast } from '../../hooks/showToast';
const insertNovelQuery =
'INSERT INTO novels (novelUrl, sourceUrl, sourceId, source, novelName, novelCover, novelSummary, author, artist, status, genre) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
@@ -23,14 +23,14 @@ export const insertNovel = async novel => {
novel.sourceId,
novel.source,
novel.novelName,
novel.novelCover,
novel.novelCover || '',
novel.novelSummary,
novel.author,
novel.artist,
novel.status,
novel.genre,
],
(txObj, {insertId}) => resolve(insertId),
(txObj, { insertId }) => resolve(insertId),
(txObj, error) => {
// console.log('Error ', error)
},
@@ -94,7 +94,7 @@ export const getNovel = async (sourceId, novelUrl) => {
tx.executeSql(
'SELECT * FROM novels WHERE novelUrl = ? AND sourceId = ?',
[novelUrl, sourceId],
(txObj, {rows}) => resolve(rows.item(0)),
(txObj, { rows }) => resolve(rows.item(0)),
(txObj, error) => {
// console.log('Error ', error)
},
@@ -120,7 +120,7 @@ const restoreFromBackupQuery =
'INSERT INTO novels (novelUrl, sourceUrl, sourceId, source, novelName, novelCover, novelSummary, author, artist, status, genre, followed, unread) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
export const restoreLibrary = async novel => {
return new Promise((resolve, reject) =>
return new Promise((resolve, reject) => {
db.transaction(tx =>
tx.executeSql(
restoreFromBackupQuery,
@@ -139,16 +139,21 @@ export const restoreLibrary = async novel => {
novel.followed,
novel.unread,
],
async (txObj, {insertId}) => {
async (txObj, { insertId }) => {
const chapters = await fetchChapters(novel.sourceId, novel.novelUrl);
await insertChapters(insertId, chapters);
if (chapters) {
await insertChapters(insertId, chapters);
resolve();
}
},
(txObj, error) => {
// console.log('Error ', error)
resolve();
},
),
),
);
);
});
};
const migrateNovelQuery =
@@ -196,7 +201,7 @@ export const migrateNovel = async (sourceId, novelUrl) => {
novel.genre,
1,
],
async (txObj, {insertId}) => {
async (txObj, { insertId }) => {
const chapters = await fetchChapters(
novel.sourceId,
novel.novelUrl,
@@ -214,7 +219,7 @@ export const migrateNovel = async (sourceId, novelUrl) => {
await BackgroundService.start(veryIntensiveTask, options);
await BackgroundService.updateNotification({
progressBar: {value: 1, indeterminate: false},
progressBar: { value: 1, indeterminate: false },
});
} catch (error) {
showToast(error.message);
@@ -242,7 +247,7 @@ export const updateNovelInfo = async (info, novelId) => {
};
export const pickCustomNovelCover = async novelId => {
const image = await DocumentPicker.getDocumentAsync({type: 'image/*'});
const image = await DocumentPicker.getDocumentAsync({ type: 'image/*' });
if (image.type === 'success' && image.uri) {
const uri = 'file://' + image.uri;
-46
View File
@@ -1,46 +0,0 @@
import * as SQLite from 'expo-sqlite';
const db = SQLite.openDatabase('lnreader.db');
const getUpdatesQuery = `
SELECT chapters.chapterId, chapters.read, chapters.downloaded, chapters.chapterUrl, chapters.chapterName, novels.novelUrl, novels.novelId, novels.novelCover, novels.novelName, novels.sourceId, updates.updateTime, updates.updateId
FROM updates
JOIN chapters
ON updates.chapterId = chapters.chapterId
JOIN novels
ON updates.novelId = novels.novelId
WHERE date(updates.updateTime) > date('now','-3 months')
ORDER BY updates.updateTime DESC
`;
export const getUpdates = async () => {
return new Promise((resolve, reject) => {
db.transaction(tx => {
tx.executeSql(
getUpdatesQuery,
null,
(txObj, {rows: {_array}}) => {
resolve(_array);
},
(txObj, error) => {
// console.log('Error ', error)
},
);
});
});
};
export const deleteNovelUpdates = novelId => {
db.transaction(tx => {
tx.executeSql(
'DELETE FROM updates WHERE novelId = ?',
[novelId],
(txObj, res) => {},
(txObj, error) => {
// console.log('Error ', error);
},
);
});
};
export const clearUpdates = () =>
db.transaction(tx => tx.executeSql('DELETE FROM updates; VACCUM;'));
+104
View File
@@ -0,0 +1,104 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import * as SQLite from 'expo-sqlite';
import { showToast } from '../../hooks/showToast';
import { Update } from '../types';
const db = SQLite.openDatabase('lnreader.db');
const getUpdatesFromDbQuery = `
SELECT
chapters.chapterId,
chapters.read,
chapters.downloaded,
chapters.chapterUrl,
chapters.chapterName,
chapters.bookmark,
chapters.releaseDate,
novels.novelUrl,
novels.novelId,
novels.novelCover,
novels.novelName,
novels.sourceId,
updates.updateTime,
updates.updateId
FROM
updates
JOIN
chapters
ON
updates.chapterId = chapters.chapterId
JOIN
novels
ON
updates.novelId = novels.novelId
WHERE
date(updates.updateTime) > date('now','-3 months')
ORDER BY
updates.updateTime DESC
`;
export const getUpdatesFromDb = async (): Promise<Update[]> => {
return new Promise((resolve, reject) => {
db.transaction(tx => {
tx.executeSql(
getUpdatesFromDbQuery,
[],
(txObj, { rows: { _array } }) => resolve(_array),
(txObj, error) => reject(error),
);
});
});
};
const insertChapterUpdateQuery = `
INSERT OR IGNORE INTO
updates
(chapterId, novelId, updateTime)
VALUES
(?, ?, (datetime('now','localtime')))`;
export const insertChapterUpdate = async (
chapterId: number,
novelId: number,
) => {
db.transaction(tx => {
tx.executeSql(
insertChapterUpdateQuery,
[chapterId, novelId],
(txOBJ, res) => {},
(txOBJ, error) => showToast(error.message),
);
});
};
const deleteUpdateFromDbQuery = `
DELETE FROM
updates
WHERE
novelId = ?`;
export const deleteUpdateFromDb = (novelId: number) => {
db.transaction(tx => {
tx.executeSql(
deleteUpdateFromDbQuery,
[novelId],
(txObj, res) => {},
(txObj, error) => showToast(error.message),
);
});
};
const clearUpdatesQuery = `
DELETE FROM updates;
VACCUM;
`;
export const clearUpdates = () =>
db.transaction(tx =>
tx.executeSql(
clearUpdatesQuery,
[],
() => showToast('Updates cleared.'),
(txObj, error) => showToast(error.message),
),
);
+1 -1
View File
@@ -6,7 +6,7 @@ export const createNovelTableQuery = `
sourceId INTEGER NOT NULL,
source TEXT NOT NULL,
novelName TEXT NOT NULL,
novelCover TEXT NOT NULL,
novelCover TEXT,
novelSummary TEXT,
author TEXT,
artist TEXT,
+68
View File
@@ -0,0 +1,68 @@
export interface NovelInfo {
novelId: number;
sourceUrl: string;
novelUrl: string;
sourceId: number;
source: string;
novelName: string;
novelCover?: string;
novelSummary?: string;
genre?: string;
author?: string;
status?: string;
followed: number;
}
export interface LibraryNovelInfo extends NovelInfo {
chaptersUnread: number;
chaptersDownloaded: number;
}
export interface ChapterItem {
chapterId: number;
novelId: number;
chapterUrl: string;
chapterName: string;
releaseDate?: string;
read: number;
bookmark: number;
downloaded: number;
}
export interface DownloadedChapter {
downloadId: number;
downloadChapterId: number;
chapterName: string | null;
chapterText: string;
}
export interface History {
historyId: number;
sourceId: number;
novelId: number;
chapterId: number;
novelName: string;
novelUrl: string;
novelCover: string;
chapterName: string;
chapterUrl: string;
historyTimeRead: string;
bookmark: number;
}
export interface Update {
updateId: number;
sourceId: number;
novelId: number;
novelName: string;
novelUrl: string;
novelCover: string;
chapterId: number;
chapterUrl: string;
chapterName: string;
downloaded: number;
read: number;
updateTime: string;
bookmark: number;
releaseDate: string;
}
@@ -0,0 +1,13 @@
export const convertDateToISOString = (SQLiteDate: string) => {
const dateParts: string[] = SQLiteDate.split('-');
const JSDate = new Date(
Number(dateParts[0]),
Number(dateParts[1]) - 1,
Number(dateParts[2].substring(0, 2)),
);
const date = JSDate.toISOString();
return date;
};
+3 -3
View File
@@ -1,5 +1,5 @@
import {useState, useEffect} from 'react';
import {appversion} from '../utils/utils';
import { useState, useEffect } from 'react';
import { appVersion } from '../utils/versionUtils';
export const useGithubUpdateChecker = () => {
const latestReleaseUrl =
@@ -23,7 +23,7 @@ export const useGithubUpdateChecker = () => {
};
const isNewVersion = versionTag => {
let currentVersion = `${appversion}`;
let currentVersion = `${appVersion}`;
const regex = /[^\\d.]/;
let newVersion = versionTag.replace(regex, '');
+9
View File
@@ -0,0 +1,9 @@
export { default as useSearch } from './useSearch';
export { default as useHistory } from './useHistory';
export { default as useUpdates } from './useUpdates';
export { default as useFullscreenMode } from './useFullscreenMode';
export { default as useChapter } from './useChapter';
export { default as useNextAndPrevChapter } from './useNextAndPrevChapter';
export { default as useDeviceOrientation } from './useDeviceOrientation';
export { default as useNovelCoverHeight } from './useNovelCoverHeight';
export { default as useNovelTrackerInfo } from './useNovelTrackerInfo';
+8 -5
View File
@@ -1,5 +1,5 @@
import {useSelector} from 'react-redux';
import {darkTheme} from '../theme/theme';
import { useSelector } from 'react-redux';
import { darkTheme } from '../theme/v1/theme/theme';
const useTheme = () => {
const theme = useSelector(state => state.settingsReducer.theme);
@@ -56,7 +56,7 @@ const usePreferences = novelId => {
showChapterTitles = novel.showChapterTitles;
}
return {sort, filter, position, showChapterTitles};
return { sort, filter, position, showChapterTitles };
};
const useSavedSettings = () => {
@@ -80,11 +80,14 @@ const useContinueReading = (chapters, novelId) => {
);
}
// If the last read chapter is 100% done, set the next chapter as the 'last read'.
// If all chapters are read, then set the last chapter in the list as the last read (Fixed bug)
if (!lastReadChapter) {
lastReadChapter = chapters.find(obj => obj.read === 0);
lastReadChapter =
chapters.find(obj => obj.read === 0) || chapters[chapters.length - 1];
}
return {lastReadChapter, position};
return { lastReadChapter, position };
};
const usePosition = (novelId, chapterId) => {
-5
View File
@@ -1,5 +0,0 @@
import {ToastAndroid} from 'react-native';
export const showToast = message => {
ToastAndroid.show(message, ToastAndroid.SHORT);
};
+4
View File
@@ -0,0 +1,4 @@
import { ToastAndroid } from 'react-native';
export const showToast = (message: string) =>
ToastAndroid.show(message, ToastAndroid.SHORT);
+57
View File
@@ -0,0 +1,57 @@
import { useCallback, useEffect, useState } from 'react';
import sanitize from 'sanitize-html';
import { defaultTo } from 'lodash';
import { getChapterFromDb } from '../database/queries/DownloadQueries';
import { sourceManager } from '../sources/sourceManager';
import { DownloadedChapter } from '../database/types';
import { SourceChapter } from '../sources/types';
type Chapter = SourceChapter | DownloadedChapter;
const useChapter = (
sourceId: number,
chapterId: number,
chapterUrl: string,
novelUrl: string,
) => {
const [isLoading, setIsLoading] = useState<boolean>(true);
const [chapter, setChapter] = useState<Chapter>();
const [error, setError] = useState<string>();
const getChapter = useCallback(async () => {
try {
let res: Chapter = await getChapterFromDb(chapterId);
if (!res) {
res = await sourceManager(sourceId).parseChapter(novelUrl, chapterUrl);
}
let chapterText = sanitize(
defaultTo(
res.chapterText,
"Chapter is empty. Report if it's available in WebView.",
),
);
setChapter({ ...res, chapterText });
} catch (err: unknown) {
if (typeof err === 'string') {
setError(err);
} else if (err instanceof Error) {
setError(err.message);
}
} finally {
setIsLoading(false);
}
}, [sourceId, chapterId, chapterUrl, novelUrl]);
useEffect(() => {
getChapter();
}, [getChapter]);
return { isLoading, setIsLoading, chapter, error };
};
export default useChapter;
+65
View File
@@ -0,0 +1,65 @@
import { useEffect, useRef, useState } from 'react';
import { useAppDispatch, useSavedChapterData } from '../redux/hooks';
import { saveChapterProgress } from '../redux/localStorage/localStorageSlice';
export const useChapterProgress = (chapterId: number, readerRef: any) => {
const dispatch = useAppDispatch();
const { progressPercentage = 0, progressLocation = 0 } =
useSavedChapterData(chapterId);
const [percentage, setPercentage] = useState(progressPercentage);
const [location, setLocation] = useState(progressLocation);
const percentageRef = useRef(0);
percentageRef.current = percentage;
const locationRef = useRef(0);
locationRef.current = location;
const setProgress = ({ nativeEvent }: { nativeEvent: any }) => {
const currentOffsetY = nativeEvent.contentOffset.y;
const currentPosition =
nativeEvent.contentOffset.y + nativeEvent.layoutMeasurement.height;
const currentProgressPercentage = Math.round(
(currentPosition / nativeEvent.contentSize.height) * 100,
);
setPercentage(currentProgressPercentage);
setLocation(currentOffsetY);
};
useEffect(() => {
return () => {
dispatch(
saveChapterProgress({
chapterId,
progressLocation: locationRef.current,
progressPercentage: percentageRef.current,
}),
);
};
}, []);
const [firstLayout, setFirstLayout] = useState(true);
const scrollToSavedProgress = () => {
if (firstLayout) {
percentage < 100 &&
readerRef.current.scrollTo({
x: 0,
y: location,
animated: false,
});
setFirstLayout(false);
}
};
return {
progressLocation,
progressPercentage: percentage,
setProgress,
scrollToSavedProgress,
};
};
+18
View File
@@ -0,0 +1,18 @@
import { useWindowDimensions } from 'react-native';
export enum DeviceOrientation {
POTRAIT = 'potrait',
LANDSCAPE = 'landscape',
}
const useDeviceOrientation = (): DeviceOrientation => {
const window = useWindowDimensions();
if (window.width > window.height) {
return DeviceOrientation.LANDSCAPE;
} else {
return DeviceOrientation.POTRAIT;
}
};
export default useDeviceOrientation;
+53
View File
@@ -0,0 +1,53 @@
import { useCallback, useEffect } from 'react';
import { StatusBar } from 'react-native';
import { useNavigation } from '@react-navigation/native';
import { useReaderSettingsV1, useSettingsV1, useThemeV1 } from '../redux/hooks';
import {
changeNavigationBarColor,
hideNavigationBar,
showNavigationBar,
} from '../theme/NativeModules/NavigationBarColor';
import { readerBackground } from '../screens/reader/utils/readerStyles';
const useFullscreenMode = () => {
const { addListener } = useNavigation();
const readerSettings = useReaderSettingsV1();
const { fullScreenMode } = useSettingsV1();
const backgroundColor = readerBackground(readerSettings.theme);
const theme = useThemeV1();
const setImmersiveMode = useCallback(() => {
if (fullScreenMode) {
StatusBar.setHidden(true);
hideNavigationBar();
} else {
StatusBar.setBackgroundColor(backgroundColor);
changeNavigationBarColor(backgroundColor as any);
}
}, [backgroundColor, fullScreenMode]);
const showStatusAndNavBar = useCallback(() => {
StatusBar.setHidden(false);
showNavigationBar();
}, []);
useEffect(() => {
setImmersiveMode();
}, [setImmersiveMode]);
useEffect(() => {
const unsubscribe = addListener('beforeRemove', () => {
showStatusAndNavBar();
StatusBar.setBarStyle(theme.statusBar);
});
return unsubscribe;
}, []);
return { setImmersiveMode, showStatusAndNavBar };
};
export default useFullscreenMode;
+51
View File
@@ -0,0 +1,51 @@
import { useCallback, useState } from 'react';
import { useFocusEffect } from '@react-navigation/native';
import { History } from '../database/types';
import {
deleteAllHistory,
deleteChapterHistory,
getHistoryFromDb,
} from '../database/queries/HistoryQueries';
const useHistory = () => {
const [isLoading, setIsLoading] = useState(true);
const [history, setHistory] = useState<History[]>([]);
const [error, setError] = useState<string>();
const getHistory = async () => {
try {
const res = await getHistoryFromDb();
setHistory(res);
} catch (err) {
if (err instanceof Error) {
setError(err.message);
}
} finally {
setIsLoading(false);
}
};
const clearAllHistory = () => deleteAllHistory();
const removeChapterFromHistory = async (historyId: number) => {
deleteChapterHistory(historyId);
getHistory();
};
useFocusEffect(
useCallback(() => {
getHistory();
}, []),
);
return {
isLoading,
history,
error,
removeChapterFromHistory,
clearAllHistory,
};
};
export default useHistory;
+61
View File
@@ -0,0 +1,61 @@
import { useCallback, useState } from 'react';
import { useFocusEffect } from '@react-navigation/native';
import { LibraryNovelInfo } from '../database/types';
import { getLibraryNovelsFromDb } from '../database/queries/LibraryQueries';
import { useLibrarySettings } from '../redux/hooks';
const useLibrary = () => {
const [isLoading, setIsLoading] = useState(true);
const [novels, setNovels] = useState<LibraryNovelInfo[]>([]);
const [error, setError] = useState<string>();
const { filters, sort } = useLibrarySettings();
const getLibraryNovels = useCallback(async () => {
try {
const res = await getLibraryNovelsFromDb(filters, sort);
setNovels(res);
} catch (err) {
if (err instanceof Error) {
setError(err.message);
}
} finally {
setIsLoading(false);
}
}, [filters, sort]);
const getLibrarySearchResults = useCallback(
async (searchText: string) => {
try {
const res = await getLibraryNovelsFromDb(filters, sort, searchText);
setNovels(res);
} catch (err) {
if (err instanceof Error) {
setError(err.message);
}
} finally {
setIsLoading(false);
}
},
[filters, sort],
);
const clearSearchResults = () => getLibraryNovels();
useFocusEffect(
useCallback(() => {
getLibraryNovels();
}, [getLibraryNovels]),
);
return {
isLoading,
novels,
error,
getLibrarySearchResults,
clearSearchResults,
getLibraryNovels,
};
};
export default useLibrary;
+2 -2
View File
@@ -1,4 +1,4 @@
import {useState} from 'react';
import { useState } from 'react';
export const useModal = () => {
const [visible, setVisible] = useState(false);
@@ -6,5 +6,5 @@ export const useModal = () => {
const showModal = () => setVisible(true);
const hideModal = () => setVisible(false);
return {visible, showModal, hideModal};
return { visible, showModal, hideModal };
};
+83
View File
@@ -0,0 +1,83 @@
import { useCallback, useEffect, useState } from 'react';
import { StackActions, useNavigation } from '@react-navigation/native';
import {
getNextChapter,
getPrevChapter,
} from '../database/queries/ChapterQueries';
import { ChapterItem } from '../database/types';
import { showToast } from '../hooks/showToast';
const useNextAndPrevChapter = (
sourceId: number,
novelId: number,
chapterId: number,
) => {
const navigation = useNavigation();
const [isLoading, setIsLoading] = useState(true);
const [nextChapter, setNextChapter] = useState<ChapterItem>();
const [previousChapter, setPreviousChapter] = useState<ChapterItem>();
const getNextAndPrevChapter = useCallback(async () => {
try {
const nextChap = await getNextChapter(novelId, chapterId);
const prevChap = await getPrevChapter(novelId, chapterId);
setNextChapter(nextChap);
setPreviousChapter(prevChap);
} catch (err: unknown) {
if (typeof err === 'string') {
showToast(err);
} else if (err instanceof Error) {
showToast(err.message);
}
} finally {
setIsLoading(false);
}
}, [novelId, chapterId]);
useEffect(() => {
getNextAndPrevChapter();
}, [getNextAndPrevChapter]);
const navigateToPrevChapter = () =>
previousChapter
? navigation.dispatch(
StackActions.replace(
'ReaderScreen' as never,
{
sourceId,
novelId,
chapterId: previousChapter?.chapterId,
chapterUrl: previousChapter?.chapterUrl,
} as never,
),
)
: showToast("There's no previous chapter");
const navigateToNextChapter = () =>
nextChapter
? navigation.dispatch(
StackActions.replace(
'ReaderScreen' as never,
{
sourceId,
novelId,
chapterId: nextChapter?.chapterId,
chapterUrl: nextChapter?.chapterUrl,
} as never,
),
)
: showToast("There's no next chapter");
return {
isLoading,
nextChapter,
previousChapter,
navigateToNextChapter,
navigateToPrevChapter,
};
};
export default useNextAndPrevChapter;
+34
View File
@@ -0,0 +1,34 @@
import { useCallback, useEffect, useState } from 'react';
import { useWindowDimensions } from 'react-native';
import { useDeviceOrientation } from '../hooks';
import { DeviceOrientation } from '../hooks/useDeviceOrientation';
import { useAppearanceSettings } from '../redux/hooks';
const useNovelCoverHeight = () => {
const window = useWindowDimensions();
const orientation = useDeviceOrientation();
const { novelsPerRowLandscape, novelsPerRowPotrait } =
useAppearanceSettings();
const [coverHeight, setCoverHeight] = useState<number>();
const getCoverHeight = useCallback(
() =>
(window.width /
(orientation === DeviceOrientation.POTRAIT
? novelsPerRowPotrait
: novelsPerRowLandscape)) *
(4 / 3),
[window.width, novelsPerRowPotrait, novelsPerRowLandscape, orientation],
);
useEffect(() => {
setCoverHeight(getCoverHeight());
}, [getCoverHeight]);
return coverHeight;
};
export default useNovelCoverHeight;
View File
+13
View File
@@ -0,0 +1,13 @@
import { useAppSelector } from '../redux/hooks';
const useNovelTrackerInfo = (novelId: number) => {
const tracker = useAppSelector(state => state.trackerReducer) as any;
const isTracked = tracker.trackedNovels.find(
(novel: any) => novel.novelId === novelId,
);
return { isTracked, isTrackerAvailable: tracker.tracker };
};
export default useNovelTrackerInfo;
+10
View File
@@ -0,0 +1,10 @@
import { useCallback, useState } from 'react';
const useSearch = () => {
const [searchText, setSearchText] = useState<string>('');
const clearSearchbar = useCallback(() => setSearchText(''), []);
return { searchText, setSearchText, clearSearchbar };
};
export default useSearch;
+97
View File
@@ -0,0 +1,97 @@
import { useState, useEffect } from 'react';
import Tts from 'react-native-tts';
import { htmlToText } from '../sources/helpers/htmlToText';
export const useTextToSpeech = html => {
const [ttsStatus, setTtsStatus] = useState();
const [ttsPosition, setTtsPosition] = useState({ end: 0 });
let text = htmlToText(html);
useEffect(() => {
Tts.addEventListener('tts-start', () => setTtsStatus('start'));
Tts.addEventListener('tts-progress', event => {
setTtsStatus('progress');
setTtsPosition(event);
});
Tts.addEventListener('tts-finish', () => setTtsStatus('finish'));
Tts.addEventListener('tts-cancel', () => setTtsStatus('paused'));
return () => Tts.stop();
}, []);
const startTts = () => {
if (ttsStatus === 'progress') {
setTtsPosition({ end: 0 });
setTtsStatus('finish');
Tts.stop();
return;
}
if (text.length >= 3999) {
const splitNChars = (txt, num) => {
let result = [];
for (let i = 0; i < txt.length; i += num) {
result.push(txt.substr(i, num));
}
return result;
};
let splitMe = splitNChars(text, 3999);
splitMe.forEach(value => {
Tts.speak(value, {
androidParams: {
KEY_PARAM_STREAM: 'STREAM_MUSIC',
},
});
});
} else {
Tts.stop();
Tts.speak(text, {
androidParams: {
KEY_PARAM_STREAM: 'STREAM_MUSIC',
},
});
}
};
const pauseTts = () => {
if (ttsStatus === 'progress') {
setTtsStatus('paused');
Tts.stop();
return;
} else {
text = text.slice(ttsPosition.end);
if (text.length >= 3999) {
const splitNChars = (txt, num) => {
let result = [];
for (let i = 0; i < txt.length; i += num) {
result.push(txt.substr(i, num));
}
return result;
};
let splitMe = splitNChars(text, 3999);
splitMe.forEach(value => {
Tts.speak(value, {
androidParams: {
KEY_PARAM_STREAM: 'STREAM_MUSIC',
},
});
});
} else {
Tts.stop();
Tts.speak(text, {
androidParams: {
KEY_PARAM_STREAM: 'STREAM_MUSIC',
},
});
}
}
};
return [ttsStatus, ttsPosition, startTts, pauseTts];
};
+68
View File
@@ -0,0 +1,68 @@
import { useCallback, useState } from 'react';
import { getUpdatesFromDb } from '../database/queries/UpdateQueries';
import { useFocusEffect } from '@react-navigation/native';
import { Update } from '../database/types';
import { useAppSelector, useDownloadQueue } from '../redux/hooks';
const useUpdates = () => {
const [isLoading, setIsLoading] = useState(false);
const [updates, setUpdates] = useState<Update[]>([]);
const [searchResults, setSearchResults] = useState<Update[]>([]);
const { lastUpdateTime = null, showLastUpdateTime = false } = useAppSelector(
state => state.updatesReducer,
);
const downloadQueue = useDownloadQueue();
const [error, setError] = useState('');
const getUpdates = async () => {
try {
const res = await getUpdatesFromDb();
setUpdates(res);
} catch (err) {
if (err instanceof Error) {
setError(err.message);
}
} finally {
setIsLoading(false);
}
};
const searchUpdates = useCallback(
async (searchText: string) => {
setSearchResults(
updates.filter(item =>
item.novelName.toLowerCase().includes(searchText.toLowerCase()),
),
);
},
[updates],
);
const clearSearchResults = useCallback(() => {
setSearchResults([]);
getUpdates();
}, []);
useFocusEffect(
useCallback(() => {
getUpdates();
}, [downloadQueue]),
);
return {
isLoading,
updates,
searchResults,
clearSearchResults,
searchUpdates,
lastUpdateTime,
showLastUpdateTime,
error,
};
};
export default useUpdates;
+14 -13
View File
@@ -1,6 +1,6 @@
import React from 'react';
import {createMaterialBottomTabNavigator} from '@react-navigation/material-bottom-tabs';
import { createMaterialBottomTabNavigator } from '@react-navigation/material-bottom-tabs';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import Library from '../screens/library/LibraryScreen';
@@ -9,7 +9,8 @@ import History from '../screens/history/HistoryScreen';
import Browse from '../screens/browse/BrowseScreen';
import More from '../screens/more/MoreScreen';
import {useSettings, useTheme} from '../hooks/reduxHooks';
import { useSettings, useTheme } from '../hooks/reduxHooks';
import { getString } from '../../strings/translations';
const Tab = createMaterialBottomTabNavigator();
@@ -24,15 +25,15 @@ const BottomNavigator = () => {
return (
<Tab.Navigator
barStyle={{backgroundColor: theme.colorPrimary}}
barStyle={{ backgroundColor: theme.colorPrimary }}
activeColor={theme.colorAccent}
shifting={!showLabelsInNav}
>
<Tab.Screen
name="Library"
name={getString('library')}
component={Library}
options={{
tabBarIcon: ({color}) => (
tabBarIcon: ({ color }) => (
<MaterialCommunityIcons
name="book-variant-multiple"
color={color}
@@ -43,10 +44,10 @@ const BottomNavigator = () => {
/>
{showUpdatesTab && (
<Tab.Screen
name="Updates"
name={getString('updates')}
component={Updates}
options={{
tabBarIcon: ({color}) => (
tabBarIcon: ({ color }) => (
<MaterialCommunityIcons
name="alert-decagram-outline"
color={color}
@@ -58,20 +59,20 @@ const BottomNavigator = () => {
)}
{showHistoryTab && (
<Tab.Screen
name="History"
name={getString('history')}
component={History}
options={{
tabBarIcon: ({color}) => (
tabBarIcon: ({ color }) => (
<MaterialCommunityIcons name="history" color={color} size={24} />
),
}}
/>
)}
<Tab.Screen
name="Browse"
name={getString('browse')}
component={Browse}
options={{
tabBarIcon: ({color}) => (
tabBarIcon: ({ color }) => (
<MaterialCommunityIcons
name="compass-outline"
color={color}
@@ -81,10 +82,10 @@ const BottomNavigator = () => {
}}
/>
<Tab.Screen
name="More"
name={getString('more')}
component={More}
options={{
tabBarIcon: ({color}) => (
tabBarIcon: ({ color }) => (
<MaterialCommunityIcons
name="dots-horizontal"
color={color}
+26 -27
View File
@@ -1,12 +1,11 @@
import React, {useEffect} from 'react';
import {View} from 'react-native';
import React, { useEffect } from 'react';
import {NavigationContainer} from '@react-navigation/native';
import {createStackNavigator} from '@react-navigation/stack';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import {setBarColor} from '../theme/utils/setBarColor';
import {useTheme} from '../hooks/reduxHooks';
import {useGithubUpdateChecker} from '../hooks/githubUpdateChecker';
import { setBarColor } from '../theme/utils/setBarColor';
import { useTheme } from '../hooks/reduxHooks';
import { useGithubUpdateChecker } from '../hooks/githubUpdateChecker';
import * as SplashScreen from 'expo-splash-screen';
/**
@@ -20,7 +19,7 @@ import MoreStack from './MoreStack';
*/
import Novel from '../screens/novel/NovelScreen';
import Reader from '../screens/reader/ReaderScreen';
import Extension from '../screens/source/SourceScreen';
import SourceScreen from '../screens/source/SourceScreen';
import GlobalSearch from '../screens/browse/globalsearch/GlobalSearch';
import Migration from '../screens/browse/migration/Migration';
import SourceNovels from '../screens/browse/SourceNovels';
@@ -28,7 +27,7 @@ import MigrateNovel from '../screens/browse/migration/MigrationNovels';
import MalTopNovels from '../screens/browse/discover/MalTopNovels';
import NewUpdateDialog from '../components/NewUpdateDialog';
import BrowseSettings from '../screens/browse/SettingsBrowseScreen';
import BrowseSettings from '../screens/browse/BrowseSettings';
const Stack = createStackNavigator();
@@ -42,26 +41,26 @@ const MainNavigator = () => {
}, 500);
}, [theme]);
const {isNewVersion, latestRelease} = useGithubUpdateChecker() || {};
const { isNewVersion, latestRelease } = useGithubUpdateChecker() || {};
return (
<NavigationContainer theme={{colors: {background: theme.colorPrimaryDark}}}>
<View style={{flex: 1, backgroundColor: theme.colorPrimaryDark}}>
{isNewVersion && <NewUpdateDialog newVersion={latestRelease} />}
<Stack.Navigator screenOptions={{headerShown: false}}>
<Stack.Screen name="BottomNavigator" component={BottomNavigator} />
<Stack.Screen name="Novel" component={Novel} />
<Stack.Screen name="Chapter" component={Reader} />
<Stack.Screen name="MoreStack" component={MoreStack} />
<Stack.Screen name="Extension" component={Extension} />
<Stack.Screen name="BrowseMal" component={MalTopNovels} />
<Stack.Screen name="BrowseSettings" component={BrowseSettings} />
<Stack.Screen name="GlobalSearch" component={GlobalSearch} />
<Stack.Screen name="Migration" component={Migration} />
<Stack.Screen name="SourceNovels" component={SourceNovels} />
<Stack.Screen name="MigrateNovel" component={MigrateNovel} />
</Stack.Navigator>
</View>
<NavigationContainer
theme={{ colors: { background: theme.colorPrimaryDark } }}
>
{isNewVersion && <NewUpdateDialog newVersion={latestRelease} />}
<Stack.Navigator screenOptions={{ headerShown: false }}>
<Stack.Screen name="BottomNavigator" component={BottomNavigator} />
<Stack.Screen name="Novel" component={Novel} />
<Stack.Screen name="Chapter" component={Reader} />
<Stack.Screen name="MoreStack" component={MoreStack} />
<Stack.Screen name="SourceScreen" component={SourceScreen} />
<Stack.Screen name="BrowseMal" component={MalTopNovels} />
<Stack.Screen name="BrowseSettings" component={BrowseSettings} />
<Stack.Screen name="GlobalSearch" component={GlobalSearch} />
<Stack.Screen name="Migration" component={Migration} />
<Stack.Screen name="SourceNovels" component={SourceNovels} />
<Stack.Screen name="MigrateNovel" component={MigrateNovel} />
</Stack.Navigator>
</NavigationContainer>
);
};
+2 -2
View File
@@ -1,6 +1,6 @@
import React from 'react';
import {createStackNavigator} from '@react-navigation/stack';
import { createStackNavigator } from '@react-navigation/stack';
// Screens
import About from '../screens/more/About';
@@ -16,7 +16,7 @@ import AppearanceSettings from '../screens/setting/SettingsAppearanceScreen';
const Stack = createStackNavigator();
const stackNavigatorConfig = {headerShown: false};
const stackNavigatorConfig = { headerShown: false };
const SettingsStack = () => (
<Stack.Navigator screenOptions={stackNavigatorConfig}>
+7 -7
View File
@@ -1,8 +1,8 @@
import {CANCEL_DOWNLOAD, PAUSE_DOWNLOADS} from './donwloads.types';
import { CANCEL_DOWNLOAD, PAUSE_DOWNLOADS } from './donwloads.types';
import BackgroundService from 'react-native-background-actions';
import {downloadChapter} from '../../database/queries/ChapterQueries';
import {CHAPTER_DOWNLOADED} from '../novel/novel.types';
import {showToast} from '../../hooks/showToast';
import { downloadChapter } from '../../database/queries/ChapterQueries';
import { CHAPTER_DOWNLOADED } from '../novel/novel.types';
import { showToast } from '../../hooks/showToast';
import * as Notifications from 'expo-notifications';
@@ -11,7 +11,7 @@ export const pauseDownloads = () => async dispatch => {
await BackgroundService.stop();
dispatch({type: PAUSE_DOWNLOADS});
dispatch({ type: PAUSE_DOWNLOADS });
};
export const resumeDownloads = chapters => async dispatch => {
@@ -90,11 +90,11 @@ export const resumeDownloads = chapters => async dispatch => {
await BackgroundService.start(veryIntensiveTask, options);
dispatch({type: PAUSE_DOWNLOADS});
dispatch({ type: PAUSE_DOWNLOADS });
};
export const cancelDownload = () => async dispatch => {
await BackgroundService.stop();
dispatch({type: CANCEL_DOWNLOAD});
dispatch({ type: CANCEL_DOWNLOAD });
};
+3 -3
View File
@@ -1,12 +1,12 @@
import {CHAPTER_DOWNLOADED} from '../novel/novel.types';
import {CANCEL_DOWNLOAD, SET_DOWNLOAD_QUEUE} from './donwloads.types';
import { CHAPTER_DOWNLOADED } from '../novel/novel.types';
import { CANCEL_DOWNLOAD, SET_DOWNLOAD_QUEUE } from './donwloads.types';
const initialState = {
downloadQueue: [],
};
const downloadsReducer = (state = initialState, action) => {
const {type, payload} = action;
const { type, payload } = action;
switch (type) {
case SET_DOWNLOAD_QUEUE:

Some files were not shown because too many files have changed in this diff Show More