Compare commits

...

341 Commits

Author SHA1 Message Date
Rajarshee Chatterjee fd6a907c5f Release v1.1.12 2022-07-30 21:36:45 +05:30
Rajarshee Chatterjee c7561fc3dd fix: New Update Dialog Enhancements 2022-07-30 21:33:26 +05:30
Rajarshee Chatterjee e1401ba39f fix(source): Fix Riwyat Chapter Text 2022-07-30 21:32:20 +05:30
Rajarshee Chatterjee cbb3c7cfa2 Release v1.1.11 2022-07-30 09:53:23 +05:30
Rajarshee Chatterjee 4b515502bf fix(source): Fix ComradeMao (Closes LNReader/lnreader-sources#215) 2022-07-30 08:53:25 +05:30
Rajarshee Chatterjee baba8814f0 fix(source): Fix TravisTranslations Chapter Selector (Closes LNReader/lnreader-sources#339) 2022-07-30 08:52:59 +05:30
Rajarshee Chatterjee 5b01f61c9f feat(source): Add Novelstic (Closes LNReader/lnreader-sources#345) 2022-07-30 08:46:35 +05:30
Rajarshee Chatterjee c02a72018d fix: Remove Text Selectable Option (Closes #332) 2022-07-30 08:09:32 +05:30
Rajarshee Chatterjee 4d446d7c0b fix: Chapter not available when navigating from History/Updates (Closes #336) 2022-07-30 07:59:18 +05:30
Marcin Majewski e580f8f151 fix(wv-reader): Remove unnecesarry console.logs() (#331) 2022-07-21 21:23:26 +05:30
Rajarshee Chatterjee 92f164fc23 feat(source): Add Riwyat (Closes LNReader/lnreader-sources#332) 2022-07-21 20:35:08 +05:30
Rajarshee Chatterjee 44c805b6f2 feat(source): Add Light Novels Brasil (Closes LNReader/lnreader-sources#335) 2022-07-21 20:22:48 +05:30
Rajarshee Chatterjee d3f5619276 feat: Add option to disable Haptic Feedback (Closes #326) 2022-07-21 20:15:34 +05:30
Rajarshee Chatterjee 0261c6373d chore: Add Strings 2022-07-21 20:05:46 +05:30
Rajarshee Chatterjee fd5b9a7c17 chore: Update translations (#329)
* New translations strings.json (Russian)
[ci skip]

* New translations strings.json (Portuguese)
[ci skip]

* New translations strings.json (Portuguese, Brazilian)
[ci skip]
2022-07-21 20:04:23 +05:30
Rajarshee Chatterjee b884600dfb chore: Update translations (#296)
* New translations strings.json (Portuguese)
[ci skip]

* New translations strings.json (Hindi)
[ci skip]

* New translations strings.json (Turkish)
[ci skip]

* New translations strings.json (German)
[ci skip]

* New translations strings.json (Hungarian)
[ci skip]

* New translations strings.json (Portuguese, Brazilian)
[ci skip]

* New translations strings.json (Vietnamese)
[ci skip]

* New translations strings.json (Chinese Traditional)
[ci skip]

* New translations strings.json (Swedish)
[ci skip]

* New translations strings.json (Serbian (Cyrillic))
[ci skip]

* New translations strings.json (Albanian)
[ci skip]

* New translations strings.json (Portuguese)
[ci skip]

* New translations strings.json (Polish)
[ci skip]

* New translations strings.json (Norwegian)
[ci skip]

* New translations strings.json (Dutch)
[ci skip]

* New translations strings.json (Korean)
[ci skip]

* New translations strings.json (Japanese)
[ci skip]

* New translations strings.json (Hebrew)
[ci skip]

* New translations strings.json (Italian)
[ci skip]

* New translations strings.json (Finnish)
[ci skip]

* New translations strings.json (Greek)
[ci skip]

* New translations strings.json (Danish)
[ci skip]

* New translations strings.json (Czech)
[ci skip]

* New translations strings.json (Catalan)
[ci skip]

* New translations strings.json (Afrikaans)
[ci skip]

* New translations strings.json (Spanish)
[ci skip]

* New translations strings.json (French)
[ci skip]

* New translations strings.json (Romanian)
[ci skip]

* New translations strings.json (Arabic)
[ci skip]

* New translations strings.json (Ukrainian)
[ci skip]

* New translations strings.json (Turkish)
[ci skip]

* New translations strings.json (Russian)
[ci skip]

* New translations strings.json (Hindi)
[ci skip]

* New translations strings.json (German)
[ci skip]

* New translations strings.json (Hungarian)
[ci skip]

* New translations strings.json (Portuguese, Brazilian)
[ci skip]

* New translations strings.json (Vietnamese)
[ci skip]

* New translations strings.json (Chinese Traditional)
[ci skip]

* New translations strings.json (Chinese Simplified)
[ci skip]

* New translations strings.json (Swedish)
[ci skip]

* New translations strings.json (Serbian (Cyrillic))
[ci skip]

* New translations strings.json (Albanian)
[ci skip]

* New translations strings.json (Portuguese)
[ci skip]

* New translations strings.json (Polish)
[ci skip]

* New translations strings.json (Norwegian)
[ci skip]

* New translations strings.json (Dutch)
[ci skip]

* New translations strings.json (Korean)
[ci skip]

* New translations strings.json (Japanese)
[ci skip]

* New translations strings.json (Hebrew)
[ci skip]

* New translations strings.json (Italian)
[ci skip]

* New translations strings.json (Finnish)
[ci skip]

* New translations strings.json (Greek)
[ci skip]

* New translations strings.json (Danish)
[ci skip]

* New translations strings.json (Czech)
[ci skip]

* New translations strings.json (Catalan)
[ci skip]

* New translations strings.json (Afrikaans)
[ci skip]

* New translations strings.json (Spanish)
[ci skip]

* New translations strings.json (French)
[ci skip]

* New translations strings.json (Romanian)
[ci skip]

* New translations strings.json (Arabic)
[ci skip]

* New translations strings.json (Ukrainian)
[ci skip]

* New translations strings.json (Turkish)
[ci skip]

* New translations strings.json (Russian)
[ci skip]

* New translations strings.json (Hindi)
[ci skip]

* New translations strings.json (Turkish)
[ci skip]

* New translations strings.json (Arabic)
[ci skip]

* New translations strings.json (Russian)
[ci skip]

* New translations strings.json (Russian)
[ci skip]

* New translations strings.json (Russian)
[ci skip]

* chore: add french translation (#315)

Some small hesitations on how to translate :
    - "Offset" is difficult to translate with this context
    - "Badges" For me, it's "a small piece of metal, plastic, or cloth bearing a design or words, typically worn to identify a person or to indicate membership of an organization or support for a cause." (I know it can be virtual but I'm not sure I have the context of the application but it is a transparent word according to this definition)
    - "grid" translated into "grille" is a bit weird
    - Should I translate "Novel" by its translation ("roman") ?

* New translations strings.json (Ukrainian)
[ci skip]

* New translations strings.json (Portuguese, Brazilian)
[ci skip]

Co-authored-by: Raphael <67842535+Snowad14@users.noreply.github.com>
2022-07-21 19:28:09 +05:30
dependabot[bot] 8355907a76 chore(deps): bump moment from 2.29.2 to 2.29.4 (#327)
Bumps [moment](https://github.com/moment/moment) from 2.29.2 to 2.29.4.
- [Release notes](https://github.com/moment/moment/releases)
- [Changelog](https://github.com/moment/moment/blob/develop/CHANGELOG.md)
- [Commits](https://github.com/moment/moment/compare/2.29.2...2.29.4)

---
updated-dependencies:
- dependency-name: moment
  dependency-type: direct:production
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-07-21 19:27:45 +05:30
Marcin Majewski b391e09e53 feat(img-dl): Downloading the chapter now downloads all img files (#321)
* fix(lint): fixed ChapterQueries.ts linting

* feat(img_download): Download images for offline reading

* fix(dl): imgs in Downloads/LNReader/source/novel/chapter

* fix(dl): deleteChapter now requires novelId/sourceId to remove images
2022-07-20 16:41:38 +05:30
Marcin Majewski a4c27924f8 fix(source): Fix NovelUpdates genres (#318) 2022-07-05 20:57:10 +05:30
Rider21 221c77d2cb feat(source): Add NovelTop1 (#313)
* test

* add NovelTop1

* ok
2022-07-01 22:22:02 +05:30
Rider21 3a0047ee25 feat: Moment Sync (#310)
* moment locale sync

* da
2022-06-26 17:45:52 +05:30
Rajarshee Chatterjee 3afbb5c45d feat: Add Option to Delete Read Chapters 2022-06-19 09:16:09 +05:30
Rajarshee Chatterjee 47102ff65e feat(source): Add More Filters 2022-06-19 08:12:22 +05:30
Rider21 c10eac6cd6 feat(source): Source Updates (#306)
* update

* sort

* removed the special characters
2022-06-19 06:27:34 +05:30
Rajarshee Chatterjee b4c234ef93 feat(source): Add Madara Sources
Closes LNReader/lnreader-sources#247, Closes LNReader/lnreader-sources#271, Closes LNReader/lnreader-sources#273, Closes LNReader/lnreader-sources#284
2022-06-18 13:57:39 +05:30
Rajarshee Chatterjee 3487bbd4cf Minor fixes 2022-06-18 13:37:52 +05:30
Rajarshee Chatterjee bece88663f refactor: Global Search Screen
Closes #302
2022-06-18 12:32:15 +05:30
dependabot[bot] 054124a004 chore(deps): bump jpeg-js from 0.4.3 to 0.4.4 (#301)
Bumps [jpeg-js](https://github.com/eugeneware/jpeg-js) from 0.4.3 to 0.4.4.
- [Release notes](https://github.com/eugeneware/jpeg-js/releases)
- [Commits](https://github.com/eugeneware/jpeg-js/compare/v0.4.3...v0.4.4)

---
updated-dependencies:
- dependency-name: jpeg-js
  dependency-type: indirect
...

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

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-06-17 08:39:26 +05:30
Rider21 d2b62c0e8b feat: Source Updates (#297)
* update

* xm
2022-06-14 06:19:06 +05:30
Rajarshee Chatterjee e98980bdb9 chore: Update translations (#279)
* New translations strings.json (German)

* New translations strings.json (Russian)

* New translations strings.json (Ukrainian)

* New translations strings.json (Arabic)

* New translations strings.json (German)

* New translations strings.json (Hungarian)

* New translations strings.json (Vietnamese)

* New translations strings.json (Chinese Traditional)

* 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 (Korean)

* New translations strings.json (Japanese)

* New translations strings.json (Hebrew)

* New translations strings.json (Italian)

* New translations strings.json (Finnish)

* New translations strings.json (Greek)

* New translations strings.json (Danish)

* New translations strings.json (Czech)

* New translations strings.json (Catalan)

* New translations strings.json (Afrikaans)

* New translations strings.json (Spanish)

* New translations strings.json (French)

* New translations strings.json (Romanian)

* New translations strings.json (Arabic)

* New translations strings.json (Ukrainian)

* New translations strings.json (Turkish)

* New translations strings.json (Russian)

* New translations strings.json (Portuguese, Brazilian)

* New translations strings.json (Turkish)

* New translations strings.json (German)

* New translations strings.json (Hungarian)

* New translations strings.json (Vietnamese)

* New translations strings.json (Chinese Traditional)

* 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 (Korean)

* New translations strings.json (Japanese)

* New translations strings.json (Hebrew)

* New translations strings.json (Italian)

* New translations strings.json (Finnish)

* New translations strings.json (Greek)

* New translations strings.json (Danish)

* New translations strings.json (Czech)

* New translations strings.json (Catalan)

* New translations strings.json (Afrikaans)

* New translations strings.json (Spanish)

* New translations strings.json (French)

* New translations strings.json (Romanian)

* New translations strings.json (Arabic)

* New translations strings.json (Ukrainian)

* New translations strings.json (Turkish)

* New translations strings.json (Russian)

* New translations strings.json (Portuguese, Brazilian)
2022-06-13 09:57:21 +05:30
Rajarshee Chatterjee 62a847acb2 build: Skip CI in translations 2022-06-13 09:38:57 +05:30
Rajarshee Chatterjee 13f394c76d build: Only run CI on main branch 2022-06-13 09:11:30 +05:30
Marcin Majewski 71f6d863c0 fix(reader-settings): Fix reader settings crash (#292) 2022-06-13 08:21:25 +05:30
Rajarshee Chatterjee d5a8ca2b47 feat(source): Add Liebe Schnee Hiver Novel (Closes LNReader/lnreader-sources#232) 2022-06-12 21:15:25 +05:30
Rajarshee Chatterjee c969916cd5 feat(source): Initial Source Filter Support (Closes #285) 2022-06-12 20:27:57 +05:30
Rajarshee Chatterjee e68c72ce02 fix(source):Fix ReadLightNovel's Novel Info 2022-06-12 13:53:15 +05:30
Rajarshee Chatterjee 5f83104171 feat: Add Ability to View Latest Novels 2022-06-12 13:50:16 +05:30
Rajarshee Chatterjee 7837d7cdaf feat: Add Option to Remove Extra Paragraph Spacing (Closes #248) 2022-06-12 13:01:10 +05:30
Rajarshee Chatterjee b243d764ce feat: Add New Themes (Closes #254) 2022-06-12 12:05:39 +05:30
Rajarshee Chatterjee 56c7716595 fix: Cross Icon Overlapping Search Text (Closes #273) 2022-06-12 11:23:34 +05:30
Rajarshee Chatterjee d44abc9039 feat: Add Last Read Sorting (Closes #255) 2022-06-12 09:20:26 +05:30
Rajarshee Chatterjee 6f30bfeeab fix: Add progressViewOffset (Closes #270) 2022-06-12 08:58:50 +05:30
Rajarshee Chatterjee 3d90e04415 fix: Global Search (Closes #256) 2022-06-12 08:51:49 +05:30
Rajarshee Chatterjee 23e27e8d40 fix(source): HakoLightNovel (Closes LNReader/lnreader-sources#239) 2022-06-12 08:48:14 +05:30
Marcin Majewski d0f7e25956 feat(volume-scroll): Added option to scroll WebView using volume buttons (#290)
* feat(volume-scroll): Added webview volume scroll

Scrolling with volume buttons in WebView

* feat(volume-settings): Add volume button settings
2022-06-12 07:47:39 +05:30
Rajarshee Chatterjee 9b297620fe refactor: Browse Source Screen 2022-06-06 07:58:07 +05:30
Marcin Majewski df851bf9d1 Webview improvements (#283)
* feat(webview): Added few webview improvements

Added buttons from TextReader on the bottom of the chapter (Closes #250)
Added webview seekbar (Closes #218)
Switched the handling of the navigation bar visibility to WebView. This might solve the issue from #229 because WebView is a bit more strict in what it interprets as "click/tap"
Migrating WebViewReader.js to TypeScript

* fix(webview): Removed unnecesarry console.logs

* feat(wv-autoscroll): WebView AutoScroll

* feat(wv-swipes): WebView new swipe detection

Added webview internal swipe detection instead of the react-native one
Hopefully making the detection a bit better (effects unknown yet)
This option can be turned on/off in the settings

* fix(wv-scrollbar): Added option to switch vertical

You can now switch vertical/horizontal scrollbar setting in reader screen options
2022-06-05 07:24:36 +05:30
Rajarshee Chatterjee 5fc411e14c Merge branch 'main' of https://github.com/LNReader/lnreader into main 2022-06-04 09:04:48 +05:30
Rajarshee Chatterjee 0f221b2823 fix: Build 2022-06-04 09:04:40 +05:30
Marcin Majewski 9e50159589 fix(clipboard): Fixed clipboard dependency (#282)
Closes #257
2022-06-04 08:59:31 +05:30
Rajarshee Chatterjee 58752a5589 fix: ESLint 2022-06-04 08:57:06 +05:30
Rajarshee Chatterjee c1a76458a8 fix: Novel Updates (Closes LNReader/lnreader-sources#227) 2022-06-01 05:35:34 +05:30
Rajarshee Chatterjee 5b3a6ceff4 fix: ReadLightNovel Crash 2022-06-01 05:19:32 +05:30
Rajarshee Chatterjee c4a14784f0 fix(source): Remove RewayatAr 2022-06-01 05:08:14 +05:30
Rajarshee Chatterjee d64e2e7fdb chore: Update Translations 2022-05-31 21:11:24 +05:30
Marcin Majewski 5d8463509c fix(tts): Android11 Manifest TTS Support (#277)
Add to AndroidManifest.xml a clause about querying TTS to remove `Unresolved Promise: No TTS Engine Installed` error in Android 11 Phones.
2022-05-28 09:13:46 +05:30
Marcin Majewski 5b4e32e184 fix(syosetu): Change cover to unavailable (#275)
fix(syosetu): Set cover to not available
2022-05-28 08:05:58 +05:30
Marcin Majewski a7fb3962f1 fix(typo): spelling mistake causing a crash (#276) 2022-05-28 08:05:11 +05:30
Marcin Majewski d333381959 feat(webview-scroll): webview scrolls to saved (#274)
Now webview has webViewScroll which after any change scrolls webView to given percentage

Co-authored-by: unknown <SkillGG>
2022-05-27 20:43:33 +05:30
Rajarshee Chatterjee d599e6669f chore: Update translations (#251)
* New translations strings.json (Turkish)

* 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 (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 (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 (Portuguese, Brazilian)

* New translations strings.json (Turkish)

* 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 (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 (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 (Portuguese, Brazilian)

* New translations strings.json (Turkish)

* New translations strings.json (German)

* New translations strings.json (German)

* New translations strings.json (Italian)

* New translations strings.json (Russian)

* New translations strings.json (Russian)
2022-05-27 05:21:49 +05:30
Rider21 5436bf8aaf fix: Source Fixes (#272)
* updated link

* small correction

* Bypass

* Update renovels.js
2022-05-27 05:14:17 +05:30
Rajarshee Chatterjee 9f6f9e71ca fix: Remove Circular Dependencies 2022-05-09 07:06:48 +05:30
Rajarshee Chatterjee 6caba807df fix:(source) 1stKissNovel: No Chapters 2022-05-08 12:29:37 +05:30
Rajarshee Chatterjee 9dabf1a60e Minor fixes 2022-05-08 12:26:29 +05:30
Rajarshee Chatterjee 7716ab75de refactor: Reader Settings Screen 2022-05-08 11:47:22 +05:30
Rajarshee Chatterjee 94b2dd51cb fix(source): ReaperScans (Closes LNReader/lnreader-sources#224) 2022-05-08 07:51:59 +05:30
Rajarshee Chatterjee 010d49daea fix: Update Icon Links 2022-05-08 07:27:31 +05:30
Rajarshee Chatterjee 6eb733eb66 feat: Ability to Save Custom Themes (Closes #145) 2022-05-07 18:33:40 +05:30
Rajarshee Chatterjee 33a33842c6 feat: Add WebView Button in Reader 2022-05-07 17:21:58 +05:30
Rajarshee Chatterjee 1bfd24a1b4 fix: History Screen Fixes 2022-05-07 16:53:22 +05:30
Rajarshee Chatterjee 7e1bf4dd3f feat: Add Path Aliases 2022-05-07 16:50:53 +05:30
Rajarshee Chatterjee 955e552597 fix: Make MyAnimeListCard Scrollable 2022-05-07 16:50:23 +05:30
Rajarshee Chatterjee 8b586754d2 fix: MyAnimeList Search 2022-05-07 15:16:40 +05:30
Rajarshee Chatterjee b921f9b764 refactor: Remove Ununsed Files and Dependencies 2022-05-07 14:44:42 +05:30
Rajarshee Chatterjee 4d30546b9a build: Remove Upload Artifact 2022-05-07 14:44:32 +05:30
Rajarshee Chatterjee 58eac42b14 refactor: Refactor Reader Bottomsheet (#249)
* refactor: Refactor Reader Bottomsheet

* refactor: Remove Unused Files
2022-05-07 13:36:36 +05:30
Rajarshee Chatterjee 872ec15ca5 build: Fix Artifact Name 2022-05-06 21:23:28 +05:30
Rajarshee Chatterjee e013b3cc55 build: Fix Artifact Name 2022-05-06 20:58:20 +05:30
Rajarshee Chatterjee 8e58ce2eb1 feat(source): Add SonicMTL and MTLNovel.Club
Closes LNReader/lnreader-sources#221, Closes LNReader/lnreader-sources#223
2022-05-06 20:31:44 +05:30
Rajarshee Chatterjee a9e2d872f2 build: Add Upload Artifact 2022-05-06 20:23:01 +05:30
Rajarshee Chatterjee ccdb150d6a fix: Add Strings to Translations 2022-05-06 20:12:23 +05:30
Rajarshee Chatterjee 4e1d8533e3 Release v1.1.10 2022-05-04 07:21:14 +05:30
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
Rajarshee Chatterjee f732162688 Minor fixes
Lint / ESLint (push) Has been cancelled
2021-12-29 10:53:59 +05:30
Rajarshee Chatterjee 81a5ea0aa7 Add SakuraNovel (Closes LNReader/lnreader-sources#131) 2021-12-29 10:52:40 +05:30
Rajarshee Chatterjee 8be78f7e92 Fix ReadLightNovel 2021-12-29 10:47:32 +05:30
Rajarshee Chatterjee bb144936b5 Fix initial custom css 2021-12-26 15:13:02 +05:30
Rajarshee Chatterjee 724089d940 TravisTranslations: Fix chapter content (Closes LNReader/lnreader-sources#139) 2021-12-26 11:14:31 +05:30
Rajarshee Chatterjee c22440195d MyLoveNovel: Fix description (Closes LNReader/lnreader-sources#137) 2021-12-26 10:48:08 +05:30
Rajarshee Chatterjee dbb20e666f Add ArMTL (Closes LNReader/lnreader-sources#144) 2021-12-26 10:00:28 +05:30
Rajarshee Chatterjee 0a7f2e8948 Add Novelmt.com and Ltnovel.com 2021-12-26 09:37:29 +05:30
Rajarshee Chatterjee a83cdcc66e Add LatestNovel (Closes LNReader/lnreader-sources#136) 2021-12-21 18:13:12 +05:30
Rajarshee Chatterjee ceca91fcab Add option to clear cover cache 2021-12-21 18:03:00 +05:30
Pushpender Singh ecc8c35bfb Keep screen awake in reader (#128)
Closes #121
2021-12-21 16:37:18 +05:30
Pushpender Singh 3522b7e06d Utils for version (#129)
* ESLint Error Fixed

* Utils for version

* Remove useKeepAwake

* Update Main.js

Co-authored-by: Rajarshee Chatterjee <rajarshee.adm@gmail.com>
2021-12-21 16:26:20 +05:30
Pushpender Singh ab4fc42f69 Fix react-native-fast-image crash (#126) 2021-12-20 21:47:57 +05:30
Pushpender Singh 47676a0de5 Fix java.lang.OutOfMemoryError (#125) 2021-12-20 21:21:35 +05:30
Rajarshee Chatterjee 7854b20b1f Fix NovelPub 2021-12-13 11:01:05 +05:30
Rajarshee Chatterjee a825db2965 Add NovelFull.me (Closes LNReader/lnreader-sources#129) 2021-12-13 10:45:56 +05:30
Rajarshee Chatterjee 57cc5a7f42 Rename "Use WebView" to "Render Html" 2021-12-13 10:29:54 +05:30
Rajarshee Chatterjee 15cd0b7b6b Add HizoManga (Closes LNReader/lnreader-sources#126) 2021-12-13 10:20:19 +05:30
Rajarshee Chatterjee ca1303f422 Reorganize reader settings (Closes #110) 2021-11-21 11:44:04 +05:30
Rajarshee Chatterjee e79563f2d0 Fix custom covers 2021-11-17 14:46:18 +05:30
Rajarshee Chatterjee ffe5b04360 Minor fixes 2021-11-17 13:59:13 +05:30
Rajarshee Chatterjee 1c59964a67 Downgrade eslint 2021-11-17 13:29:21 +05:30
Rajarshee Chatterjee 5cd08a0d29 Add more download options (Closes #103) 2021-11-17 12:52:36 +05:30
Rajarshee Chatterjee 4bc61f425b Minor fixes 2021-11-17 12:05:46 +05:30
Rajarshee Chatterjee 6d631b8921 Update dependencies 2021-11-17 12:03:40 +05:30
Rajarshee Chatterjee 70833ef37f Fix NovelForest 2021-11-16 11:49:01 +05:30
Rajarshee Chatterjee cc4c242224 Add clear updates dialog 2021-11-16 11:48:37 +05:30
Rajarshee Chatterjee 35731ec38f Add option to update novel metadata (Closes #105) 2021-11-16 11:28:04 +05:30
Rajarshee Chatterjee 3735e8ca1b Fix OnlyMTL 2021-11-16 10:27:33 +05:30
Rajarshee Chatterjee e0746ff092 Add BestLightNovel (Closes LNReader/lnreader-sources#121) 2021-11-14 11:15:49 +05:30
Rajarshee Chatterjee d0c478b89f Fix RainOfSnow 2021-11-14 11:03:33 +05:30
Rajarshee Chatterjee 3f29aee4e6 Fix RanobeLib (Closes LNReader/lnreader-sources#110) 2021-11-14 10:37:04 +05:30
Rajarshee Chatterjee 76df79d9ca Add NovelPub (Closes LNReader/lnreader-sources#119) 2021-11-14 10:30:01 +05:30
Rajarshee Chatterjee 3f874e6550 Fix default chapter sort order 2021-11-09 17:37:16 +05:30
Rajarshee Chatterjee 84ec20ffb3 Enable react-freeze 2021-11-09 17:36:36 +05:30
Rajarshee Chatterjee 65027ee41e Add RanobeLib (Closes LNReader/lnreader-sources#108) 2021-11-02 10:42:44 +05:30
Rajarshee Chatterjee 3425892a16 Release v1.1.7
Lint / ESLint (push) Has been cancelled
2021-11-01 13:43:21 +05:30
Rajarshee Chatterjee 97890c1596 Add NovelForest (Closes LNReader/lnreader-sources#102) 2021-11-01 08:41:28 +05:30
Rajarshee Chatterjee c97e1b8e23 Fix VipNovel search 2021-11-01 07:43:20 +05:30
Rajarshee Chatterjee c44537a1c0 Fix default sort order 2021-11-01 07:43:10 +05:30
Rajarshee Chatterjee fd8fb367bd Allow setting default sort order for chapters (Closes #100) 2021-10-31 15:17:16 +05:30
Rajarshee Chatterjee a3d8aa5bcd Fix RanobeHub images (Closes LNReader/lnreader-sources#107) 2021-10-31 14:47:18 +05:30
Rajarshee Chatterjee e7a1f8210f Fix Madara sources 2021-10-30 12:23:53 +05:30
Rajarshee Chatterjee 9423054b10 Add OnlyMTL 2021-10-30 11:07:41 +05:30
Rajarshee Chatterjee d11348d9a3 Add parallel searching in global search and migration 2021-10-30 09:59:25 +05:30
Rajarshee Chatterjee 8ec90ae8f7 Fix NitroScans novelName 2021-10-29 16:01:14 +05:30
Rajarshee Chatterjee 5fde669106 Add NitroScans 2021-10-29 15:58:12 +05:30
Rajarshee Chatterjee c6cdf1da0e Add MTLCorner (Closes LNReader/lnreader-sources#63) 2021-10-29 14:52:39 +05:30
Rajarshee Chatterjee 8cb0a5eb73 Fix RanobeHub search (Closes LNReader/lnreader-sources#101) 2021-10-29 13:57:59 +05:30
Rajarshee Chatterjee 09e15607d5 Fix useEffect cleanup in global search and migration 2021-10-29 13:57:05 +05:30
Rajarshee Chatterjee a9d399ea6d Fix crash when genre is an empty string 2021-10-29 13:54:48 +05:30
Rajarshee Chatterjee b9eddc2cc2 Fix Yushubo (Closes LNReader/lnreader-sources#100) 2021-10-29 13:47:46 +05:30
Rajarshee Chatterjee bb68b2f407 Fix crash when chapter release date is empty string 2021-10-28 17:53:52 +05:30
Rajarshee Chatterjee 5e0d19a9c9 Fix crash when chapter release date is empty string 2021-10-28 17:26:28 +05:30
Rajarshee Chatterjee 0aa78e56c8 Fix duplicate entries in library 2021-10-28 17:23:51 +05:30
Rajarshee Chatterjee 078e311995 Fix RewayatAr (Closes LNReader/lnreader-sources#96) 2021-10-28 17:19:52 +05:30
Rajarshee Chatterjee 9a22eba72c Fix next page not loading in browse 2021-10-28 08:34:14 +05:30
Rajarshee Chatterjee 2b0032a4e4 Fix ArNovel (Closes LNReader/lnreader-sources#95) 2021-10-28 08:19:49 +05:30
Rajarshee Chatterjee 708225c855 Add Chireads (Closes LNReader/lnreader-sources#98) 2021-10-28 07:33:03 +05:30
Rajarshee Chatterjee e514876170 Release v1.1.6
Lint / ESLint (push) Has been cancelled
2021-10-27 11:08:41 +05:30
Rajarshee Chatterjee 911d25bb84 Minor fixes 2021-10-27 11:08:29 +05:30
Rajarshee Chatterjee b6d2e43d3d Add bottom inset padding 2021-10-27 07:34:47 +05:30
Rajarshee Chatterjee 7375eb7ad0 Fix splitscreen view crash (Closes #77) 2021-10-27 06:48:46 +05:30
Rajarshee Chatterjee 627592d477 Add IndoWebNovel and Fix MeioNovel
Closes LNReader/lnreader-sources#93
LNReader/lnreader-sources#88
2021-10-27 06:02:03 +05:30
Rajarshee Chatterjee dbe2d13b80 fix(source): Yushubo
Closes LNReader/lnreader-sources#92
2021-10-26 22:04:57 +05:30
Rajarshee Chatterjee 97e172a2aa Add NovelRingan
LNReader/lnreader-sources#88
2021-10-26 19:59:55 +05:30
Rajarshee Chatterjee ba083d1afe Add NovelSpace
Closes LNReader/lnreader-sources#91
2021-10-26 18:48:20 +05:30
Rajarshee Chatterjee 9e614c6753 Fix search bar icon spacing 2021-10-26 18:04:07 +05:30
Rajarshee Chatterjee 5054e60195 Add Cleanup function 2021-10-26 18:03:17 +05:30
Rajarshee Chatterjee 2aa36393a6 Add MoreNovel
LNReader/lnreader-sources#88
2021-10-26 17:45:03 +05:30
Rajarshee Chatterjee c77b166fec Fix RainOfSnow language
Closes LNReader/lnreader-sources#87
2021-10-26 08:43:35 +05:30
Rajarshee Chatterjee 73276bd05f fix(source): LunarLetters 2021-10-25 09:16:32 +05:30
Rajarshee Chatterjee 7f47661468 Add MtlReader and MyLoveNovel
Closes LNReader/lnreader-sources#72 & LNReader/lnreader-sources#82
2021-10-25 08:35:12 +05:30
Rajarshee Chatterjee ec63de8115 Fix Arabic sources
Closes LNReader/lnreader-sources#86
2021-10-24 09:13:12 +05:30
Rajarshee Chatterjee ee277bfbf1 Fix fullscreen mode crash 2021-10-21 15:44:40 +05:30
Rajarshee Chatterjee e330eb122f fix(source): Madara 2021-10-20 20:21:52 +05:30
Rajarshee Chatterjee d84935a6a6 Fix UpdateScreen search crash 2021-10-20 20:21:34 +05:30
Rajarshee Chatterjee 6d852dc138 Fix workflow 2021-10-19 16:15:13 +05:30
Bread Genie 4bd5e9b4f5 ci: add eslint check (#86)
* ci: add eslint check

* ci: add lint name
2021-10-19 16:07:14 +05:30
Rajarshee Chatterjee 4fc480d2e9 Ignore/remove eslint errors 2021-10-19 16:05:29 +05:30
Rajarshee Chatterjee 3760b5ae4e Revert BottomNavigator and IconButton changes 2021-10-19 09:41:44 +05:30
Rajarshee Chatterjee f286f9337e Merge branch 'main' of https://github.com/LNReader/lnreader into main 2021-10-18 20:50:14 +05:30
Rajarshee Chatterjee bca620ea37 Fix updates tab crash 2021-10-18 20:50:03 +05:30
Rajarshee Chatterjee eb9516adda Revert changes in SourceScreen 2021-10-18 20:49:41 +05:30
Rajarshee Chatterjee 8dbec40b9d fix(source): Madara new chapter endpoint 2021-10-18 20:41:18 +05:30
Bread Genie bdac5161d2 docs: add contributing guide (#89) 2021-10-18 19:47:53 +05:30
Rajarshee Chatterjee 6eca36c295 Create NativeModule for navigation bar color 2021-10-18 09:42:14 +05:30
Rajarshee Chatterjee 5cccc7c556 Fix chapterName color in UpdateItem 2021-10-18 08:36:21 +05:30
Rajarshee Chatterjee 5350bf92de Fix UpdateItem chapterName color 2021-10-17 11:07:06 +05:30
Rajarshee Chatterjee 662610b227 Update light themes 2021-10-17 10:59:53 +05:30
Rajarshee Chatterjee 8d53125184 fix(source): NovelHall
Closes LNReader/lnreader-sources#83
2021-10-17 10:10:10 +05:30
Rajarshee Chatterjee a61d08f443 Fix SourceScreen 2021-10-17 10:04:35 +05:30
Rajarshee Chatterjee d8cd2b184a Revert readme icon 2021-10-16 13:21:11 +05:30
Marcin Majewski 33ceb60850 eslint quick-fix (#87)
* fix(github-check): Catch github connection errors

* fix(eslint): fixed eslint warnings and errors

Fixed every eslint warning and error that wasn't one of the following:
- react-hooks/exhaustive-deps
- no-console

Added .vscode to .gitignore
Added no-console:warn to eslintrc

* fix(gradlew): set file to runnable (chmod 755)

* remove .vscode
2021-10-15 18:42:40 +05:30
Rajarshee Chatterjee ef1a9d1f35 Horizontal seekbar 2021-10-13 10:50:46 +05:30
Rajarshee Chatterjee dabd0c5401 Refactor library 2021-10-13 10:50:36 +05:30
Rajarshee Chatterjee 3ae029c77b Rename files and run prettier 2021-10-13 06:48:43 +05:30
Rajarshee Chatterjee efe96d45ea Add delete downloaded chapters snackbar (Closes #72) 2021-10-11 11:25:53 +05:30
Rajarshee Chatterjee 06ca51a841 Fix updates search crash 2021-10-10 21:05:46 +05:30
Rajarshee Chatterjee b947ec2665 Update splash screen and delete old assets 2021-10-10 21:05:21 +05:30
Rajarshee Chatterjee c262c1e1de Add option to only show pinned sources 2021-10-10 10:45:57 +05:30
Rajarshee Chatterjee 19d089fa7a Use SectionList for updates and history 2021-10-10 10:18:23 +05:30
Rajarshee Chatterjee 9447d10c79 fix(source): Madara 2021-10-09 17:38:52 +05:30
Rajarshee Chatterjee d2e2314a3e fix(source): Madara 2021-10-09 11:38:11 +05:30
Rajarshee Chatterjee 9fe74ad137 Minor fixes 2021-10-09 11:37:56 +05:30
Rajarshee Chatterjee 5877448f9d fix(source): WoopRead 2021-10-05 14:54:16 +05:30
Rajarshee Chatterjee fb5d5cf186 Fix duplicate chapters in download queue 2021-10-05 09:20:52 +05:30
Rajarshee Chatterjee 8faac73558 Minor fixes 2021-10-05 09:02:21 +05:30
Rajarshee Chatterjee 3f9c662c68 Refactor Checkbox and RadioButton 2021-10-04 13:26:49 +05:30
Rajarshee Chatterjee ecce374abe Create CODE_OF_CONDUCT.md 2021-10-02 16:45:49 +05:30
Rajarshee Chatterjee 48a67402ff Clear git cache 2021-10-02 15:27:14 +05:30
Rajarshee Chatterjee 7c4da7f761 Refactor (#82) 2021-10-02 15:21:56 +05:30
Rajarshee Chatterjee 3daa11f642 feat(source): Add MtlNovel (Id)
LNReader/lnreader-sources#74
2021-10-02 14:26:37 +05:30
Rajarshee Chatterjee f26292f73a Fix workflow 2021-09-30 09:28:30 +05:30
Rajarshee Chatterjee 49160b7d98 Add ESLint and Prettier 2021-09-30 08:06:43 +05:30
Rajarshee Chatterjee e40af59230 Release v1.1.5 2021-09-24 17:11:38 +05:30
Rajarshee Chatterjee 8084cdbffb feat(source): Add ArthurScans
LNReader/lnreader-sources#71
2021-09-23 11:31:45 +05:30
Rajarshee Chatterjee dc9529c322 feat(source): Add ReaperScans Br
LNReader/lnreader-sources#71
2021-09-23 10:40:26 +05:30
Rajarshee Chatterjee 91168a335b Minor fixes 2021-09-23 10:39:08 +05:30
Rajarshee Chatterjee a6b3eb9350 Add text to speech play/pause 2021-09-21 09:51:01 +05:30
Rajarshee Chatterjee 0ce4e6b985 Increase bottom padding 2021-09-21 09:49:47 +05:30
Rajarshee Chatterjee 6684848a42 Fix FAB text color 2021-09-21 08:56:03 +05:30
Rajarshee Chatterjee 87d12cabb3 fix(source): RanobeHub 2021-09-21 08:51:40 +05:30
Rajarshee Chatterjee e01fc21a49 feat(source): Add Rain Of Snow
Closes LNReader/lnreader-sources#60
2021-09-15 10:09:09 +05:30
Rajarshee Chatterjee f0cfd9066b feat(source): Add Novel De Glace
Closes LNReader/lnreader-sources#57
2021-09-15 09:41:58 +05:30
Rajarshee Chatterjee c2cc6ea96b Option to clear updates tab 2021-09-15 09:26:24 +05:30
Rajarshee Chatterjee eeeea6b4e0 feat(source): Add Travis Translations
Closes (lnreader-sources#59)
2021-09-13 16:48:10 +05:30
Rajarshee Chatterjee ddce153b53 fix(source): ReaperScans 2021-09-13 16:01:11 +05:30
Rajarshee Chatterjee 38e45ca097 Downloaded only mode 2021-09-13 15:36:05 +05:30
Rajarshee Chatterjee a6a60607a7 Minor fixes 2021-09-12 08:46:58 +05:30
Rajarshee Chatterjee d3e772f0dd Add option to remove backdrop 2021-09-12 08:46:35 +05:30
Rajarshee Chatterjee 095d46681f fix(source): TuNovelaLigera 2021-09-12 08:46:06 +05:30
Rajarshee Chatterjee 3e4e967197 Enlarge cover 2021-09-11 21:42:54 +05:30
Rajarshee Chatterjee c5b4e7b902 Option to use fab instead of button 2021-09-11 10:13:41 +05:30
Rajarshee Chatterjee e9421a5fd0 fix: WPMangaStream (Closes #75) 2021-09-11 08:44:18 +05:30
Rajarshee Chatterjee afef0d63ce Edit status in edit info modal 2021-09-11 08:40:32 +05:30
Rajarshee Chatterjee e68a5c5f82 Minor fixes 2021-09-10 10:04:07 +05:30
Rajarshee Chatterjee ff1635b817 fix(source): Fix ReadLightNovel
Closes LNReader/LNReader-sources#65
2021-09-10 09:35:25 +05:30
Rajarshee Chatterjee f1ca221b91 Add option to only update ongoing novels (Closes #73) 2021-09-10 09:21:13 +05:30
Rajarshee Chatterjee 5a1106fc68 Add Text to Speech (Closes #22) 2021-08-31 10:01:23 +05:30
Rajarshee Chatterjee 3b49a1e516 Minor fixes 2021-08-31 08:03:33 +05:30
Rajarshee Chatterjee 85acbc8c4a Appearance settings 2021-08-29 11:02:24 +05:30
Rajarshee Chatterjee 66e0be5e84 Add option to turn off fullscreen (Closes #70) 2021-08-28 07:27:05 +05:30
Rajarshee Chatterjee 2234006272 Fix fullscreen not working on changing chapters 2021-08-28 07:00:48 +05:30
Rajarshee Chatterjee eca00ddcfa Experimental auto scroll (Closes #63) 2021-08-27 14:28:15 +05:30
Rajarshee Chatterjee 2328e55105 Show time and battery level (Closes #65) 2021-08-27 11:43:17 +05:30
Rajarshee Chatterjee b8a25b9172 Multiple select in chapters (Closes #69) 2021-08-26 21:50:05 +05:30
Rajarshee Chatterjee 9af8d55985 Add option to download unread chapters 2021-08-26 20:18:07 +05:30
Rajarshee Chatterjee 4777f453a4 Refactor themes 2021-08-25 11:15:40 +05:30
Rajarshee Chatterjee 4a0195762d Cross fade 2021-08-25 08:52:51 +05:30
Rajarshee Chatterjee e8785c2905 Minor UI adjustments 2021-08-24 19:26:11 +05:30
Rajarshee Chatterjee 1302cbde16 Draw content under navbar 2021-08-24 16:09:09 +05:30
Rajarshee Chatterjee 43d0adced8 feat(source): Add Mystical Series
Closes LNReader/LNReader-sources#61
2021-08-24 11:16:19 +05:30
Rajarshee Chatterjee 0c8b30cd72 feat(source): Add WuxiaWorld.Site
Closes LNReader/LNReader-sources#58
2021-08-24 11:08:58 +05:30
Rajarshee Chatterjee cc86fc7da4 feat(source): Add EpikNovel
Closes LNReader/LNReader-sources#55
2021-08-24 10:56:16 +05:30
467 changed files with 72100 additions and 42826 deletions
+3
View File
@@ -0,0 +1,3 @@
# Windows files
[*.bat]
end_of_line = crlf
+19
View File
@@ -0,0 +1,19 @@
module.exports = {
root: true,
extends: '@react-native-community',
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',
},
},
],
};
+3
View File
@@ -0,0 +1,3 @@
# Windows files should use crlf line endings
# https://help.github.com/articles/dealing-with-line-endings/
*.bat text eol=crlf
Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.0 KiB

After

Width:  |  Height:  |  Size: 48 KiB

+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 --legacy-peer-deps
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
+19
View File
@@ -0,0 +1,19 @@
name: CI
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
build:
name: ESLint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install modules
run: npm i --legacy-peer-deps
- name: Run ESLint
run: npm run lint
+4 -1
View File
@@ -70,4 +70,7 @@ buck-out/
.expo/
web-build/
# @end expo-cli
# @end expo-cli
.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
+1
View File
@@ -0,0 +1 @@
**/node_modules
+10
View File
@@ -0,0 +1,10 @@
module.exports = {
tabWidth: 2,
bracketSpacing: true,
useTabs: false,
bracketSameLine: false,
singleQuote: true,
trailingComma: 'all',
arrowParens: 'avoid',
quoteProps: 'preserve',
};
+30 -34
View File
@@ -1,43 +1,39 @@
import React, { useEffect } from "react";
import { StatusBar } from "react-native";
import { enableFreeze } from 'react-native-screens';
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 * as SplashScreen from "expo-splash-screen";
enableFreeze(true);
import Main from "./src/navigators/Main";
import React, { useEffect } from 'react';
import { StatusBar } from 'react-native';
import { createDB } from "./src/database/DBHelper";
import { fonts } from "./src/theme/fonts";
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/db';
import { SafeAreaProvider } from 'react-native-safe-area-context';
const App = () => {
const [loaded] = useFonts(fonts);
useEffect(() => {
SplashScreen.preventAutoHideAsync();
createDB();
}, []);
useEffect(() => {
SplashScreen.preventAutoHideAsync();
createDB();
}, []);
if (!loaded) {
return null;
}
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<PaperProvider>
<StatusBar
translucent={true}
backgroundColor="transparent"
/>
<Main />
</PaperProvider>
</PersistGate>
</Provider>
);
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<SafeAreaProvider>
<PaperProvider>
<StatusBar translucent={true} backgroundColor="transparent" />
<Main />
</PaperProvider>
</SafeAreaProvider>
</PersistGate>
</Provider>
);
};
export default App;
+128
View File
@@ -0,0 +1,128 @@
# Contributor Covenant Code of Conduct
## Our Pledge
We as members, contributors, and leaders pledge to make participation in our
community a harassment-free experience for everyone, regardless of age, body
size, visible or invisible disability, ethnicity, sex characteristics, gender
identity and expression, level of experience, education, socio-economic status,
nationality, personal appearance, race, religion, or sexual identity
and orientation.
We pledge to act and interact in ways that contribute to an open, welcoming,
diverse, inclusive, and healthy community.
## Our Standards
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,
and learning from the experience
- 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
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
address, without their explicit permission
- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
Community leaders are responsible for clarifying and enforcing our standards of
acceptable behavior and will take appropriate and fair corrective action in
response to any behavior that they deem inappropriate, threatening, offensive,
or harmful.
Community leaders have the right and responsibility to remove, edit, or reject
comments, commits, code, wiki edits, issues, and other contributions that are
not aligned to this Code of Conduct, and will communicate reasons for moderation
decisions when appropriate.
## Scope
This Code of Conduct applies within all community spaces, and also applies when
an individual is officially representing the community in public spaces.
Examples of representing our community include using an official e-mail address,
posting via an official social media account, or acting as an appointed
representative at an online or offline event.
## Enforcement
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
rajarshee.adm@gmail.com.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the
reporter of any incident.
## Enforcement Guidelines
Community leaders will follow these Community Impact Guidelines in determining
the consequences for any action they deem in violation of this Code of Conduct:
### 1. Correction
**Community Impact**: Use of inappropriate language or other behavior deemed
unprofessional or unwelcome in the community.
**Consequence**: A private, written warning from community leaders, providing
clarity around the nature of the violation and an explanation of why the
behavior was inappropriate. A public apology may be requested.
### 2. Warning
**Community Impact**: A violation through a single incident or series
of actions.
**Consequence**: A warning with consequences for continued behavior. No
interaction with the people involved, including unsolicited interaction with
those enforcing the Code of Conduct, for a specified period of time. This
includes avoiding interactions in community spaces as well as external channels
like social media. Violating these terms may lead to a temporary or
permanent ban.
### 3. Temporary Ban
**Community Impact**: A serious violation of community standards, including
sustained inappropriate behavior.
**Consequence**: A temporary ban from any sort of interaction or public
communication with the community for a specified period of time. No public or
private interaction with the people involved, including unsolicited interaction
with those enforcing the Code of Conduct, is allowed during this period.
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
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
the community.
## Attribution
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
version 2.0, available at
https://www.contributor-covenant.org/version/2/0/code_of_conduct.html.
Community Impact Guidelines were inspired by [Mozilla's code of conduct
enforcement ladder](https://github.com/mozilla/diversity).
[homepage]: https://www.contributor-covenant.org
For answers to common questions about this code of conduct, see the FAQ at
https://www.contributor-covenant.org/faq. Translations are available at
https://www.contributor-covenant.org/translations.
+40
View File
@@ -0,0 +1,40 @@
# Contributing Guide
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:
```bash
# clone your fork to your local machine
git clone https://github.com/<your-account-name>/lnreader.git
# step into local repo
cd lnreader
# install dependencies
npm install
# run metro for development
npm start
```
### Developing on Android
While the packager is running and you have an Android device or emulator connected to your computer, build and launch the Android app.
```
npm run android
```
### Style & Linting
This codebase's linting rules are enforced using [ESLint](http://eslint.org/).
It is recommended that you install an eslint plugin for your editor of choice when working on this
codebase, however you can always check to see if the source code is compliant by running:
```bash
npm run lint
```
+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)
+5 -2
View File
@@ -84,7 +84,6 @@ project.ext.react = [
apply from: '../../node_modules/react-native-unimodules/gradle.groovy'
apply from: "../../node_modules/react-native/react.gradle"
apply from: "../../node_modules/expo-constants/scripts/get-app-config-android.gradle"
apply from: "../../node_modules/expo-updates/scripts/create-manifest-android.gradle"
/**
* Set this to true to create two separate APKs instead of one:
@@ -136,7 +135,7 @@ android {
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.1.4"
versionName "1.1.12"
}
splits {
abi {
@@ -188,6 +187,10 @@ dependencies {
//noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:+" // From node_modules
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
implementation 'com.github.bumptech.glide:glide:4.12.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
debugImplementation("com.facebook.flipper:flipper:${FLIPPER_VERSION}") {
exclude group:'com.facebook.fbjni'
}
+10
View File
@@ -8,3 +8,13 @@
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
-keep class com.facebook.hermes.unicode.** { *; }
-keep class com.facebook.jni.** { *; }
-keep public class com.dylanvann.fastimage.* {*;}
-keep public class com.dylanvann.fastimage.** {*;}
-keep public class * implements com.bumptech.glide.module.GlideModule
-keep public class * extends com.bumptech.glide.module.AppGlideModule
-keep public enum com.bumptech.glide.load.ImageHeaderParser$** {
**[] $VALUES;
public *;
}
+77 -38
View File
@@ -1,38 +1,77 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.rajarsheechatterjee.LNReader">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
<application android:name=".MainApplication" android:label="@string/app_name" android:icon="@mipmap/ic_launcher" android:roundIcon="@mipmap/ic_launcher_round" android:allowBackup="true" android:theme="@style/AppTheme" android:usesCleartextTraffic="true">
<meta-data android:name="expo.modules.updates.EXPO_UPDATE_URL" android:value="https://exp.host/@rajarsheechatterjee/LNReader"/>
<meta-data android:name="expo.modules.updates.EXPO_SDK_VERSION" android:value="41.0.0"/>
<meta-data android:name="expo.modules.updates.ENABLED" android:value="false"/>
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_CHECK_ON_LAUNCH" android:value="ALWAYS"/>
<meta-data android:name="expo.modules.updates.EXPO_UPDATES_LAUNCH_WAIT_MS" android:value="0"/>
<meta-data android:name="expo.modules.notifications.default_notification_icon" android:resource="@drawable/notification_icon"/>
<meta-data android:name="expo.modules.notifications.default_notification_color" android:resource="@color/notification_icon_color"/>
<activity android:name=".MainActivity" android:label="@string/app_name" android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode" android:launchMode="singleTask" android:windowSoftInputMode="adjustResize" android:theme="@style/Theme.App.SplashScreen" android:screenOrientation="unspecified">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="lnreader"/>
<data android:scheme="com.rajarsheechatterjee.LNReader"/>
</intent-filter>
</activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity"/>
<service android:name="com.asterinet.react.bgactions.RNBackgroundActionsTask" />
</application>
</manifest>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.rajarsheechatterjee.LNReader">
<!-- Internet -->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" />
<!-- Storage -->
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<!-- For background jobs -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.WAKE_LOCK"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS"/>
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
<uses-permission android:name="com.google.android.c2dm.permission.RECEIVE"/>
<queries>
<intent>
<action android:name="android.intent.action.TTS_SERVICE" />
</intent>
</queries>
<application
android:name=".MainApplication"
android:largeHeap="true"
android:label="@string/app_name"
android:icon="@mipmap/ic_launcher"
android:roundIcon="@mipmap/ic_launcher_round"
android:allowBackup="true"
android:theme="@style/AppTheme"
android:usesCleartextTraffic="true">
<meta-data
android:name="expo.modules.notifications.default_notification_icon"
android:resource="@drawable/notification_icon"/>
<meta-data
android:name="expo.modules.notifications.default_notification_color"
android:resource="@color/notification_icon_color"/>
<activity
android:name=".MainActivity"
android:label="@string/app_name"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|uiMode"
android:launchMode="singleTask"
android:windowSoftInputMode="adjustResize"
android:theme="@style/Theme.App.SplashScreen"
android:screenOrientation="unspecified">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="lnreader"/>
<data android:scheme="com.rajarsheechatterjee.LNReader"/>
</intent-filter>
</activity>
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
<service android:name="com.asterinet.react.bgactions.RNBackgroundActionsTask" />
</application>
</manifest>
File diff suppressed because one or more lines are too long
@@ -1,14 +1,20 @@
package com.rajarsheechatterjee.LNReader;
import android.content.res.Configuration;
import android.content.Intent;
import android.os.Bundle;
import android.graphics.Color;
import android.view.KeyEvent;
import android.view.WindowManager;
import android.os.Build;
import android.view.View;
import android.view.Window;
import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate;
import com.facebook.react.ReactRootView;
import com.rajarsheechatterjee.VolumeButtonListener.VolumeButtonListener;
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
import expo.modules.splashscreen.singletons.SplashScreen;
@@ -26,20 +32,55 @@ public class MainActivity extends ReactActivity {
sendBroadcast(intent);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
// SplashScreen.show(...) has to be called after super.onCreate(...)
// Below line is handled by '@expo/configure-splash-screen' command and it's discouraged to modify it manually
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
layoutParams.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
getWindow().setAttributes(layoutParams);
@Override
protected void onCreate(Bundle savedInstanceState) {
// SplashScreen.show(...) has to be called after super.onCreate(...)
// Below line is handled by '@expo/configure-splash-screen' command and it's discouraged to modify it manually
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
layoutParams.layoutInDisplayCutoutMode = WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES;
getWindow().setAttributes(layoutParams);
}
Window w = getWindow();
w.setStatusBarColor(Color.TRANSPARENT);
w.setNavigationBarColor(Color.TRANSPARENT);
w.getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
super.onCreate(null);
SplashScreen.show(this, SplashScreenImageResizeMode.COVER, ReactRootView.class, false);
}
super.onCreate(savedInstanceState);
SplashScreen.show(this, SplashScreenImageResizeMode.COVER, ReactRootView.class, false);
}
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
if (VolumeButtonListener.isActive()) {
int action = event.getAction();
int keyCode = event.getKeyCode();
switch (keyCode) {
case KeyEvent.KEYCODE_VOLUME_UP:
if (action == KeyEvent.ACTION_DOWN) {
// volup
VolumeButtonListener.up();
if (!VolumeButtonListener.prevent)
return super.dispatchKeyEvent(event);
}
return true;
case KeyEvent.KEYCODE_VOLUME_DOWN:
if (action == KeyEvent.ACTION_DOWN) {
//voldown
VolumeButtonListener.down();
if (!VolumeButtonListener.prevent)
return super.dispatchKeyEvent(event);
}
return true;
default:
return super.dispatchKeyEvent(event);
}
}
return super.dispatchKeyEvent(event);
}
/**
* Returns the name of the main component registered from JavaScript.
@@ -59,4 +100,5 @@ public class MainActivity extends ReactActivity {
}
};
}
}
@@ -2,34 +2,24 @@ package com.rajarsheechatterjee.LNReader;
import android.app.Application;
import android.content.Context;
import android.net.Uri;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;
import com.facebook.soloader.SoLoader;
import com.rajarsheechatterjee.LNReader.generated.BasePackageList;
import com.rajarsheechatterjee.VolumeButtonListener.VolumeButtonListenerPackage;
import org.unimodules.adapters.react.ReactAdapterPackage;
import org.unimodules.adapters.react.ModuleRegistryAdapter;
import org.unimodules.adapters.react.ReactModuleRegistryProvider;
import org.unimodules.core.interfaces.Package;
import org.unimodules.core.interfaces.SingletonModule;
import expo.modules.constants.ConstantsPackage;
import expo.modules.permissions.PermissionsPackage;
import expo.modules.filesystem.FileSystemPackage;
import expo.modules.updates.UpdatesController;
import com.facebook.react.bridge.JSIModulePackage;
import com.swmansion.reanimated.ReanimatedJSIModulePackage;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.List;
import javax.annotation.Nullable;
public class MainApplication extends Application implements ReactApplication {
private final ReactModuleRegistryProvider mModuleRegistryProvider = new ReactModuleRegistryProvider(
@@ -46,6 +36,8 @@ public class MainApplication extends Application implements ReactApplication {
protected List<ReactPackage> getPackages() {
List<ReactPackage> packages = new PackageList(this).getPackages();
packages.add(new ModuleRegistryAdapter(mModuleRegistryProvider));
packages.add(new com.rajarsheechatterjee.LNReader.NavigationBarColorPackage());
packages.add(new VolumeButtonListenerPackage());
return packages;
}
@@ -58,24 +50,6 @@ public class MainApplication extends Application implements ReactApplication {
protected JSIModulePackage getJSIModulePackage() {
return new ReanimatedJSIModulePackage();
}
@Override
protected @Nullable String getJSBundleFile() {
if (BuildConfig.DEBUG) {
return super.getJSBundleFile();
} else {
return UpdatesController.getInstance().getLaunchAssetFile();
}
}
@Override
protected @Nullable String getBundleAssetName() {
if (BuildConfig.DEBUG) {
return super.getBundleAssetName();
} else {
return UpdatesController.getInstance().getBundleAssetName();
}
}
};
@Override
@@ -88,10 +62,6 @@ public class MainApplication extends Application implements ReactApplication {
super.onCreate();
SoLoader.init(this, /* native exopackage */ false);
if (!BuildConfig.DEBUG) {
UpdatesController.initialize(this);
}
initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
}
@@ -13,14 +13,15 @@ public class BasePackageList {
new expo.modules.errorrecovery.ErrorRecoveryPackage(),
new expo.modules.filesystem.FileSystemPackage(),
new expo.modules.font.FontLoaderPackage(),
new expo.modules.haptics.HapticsPackage(),
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(),
new expo.modules.sqlite.SQLitePackage(),
new expo.modules.updates.UpdatesPackage(),
new expo.modules.webbrowser.WebBrowserPackage()
);
}
@@ -0,0 +1,170 @@
package com.rajarsheechatterjee.LNReader;
import android.animation.ArgbEvaluator;
import android.animation.ValueAnimator;
import android.annotation.TargetApi;
import android.graphics.Color;
import android.os.Build;
import android.app.Activity;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import androidx.annotation.UiThread;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.UiThreadUtil;
import com.facebook.react.bridge.WritableMap;
import java.util.HashMap;
import java.util.Map;
import com.facebook.react.uimanager.IllegalViewOperationException;
import static com.facebook.react.bridge.UiThreadUtil.runOnUiThread;
public class NavigationBarColorModule extends ReactContextBaseJavaModule {
public static final String REACT_CLASS = "NavigationBarColor";
private static final String ERROR_NO_ACTIVITY = "E_NO_ACTIVITY";
private static final String ERROR_NO_ACTIVITY_MESSAGE = "Tried to change the navigation bar while not attached to an Activity";
private static final String ERROR_API_LEVEL = "API_LEVEl";
private static final String ERROR_API_LEVEL_MESSAGE = "Only Android Oreo and above is supported";
private static ReactApplicationContext reactContext = null;
private static final int UI_FLAG_HIDE_NAV_BAR = View.SYSTEM_UI_FLAG_LAYOUT_STABLE
| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION
| View.SYSTEM_UI_FLAG_HIDE_NAVIGATION // hide nav bar
| View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY;
public NavigationBarColorModule(ReactApplicationContext context) {
super(context);
reactContext = context;
}
public void setNavigationBarTheme(Activity activity, Boolean light) {
if (activity != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Window window = activity.getWindow();
int flags = window.getDecorView().getSystemUiVisibility();
if (light) {
flags |= View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
} else {
flags &= ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
}
window.getDecorView().setSystemUiVisibility(flags);
}
}
@Override
public String getName() {
return REACT_CLASS;
}
@Override
public Map<String, Object> getConstants() {
final Map<String, Object> constants = new HashMap<>();
constants.put("EXAMPLE_CONSTANT", "example");
return constants;
}
@ReactMethod
public void changeNavigationBarColor(final String color, final Boolean light, final Boolean animated, final Promise promise) {
final WritableMap map = Arguments.createMap();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
if (getCurrentActivity() != null) {
try {
final Window window = getCurrentActivity().getWindow();
runOnUiThread(new Runnable() {
@Override
public void run() {
if (color.equals("transparent") || color.equals("translucent")) {
window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
if (color.equals("transparent")) {
window.setFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS, WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
} else {
window.setFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION, WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
}
setNavigationBarTheme(getCurrentActivity(), light);
map.putBoolean("success", true);
promise.resolve(map);
return;
} else {
window.clearFlags(WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS);
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION);
}
if (animated) {
Integer colorFrom = window.getNavigationBarColor();
Integer colorTo = Color.parseColor(String.valueOf(color));
//window.setNavigationBarColor(colorTo);
ValueAnimator colorAnimation = ValueAnimator.ofObject(new ArgbEvaluator(), colorFrom, colorTo);
colorAnimation.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animator) {
window.setNavigationBarColor((Integer) animator.getAnimatedValue());
}
});
colorAnimation.start();
} else {
window.setNavigationBarColor(Color.parseColor(String.valueOf(color)));
}
setNavigationBarTheme(getCurrentActivity(), light);
WritableMap map = Arguments.createMap();
map.putBoolean("success", true);
promise.resolve(map);
}
});
} catch (IllegalViewOperationException e) {
map.putBoolean("success", false);
promise.reject("error", e);
}
} else {
promise.reject(ERROR_NO_ACTIVITY, new Throwable(ERROR_NO_ACTIVITY_MESSAGE));
}
} else {
promise.reject(ERROR_API_LEVEL, new Throwable(ERROR_API_LEVEL_MESSAGE));
}
}
@ReactMethod
public void hideNavigationBar(Promise promise) {
try {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (getCurrentActivity() != null) {
View decorView = getCurrentActivity().getWindow().getDecorView();
decorView.setSystemUiVisibility(UI_FLAG_HIDE_NAV_BAR);
}
}
});
} catch (IllegalViewOperationException e) {
WritableMap map = Arguments.createMap();
map.putBoolean("success", false);
promise.reject("error", e);
}
}
@ReactMethod
public void showNavigationBar(Promise promise) {
try {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (getCurrentActivity() != null) {
Window w = getCurrentActivity().getWindow();
w.setStatusBarColor(Color.TRANSPARENT);
w.setNavigationBarColor(Color.TRANSPARENT);
View decorView = w.getDecorView();
decorView.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
}
}
});
} catch (IllegalViewOperationException e) {
WritableMap map = Arguments.createMap();
map.putBoolean("success", false);
promise.reject("error", e);
}
}
}
@@ -0,0 +1,25 @@
package com.rajarsheechatterjee.LNReader;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class NavigationBarColorPackage implements ReactPackage {
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new NavigationBarColorModule(reactContext));
return modules;
}
}
@@ -0,0 +1,93 @@
package com.rajarsheechatterjee.VolumeButtonListener;
import androidx.annotation.NonNull;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.modules.core.DeviceEventManagerModule;
public class VolumeButtonListener extends ReactContextBaseJavaModule {
static ReactApplicationContext appContext;
public VolumeButtonListener(ReactApplicationContext context) {
super(context);
appContext = context;
}
private static boolean active = false;
public static boolean isActive() {
return active;
}
public static boolean prevent = false;
public static void sendEvent(boolean up) {
if (active) {
WritableMap args = Arguments.createMap();
if (up)
appContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("VolumeUp", args);
else
appContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("VolumeDown", args);
}
}
public static void down() {
sendEvent(false);
}
public static void up() {
sendEvent(true);
}
@ReactMethod
public void connect() {
active = true;
}
@ReactMethod
void disconnect() {
active = false;
}
@ReactMethod
public void pause() {
active = false;
}
@ReactMethod
public void unpause() {
active = true;
}
@ReactMethod
public void preventDefault() {
prevent = true;
}
@ReactMethod
public void noPreventDefault() {
prevent = false;
}
@NonNull
@Override
public String getName() {
return "VolumeButtonListener";
}
@ReactMethod
public void addListener(String eventName) {
// Keep: Required for RN built in Event Emitter Calls.
}
@ReactMethod
public void removeListeners(Integer count) {
// Keep: Required for RN built in Event Emitter Calls.
}
}
@@ -0,0 +1,30 @@
package com.rajarsheechatterjee.VolumeButtonListener;
import androidx.annotation.NonNull;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class VolumeButtonListenerPackage implements ReactPackage {
@NonNull
@Override
public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<NativeModule>();
modules.add(new VolumeButtonListener(reactContext));
return modules;
}
@NonNull
@Override
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

After

Width:  |  Height:  |  Size: 29 KiB

+3 -3
View File
@@ -1,9 +1,9 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<resources>
<color name="iconBackground">#FFFFFF</color>
<color name="splashscreen_background">#000000</color>
<color name="splashscreen_background">#202125</color>
<color name="colorPrimary">#023c69</color>
<color name="colorPrimaryDark">#00000000</color>
<color name="navigationBarColor">#000000</color>
<color name="colorPrimaryDark">#202125</color>
<color name="navigationBarColor">#202125</color>
<color name="notification_icon_color">#00adb5</color>
</resources>
+4 -4
View File
@@ -8,16 +8,16 @@
<item name="android:textColor">#000000</item>
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="android:navigationBarColor">#000000</item>
<item name="android:statusBarColor">#000000</item>
<item name="android:navigationBarColor">#202125</item>
<item name="android:statusBarColor">#202125</item>
</style>
<style name="Theme.App.SplashScreen" parent="Theme.AppCompat.Light.NoActionBar">
<!-- Below line is handled by '@expo/configure-splash-screen' command and it's discouraged to modify it manually -->
<!-- Customize your splash screen theme here -->
<item name="android:windowBackground">@drawable/splashscreen</item>
<item name="android:navigationBarColor">#000000</item>
<item name="android:statusBarColor">#000000</item>
<item name="android:navigationBarColor">#202125</item>
<item name="android:statusBarColor">#202125</item>
</style>
</resources>
+8 -8
View File
@@ -1,11 +1,11 @@
{
"expo": {
"name": "LNReader",
"slug": "LNReader",
"scheme": "lnreader",
"notification": {
"icon": "./assets/notification.png",
"color": "#00adb5"
}
"expo": {
"name": "LNReader",
"slug": "LNReader",
"scheme": "lnreader",
"notification": {
"icon": "./assets/notification.png",
"color": "#00adb5"
}
}
}
Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 266 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 56 KiB

BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

+17 -1
View File
@@ -1,6 +1,22 @@
module.exports = function(api) {
module.exports = function (api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
plugins: [
[
'module-resolver',
{
root: ['./src'],
alias: {
'@components': './src/components',
'@hooks': './src/hooks',
'@screens': './src/screens',
'@strings': './strings',
'@redux': './src/redux',
},
extensions: ['.ios.js', '.android.js', '.js', '.ts', '.tsx', '.json'],
},
],
],
};
};
+5
View File
@@ -0,0 +1,5 @@
pull_request_title: 'chore: Update translations'
commit_message: '[ci skip]'
files:
- source: /strings/languages/en/strings.json
translation: /strings/languages/%two_letters_code%/strings.json
+4
View File
@@ -0,0 +1,4 @@
module.exports = {
preset: 'react-native',
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
};
+28444 -19735
View File
File diff suppressed because it is too large Load Diff
+100 -70
View File
@@ -1,72 +1,102 @@
{
"scripts": {
"start": "react-native start",
"android": "react-native run-android",
"ios": "react-native run-ios",
"web": "expo start --web",
"eject": "expo eject"
},
"dependencies": {
"@react-native-async-storage/async-storage": "^1.13.0",
"@react-native-community/masked-view": "0.1.10",
"@react-native-community/netinfo": "6.0.0",
"@react-native-community/slider": "3.0.3",
"@react-navigation/material-bottom-tabs": "^5.3.10",
"@react-navigation/native": "^5.8.10",
"@react-navigation/stack": "^5.12.8",
"expo": "^41.0.1",
"expo-document-picker": "^9.1.2",
"expo-file-system": "~11.0.2",
"expo-font": "~9.1.0",
"expo-linear-gradient": "~9.1.0",
"expo-linking": "~2.2.3",
"expo-notifications": "~0.11.6",
"expo-splash-screen": "^0.11.2",
"expo-sqlite": "~9.1.0",
"expo-updates": "~0.5.4",
"expo-web-browser": "~9.1.0",
"moment": "^2.29.1",
"qs": "^6.10.1",
"react": "16.13.1",
"react-dom": "16.13.1",
"react-native": "^0.64.2",
"react-native-background-actions": "^2.6.1",
"react-native-cheerio": "^1.0.0-rc.4",
"react-native-device-info": "^8.1.3",
"react-native-easing-gradient": "^0.1.2",
"react-native-fast-image": "^8.3.4",
"react-native-gesture-handler": "~1.10.2",
"react-native-navigation-bar-color": "^2.0.1",
"react-native-pager-view": "^5.1.10",
"react-native-paper": "^4.5.0",
"react-native-reanimated": "^2.1.0",
"react-native-safe-area-context": "3.2.0",
"react-native-screens": "~3.0.0",
"react-native-swipe-gestures": "^1.0.5",
"react-native-tab-view": "^3.0.1",
"react-native-unimodules": "~0.13.3",
"react-native-vector-icons": "^7.1.0",
"react-native-web": "^0.16.5",
"react-native-webview": "^11.6.2",
"react-redux": "^7.2.2",
"redux": "^4.0.5",
"redux-persist": "^6.0.0",
"redux-thunk": "^2.3.0",
"rn-sliding-up-panel": "^2.4.4"
},
"devDependencies": {
"@babel/core": "~7.9.0"
},
"prettier": {
"tabWidth": 4,
"semi": true,
"singleQuote": false,
"quoteProps": "as-needed",
"trailingComma": "es5",
"bracketSpacing": true,
"arrowParens": "always"
},
"private": true,
"name": "lnreader",
"version": "1.0.0"
"name": "lnreader",
"version": "1.1.12",
"private": true,
"scripts": {
"android": "react-native run-android",
"ios": "react-native run-ios",
"start": "react-native start",
"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/clipboard": "^1.5.1",
"@react-native-community/masked-view": "^0.1.11",
"@react-native-community/slider": "^4.1.12",
"@react-native-picker/picker": "^2.4.1",
"@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",
"buffer": "^6.0.3",
"cheerio": "^1.0.0-rc.10",
"expo": "^41.0.1",
"expo-document-picker": "^9.2.4",
"expo-file-system": "~11.0.2",
"expo-haptics": "~10.0.0",
"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-web-browser": "~9.1.0",
"i18n-js": "^3.8.0",
"lodash": "^4.17.21",
"moment": "^2.29.4",
"prop-types": "^15.7.2",
"qs": "^6.10.1",
"react": "16.13.1",
"react-dom": "16.13.1",
"react-native": "^0.64.3",
"react-native-background-actions": "^2.6.5",
"react-native-device-info": "^8.4.7",
"react-native-easing-gradient": "^1.0.0",
"react-native-fast-image": "^8.5.11",
"react-native-gesture-handler": "~1.10.2",
"react-native-markdown-display": "^7.0.0-alpha.2",
"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-swipe-gestures": "^1.0.5",
"react-native-tab-view": "^3.1.1",
"react-native-tts": "^4.1.0",
"react-native-unimodules": "^0.13.3",
"react-native-vector-icons": "^9.0.0",
"react-native-webview": "^11.14.3",
"react-redux": "^7.2.6",
"redux": "^4.1.2",
"redux-persist": "^6.0.0",
"rn-fetch-blob": "^0.12.0",
"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-native-vector-icons": "^6.4.10",
"@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",
"babel-plugin-module-resolver": "^4.1.0",
"eslint": "^7.32.0",
"eslint-plugin-react-native": "^3.11.0",
"husky": "^7.0.4",
"lint-staged": "^12.3.7",
"typescript": "^4.5.5"
},
"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/'],
};
+65
View File
@@ -0,0 +1,65 @@
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(/\s+/g, '')
.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
.replace(/\s+/g, '')
.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('${source.sourceName}: 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);
}
},
);
+39 -34
View File
@@ -1,42 +1,47 @@
import React from "react";
import { Dimensions, StyleSheet, View } from "react-native";
import React from 'react';
import { Dimensions, StyleSheet, View } from 'react-native';
import { IconButton } from "react-native-paper";
import { IconButton } from 'react-native-paper';
export const Actionbar = ({ actions, theme }) => {
export const Actionbar = ({ active, actions, theme, style }) => {
if (active) {
return (
<View
style={[
styles.actionbarContainer,
{ backgroundColor: theme.colorPrimary },
]}
>
{actions.map(
(action, index) =>
action.icon && (
<IconButton
key={index}
icon={action.icon}
color={theme.textColorPrimary}
onPress={action.onPress}
/>
)
)}
</View>
<View
style={[
styles.actionbarContainer,
{ backgroundColor: theme.colorPrimary },
style,
]}
>
{actions.map(
(action, index) =>
action.icon && (
<IconButton
key={index}
icon={action.icon}
color={theme.textColorPrimary}
onPress={action.onPress}
/>
),
)}
</View>
);
} else {
return null;
}
};
const styles = StyleSheet.create({
actionbarContainer: {
position: "absolute",
width: Dimensions.get("window").width - 32,
bottom: 0,
margin: 16,
borderRadius: 6,
flexDirection: "row",
justifyContent: "space-between",
paddingHorizontal: 8,
paddingVertical: 4,
elevation: 2,
},
actionbarContainer: {
position: 'absolute',
width: Dimensions.get('window').width - 32,
bottom: 0,
margin: 16,
borderRadius: 6,
flexDirection: 'row',
justifyContent: 'space-between',
paddingHorizontal: 8,
paddingVertical: 4,
elevation: 2,
},
});
+24 -18
View File
@@ -1,24 +1,30 @@
import React from "react";
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 { useTheme } from '../hooks/reduxHooks';
import { ViewPropTypes } from 'react-native';
export const Appbar = ({ title, onBackAction, style, children }) => {
const theme = useTheme();
const theme = useTheme();
return (
<MaterialAppbar.Header
style={[{ backgroundColor: theme.colorPrimary }, style]}
>
{onBackAction && (
<MaterialAppbar.BackAction onPress={onBackAction} />
)}
<MaterialAppbar.Content
title={title}
titleStyle={{ color: theme.textColorPrimary }}
/>
{children}
</MaterialAppbar.Header>
);
return (
<MaterialAppbar.Header
style={[{ backgroundColor: theme.colorPrimary }, style]}
>
{onBackAction && <MaterialAppbar.BackAction onPress={onBackAction} />}
<MaterialAppbar.Content
title={title}
titleStyle={{ color: theme.textColorPrimary }}
/>
{children}
</MaterialAppbar.Header>
);
};
Appbar.propTypes = {
title: PropTypes.string,
onBackAction: PropTypes.func,
style: ViewPropTypes.style,
};
+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 { ThemeType } from '../../theme/types';
interface AppbarProps {
title: string;
handleGoBack: () => void;
theme: ThemeType;
}
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;
-17
View File
@@ -1,17 +0,0 @@
import React from "react";
import { View } from "react-native";
const BottomSheetHandle = ({ theme }) => (
<View
style={{
backgroundColor: "rgba(255,255,255,0.4)",
height: 4,
width: 40,
borderRadius: 50,
top: 8,
alignSelf: "center",
}}
/>
);
export default BottomSheetHandle;
+87
View File
@@ -0,0 +1,87 @@
import { Pressable, StyleSheet, Text, View, ViewStyle } from 'react-native';
import React from 'react';
import { ThemeType } from '../../theme/types';
export enum ButtonVariation {
DEFAULT = 'default',
OUTLINED = 'outlined',
CLEAR = 'clear',
}
interface Props {
variation?: ButtonVariation;
title: string;
textColor?: string;
onPress?: () => void;
theme: ThemeType;
style?: ViewStyle;
}
const Button: React.FC<Props> = ({
variation = ButtonVariation.DEFAULT,
title,
textColor,
onPress,
theme,
style,
}) => {
return (
<View
style={[
styles.button,
variation === ButtonVariation.OUTLINED
? {
...styles.outlined,
borderColor: theme.textColorHint,
}
: variation !== ButtonVariation.CLEAR && {
backgroundColor: theme.colorAccent,
},
style,
]}
>
<Pressable
style={styles.pressable}
onPress={onPress}
android_ripple={{ color: theme.rippleColor }}
>
<Text
style={{
color: textColor
? textColor
: variation === ButtonVariation.OUTLINED
? theme.colorAccent
: variation === ButtonVariation.CLEAR
? theme.colorAccent
: 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,
},
});
+60 -76
View File
@@ -1,85 +1,69 @@
import React from "react";
import { Pressable, StyleSheet, Text, View } from "react-native";
import React from 'react';
import { Pressable, StyleSheet, Text } from 'react-native';
import { Checkbox as PaperCheckbox } from 'react-native-paper';
import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons";
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
export const Checkbox = ({ label, status, onPress, theme }) => (
<View
style={[
styles.checkboxContainer,
status && { backgroundColor: theme.rippleColor },
]}
>
<Pressable
android_ripple={{ color: theme.rippleColor }}
style={styles.pressable}
onPress={onPress}
>
<Text
style={{
color: status ? theme.colorAccent : theme.textColorPrimary,
}}
>
{label}
</Text>
{status && (
<MaterialCommunityIcons
name="check"
color={theme.colorAccent}
size={21}
style={styles.icon}
/>
)}
</Pressable>
</View>
export const Checkbox = ({
label,
status,
onPress,
disabled = false,
theme,
}) => (
<Pressable
android_ripple={{ color: theme.rippleColor }}
style={styles.pressable}
onPress={onPress}
disabled={disabled}
>
<PaperCheckbox
status={
status === 'indeterminate'
? 'indeterminate'
: status
? 'checked'
: 'unchecked'
}
onPress={onPress}
color={theme.colorAccent}
uncheckedColor={theme.textColorSecondary}
disabled={disabled}
/>
<Text style={{ color: theme.textColorSecondary, marginLeft: 12 }}>
{label}
</Text>
</Pressable>
);
export const SortItem = ({ label, status, onPress, theme }) => (
<View
style={[
styles.checkboxContainer,
status && { backgroundColor: theme.rippleColor },
]}
>
<Pressable
android_ripple={{ color: theme.rippleColor }}
style={styles.pressable}
onPress={onPress}
>
<Text
style={{
color: status ? theme.colorAccent : theme.textColorPrimary,
}}
>
{label}
</Text>
{status && (
<MaterialCommunityIcons
name={status === "asc" ? "arrow-up" : "arrow-down"}
color={theme.colorAccent}
size={21}
style={styles.icon}
/>
)}
</Pressable>
</View>
<Pressable
android_ripple={{ color: theme.rippleColor }}
style={[styles.pressable, { paddingVertical: 16, paddingLeft: 64 }]}
onPress={onPress}
>
{status && (
<MaterialCommunityIcons
name={status === 'asc' ? 'arrow-up' : 'arrow-down'}
color={theme.colorAccent}
size={21}
style={styles.icon}
/>
)}
<Text style={{ color: theme.textColorPrimary }}>{label}</Text>
</Pressable>
);
const styles = StyleSheet.create({
checkboxContainer: {
borderRadius: 8,
overflow: "hidden",
marginVertical: 4,
},
pressable: {
paddingHorizontal: 16,
paddingVertical: 12,
flexDirection: "row",
justifyContent: "space-between",
},
icon: {
position: "absolute",
right: 16,
alignSelf: "center",
},
pressable: {
paddingHorizontal: 16,
paddingVertical: 6,
flexDirection: 'row',
alignItems: 'center',
},
icon: {
position: 'absolute',
left: 24,
alignSelf: 'center',
},
});
+42
View File
@@ -0,0 +1,42 @@
import React from 'react';
import { Pressable, StyleSheet, Text, View } from 'react-native';
import { ThemeType } from '../../theme/types';
interface ChipProps {
label: string;
theme: ThemeType;
}
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,
},
});
-98
View File
@@ -1,98 +0,0 @@
import React, { useState } from "react";
import { StyleSheet, Text } from "react-native";
import { Modal, Portal, TextInput } from "react-native-paper";
const ColorPickerModal = ({
theme,
color,
title,
onSubmit,
hideModal,
modalVisible,
showAccentColors,
}) => {
const [text, setText] = useState(color);
const [error, setError] = useState();
const onDismiss = () => {
hideModal();
setText();
setError();
};
const onChangeText = (text) => setText(text);
const onSubmitEditing = () => {
const re = /^#([0-9a-f]{6}|[0-9a-f]{3})$/i;
if (text.match(re)) {
onSubmit(text);
hideModal();
} else {
setError("Enter a valid hex color code");
}
};
const textInputTheme = {
colors: {
primary: color,
placeholder: theme.textColorHint,
text: theme.textColorPrimary,
background: "transparent",
},
};
return (
<Portal>
<Modal
visible={modalVisible}
onDismiss={onDismiss}
contentContainerStyle={[
styles.modalContainer,
{ backgroundColor: theme.colorPrimary },
]}
>
<Text
style={[
styles.modalTitle,
{ color: theme.textColorPrimary },
]}
>
{title}
</Text>
<TextInput
value={text}
defaultValue={color}
placeholder="Hex Color Code (E.g. #3399FF)"
onChangeText={onChangeText}
onSubmitEditing={onSubmitEditing}
mode="outlined"
theme={textInputTheme}
underlineColor={theme.textColorHint}
dense
error={error}
/>
<Text style={styles.errorText}>{error}</Text>
</Modal>
</Portal>
);
};
export default ColorPickerModal;
const styles = StyleSheet.create({
modalContainer: {
margin: 30,
padding: 20,
borderRadius: 8,
},
modalTitle: {
fontSize: 18,
marginBottom: 16,
},
errorText: {
color: "#FF0033",
paddingTop: 8,
},
});
@@ -0,0 +1,161 @@
import React, { useState } from 'react';
import { FlatList, Pressable, StyleSheet, Text, View } from 'react-native';
import { Modal, Portal, TextInput } from 'react-native-paper';
import { ThemeType } from '../../theme/types';
interface ColorPickerModalProps {
visible: boolean;
title: string;
color: string;
onSubmit: (val: string) => void;
closeModal: () => void;
theme: ThemeType;
showAccentColors?: boolean;
}
const ColorPickerModal: React.FC<ColorPickerModalProps> = ({
theme,
color,
title,
onSubmit,
closeModal,
visible,
showAccentColors,
}) => {
const [text, setText] = useState<string>(color);
const [error, setError] = useState<string | null>();
const onDismiss = () => {
closeModal();
if (error) {
setText(color);
}
setError(null);
};
const onChangeText = (txt: string) => setText(txt);
const onSubmitEditing = () => {
const re = /^#([0-9a-f]{8}|[0-9a-f]{6}|[0-9a-f]{3})$/i;
if (text.match(re)) {
onSubmit(text);
closeModal();
} else {
setError('Enter a valid hex color code');
}
};
const textInputTheme = {
colors: {
primary: color,
placeholder: theme.textColorHint,
text: theme.textColorPrimary,
background: 'transparent',
},
};
const accentColors = [
'#EF5350',
'#EC407A',
'#AB47BC',
'#7E57C2',
'#5C6BC0',
'#42A5F5',
'#29B6FC',
'#26C6DA',
'#26A69A',
'#66BB6A',
'#9CCC65',
'#D4E157',
'#FFEE58',
'#FFCA28',
'#FFA726',
'#FF7043',
'#8D6E63',
'#BDBDBD',
'#78909C',
'#000000',
];
return (
<Portal>
<Modal
visible={visible}
onDismiss={onDismiss}
contentContainerStyle={[
styles.modalContainer,
{ backgroundColor: theme.colorPrimary },
]}
>
<Text style={[styles.modalTitle, { color: theme.textColorPrimary }]}>
{title}
</Text>
{showAccentColors ? (
<FlatList
contentContainerStyle={{ marginBottom: 8 }}
data={accentColors}
numColumns={4}
keyExtractor={item => item}
renderItem={({ item }) => (
<View
style={{
borderRadius: 4,
overflow: 'hidden',
backgroundColor: item,
flex: 1 / 4,
height: 40,
marginHorizontal: 4,
marginVertical: 4,
}}
>
<Pressable
style={{ flex: 1 }}
android_ripple={{
color: 'rgba(0,0,0,0.12)',
}}
onPress={() => {
onSubmit(item);
closeModal();
}}
/>
</View>
)}
/>
) : null}
<TextInput
value={text}
defaultValue={typeof color === 'string' ? color : ''}
placeholder="Hex Color Code (E.g. #3399FF)"
onChangeText={onChangeText}
onSubmitEditing={onSubmitEditing}
mode="outlined"
theme={textInputTheme}
underlineColor={theme.textColorHint}
dense
error={Boolean(error)}
/>
<Text style={styles.errorText}>{error}</Text>
</Modal>
</Portal>
);
};
export default ColorPickerModal;
const styles = StyleSheet.create({
modalContainer: {
margin: 30,
padding: 20,
borderRadius: 8,
},
modalTitle: {
fontSize: 18,
marginBottom: 16,
},
errorText: {
color: '#FF0033',
paddingTop: 8,
},
});
@@ -0,0 +1,54 @@
import React from 'react';
import { Pressable, StyleSheet, Text, View } from 'react-native';
import { ThemeType } from '../../theme/types';
interface ColorPreferenceItemProps {
label: string;
description?: string;
onPress: () => void;
theme: ThemeType;
}
const ColorPreferenceItem: React.FC<ColorPreferenceItemProps> = ({
label,
description,
theme,
onPress,
}) => (
<Pressable
style={styles.container}
android_ripple={{ color: theme.rippleColor }}
onPress={onPress}
>
<View>
<Text style={[styles.label, { color: theme.textColorPrimary }]}>
{label}
</Text>
<Text style={{ color: theme.textColorSecondary }}>
{description?.toUpperCase?.()}
</Text>
</View>
<View style={[{ backgroundColor: description }, styles.colorPreview]} />
</Pressable>
);
export default ColorPreferenceItem;
const styles = StyleSheet.create({
container: {
padding: 16,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
},
label: {
fontSize: 16,
},
colorPreview: {
height: 24,
width: 24,
borderRadius: 50,
marginRight: 16,
},
});
+22 -11
View File
@@ -1,21 +1,32 @@
import React from "react";
import { View, Text, Image, StyleSheet } from "react-native";
import React from 'react';
import { View, StyleSheet } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
const ScreenContainer = ({ children, theme }) => (
<View style={{ flex: 1, backgroundColor: theme.colorPrimaryDark }}>
{children}
const ScreenContainer = ({ children, theme }) => {
const { bottom } = useSafeAreaInsets();
return (
<View
style={{
flex: 1,
backgroundColor: theme.colorPrimaryDark,
paddingBottom: bottom,
}}
>
{children}
</View>
);
);
};
const Row = ({ children, style }) => (
<View style={[styles.row, style]}>{children}</View>
<View style={[styles.row, style]}>{children}</View>
);
export { ScreenContainer, Row };
const styles = StyleSheet.create({
row: {
flexDirection: "row",
alignItems: "center",
},
row: {
flexDirection: 'row',
alignItems: 'center',
},
});
+110
View File
@@ -0,0 +1,110 @@
/* eslint-disable no-bitwise */
import PropTypes from 'prop-types';
import React, { PureComponent, Children } from 'react';
import { View, Animated, StyleSheet } from 'react-native';
export default class FadeView extends PureComponent {
static defaultProps = {
animationDuration: 225,
active: false,
removeHiddenSubviews: true,
};
static propTypes = {
animationDuration: PropTypes.number,
active: PropTypes.bool,
removeHiddenSubviews: PropTypes.bool,
children: PropTypes.oneOfType([
PropTypes.arrayOf(PropTypes.node),
PropTypes.node,
]),
};
constructor(props) {
super(props);
this.renderChild = this.renderChild.bind(this);
this.mounted = false;
let { active } = this.props;
this.state = {
progress: new Animated.Value(Number(active)),
animating: false,
};
}
componentDidMount() {
this.mounted = true;
}
componentWillUnmount() {
this.mounted = false;
}
UNSAFE_componentWillReceiveProps({ active, animationDuration }) {
let { progress } = this.state;
if (active ^ this.props.active) {
this.setState({ animating: true });
Animated.timing(progress, {
toValue: Number(active),
duration: animationDuration,
useNativeDriver: true,
}).start(() => {
if (this.mounted) {
this.setState({ animating: false });
}
});
}
}
renderChild(child, index) {
let { active, removeHiddenSubviews } = this.props;
let { animating, progress } = this.state;
let opacity = progress.interpolate({
inputRange: [0, 1],
outputRange: index ? [0, 1] : [1, 0],
});
let hidden = active ^ !!index;
if (removeHiddenSubviews) {
if (!animating && hidden) {
return null;
}
}
let pointerEvents = hidden ? 'none' : 'box-none';
return (
<Animated.View
style={[styles.container, { opacity }]}
pointerEvents={pointerEvents}
>
{child}
</Animated.View>
);
}
render() {
let { children, ...props } = this.props;
return <View {...props}>{Children.map(children, this.renderChild)}</View>;
}
}
const styles = StyleSheet.create({
container: {
// ...StyleSheet.absoluteFillObject,
flexDirection: 'row',
justifyContent: 'center',
alignItems: 'center',
},
});
-72
View File
@@ -1,72 +0,0 @@
import React from "react";
import { Pressable, View } from "react-native";
import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons";
export const ToggleButton = ({ icon, selected, theme, color, onPress }) => (
<View
style={{
borderRadius: 8,
overflow: "hidden",
marginHorizontal: 4,
}}
>
<Pressable
android_ripple={{ color: theme.rippleColor }}
style={{
padding: 10,
alignItems: "center",
justifyContent: "center",
}}
onPress={onPress}
>
<MaterialCommunityIcons
name={icon}
color={
selected
? theme.colorAccent
: color
? color
: theme.textColorPrimary
}
size={24}
/>
</Pressable>
</View>
);
export const ToggleColorButton = ({
selected,
backgroundColor,
textColor,
onPress,
}) => (
<View
style={{
borderRadius: 50,
overflow: "hidden",
marginHorizontal: 6,
height: 44,
width: 44,
}}
>
<Pressable
android_ripple={{ color: textColor }}
style={{
flex: 1,
padding: 10,
backgroundColor: backgroundColor,
alignItems: "center",
justifyContent: "center",
}}
onPress={onPress}
>
{selected && (
<MaterialCommunityIcons
name="check"
color={textColor}
size={24}
/>
)}
</Pressable>
</View>
);
+89
View File
@@ -0,0 +1,89 @@
import React from 'react';
import { Pressable, View } from 'react-native';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import { ThemeType } from '../../theme/types';
interface ToggleButtonProps {
icon: string;
selected: boolean;
theme: ThemeType;
color?: string;
onPress: () => void;
}
export const ToggleButton: React.FC<ToggleButtonProps> = ({
icon,
selected,
theme,
color,
onPress,
}) => (
<View
style={{
borderRadius: 6,
overflow: 'hidden',
marginHorizontal: 6,
}}
>
<Pressable
android_ripple={{ color: theme.rippleColor }}
style={{
padding: 8,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: selected ? theme.rippleColor : 'transparent',
}}
onPress={onPress}
>
<MaterialCommunityIcons
name={icon}
color={
selected ? theme.colorAccent : color ? color : theme.textColorPrimary
}
size={24}
/>
</Pressable>
</View>
);
interface ToggleColorButtonProps {
selected: boolean;
backgroundColor: string;
textColor: string;
onPress: () => void;
}
export const ToggleColorButton: React.FC<ToggleColorButtonProps> = ({
selected,
backgroundColor,
textColor,
onPress,
}) => (
<View
style={{
borderRadius: 50,
overflow: 'hidden',
marginHorizontal: 6,
height: 44,
width: 44,
}}
>
<Pressable
android_ripple={{ color: textColor }}
style={{
flex: 1,
padding: 10,
backgroundColor: backgroundColor,
alignItems: 'center',
justifyContent: 'center',
}}
onPress={onPress}
>
<MaterialCommunityIcons
name={selected ? 'check' : 'format-color-text'}
color={textColor}
size={24}
/>
</Pressable>
</View>
);
@@ -0,0 +1,74 @@
import React from 'react';
import { StyleSheet } from 'react-native';
import { getString } from '@strings/translations';
import { Dialog, Portal } from 'react-native-paper';
import { ThemeType } from '../../theme/types';
import { Button } from '@components/index';
import { ButtonVariation } from '@components/Button/Button';
interface ConfirmationDialogProps {
title: string;
visible: boolean;
theme: ThemeType;
onSubmit: () => void;
onDismiss: () => void;
}
const ConfirmationDialog: React.FC<ConfirmationDialogProps> = ({
title,
visible,
onDismiss,
theme,
onSubmit,
}) => {
const handleOnSubmit = () => {
onSubmit();
onDismiss();
};
return (
<Portal>
<Dialog
visible={visible}
onDismiss={onDismiss}
style={[styles.container, { backgroundColor: theme.colorPrimary }]}
>
<Dialog.Title style={[styles.title, { color: theme.textColorPrimary }]}>
{title}
</Dialog.Title>
<Dialog.Actions>
<Button
theme={theme}
onPress={onDismiss}
title={getString('common.cancel')}
variation={ButtonVariation.CLEAR}
/>
<Button
theme={theme}
onPress={handleOnSubmit}
title={getString('common.ok')}
variation={ButtonVariation.CLEAR}
style={styles.button}
/>
</Dialog.Actions>
</Dialog>
</Portal>
);
};
export default ConfirmationDialog;
const styles = StyleSheet.create({
container: {
borderRadius: 8,
},
title: {
letterSpacing: 0,
fontSize: 16,
},
button: {
marginLeft: 4,
},
});
+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,
},
});
+38 -42
View File
@@ -1,51 +1,47 @@
import React from "react";
import { StyleSheet, Text, View } from "react-native";
import { useSelector } from "react-redux";
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { useSelector } from 'react-redux';
const EmptyView = ({ icon, description, style, children, iconStyle }) => {
const theme = useSelector((state) => state.settingsReducer.theme);
const theme = useSelector(state => state.settingsReducer.theme);
return (
<View style={styles.emptyViewContainer}>
<Text
style={[
styles.emptyViewIcon,
{ color: theme.textColorHint },
style,
iconStyle,
]}
>
{icon}
</Text>
<Text
style={[
styles.emptyViewText,
{ color: theme.textColorHint },
style,
]}
>
{description}
</Text>
{children}
</View>
);
return (
<View style={styles.emptyViewContainer}>
<Text
style={[
styles.emptyViewIcon,
{ color: theme.textColorHint },
style,
iconStyle,
]}
>
{icon}
</Text>
<Text
style={[styles.emptyViewText, { color: theme.textColorHint }, style]}
>
{description}
</Text>
{children}
</View>
);
};
export default EmptyView;
const styles = StyleSheet.create({
emptyViewContainer: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
emptyViewIcon: {
fontSize: 45,
},
emptyViewText: {
fontWeight: "bold",
marginTop: 10,
textAlign: "center",
paddingHorizontal: 30,
},
emptyViewContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
emptyViewIcon: {
fontSize: 45,
},
emptyViewText: {
fontWeight: 'bold',
marginTop: 10,
textAlign: 'center',
paddingHorizontal: 30,
},
});
+38
View File
@@ -0,0 +1,38 @@
import React from 'react';
import { StyleSheet, View, Text } from 'react-native';
import { ThemeType } from '../../theme/types';
interface EmptyViewProps {
icon?: string;
description: string;
theme: ThemeType;
}
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,38 @@
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { ThemeType } from '../../theme/types';
interface ErrorScreenProps {
error?: string;
theme: ThemeType;
}
const ErrorScreen: React.FC<ErrorScreenProps> = ({ error, theme }) => {
return (
<View style={styles.container}>
<Text style={[styles.icon, { color: theme.textColorHint }]}>ಥ_ಥ</Text>
<Text style={[styles.error, { color: theme.textColorHint }]}>
{error}
</Text>
</View>
);
};
export default ErrorScreen;
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
icon: {
fontSize: 44,
},
error: {
marginTop: 16,
paddingHorizontal: 16,
textAlign: 'center',
},
});
-28
View File
@@ -1,28 +0,0 @@
import React from "react";
import { Text, View } from "react-native";
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" }}>
<EmptyView
icon="(; ̄Д ̄)"
description={error}
style={{ color: theme.textColorSecondary }}
>
<IconButton
icon="reload"
size={25}
style={{ margin: 0, marginTop: 24 }}
color={theme.text}
onPress={onRetry}
/>
<Text style={{ color: theme.textColorPrimary }}>Retry</Text>
</EmptyView>
</View>
);
export default ErrorView;
+66 -66
View File
@@ -1,75 +1,75 @@
import React from "react";
import { Pressable, StyleSheet, Text, View } from "react-native";
import { IconButton } from "react-native-paper";
import React from 'react';
import { Pressable, StyleSheet, Text, View } from 'react-native';
import { IconButton } from 'react-native-paper';
const icons = ["(・o・;)", "Σ(ಠ_ಠ)", "ಥ_ಥ", "(˘・_・˘)", "(; ̄Д ̄)", "(・Д・。"];
// const icons = ['(・o・;)', 'Σ(ಠ_ಠ)', 'ಥ_ಥ', '(˘・_・˘)', '(; ̄Д ̄)', '(・Д・。'];
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 }]}>
{errorName}
</Text>
<View style={{ flexDirection: "row" }}>
{actions.map((action) => (
<View
style={{
borderRadius: 4,
overflow: "hidden",
margin: 16,
}}
>
<Pressable
android_ripple={{
color: theme.rippleColor,
borderless: false,
}}
onPress={action.onPress}
style={{
justifyContent: "center",
alignItems: "center",
paddingBottom: 8,
paddingHorizontal: 20,
minWidth: 100,
}}
>
<IconButton
icon={action.icon}
color={theme.textColorHint}
size={24}
style={{ margin: 0 }}
/>
<Text
style={{
fontSize: 12,
color: theme.textColorHint,
}}
>
{action.name}
</Text>
</Pressable>
</View>
))}
<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 }]}>
{errorName}
</Text>
<View style={{ flexDirection: 'row' }}>
{actions.map(action => (
<View
style={{
borderRadius: 4,
overflow: 'hidden',
margin: 16,
}}
>
<Pressable
android_ripple={{
color: theme.rippleColor,
borderless: false,
}}
onPress={action.onPress}
style={{
justifyContent: 'center',
alignItems: 'center',
paddingBottom: 8,
paddingHorizontal: 20,
minWidth: 100,
}}
>
<IconButton
icon={action.icon}
color={theme.textColorHint}
size={24}
style={{ margin: 0 }}
/>
<Text
style={{
fontSize: 12,
color: theme.textColorHint,
}}
>
{action.name}
</Text>
</Pressable>
</View>
))}
</View>
</View>
);
const styles = StyleSheet.create({
emptyViewContainer: {
flex: 1,
justifyContent: "center",
alignItems: "center",
},
emptyViewIcon: {
fontSize: 45,
},
emptyViewText: {
fontWeight: "bold",
marginTop: 10,
textAlign: "center",
paddingHorizontal: 30,
},
emptyViewContainer: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
emptyViewIcon: {
fontSize: 45,
},
emptyViewText: {
fontWeight: 'bold',
marginTop: 10,
textAlign: 'center',
paddingHorizontal: 30,
},
});
-2
View File
@@ -1,2 +0,0 @@
import { NativeModules } from "react-native";
module.exports = NativeModules.FullScreen;
+42
View File
@@ -0,0 +1,42 @@
import React from 'react';
import { Pressable, StyleSheet, ViewPropTypes } from 'react-native';
import PropTypes from 'prop-types';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
export const IconButton = ({
icon,
size,
color,
onPress,
containerStyle,
theme,
}) => (
<Pressable
style={[styles.container, containerStyle]}
android_ripple={{ color: theme.rippleColor, borderless: true, radius: 20 }}
onPress={onPress}
>
<MaterialCommunityIcons name={icon} size={size} color={color} />
</Pressable>
);
IconButton.defaultProps = {
size: 24,
color: '#FFFFFF',
};
IconButton.propTypes = {
icon: PropTypes.string.isRequired,
size: PropTypes.number.isRequired,
color: PropTypes.string,
onPress: PropTypes.func,
containerStyle: ViewPropTypes.style,
theme: PropTypes.object.isRequired,
};
const styles = StyleSheet.create({
container: {
margin: 6,
},
});
@@ -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 { ThemeType } from '../../theme/types';
type Props = {
name: string;
color?: string;
size?: number;
disabled?: boolean;
padding?: number;
onPress?: () => void;
theme: ThemeType;
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,
},
});
+101 -113
View File
@@ -1,143 +1,131 @@
import React from "react";
import { Pressable, StyleSheet, Text, View } from "react-native";
import React from 'react';
import { Pressable, StyleSheet, Text, View } from 'react-native';
import { List as PaperList, Divider as PaperDivider } from "react-native-paper";
import MaterialCommunityIcons from "react-native-vector-icons/MaterialCommunityIcons";
import { List as PaperList, Divider as PaperDivider } from 'react-native-paper';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
const ListSection = ({ children }) => (
<PaperList.Section style={styles.listSection}>{children}</PaperList.Section>
<PaperList.Section style={styles.listSection}>{children}</PaperList.Section>
);
const ListSubHeader = ({ children, theme }) => (
<PaperList.Subheader
style={[styles.listSubHeader, { color: theme.colorAccent }]}
>
{children}
</PaperList.Subheader>
<PaperList.Subheader
style={[styles.listSubHeader, { color: theme.colorAccent }]}
>
{children}
</PaperList.Subheader>
);
const ListItem = ({
title,
description,
icon,
onPress,
theme,
right,
iconColor,
titleStyle,
style,
title,
description,
icon,
onPress,
theme,
right,
iconColor,
titleStyle,
style,
}) => (
<PaperList.Item
title={title}
style={style}
titleStyle={[{ color: theme.textColorPrimary }, titleStyle]}
description={description}
descriptionStyle={{ color: theme.textColorSecondary }}
descriptionNumberOfLines={1}
left={() => (
<View style={{ justifyContent: "center" }}>
{icon && (
<PaperList.Icon
color={theme.colorAccent}
icon={icon}
style={{
marginVertical: 0,
}}
/>
)}
</View>
<PaperList.Item
title={title}
style={style}
titleStyle={[{ color: theme.textColorPrimary }, titleStyle]}
description={description}
descriptionStyle={{ color: theme.textColorSecondary }}
descriptionNumberOfLines={1}
left={() => (
<View style={{ justifyContent: 'center' }}>
{icon && (
<PaperList.Icon
color={theme.colorAccent}
icon={icon}
style={{
marginVertical: 0,
}}
/>
)}
right={() =>
right && (
<MaterialCommunityIcons
name={right}
color={iconColor || "#47a84a"}
size={23}
style={{ marginRight: 16, textAlignVertical: "center" }}
/>
)
}
onPress={onPress}
rippleColor={theme.rippleColor}
/>
</View>
)}
right={() =>
right && (
<MaterialCommunityIcons
name={right}
color={iconColor || '#47a84a'}
size={23}
style={{ marginRight: 16, textAlignVertical: 'center' }}
/>
)
}
onPress={onPress}
rippleColor={theme.rippleColor}
/>
);
const Divider = ({ theme }) => (
<PaperDivider style={{ backgroundColor: theme.dividerColor, height: 1 }} />
<PaperDivider style={{ backgroundColor: theme.dividerColor, height: 1 }} />
);
const InfoItem = ({ title, icon, theme }) => (
<PaperList.Item
title={title}
titleStyle={{ color: theme.textColorSecondary, fontSize: 14 }}
titleNumberOfLines={5}
left={() =>
icon && (
<PaperList.Icon
color={theme.textColorSecondary}
icon={icon}
style={{ marginVertical: 0 }}
/>
)
}
/>
<PaperList.Item
title={title}
titleStyle={{ color: theme.textColorSecondary, fontSize: 14 }}
titleNumberOfLines={5}
left={() =>
icon && <PaperList.Icon color={theme.textColorSecondary} icon={icon} />
}
/>
);
const ColorItem = ({ title, description, theme, onPress }) => (
<Pressable
style={{
padding: 16,
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
}}
android_ripple={{ color: theme.rippleColor }}
onPress={onPress}
>
<View>
<Text style={{ color: theme.textColorPrimary, fontSize: 16 }}>
{title}
</Text>
<Text style={{ color: theme.textColorSecondary }}>
{description}
</Text>
</View>
<View
style={{
backgroundColor: description,
height: 24,
width: 24,
borderRadius: 50,
marginRight: 16,
}}
/>
</Pressable>
<Pressable
style={{
padding: 16,
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
}}
android_ripple={{ color: theme.rippleColor }}
onPress={onPress}
>
<View>
<Text style={{ color: theme.textColorPrimary, fontSize: 16 }}>
{title}
</Text>
<Text style={{ color: theme.textColorSecondary }}>{description}</Text>
</View>
<View
style={{
backgroundColor: description,
height: 24,
width: 24,
borderRadius: 50,
marginRight: 16,
}}
/>
</Pressable>
);
const Icon = ({ icon, theme }) => (
<PaperList.Icon
color={theme.colorAccent}
icon={icon}
style={{ margin: 0 }}
/>
<PaperList.Icon color={theme.colorAccent} icon={icon} style={{ margin: 0 }} />
);
export const List = {
Section: ListSection,
SubHeader: ListSubHeader,
Item: ListItem,
Divider,
InfoItem,
ColorItem,
Icon,
Section: ListSection,
SubHeader: ListSubHeader,
Item: ListItem,
Divider,
InfoItem,
ColorItem,
Icon,
};
const styles = StyleSheet.create({
listSection: {
flex: 1,
marginVertical: 0,
},
listSubHeader: {
paddingBottom: 5,
},
listSection: {
flex: 1,
marginVertical: 0,
},
listSubHeader: {
paddingBottom: 5,
},
});
+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 { ThemeType } from '../../theme/types';
interface ListItemProps {
title: string;
description?: string | null;
icon?: string;
onPress?: () => void;
theme: ThemeType;
}
const Section = ({ children }: { children: ReactNode }) => (
<PaperList.Section style={styles.listSection}>{children}</PaperList.Section>
);
const SubHeader = ({
children,
theme,
}: {
children: ReactNode;
theme: ThemeType;
}) => (
<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: ThemeType }) => (
<PaperDivider
style={[styles.divider, { backgroundColor: theme.dividerColor }]}
/>
);
const InfoItem = ({
title,
icon,
theme,
}: {
title: string;
icon: string;
theme: ThemeType;
}) => (
<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,
},
});
+67 -70
View File
@@ -1,80 +1,77 @@
import React from "react";
import { StyleSheet, View, Text, Pressable } from "react-native";
import React from 'react';
import { StyleSheet, View, Text, Pressable } from 'react-native';
import FastImage from "react-native-fast-image";
import FastImage from 'react-native-fast-image';
import { defaultCoverUri } from '../sources/helpers/constants';
import { coverPlaceholderColor } from '../theme/colors';
const ListView = ({
item,
downloadBadge,
unreadBadge,
inLibraryBadge,
theme,
onPress,
isSelected,
onLongPress,
item,
downloadBadge,
unreadBadge,
inLibraryBadge,
theme,
onPress,
isSelected,
onLongPress,
}) => {
return (
<Pressable
android_ripple={{ color: theme.rippleColor }}
style={[
styles.listView,
isSelected && { backgroundColor: theme.rippleColor },
]}
onPress={onPress}
onLongPress={onLongPress}
>
<FastImage
source={{
uri:
item.novelCover && !item.novelCover.startsWith("/")
? item.novelCover
: "https://github.com/LNReader/lnreader-sources/blob/main/src/coverNotAvailable.jpg?raw=true",
}}
style={[
styles.extensionIcon,
inLibraryBadge && { opacity: 0.5 },
]}
/>
<Text
style={[{ color: theme.textColorPrimary }, styles.novelName]}
numberOfLines={1}
>
{item.novelName}
</Text>
<View style={styles.badgeContainer}>
{downloadBadge}
{unreadBadge}
{inLibraryBadge}
</View>
</Pressable>
);
return (
<Pressable
android_ripple={{ color: theme.rippleColor }}
style={[
styles.listView,
isSelected && { backgroundColor: theme.rippleColor },
]}
onPress={onPress}
onLongPress={onLongPress}
>
<FastImage
source={{
uri: item.novelCover,
}}
style={[styles.extensionIcon, inLibraryBadge && { opacity: 0.5 }]}
/>
<Text
style={[{ color: theme.textColorPrimary }, styles.novelName]}
numberOfLines={1}
>
{item.novelName}
</Text>
<View style={styles.badgeContainer}>
{downloadBadge}
{unreadBadge}
{inLibraryBadge}
</View>
</Pressable>
);
};
export default ListView;
const styles = StyleSheet.create({
listView: {
flex: 1,
flexDirection: "row",
alignItems: "center",
paddingVertical: 8,
paddingHorizontal: 12,
},
extensionIcon: {
width: 40,
height: 40,
borderRadius: 4,
},
novelName: {
flex: 1,
marginLeft: 16,
fontSize: 15,
paddingRight: 8,
flexWrap: "wrap",
},
badgeContainer: {
flexDirection: "row",
justifyContent: "flex-end",
alignItems: "flex-end",
},
listView: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 8,
paddingHorizontal: 12,
},
extensionIcon: {
width: 40,
height: 40,
borderRadius: 4,
backgroundColor: coverPlaceholderColor,
},
novelName: {
flex: 1,
marginLeft: 16,
fontSize: 15,
paddingRight: 8,
flexWrap: 'wrap',
},
badgeContainer: {
flexDirection: 'row',
justifyContent: 'flex-end',
alignItems: 'flex-end',
},
});
@@ -0,0 +1,22 @@
import React from 'react';
import { ActivityIndicator, StyleSheet } from 'react-native';
import { ThemeType } from '../../theme/types';
interface Props {
theme: ThemeType;
}
const LoadingMoreIndicator: React.FC<Props> = ({ theme }) => (
<ActivityIndicator color={theme.colorAccent} style={styles.indicator} />
);
export default LoadingMoreIndicator;
const styles = StyleSheet.create({
indicator: {
padding: 32,
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
});
@@ -1,8 +1,8 @@
import React from "react";
import { ActivityIndicator, View } from "react-native";
import React from 'react';
import { ActivityIndicator, View } from 'react-native';
export const LoadingScreen = ({ theme }) => (
<View style={{ flex: 1, justifyContent: "center" }}>
<ActivityIndicator size={50} color={theme.colorAccent} />
</View>
<View style={{ flex: 1, justifyContent: 'center' }}>
<ActivityIndicator size={50} color={theme.colorAccent} />
</View>
);
@@ -0,0 +1,22 @@
import React from 'react';
import { StyleSheet, ActivityIndicator } from 'react-native';
import { ThemeType } from '../../theme/types';
const LoadingScreen: React.FC<{ theme: ThemeType }> = ({ theme }) => (
<ActivityIndicator
size={50}
color={theme.colorAccent}
style={styles.indicator}
/>
);
export default LoadingScreen;
const styles = StyleSheet.create({
indicator: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
});
-80
View File
@@ -1,80 +0,0 @@
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";
const NewUpdateDialog = ({ newVersion }) => {
const [newUpdateDialog, showNewUpdateDialog] = useState(true);
const theme = useTheme();
return (
<Portal>
<Modal
visible={newUpdateDialog}
onDismiss={() => showNewUpdateDialog(false)}
contentContainerStyle={[
styles.containerStyle,
{ backgroundColor: theme.colorPrimaryDark },
]}
>
<Text
style={[
styles.modalHeader,
{ color: theme.textColorPrimary },
]}
>
{`New update available ${newVersion.tag_name}`}
</Text>
<Text style={{ color: theme.textColorSecondary, fontSize: 16 }}>
{newVersion.body}
</Text>
<View
style={{
flexDirection: "row",
marginTop: 30,
justifyContent: "flex-end",
}}
>
<Button
labelStyle={{
color: theme.colorAccent,
letterSpacing: 0,
textTransform: "none",
}}
theme={{ colors: { primary: theme.colorAccent } }}
onPress={() => showNewUpdateDialog(false)}
>
Cancel
</Button>
<Button
labelStyle={{
color: theme.colorAccent,
letterSpacing: 0,
textTransform: "none",
}}
theme={{ colors: { primary: theme.colorAccent } }}
onPress={() => Linking.openURL(newVersion.downloadUrl)}
>
Install
</Button>
</View>
</Modal>
</Portal>
);
};
export default NewUpdateDialog;
const styles = StyleSheet.create({
containerStyle: {
padding: 20,
margin: 20,
borderRadius: 6,
},
modalHeader: {
fontSize: 20,
marginBottom: 10,
},
});
+97
View File
@@ -0,0 +1,97 @@
import React, { useState } from 'react';
import { StyleSheet, View, Text, Dimensions } from 'react-native';
import { Portal, Modal } from 'react-native-paper';
import * as Linking from 'expo-linking';
import { useTheme } from '../hooks/reduxHooks';
import { ScrollView } from 'react-native-gesture-handler';
import Button, { ButtonVariation } from './Button/Button';
import { getString } from '@strings/translations';
import Markdown from 'react-native-markdown-display';
interface NewUpdateDialogProps {
newVersion: {
tag_name: string;
body: string;
downloadUrl: string;
};
}
const NewUpdateDialog: React.FC<NewUpdateDialogProps> = ({ newVersion }) => {
const [newUpdateDialog, showNewUpdateDialog] = useState(true);
const theme = useTheme();
const modalHeight = Dimensions.get('window').height / 2;
return (
<Portal>
<Modal
visible={newUpdateDialog}
onDismiss={() => showNewUpdateDialog(false)}
contentContainerStyle={[
styles.containerStyle,
{ backgroundColor: theme.colorPrimaryDark },
]}
>
<Text style={[styles.modalHeader, { color: theme.textColorPrimary }]}>
{`${getString('common.newUpdateAvailable')} ${newVersion.tag_name}`}
</Text>
<ScrollView style={{ height: modalHeight }}>
<Markdown
style={{
body: {
color: theme.textColorSecondary,
lineHeight: 20,
paddingVertical: 8,
},
hr: {
backgroundColor: theme.dividerColor,
marginVertical: 16,
},
code_inline: {
backgroundColor: theme.colorPrimaryDark,
},
}}
>
{newVersion.body}
</Markdown>
</ScrollView>
<View style={styles.buttonCtn}>
<Button
title={getString('common.cancel')}
theme={theme}
variation={ButtonVariation.CLEAR}
onPress={() => showNewUpdateDialog(false)}
/>
<Button
title={getString('common.install')}
theme={theme}
variation={ButtonVariation.CLEAR}
onPress={() => Linking.openURL(newVersion.downloadUrl)}
/>
</View>
</Modal>
</Portal>
);
};
export default NewUpdateDialog;
const styles = StyleSheet.create({
containerStyle: {
padding: 20,
margin: 20,
borderRadius: 6,
},
modalHeader: {
fontWeight: 'bold',
fontSize: 20,
marginBottom: 16,
},
body: {},
buttonCtn: {
flexDirection: 'row',
marginTop: 16,
justifyContent: 'flex-end',
},
});
+275 -284
View File
@@ -1,322 +1,313 @@
import React, { useCallback } from "react";
import React, { memo, useCallback } from 'react';
import {
StyleSheet,
View,
Text,
useWindowDimensions,
Pressable,
} from "react-native";
StyleSheet,
View,
Text,
useWindowDimensions,
Pressable,
} from 'react-native';
import { LinearGradient } from "expo-linear-gradient";
import FastImage from "react-native-fast-image";
import { LinearGradient } from 'expo-linear-gradient';
import FastImage from 'react-native-fast-image';
import ListView from "./ListView";
import ListView from './ListView';
import { getDeviceOrientation } from "../services/utils/helpers";
import { useSettings } from "../hooks/reduxHooks";
import { useDeviceOrientation } from '../services/utils/helpers';
import { useSettings } from '../hooks/reduxHooks';
import { coverPlaceholderColor } from '../theme/colors';
const NovelCover = ({
item,
onPress,
libraryStatus,
theme,
isSelected,
onLongPress,
selectedNovels,
item,
onPress,
libraryStatus,
theme,
isSelected,
onLongPress,
selectedNovels,
}) => {
const { displayMode, novelsPerRow, showDownloadBadges, showUnreadBadges } =
useSettings();
const { displayMode, novelsPerRow, showDownloadBadges, showUnreadBadges } =
useSettings();
const window = useWindowDimensions();
const window = useWindowDimensions();
const orientation = getDeviceOrientation();
const orientation = useDeviceOrientation();
const getNovelsPerRow = () =>
orientation === "landscape" ? 6 : novelsPerRow;
const getNovelsPerRow = () =>
orientation === 'landscape' ? 6 : novelsPerRow;
const getHeight = useCallback(
() => (window.width / getNovelsPerRow()) * (4 / 3),
[]
);
const getHeight = useCallback(
() => (window.width / getNovelsPerRow()) * (4 / 3),
// eslint-disable-next-line react-hooks/exhaustive-deps
[],
);
const selectNovel = () => onLongPress && onLongPress(item.novelId);
const selectNovel = () => onLongPress && onLongPress(item.novelId);
return displayMode !== 2 ? (
<View
style={[
{
flex: 1 / getNovelsPerRow(),
borderRadius: 6,
overflow: "hidden",
margin: 2,
},
isSelected && {
backgroundColor: theme.colorAccent,
opacity: 0.8,
},
]}
>
<Pressable
android_ripple={{ color: theme.colorAccent }}
style={styles.opac}
onPress={
selectedNovels && selectedNovels.length > 0
? selectNovel
: onPress
}
onLongPress={selectNovel}
>
<>
<View style={styles.badgeContainer}>
{libraryStatus && <InLibraryBadge theme={theme} />}
{showDownloadBadges && item.chaptersDownloaded && (
<DownloadBadge
showUnreadBadges={showUnreadBadges}
chaptersDownloaded={item.chaptersDownloaded}
chaptersUnread={item.chaptersUnread}
/>
)}
{showUnreadBadges && item.chaptersUnread && (
<UnreadBadge
theme={theme}
chaptersDownloaded={item.chaptersDownloaded}
chaptersUnread={item.chaptersUnread}
showDownloadBadges={showDownloadBadges}
/>
)}
</View>
<FastImage
source={{
uri:
item.novelCover &&
!item.novelCover.startsWith("/")
? item.novelCover
: "https://github.com/LNReader/lnreader-sources/blob/main/src/coverNotAvailable.jpg?raw=true",
}}
style={[
{ height: getHeight(), borderRadius: 4 },
libraryStatus && { opacity: 0.5 },
]}
/>
<View style={styles.compactTitleContainer}>
{displayMode === 0 && (
<CompactTitle novelName={item.novelName} />
)}
</View>
{displayMode === 1 && (
<ComfortableTitle
novelName={item.novelName}
theme={theme}
/>
)}
</>
</Pressable>
const uri = item.novelCover;
return displayMode !== 2 ? (
<View
style={[
{
flex: 1 / getNovelsPerRow(),
borderRadius: 6,
overflow: 'hidden',
margin: 2,
},
isSelected && {
backgroundColor: theme.colorAccent,
opacity: 0.8,
},
]}
>
<Pressable
android_ripple={{ color: theme.colorAccent }}
style={styles.opac}
onPress={
selectedNovels && selectedNovels.length > 0 ? selectNovel : onPress
}
onLongPress={selectNovel}
>
<View style={styles.badgeContainer}>
{libraryStatus && <InLibraryBadge theme={theme} />}
{showDownloadBadges && item.chaptersDownloaded && (
<DownloadBadge
showUnreadBadges={showUnreadBadges}
chaptersDownloaded={item.chaptersDownloaded}
chaptersUnread={item.chaptersUnread}
/>
)}
{showUnreadBadges && item.chaptersUnread && (
<UnreadBadge
theme={theme}
chaptersDownloaded={item.chaptersDownloaded}
chaptersUnread={item.chaptersUnread}
showDownloadBadges={showDownloadBadges}
/>
)}
</View>
) : (
<ListView
item={item}
downloadBadge={
showDownloadBadges &&
item.chaptersDownloaded && (
<DownloadBadge
showUnreadBadges={showUnreadBadges}
chaptersDownloaded={item.chaptersDownloaded}
chaptersUnread={item.chaptersUnread}
/>
)
}
unreadBadge={
showUnreadBadges &&
item.chaptersUnread && (
<UnreadBadge
theme={theme}
chaptersDownloaded={item.chaptersDownloaded}
chaptersUnread={item.chaptersUnread}
showDownloadBadges={showDownloadBadges}
/>
)
}
inLibraryBadge={libraryStatus && <InLibraryBadge theme={theme} />}
theme={theme}
onPress={
selectedNovels && selectedNovels.length > 0
? selectNovel
: onPress
}
onLongPress={selectNovel}
isSelected={isSelected}
<FastImage
source={{ uri }}
style={[
{
height: getHeight(),
borderRadius: 4,
backgroundColor: coverPlaceholderColor,
},
libraryStatus && { opacity: 0.5 },
]}
/>
);
<View style={styles.compactTitleContainer}>
{displayMode === 0 && <CompactTitle novelName={item.novelName} />}
</View>
{displayMode === 1 && (
<ComfortableTitle novelName={item.novelName} theme={theme} />
)}
</Pressable>
</View>
) : (
<ListView
item={item}
downloadBadge={
showDownloadBadges &&
item.chaptersDownloaded && (
<DownloadBadge
showUnreadBadges={showUnreadBadges}
chaptersDownloaded={item.chaptersDownloaded}
chaptersUnread={item.chaptersUnread}
/>
)
}
unreadBadge={
showUnreadBadges &&
item.chaptersUnread && (
<UnreadBadge
theme={theme}
chaptersDownloaded={item.chaptersDownloaded}
chaptersUnread={item.chaptersUnread}
showDownloadBadges={showDownloadBadges}
/>
)
}
inLibraryBadge={libraryStatus && <InLibraryBadge theme={theme} />}
theme={theme}
onPress={
selectedNovels && selectedNovels.length > 0 ? selectNovel : onPress
}
onLongPress={selectNovel}
isSelected={isSelected}
/>
);
};
export default NovelCover;
export default memo(NovelCover);
const ComfortableTitle = ({ theme, novelName }) => (
<Text
numberOfLines={2}
style={[
styles.title,
{
color: theme.textColorPrimary,
padding: 4,
},
]}
>
{novelName}
</Text>
<Text
numberOfLines={2}
style={[
styles.title,
{
color: theme.textColorPrimary,
padding: 4,
},
]}
>
{novelName}
</Text>
);
const CompactTitle = ({ novelName }) => (
<View style={styles.titleContainer}>
<LinearGradient
colors={["transparent", "rgba(0,0,0,0.7)"]}
style={styles.linearGradient}
>
<Text
numberOfLines={2}
style={[
styles.title,
{
color: "rgba(255,255,255,1)",
textShadowColor: "rgba(0, 0, 0, 0.75)",
textShadowOffset: { width: -1, height: 1 },
textShadowRadius: 10,
},
]}
>
{novelName}
</Text>
</LinearGradient>
</View>
<View style={styles.titleContainer}>
<LinearGradient
colors={['transparent', 'rgba(0,0,0,0.7)']}
style={styles.linearGradient}
>
<Text
numberOfLines={2}
style={[
styles.title,
{
color: 'rgba(255,255,255,1)',
textShadowColor: 'rgba(0, 0, 0, 0.75)',
textShadowOffset: { width: -1, height: 1 },
textShadowRadius: 10,
},
]}
>
{novelName}
</Text>
</LinearGradient>
</View>
);
const InLibraryBadge = ({ theme }) => (
<Text
style={[
styles.inLibraryBadge,
{
backgroundColor: theme.colorAccent,
color: theme.colorButtonText,
borderRadius: 4,
},
]}
>
In library
</Text>
<Text
style={[
styles.inLibraryBadge,
{
backgroundColor: theme.colorAccent,
color: theme.colorButtonText,
borderRadius: 4,
},
]}
>
In library
</Text>
);
const UnreadBadge = ({
chaptersDownloaded,
chaptersUnread,
showDownloadBadges,
theme,
chaptersDownloaded,
chaptersUnread,
showDownloadBadges,
theme,
}) => (
<Text
style={[
styles.unreadBadge,
!chaptersDownloaded && {
borderTopLeftRadius: 4,
borderBottomLeftRadius: 4,
},
!showDownloadBadges && {
borderRadius: 4,
},
{
backgroundColor: theme.colorAccent,
color: theme.colorButtonText,
},
]}
>
{chaptersUnread}
</Text>
<Text
style={[
styles.unreadBadge,
!chaptersDownloaded && {
borderTopLeftRadius: 4,
borderBottomLeftRadius: 4,
},
!showDownloadBadges && {
borderRadius: 4,
},
{
backgroundColor: theme.colorAccent,
color: theme.colorButtonText,
},
]}
>
{chaptersUnread}
</Text>
);
const DownloadBadge = ({
chaptersDownloaded,
showUnreadBadges,
chaptersUnread,
chaptersDownloaded,
showUnreadBadges,
chaptersUnread,
}) => (
<Text
style={[
styles.downloadBadge,
!chaptersUnread && {
borderTopRightRadius: 4,
borderBottomRightRadius: 4,
},
!showUnreadBadges && {
borderRadius: 4,
},
]}
>
{chaptersDownloaded}
</Text>
<Text
style={[
styles.downloadBadge,
!chaptersUnread && {
borderTopRightRadius: 4,
borderBottomRightRadius: 4,
},
!showUnreadBadges && {
borderRadius: 4,
},
]}
>
{chaptersDownloaded}
</Text>
);
const styles = StyleSheet.create({
titleContainer: {
flex: 1,
borderRadius: 4,
},
title: {
fontFamily: "pt-sans-bold",
fontSize: 14,
padding: 8,
},
linearGradient: {
borderRadius: 4,
},
opac: {
padding: 4.8,
borderRadius: 4,
flex: 1,
},
extensionIcon: {
width: 42,
height: 42,
borderRadius: 4,
},
listView: {
flex: 1,
paddingVertical: 8,
paddingHorizontal: 12,
flexDirection: "row",
alignItems: "center",
borderRadius: 4,
},
titleContainer: {
flex: 1,
borderRadius: 4,
},
title: {
fontFamily: 'pt-sans-bold',
fontSize: 14,
padding: 8,
},
linearGradient: {
borderRadius: 4,
},
opac: {
padding: 4.8,
borderRadius: 4,
flex: 1,
},
extensionIcon: {
width: 42,
height: 42,
borderRadius: 4,
},
listView: {
flex: 1,
paddingVertical: 8,
paddingHorizontal: 12,
flexDirection: 'row',
alignItems: 'center',
borderRadius: 4,
},
downloadBadge: {
backgroundColor: "#2979FF",
color: "#FFFFFF",
borderTopLeftRadius: 4,
borderBottomLeftRadius: 4,
paddingTop: 2,
paddingHorizontal: 4,
fontSize: 12,
},
unreadBadge: {
color: "#FFFFFF",
borderTopRightRadius: 4,
borderBottomRightRadius: 4,
paddingTop: 2,
paddingHorizontal: 4,
fontSize: 12,
},
inLibraryBadge: {
paddingVertical: 2,
paddingHorizontal: 4,
fontSize: 12,
},
compactTitleContainer: {
position: "absolute",
bottom: 4,
left: 4,
right: 4,
},
badgeContainer: {
position: "absolute",
zIndex: 1,
top: 8,
left: 8,
flexDirection: "row",
},
downloadBadge: {
backgroundColor: '#2979FF',
color: '#FFFFFF',
borderTopLeftRadius: 4,
borderBottomLeftRadius: 4,
paddingTop: 2,
paddingHorizontal: 4,
fontSize: 12,
},
unreadBadge: {
color: '#FFFFFF',
borderTopRightRadius: 4,
borderBottomRightRadius: 4,
paddingTop: 2,
paddingHorizontal: 4,
fontSize: 12,
},
inLibraryBadge: {
paddingVertical: 2,
paddingHorizontal: 4,
fontSize: 12,
},
compactTitleContainer: {
position: 'absolute',
bottom: 4,
left: 4,
right: 4,
},
badgeContainer: {
position: 'absolute',
zIndex: 1,
top: 10,
left: 10,
flexDirection: 'row',
},
});
+46 -46
View File
@@ -1,61 +1,61 @@
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 { getDeviceOrientation } from "../services/utils/helpers";
import { useSettings } from '../hooks/reduxHooks';
import { useDeviceOrientation } from '../services/utils/helpers';
const NovelList = ({
data,
onScroll,
onEndReached,
renderItem,
refreshControl,
ListEmptyComponent,
ListFooterComponent,
data,
onScroll,
onEndReached,
renderItem,
refreshControl,
ListEmptyComponent,
ListFooterComponent,
}) => {
const { displayMode, novelsPerRow } = useSettings();
const { displayMode, novelsPerRow } = useSettings();
const orientation = getDeviceOrientation();
const orientation = useDeviceOrientation();
const getNovelsPerRow = () => {
if (displayMode === 2) {
return 1;
}
const getNovelsPerRow = () => {
if (displayMode === 2) {
return 1;
}
if (orientation === "landscape") {
return 6;
} else {
return novelsPerRow;
}
};
if (orientation === 'landscape') {
return 6;
} else {
return novelsPerRow;
}
};
const keyExtractor = useCallback((item) => item.novelUrl, []);
const keyExtractor = useCallback(item => item.sourceId + item.novelUrl, []);
return (
<FlatList
contentContainerStyle={[
styles.flatListCont,
displayMode !== 2 && { paddingHorizontal: 4 },
]}
numColumns={getNovelsPerRow()}
key={[orientation, getNovelsPerRow()]}
data={data}
keyExtractor={keyExtractor}
renderItem={renderItem}
refreshControl={refreshControl}
ListEmptyComponent={ListEmptyComponent}
ListFooterComponent={ListFooterComponent}
onScroll={onScroll}
onEndReached={onEndReached}
/>
);
return (
<FlatList
contentContainerStyle={[
styles.flatListCont,
displayMode !== 2 && { paddingHorizontal: 4 },
]}
numColumns={getNovelsPerRow()}
key={[orientation, getNovelsPerRow()]}
data={data}
keyExtractor={keyExtractor}
renderItem={renderItem}
refreshControl={refreshControl}
ListEmptyComponent={ListEmptyComponent}
ListFooterComponent={ListFooterComponent}
onScroll={onScroll}
onEndReached={onEndReached}
/>
);
};
export default NovelList;
const styles = StyleSheet.create({
flatListCont: {
flexGrow: 1,
paddingVertical: 8,
},
flatListCont: {
flexGrow: 1,
paddingBottom: 56,
},
});
+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,
},
});
+31 -31
View File
@@ -1,40 +1,40 @@
import React from "react";
import { StyleSheet, View, Text } from "react-native";
import { RadioButton as MaterialRadioButton } from "react-native-paper";
import React from 'react';
import { StyleSheet, View, Text } from 'react-native';
import { RadioButton as MaterialRadioButton } from 'react-native-paper';
export const RadioButtonGroup = ({ children, value, onValueChange }) => (
<MaterialRadioButton.Group onValueChange={onValueChange} value={value}>
{children}
</MaterialRadioButton.Group>
<MaterialRadioButton.Group onValueChange={onValueChange} value={value}>
{children}
</MaterialRadioButton.Group>
);
export const RadioButton = ({ value, label, theme, labelStyle }) => (
<View style={styles.radioButtonContainer}>
<MaterialRadioButton
value={value}
color={theme.colorAccent}
uncheckedColor={theme.textColorSecondary}
/>
<Text
style={[
styles.radioButtonLabel,
{ color: theme.textColorPrimary },
labelStyle,
]}
>
{label}
</Text>
</View>
<View style={styles.radioButtonContainer}>
<MaterialRadioButton
value={value}
color={theme.colorAccent}
uncheckedColor={theme.textColorSecondary}
/>
<Text
style={[
styles.radioButtonLabel,
{ color: theme.textColorPrimary },
labelStyle,
]}
>
{label}
</Text>
</View>
);
const styles = StyleSheet.create({
radioButtonContainer: {
flexDirection: "row",
alignItems: "center",
paddingVertical: 8,
},
radioButtonLabel: {
marginLeft: 16,
fontSize: 16,
},
radioButtonContainer: {
flexDirection: 'row',
alignItems: 'center',
paddingVertical: 8,
},
radioButtonLabel: {
marginLeft: 16,
fontSize: 16,
},
});
+44
View File
@@ -0,0 +1,44 @@
import React from 'react';
import { Pressable, StyleSheet, Text } from 'react-native';
import { RadioButton as PaperRadioButton } from 'react-native-paper';
export const RadioButton = ({
label,
status,
onPress,
style,
labelStyle,
theme,
}) => (
<Pressable
android_ripple={{ color: theme.rippleColor }}
style={[styles.pressable, style]}
onPress={onPress}
>
<PaperRadioButton
status={status ? 'checked' : 'unchecked'}
onPress={onPress}
color={theme.colorAccent}
uncheckedColor={theme.textColorSecondary}
/>
<Text
style={[labelStyle, { color: theme.textColorPrimary, marginLeft: 12 }]}
>
{label}
</Text>
</Pressable>
);
const styles = StyleSheet.create({
pressable: {
paddingHorizontal: 16,
paddingVertical: 6,
flexDirection: 'row',
alignItems: 'center',
},
icon: {
position: 'absolute',
left: 24,
alignSelf: 'center',
},
});
+88 -88
View File
@@ -1,97 +1,97 @@
import React, { useRef } from "react";
import { StyleSheet, View, TextInput, Text, StatusBar } from "react-native";
import React, { useRef } from 'react';
import {
StyleSheet,
View,
TextInput,
StatusBar,
Pressable,
} from 'react-native';
import { TouchableRipple, IconButton } from "react-native-paper";
import { Row } from "../Common";
import { Row } from '../Common';
import { IconButton } from '../IconButton/IconButton';
export const Searchbar = ({
theme,
placeholder,
searchText,
backAction,
onBackAction,
clearSearchbar,
onChangeText,
onSubmitEditing,
actions,
theme,
placeholder,
searchText,
backAction,
onBackAction,
clearSearchbar,
onChangeText,
onSubmitEditing,
actions,
}) => {
const searchRef = useRef(null);
const searchRef = useRef(null);
return (
<TouchableRipple
borderless
onPress={() => searchRef.current.focus()}
style={[
styles.searchbarContainer,
{ backgroundColor: theme.searchBarColor },
]}
rippleColor={theme.rippleColor}
>
<View style={styles.container}>
<Row style={{ flex: 1 }}>
{backAction && (
<IconButton
icon={backAction}
color={theme.textColorSecondary}
style={{ marginLeft: 0 }}
size={23}
onPress={onBackAction}
/>
)}
<TextInput
ref={searchRef}
style={{
fontSize: 16,
color: theme.textColorSecondary,
}}
placeholder={placeholder}
placeholderTextColor={theme.textColorSecondary}
onChangeText={onChangeText}
onSubmitEditing={onSubmitEditing}
defaultValue={searchText}
/>
</Row>
{searchText !== "" && (
<IconButton
icon="close"
color={theme.textColorSecondary}
style={{ marginRight: 0 }}
size={23}
onPress={clearSearchbar}
/>
)}
{actions &&
actions.map((action, index) => (
<IconButton
key={index}
icon={action.icon}
color={action.color ?? theme.textColorSecondary}
style={{ marginRight: 0 }}
size={23}
onPress={action.onPress}
/>
))}
</View>
</TouchableRipple>
);
return (
<View style={styles.searchBar}>
<Pressable
onPress={() => searchRef.current.focus()}
style={[styles.pressable, { backgroundColor: theme.searchBarColor }]}
android_ripple={{ color: theme.rippleColor }}
>
<Row style={{ flex: 1 }}>
{backAction && (
<IconButton
icon={backAction}
color={theme.textColorPrimary}
onPress={onBackAction}
theme={theme}
containerStyle={{ marginLeft: 0 }}
/>
)}
<TextInput
ref={searchRef}
style={{
flex: 1,
fontSize: 16,
color: theme.textColorPrimary,
marginLeft: 4,
}}
placeholder={placeholder}
placeholderTextColor={theme.textColorSecondary}
onChangeText={onChangeText}
onSubmitEditing={onSubmitEditing}
defaultValue={searchText}
/>
</Row>
{searchText !== '' && (
<IconButton
icon="close"
color={theme.textColorPrimary}
onPress={clearSearchbar}
theme={theme}
/>
)}
{actions &&
actions.map((action, index) => (
<IconButton
key={index}
icon={action.icon}
color={action.color ?? theme.textColorPrimary}
onPress={action.onPress}
theme={theme}
containerStyle={{ marginRight: 0, marginLeft: 16 }}
/>
))}
</Pressable>
</View>
);
};
const styles = StyleSheet.create({
searchbarContainer: {
marginTop: StatusBar.currentHeight + 8,
height: 46,
flexDirection: "row",
alignItems: "center",
paddingHorizontal: 12,
marginBottom: 6,
borderRadius: 8,
marginHorizontal: 12,
elevation: 2,
},
container: {
flex: 1,
flexDirection: "row",
justifyContent: "space-between",
alignItems: "center",
},
searchBar: {
marginTop: StatusBar.currentHeight + 8,
marginBottom: 8,
marginHorizontal: 16,
height: 46,
borderRadius: 50,
overflow: 'hidden',
},
pressable: {
flexDirection: 'row',
justifyContent: 'space-between',
alignItems: 'center',
paddingHorizontal: 16,
},
});
+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 '../IconButtonV2/IconButtonV2';
import { ThemeType } from '../../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: ThemeType;
}
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',
},
});
+43 -46
View File
@@ -1,56 +1,53 @@
import React from "react";
import { Pressable, StyleSheet, View, Text } from "react-native";
import React from 'react';
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 }) => (
<Pressable
android_ripple={{ color: theme.rippleColor }}
style={styles.container}
onPress={onPress}
>
<View style={{ flexDirection: "row", flex: 1 }}>
{icon && (
<List.Icon
color={theme.colorAccent}
icon={icon}
style={{ margin: 0 }}
/>
)}
<View style={[icon && { marginLeft: 16 }]}>
<Text
style={{
color: theme.textColorPrimary,
fontSize: 16,
flex: 1,
textAlignVertical: "center",
}}
>
{label}
</Text>
{description && (
<Text style={{ color: theme.textColorSecondary }}>
{description}
</Text>
)}
</View>
</View>
<Switch
value={value}
onValueChange={onPress}
color={theme.colorAccent}
style={{ marginRight: 8 }}
<Pressable
android_ripple={{ color: theme.rippleColor }}
style={[styles.container, { paddingVertical: description ? 16 : 12 }]}
onPress={onPress}
>
<View style={{ flexDirection: 'row', flex: 1 }}>
{icon && (
<List.Icon
color={theme.colorAccent}
icon={icon}
style={{ margin: 0 }}
/>
</Pressable>
)}
<View style={[icon && { marginLeft: 16 }]}>
<Text
style={{
color: theme.textColorPrimary,
fontSize: 16,
flex: 1,
textAlignVertical: 'center',
}}
>
{label}
</Text>
{description && (
<Text style={{ color: theme.textColorSecondary }}>{description}</Text>
)}
</View>
</View>
<Switch
value={value}
onValueChange={onPress}
color={theme.colorAccent}
style={{ marginLeft: 8 }}
/>
</Pressable>
);
export default SwitchSetting;
const styles = StyleSheet.create({
container: {
paddingHorizontal: 16,
paddingVertical: 12,
flexDirection: "row",
justifyContent: "space-between",
},
container: {
paddingHorizontal: 16,
flexDirection: 'row',
justifyContent: 'space-between',
},
});
+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 { ThemeType } from '../../theme/types';
interface SwitchItemProps {
value: boolean;
label: string;
description?: string;
onPress: () => void;
theme: ThemeType;
}
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,
justifyContent: 'center',
},
label: {
fontSize: 16,
},
switch: {
marginLeft: 8,
},
});
+172
View File
@@ -0,0 +1,172 @@
import React from 'react';
import { View, Text, /* StyleSheet, */ Pressable } from 'react-native';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
export const ThemePicker = ({ theme, currentTheme, onPress }) => (
<View
style={{
justifyContent: 'center',
alignItems: 'center',
marginRight: 16,
}}
>
<View
style={{
backgroundColor: theme.colorPrimaryDark,
borderWidth: 3.6,
borderColor:
currentTheme.id === theme.id
? theme.colorAccent
: currentTheme.colorPrimaryDark,
width: 95,
height: 140,
borderRadius: 16,
overflow: 'hidden',
elevation: 1,
}}
>
<Pressable style={{ flex: 1 }} onPress={onPress}>
{currentTheme.id === theme.id && (
<MaterialCommunityIcons
name="check"
color={theme.colorButtonText}
size={15}
style={{
backgroundColor: theme.colorAccent,
position: 'absolute',
top: 5,
right: 5,
elevation: 2,
borderRadius: 50,
padding: 1.6,
}}
/>
)}
<View
style={{
height: 20,
backgroundColor: theme.colorPrimary,
elevation: 1,
justifyContent: 'center',
}}
>
<View
style={{
backgroundColor: theme.textColorPrimary,
width: 44,
height: 10,
marginLeft: 8,
borderRadius: 50,
}}
/>
</View>
<View style={{ padding: 8 }}>
<View
style={{
height: 18,
backgroundColor: theme.textColorSecondary,
borderRadius: 4,
}}
/>
<View style={{ paddingVertical: 4, flexDirection: 'row' }}>
<View
style={{
height: 10,
width: 44,
backgroundColor: theme.textColorPrimary,
borderRadius: 50,
}}
/>
<View
style={{
height: 10,
width: 16,
marginLeft: 4,
backgroundColor: theme.colorAccent,
borderRadius: 50,
}}
/>
</View>
<View style={{ flexDirection: 'row' }}>
<View
style={{
height: 10,
width: 24,
backgroundColor: theme.textColorSecondary,
borderRadius: 50,
}}
/>
<View
style={{
height: 10,
width: 24,
backgroundColor: theme.textColorSecondary,
borderRadius: 50,
marginLeft: 4,
}}
/>
</View>
</View>
<View
style={{
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
height: 24,
backgroundColor: theme.colorPrimary,
justifyContent: 'center',
}}
>
<View
style={{
flex: 1,
justifyContent: 'space-between',
alignItems: 'center',
flexDirection: 'row',
paddingHorizontal: 16,
}}
>
<View
style={{
height: 12,
width: 12,
borderRadius: 50,
backgroundColor: theme.textColorPrimary,
opacity: 0.54,
}}
/>
<View
style={{
height: 12,
width: 12,
borderRadius: 50,
backgroundColor: theme.colorAccent,
}}
/>
<View
style={{
height: 12,
width: 12,
borderRadius: 50,
backgroundColor: theme.textColorPrimary,
opacity: 0.54,
}}
/>
</View>
</View>
</Pressable>
</View>
<Text
style={{
color: currentTheme.textColorPrimary,
fontSize: 12,
paddingVertical: 4,
}}
>
{theme.name}
</Text>
</View>
);
// const styles = StyleSheet.create({});
+14
View File
@@ -0,0 +1,14 @@
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';
export { default as ColorPreferenceItem } from './ColorPreferenceItem/ColorPreferenceItem';
export { default as LoadingMoreIndicator } from './LoadingMoreIndicator/LoadingMoreIndicator';
-55
View File
@@ -1,55 +0,0 @@
import * as SQLite from "expo-sqlite";
import {
createNovelTableQuery,
createUrlIndexQuery,
createLibraryIndexQuery,
} from "./tables/NovelTable";
import {
createChapterTableQuery,
createNovelIdIndexQuery,
createUnreadChaptersIndexQuery,
} from "./tables/ChapterTable";
import {
createHistoryTableQuery,
createChapterIdIndexQuery,
} from "./tables/HistoryTable";
import { createDownloadTableQuery } from "./tables/DownloadTable";
import {
createUpdatesTableQuery,
updatesSeedDataQuery,
} from "./tables/UpdateTable";
/**
* Database Version = 2
*/
const dbName = "lnreader.db";
const db = SQLite.openDatabase(dbName);
export const createDB = () => {
db.transaction((tx) => {
tx.executeSql(createNovelTableQuery);
tx.executeSql(createChapterTableQuery);
tx.executeSql(createHistoryTableQuery);
tx.executeSql(createDownloadTableQuery);
tx.executeSql(createUpdatesTableQuery);
//Db indexes
tx.executeSql(createUrlIndexQuery);
tx.executeSql(createLibraryIndexQuery);
tx.executeSql(createNovelIdIndexQuery);
tx.executeSql(createUnreadChaptersIndexQuery);
tx.executeSql(createChapterIdIndexQuery);
});
};
export const deleteDb = () => {
db.transaction((tx) => {
tx.executeSql("DROP TABLE novels");
tx.executeSql("DROP TABLE chapters");
tx.executeSql("DROP TABLE history");
tx.executeSql("DROP TABLE downloads");
tx.executeSql("DROP TABLE updates");
});
};
+50
View File
@@ -0,0 +1,50 @@
import * as SQLite from 'expo-sqlite';
import {
createNovelTableQuery,
createUrlIndexQuery,
createLibraryIndexQuery,
} from './tables/NovelTable';
import {
createChapterTableQuery,
createNovelIdIndexQuery,
createUnreadChaptersIndexQuery,
} from './tables/ChapterTable';
import {
createHistoryTableQuery,
createChapterIdIndexQuery,
} from './tables/HistoryTable';
import { createDownloadTableQuery } from './tables/DownloadTable';
import { createUpdatesTableQuery } from './tables/UpdateTable';
const dbName = 'lnreader.db';
const db = SQLite.openDatabase(dbName);
export const createDB = () => {
db.transaction(tx => {
tx.executeSql(createNovelTableQuery);
tx.executeSql(createChapterTableQuery);
tx.executeSql(createHistoryTableQuery);
tx.executeSql(createDownloadTableQuery);
tx.executeSql(createUpdatesTableQuery);
/**
* Indexes
*/
tx.executeSql(createUrlIndexQuery);
tx.executeSql(createLibraryIndexQuery);
tx.executeSql(createNovelIdIndexQuery);
tx.executeSql(createUnreadChaptersIndexQuery);
tx.executeSql(createChapterIdIndexQuery);
});
};
export const deleteDb = () => {
db.transaction(tx => {
tx.executeSql('DROP TABLE novels');
tx.executeSql('DROP TABLE chapters');
tx.executeSql('DROP TABLE history');
tx.executeSql('DROP TABLE downloads');
tx.executeSql('DROP TABLE updates');
});
};
-311
View File
@@ -1,311 +0,0 @@
import * as SQLite from "expo-sqlite";
import { getSource } from "../../sources/sources";
const db = SQLite.openDatabase("lnreader.db");
const insertChaptersQuery = `INSERT INTO chapters (chapterUrl, chapterName, releaseDate, novelId) values (?, ?, ?, ?)`;
export const insertChapters = async (novelId, chapters) => {
db.transaction((tx) => {
chapters.map(
(chapter) =>
tx.executeSql(
insertChaptersQuery,
[
chapter.chapterUrl,
chapter.chapterName,
chapter.releaseDate,
novelId,
],
(txObj, res) => {},
(txObj, error) => {}
// console.log(
// "Error ",
// error,
// " Chapter URL: ",
// chapter.chapterUrl,
// " Novel ID ",
// novelId
// )
),
(txObj, res) => {
console.log("Success");
},
(txObj, error) => console.log("Error ", error)
);
});
};
const getChaptersQuery = (sort = "ORDER BY chapterId ASC", filter = "") =>
`SELECT * FROM chapters WHERE novelId = ? ${filter} ${sort}`;
export const getChapters = (novelId, sort, filter) => {
return new Promise((resolve, reject) =>
db.transaction((tx) => {
tx.executeSql(
getChaptersQuery(sort, filter),
[novelId],
(txObj, { rows: { _array } }) => {
resolve(_array);
},
(txObj, error) => console.log("Error ", error)
);
})
);
};
const getChapterQuery = `SELECT * FROM downloads WHERE downloadChapterId = ?`;
export const getChapterFromDB = async (chapterId) => {
return new Promise((resolve, reject) =>
db.transaction((tx) => {
tx.executeSql(
getChapterQuery,
[chapterId],
(tx, results) => {
resolve(results.rows.item(0));
},
(txObj, error) => console.log("Error ", error)
);
})
);
};
export const getNextChapterFromDB = async (novelId, chapterId) => {
return new Promise((resolve, reject) =>
db.transaction((tx) => {
tx.executeSql(
`SELECT * FROM chapters WHERE novelId = ? AND chapterId > ?`,
[novelId, chapterId],
(tx, results) => {
resolve(results.rows.item(0));
},
(txObj, error) => console.log("Error ", error)
);
})
);
};
export const getPrevChapterFromDB = async (novelId, chapterId) => {
return new Promise((resolve, reject) =>
db.transaction((tx) => {
tx.executeSql(
`SELECT * FROM chapters WHERE novelId = ? AND chapterId < ?`,
[novelId, chapterId],
(tx, results) => {
resolve(results.rows.item(results.rows.length - 1));
},
(txObj, error) => console.log("Error ", error)
);
})
);
};
const markChapterReadQuery = `UPDATE chapters SET \`read\` = 1 WHERE chapterId = ?`;
export const markChapterRead = async (chapterId) => {
db.transaction((tx) => {
tx.executeSql(
markChapterReadQuery,
[chapterId],
(tx, res) => {},
(tx, error) => console.log(error)
);
});
};
const markChapterUnreadQuery = `UPDATE chapters SET \`read\` = 0 WHERE chapterId = ?`;
export const markChapterUnread = async (chapterId) => {
db.transaction((tx) => {
tx.executeSql(
markChapterUnreadQuery,
[chapterId],
(tx, res) => {},
(tx, error) => console.log(error)
);
});
};
const markAllChaptersReadQuery = `UPDATE chapters SET \`read\` = 1 WHERE novelId = ?`;
export const markAllChaptersRead = async (novelId) => {
db.transaction((tx) => {
tx.executeSql(
markAllChaptersReadQuery,
[novelId],
(tx, res) => {},
(tx, error) => console.log(error)
);
});
};
const markAllChaptersUnreadQuery = `UPDATE chapters SET \`read\` = 0 WHERE novelId = ?`;
export const markAllChaptersUnread = async (novelId) => {
db.transaction((tx) => {
tx.executeSql(
markAllChaptersUnreadQuery,
[novelId],
(tx, res) => {},
(tx, error) => console.log(error)
);
});
};
const isChapterDownloadedQuery = `SELECT * FROM downloads WHERE downloadChapterId=?`;
export const isChapterDownloaded = async (chapterId) => {
return new Promise((resolve, reject) =>
db.transaction((tx) => {
tx.executeSql(
isChapterDownloadedQuery,
[chapterId],
(txObj, res) => {
if (res.rows.length !== 0) {
resolve(true);
} else {
resolve(false);
}
},
(txObj, error) => console.log("Error ", error)
);
})
);
};
const downloadChapterQuery = `INSERT INTO downloads (downloadChapterId, chapterName, chapterText) VALUES (?, ?, ?)`;
export const downloadChapter = async (
sourceId,
novelUrl,
chapterUrl,
chapterId
) => {
const source = getSource(sourceId);
const chapter = await source.parseChapter(novelUrl, chapterUrl);
db.transaction((tx) => {
tx.executeSql(
`UPDATE chapters SET downloaded = 1 WHERE chapterId = ?`,
[chapterId]
);
tx.executeSql(
downloadChapterQuery,
[chapterId, chapter.chapterName, chapter.chapterText],
(tx, res) => {
// console.log(`Downloaded Chapter ${chapter.chapterUrl}`);
},
(txObj, error) => console.log("Error ", error)
);
});
};
export const deleteChapter = async (chapterId) => {
const updateIsDownloadedQuery = `UPDATE chapters SET downloaded = 0 WHERE chapterId=?`;
const deleteChapterQuery = `DELETE FROM downloads WHERE downloadChapterId=?`;
db.transaction((tx) => {
tx.executeSql(
updateIsDownloadedQuery,
[chapterId],
(tx, res) => {},
(txObj, error) => console.log("Error ", error)
);
tx.executeSql(
deleteChapterQuery,
[chapterId],
(tx, res) => console.log(`Chapter deleted`),
(txObj, error) => console.log("Error ", error)
);
});
};
const getLastReadChapterQuery = `
SELECT chapters.*
FROM history
JOIN chapters
ON history.historyChapterId = chapters.chapterId
WHERE history.historyNovelId = ?
`;
export const getLastReadChapter = async (novelId) => {
return new Promise((resolve, reject) => {
db.transaction((tx) => {
tx.executeSql(
getLastReadChapterQuery,
[novelId],
(tx, { rows }) => resolve(rows.item(0)),
(txObj, error) => console.log("Error ", error)
);
});
});
};
const bookmarkChapterQuery = `UPDATE chapters SET bookmark = ? WHERE chapterId = ?`;
export const bookmarkChapter = async (bookmark, chapterId) => {
db.transaction((tx) => {
tx.executeSql(
bookmarkChapterQuery,
[!bookmark, chapterId],
(tx, res) => {},
(txObj, error) => console.log("Error ", error)
);
});
};
const markPreviuschaptersReadQuery = `UPDATE chapters SET \`read\` = 1 WHERE chapterId < ? AND novelId = ?`;
export const markPreviuschaptersRead = async (chapterId, novelId) => {
db.transaction((tx) => {
tx.executeSql(
markPreviuschaptersReadQuery,
[chapterId, novelId],
(tx, res) => {},
(tx, error) => console.log(error)
);
});
};
const markPreviousChaptersUnreadQuery = `UPDATE chapters SET \`read\` = 0 WHERE chapterId < ? AND novelId = ?`;
export const markPreviousChaptersUnread = async (chapterId, novelId) => {
db.transaction((tx) => {
tx.executeSql(
markPreviousChaptersUnreadQuery,
[chapterId, novelId],
(tx, res) => {},
(tx, error) => console.log(error)
);
});
};
const getDownloadedChaptersQuery = `
SELECT chapters.*, novels.sourceId, novels.novelName, novels.novelCover, novels.novelUrl
FROM chapters
JOIN novels
ON chapters.novelId = novels.novelId
WHERE chapters.downloaded = 1`;
export const getDownloadedChapters = () => {
return new Promise((resolve, reject) =>
db.transaction((tx) => {
tx.executeSql(
getDownloadedChaptersQuery,
null,
(txObj, { rows: { _array } }) => {
resolve(_array);
},
(txObj, error) => console.log("Error ", error)
);
})
);
};
export const deleteDownloads = async () => {
db.transaction((tx) => {
tx.executeSql(`UPDATE chapters SET downloaded = 0`);
tx.executeSql("DELETE FROM downloads; VACCUM;");
});
};
+525
View File
@@ -0,0 +1,525 @@
import * as SQLite from 'expo-sqlite';
import { showToast } from '../../hooks/showToast';
import { sourceManager } from '../../sources/sourceManager';
import { ChapterItem } from '../types';
import * as cheerio from 'cheerio';
import RNFetchBlob from 'rn-fetch-blob';
const db = SQLite.openDatabase('lnreader.db');
const insertChaptersQuery =
'INSERT INTO chapters (chapterUrl, chapterName, releaseDate, novelId) values (?, ?, ?, ?)';
export const insertChapters = async (
novelId: number,
chapters: ChapterItem[],
) => {
db.transaction(
tx => {
chapters.map(chapter =>
tx.executeSql(
insertChaptersQuery,
[
chapter.chapterUrl,
chapter.chapterName,
chapter.releaseDate,
novelId,
],
(_txObj, _res) => {},
(_txObj, _error) => false,
// console.log(
// "Error ",
// error,
// " Chapter URL: ",
// chapter.chapterUrl,
// " Novel ID ",
// novelId
// )
),
);
},
_err => {
// console.log('Error', err);
},
() => {
// console.log('Success ')
},
);
};
const getChaptersQuery = (sort = 'ORDER BY chapterId ASC', filter = '') =>
`SELECT * FROM chapters WHERE novelId = ? ${filter} ${sort}`;
export const getChapters = (novelId: number, sort: string, filter: string) => {
return new Promise(resolve =>
db.transaction(tx => {
tx.executeSql(
getChaptersQuery(sort, filter),
[novelId],
(txObj, { rows }) => {
const _array = (rows as any)._array;
resolve(_array);
},
(_txObj, _error) => {
// console.log('Error ', error)
return false;
},
);
}),
);
};
const getChapterQuery = 'SELECT * FROM downloads WHERE downloadChapterId = ?';
export const getChapterFromDB = async (chapterId: number) => {
return new Promise(resolve =>
db.transaction(tx => {
tx.executeSql(
getChapterQuery,
[chapterId],
(txObj, results) => {
resolve(results.rows.item(0));
},
(_txObj, _error) => {
// console.log('Error ', error)
return false;
},
);
}),
);
};
const getPrevChapterQuery = `
SELECT
*
FROM
chapters
WHERE
novelId = ?
AND
chapterId < ?
`;
export const getPrevChapter = (
novelId: number,
chapterId: number,
): Promise<ChapterItem> => {
return new Promise(resolve =>
db.transaction(tx => {
tx.executeSql(
getPrevChapterQuery,
[novelId, chapterId],
(_txObj, results) =>
resolve(results.rows.item(results.rows.length - 1)),
(_txObj, error) => {
showToast(error.message);
return false;
},
);
}),
);
};
const getNextChapterQuery = `
SELECT
*
FROM
chapters
WHERE
novelId = ?
AND
chapterId > ?
`;
export const getNextChapter = (
novelId: number,
chapterId: number,
): Promise<ChapterItem> => {
return new Promise(resolve =>
db.transaction(tx => {
tx.executeSql(
getNextChapterQuery,
[novelId, chapterId],
(_txObj, results) => resolve(results.rows.item(0)),
(_txObj, error) => {
showToast(error.message);
return false;
},
);
}),
);
};
const markChapterReadQuery =
'UPDATE chapters SET `read` = 1 WHERE chapterId = ?';
export const markChapterRead = async (chapterId: number) => {
db.transaction(tx => {
tx.executeSql(
markChapterReadQuery,
[chapterId],
(_txObj, _res) => {},
(_txObj, _error) => {
// console.log(error)
return false;
},
);
});
};
const markChapterUnreadQuery =
'UPDATE chapters SET `read` = 0 WHERE chapterId = ?';
export const markChapterUnread = async (chapterId: number) => {
db.transaction(tx => {
tx.executeSql(
markChapterUnreadQuery,
[chapterId],
(_txObj, _res) => {},
(_txObj, _error) => {
// console.log(error)
return false;
},
);
});
};
const markAllChaptersReadQuery =
'UPDATE chapters SET `read` = 1 WHERE novelId = ?';
export const markAllChaptersRead = async (novelId: number) => {
db.transaction(tx => {
tx.executeSql(
markAllChaptersReadQuery,
[novelId],
(_txObj, _res) => {},
(_txObj, _error) => {
// console.log(error)
return false;
},
);
});
};
const markAllChaptersUnreadQuery =
'UPDATE chapters SET `read` = 0 WHERE novelId = ?';
export const markAllChaptersUnread = async (novelId: number) => {
db.transaction(tx => {
tx.executeSql(
markAllChaptersUnreadQuery,
[novelId],
(_txObj, _res) => {},
(_txObj, _error) => {
// console.log(error)
return false;
},
);
});
};
const isChapterDownloadedQuery =
'SELECT * FROM downloads WHERE downloadChapterId=?';
export const isChapterDownloaded = async (chapterId: number) => {
return new Promise(resolve =>
db.transaction(tx => {
tx.executeSql(
isChapterDownloadedQuery,
[chapterId],
(txObj, res) => {
if (res.rows.length !== 0) {
resolve(true);
} else {
resolve(false);
}
},
(_txObj, _error) => {
// console.log('Error ', error)
return false;
},
);
}),
);
};
const downloadChapterQuery =
'INSERT INTO downloads (downloadChapterId, chapterName, chapterText) VALUES (?, ?, ?)';
const createImageFolder = async (
path: string,
data?: {
sourceId: number;
novelId: number;
chapterId: number;
},
): Promise<string> => {
const mkdirIfNot = async (p: string) => {
if (!(await RNFetchBlob.fs.exists(p))) {
await RNFetchBlob.fs.mkdir(p);
}
};
await mkdirIfNot(path);
if (data) {
const { sourceId, novelId, chapterId } = data;
await mkdirIfNot(`${path}/${sourceId}/`);
await mkdirIfNot(`${path}/${sourceId}/${novelId}/`);
await mkdirIfNot(`${path}/${sourceId}/${novelId}/${chapterId}/`);
return `${path}/${sourceId}/${novelId}/${chapterId}/`;
} else {
return path;
}
};
const downloadImages = async (
html: string,
sourceId: number,
novelId: number,
chapterId: number,
): Promise<string> => {
try {
const loadedCheerio = cheerio.load(html);
const imgs = loadedCheerio('img').toArray();
for (let i = 0; i < imgs.length; i++) {
const elem = loadedCheerio(imgs[i]);
const url = elem.attr('src');
if (url) {
const imageb64 = (await RNFetchBlob.fetch('GET', url)).base64();
const fileurl =
(await createImageFolder(
`${RNFetchBlob.fs.dirs.DownloadDir}/LNReader`,
{ sourceId, novelId, chapterId },
)) +
i +
'.b64.png';
elem.replaceWith(
`<img type="file" src="${url}" file-src="${fileurl}" file-id="${i}">`,
);
const exists = await RNFetchBlob.fs.exists(fileurl);
if (!exists) {
RNFetchBlob.fs.createFile(fileurl, imageb64, 'utf8');
} else {
RNFetchBlob.fs.writeFile(fileurl, imageb64, 'utf8');
}
}
}
return loadedCheerio.html();
} catch (e) {
return html;
}
};
export const downloadChapter = async (
sourceId: number,
novelUrl: string,
novelId: number,
chapterUrl: string,
chapterId: number,
) => {
const source = sourceManager(sourceId);
const chapter = await source.parseChapter(novelUrl, chapterUrl);
const imagedChapterText =
chapter.chapterText &&
(await downloadImages(chapter.chapterText, sourceId, novelId, chapterId));
db.transaction(tx => {
tx.executeSql('UPDATE chapters SET downloaded = 1 WHERE chapterId = ?', [
chapterId,
]);
tx.executeSql(
downloadChapterQuery,
[chapterId, chapter.chapterName, imagedChapterText],
(_txObj, _res) => {
// console.log(`Downloaded Chapter ${chapter.chapterUrl}`);
},
(_txObj, _error) => {
// console.log('Error ', error)
return false;
},
);
});
};
export const deleteChapter = async (
sourceId: number,
novelId: number,
chapterId: number,
) => {
const updateIsDownloadedQuery =
'UPDATE chapters SET downloaded = 0 WHERE chapterId=?';
const deleteChapterQuery = 'DELETE FROM downloads WHERE downloadChapterId=?';
const path = `${RNFetchBlob.fs.dirs.DownloadDir}/LNReader/`;
const files = await RNFetchBlob.fs.ls(
await createImageFolder(path, { sourceId, novelId, chapterId }),
);
for (let i = 0; i < files.length; i++) {
const ex = /(.*?)_(.*?)#(.*?)/.exec(files[i]);
if (ex) {
if (
parseInt(ex[1], 10) === sourceId &&
parseInt(ex[2], 10) === chapterId
) {
if (await RNFetchBlob.fs.exists(`${path}${files[i]}`)) {
RNFetchBlob.fs.unlink(`${path}${files[i]}`);
}
}
}
}
db.transaction(tx => {
tx.executeSql(
updateIsDownloadedQuery,
[chapterId],
(_txObj, _res) => {},
(_txObj, _error) => {
// console.log('Error ', error)
return false;
},
);
tx.executeSql(
deleteChapterQuery,
[chapterId],
(_txObj, _res) => {
// console.log('Chapter deleted');
},
(_txObj, _error) => {
// console.log('Error ', error)
return false;
},
);
});
};
const getLastReadChapterQuery = `
SELECT chapters.*
FROM history
JOIN chapters
ON history.historyChapterId = chapters.chapterId
WHERE history.historyNovelId = ?
`;
export const getLastReadChapter = async (novelId: number) => {
return new Promise(resolve => {
db.transaction(tx => {
tx.executeSql(
getLastReadChapterQuery,
[novelId],
(txObj, { rows }) => resolve(rows.item(0)),
(_txObj, _error) => {
// console.log('Error ', error)
return false;
},
);
});
});
};
const bookmarkChapterQuery =
'UPDATE chapters SET bookmark = ? WHERE chapterId = ?';
export const bookmarkChapter = async (bookmark: boolean, chapterId: number) => {
db.transaction(tx => {
tx.executeSql(
bookmarkChapterQuery,
[!bookmark, chapterId],
(_txObj, _res) => {},
(_txObj, _error) => {
// console.log('Error ', error)
return false;
},
);
});
};
const markPreviuschaptersReadQuery =
'UPDATE chapters SET `read` = 1 WHERE chapterId < ? AND novelId = ?';
export const markPreviuschaptersRead = async (
chapterId: number,
novelId: number,
) => {
db.transaction(tx => {
tx.executeSql(
markPreviuschaptersReadQuery,
[chapterId, novelId],
(_txObj, _res) => {},
(_txObj, _error) => {
// console.log(error)
return false;
},
);
});
};
const markPreviousChaptersUnreadQuery =
'UPDATE chapters SET `read` = 0 WHERE chapterId < ? AND novelId = ?';
export const markPreviousChaptersUnread = async (
chapterId: number,
novelId: number,
) => {
db.transaction(tx => {
tx.executeSql(
markPreviousChaptersUnreadQuery,
[chapterId, novelId],
(_txObj, _res) => {},
(_txObj, _error) => {
// console.log(error)
return false;
},
);
});
};
const getDownloadedChaptersQuery = `
SELECT chapters.*, novels.sourceId, novels.novelName, novels.novelCover, novels.novelUrl
FROM chapters
JOIN novels
ON chapters.novelId = novels.novelId
WHERE chapters.downloaded = 1`;
export const getDownloadedChapters = () => {
return new Promise(resolve =>
db.transaction(tx => {
tx.executeSql(
getDownloadedChaptersQuery,
undefined,
(txObj, { rows }) => {
const _array = (rows as any)._array;
resolve(_array);
},
(_txObj, _error) => {
// console.log('Error ', error)
return false;
},
);
}),
);
};
export const deleteDownloads = async () => {
db.transaction(tx => {
tx.executeSql('UPDATE chapters SET downloaded = 0');
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 = ?`;
+134
View File
@@ -0,0 +1,134 @@
/* 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),
);
}),
);
};
const deleteReadChaptersFromDbQuery = `
DELETE FROM
downloads
WHERE
downloads.downloadChapterId IN (
SELECT
chapters.chapterId
FROM
downloads
INNER JOIN chapters ON chapters.chapterId = downloads.downloadChapterId
WHERE
chapters.read = 1
);
`;
let updateChaptersDeletedQuery = `
UPDATE
chapters
SET
downloaded = 0
WHERE
chapters.chapterId IN (
SELECT
downloads.downloadChapterId
FROM
downloads
INNER JOIN chapters ON chapters.chapterId = downloads.downloadChapterId
WHERE
chapters.read = 1
);
`;
export const deleteReadChaptersFromDb = (chapterId: number) => {
db.transaction(tx => {
tx.executeSql(
updateChaptersDeletedQuery,
[],
(txObj, res) => {},
(txObj, error) => showToast(error.message),
);
tx.executeSql(
deleteReadChaptersFromDbQuery,
[],
(txObj, res) => showToast('Deleted read chapters.'),
(txObj, error) => showToast(error.message),
);
});
};
+43 -36
View File
@@ -1,5 +1,7 @@
import * as SQLite from "expo-sqlite";
const db = SQLite.openDatabase("lnreader.db");
import * as SQLite from 'expo-sqlite';
const db = SQLite.openDatabase('lnreader.db');
import { showToast } from '../../hooks/showToast';
const getHistoryQuery = `
SELECT history.*, chapters.*, novels.*
@@ -14,49 +16,54 @@ const getHistoryQuery = `
`;
export const getHistoryFromDb = async () => {
return new Promise((resolve, reject) => {
db.transaction((tx) => {
tx.executeSql(
getHistoryQuery,
null,
(txObj, { rows: { _array } }) => {
resolve(_array);
},
(txObj, error) => console.log("Error ", error)
);
});
return new Promise((resolve, reject) => {
db.transaction(tx => {
tx.executeSql(
getHistoryQuery,
null,
(txObj, { rows: { _array } }) => {
resolve(_array);
},
(txObj, error) => {
// console.log('Error ', error)
},
);
});
});
};
// const insertHistoryQuery =
export const insertHistory = async (novelId, chapterId) => {
db.transaction((tx) => {
tx.executeSql(
"INSERT OR REPLACE INTO history (historyNovelId, historyChapterId, historyTimeRead) VALUES (?, ?, (datetime('now','localtime')))",
[novelId, chapterId],
(txObj, res) => {},
(txObj, error) => console.log("Error ", error)
);
tx.executeSql("UPDATE novels SET unread = 0 WHERE novelId = ?", [
novelId,
]);
});
db.transaction(tx => {
tx.executeSql(
"INSERT OR REPLACE INTO history (historyNovelId, historyChapterId, historyTimeRead) VALUES (?, ?, (datetime('now','localtime')))",
[novelId, chapterId],
(txObj, res) => {},
(txObj, error) => {
// console.log('Error ', error)
},
);
tx.executeSql('UPDATE novels SET unread = 0 WHERE novelId = ?', [novelId]);
});
};
export const deleteHistory = async (historyId) => {
db.transaction((tx) => {
tx.executeSql(
"DELETE FROM history WHERE historyId = ?",
[historyId],
(txObj, res) => {},
(txObj, error) => console.log("Error ", error)
);
});
export const deleteChapterHistory = async historyId => {
db.transaction(tx => {
tx.executeSql(
'DELETE FROM history WHERE historyId = ?',
[historyId],
(txObj, res) => {},
(txObj, error) => {
// console.log('Error ', error)
},
);
});
};
export const deleteAllHistory = async () => {
db.transaction((tx) => {
tx.executeSql("DELETE FROM history; VACCUM;");
});
db.transaction(tx => {
tx.executeSql('DELETE FROM history; VACCUM;');
});
showToast('History deleted.');
};
+52 -32
View File
@@ -1,8 +1,8 @@
import * as SQLite from "expo-sqlite";
const db = SQLite.openDatabase("lnreader.db");
import * as SQLite from 'expo-sqlite';
const db = SQLite.openDatabase('lnreader.db');
const getLibraryQuery = (sort, filter) => `
SELECT novels.*, C.chaptersUnread, D.chaptersDownloaded
SELECT novels.*, C.chaptersUnread, D.chaptersDownloaded, H.lastReadAt
FROM novels
LEFT JOIN (
SELECT chapters.novelId, COUNT(*) AS chaptersUnread
@@ -18,29 +18,39 @@ const getLibraryQuery = (sort, filter) => `
GROUP BY chapters.novelId
) AS D
ON novels.novelId = D.novelId
WHERE novels.followed = 1 ${filter ? "AND " + filter : ""}
${sort ? "ORDER BY " + sort : ""}
LEFT JOIN (
SELECT history.historyNovelId as novelId, historyTimeRead AS lastReadAt
FROM history
GROUP BY history.historyNovelId
HAVING history.historyTimeRead = MAX(history.historyTimeRead)
ORDER BY history.historyTimeRead DESC
) AS H
ON novels.novelId = H.novelId
WHERE novels.followed = 1 ${filter ? 'AND ' + filter : ''}
${sort ? 'ORDER BY ' + sort : ''}
`;
export const getLibrary = (sort, filter) => {
return new Promise((resolve, reject) =>
db.transaction((tx) => {
tx.executeSql(
getLibraryQuery(sort, filter),
null,
(txObj, { rows: { _array } }) => {
// console.log(_array);
resolve(_array);
},
(txObj, error) => console.log("Error ", error)
);
})
);
return new Promise((resolve, reject) =>
db.transaction(tx => {
tx.executeSql(
getLibraryQuery(sort, filter),
null,
(txObj, { rows: { _array } }) => {
// console.log(JSON.stringify(_array, null, 2));
resolve(_array);
},
(txObj, error) => {
// console.log('Error ', error);
},
);
}),
);
};
const searchLibraryQuery = (searchText, sort, filter) =>
`
SELECT novels.*, C.chaptersUnread, D.chaptersDownloaded
`
SELECT novels.*, C.chaptersUnread, D.chaptersDownloaded, H.lastReadAt
FROM novels
LEFT JOIN (
SELECT chapters.novelId, COUNT(*) AS chaptersUnread
@@ -56,20 +66,30 @@ const searchLibraryQuery = (searchText, sort, filter) =>
GROUP BY chapters.novelId
) AS D
ON novels.novelId = D.novelId
LEFT JOIN (
SELECT history.historyNovelId as novelId, historyTimeRead AS lastReadAt
FROM history
GROUP BY history.historyNovelId
HAVING history.historyTimeRead = MAX(history.historyTimeRead)
ORDER BY history.historyTimeRead DESC
) AS H
ON novels.novelId = H.novelId
WHERE novels.followed = 1 AND novelName LIKE '%${searchText}%' ${
filter ? "AND " + filter : ""
}
${sort ? "ORDER BY " + sort : ""} `;
filter ? 'AND ' + filter : ''
}
${sort ? 'ORDER BY ' + sort : ''} `;
export const searchLibrary = (searchText, sort, filter) => {
return new Promise((resolve, reject) => {
db.transaction((tx) => {
tx.executeSql(
searchLibraryQuery(searchText, sort, filter),
null,
(txObj, { rows: { _array } }) => resolve(_array),
(txObj, error) => console.log("Error ", error)
);
});
return new Promise((resolve, reject) => {
db.transaction(tx => {
tx.executeSql(
searchLibraryQuery(searchText, sort, filter),
null,
(txObj, { rows: { _array } }) => resolve(_array),
(txObj, error) => {
// console.log('Error ', error);
},
);
});
});
};
+238 -214
View File
@@ -1,242 +1,266 @@
import * as SQLite from "expo-sqlite";
const db = SQLite.openDatabase("lnreader.db");
import * as SQLite from 'expo-sqlite';
const db = SQLite.openDatabase('lnreader.db');
import BackgroundService from "react-native-background-actions";
import * as DocumentPicker from "expo-document-picker";
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(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;
const insertNovelQuery =
'INSERT INTO novels (novelUrl, sourceUrl, sourceId, source, novelName, novelCover, novelSummary, author, artist, status, genre) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
export const insertNovel = async (novel) => {
return new Promise((resolve, reject) =>
db.transaction((tx) =>
tx.executeSql(
insertNovelQuery,
[
novel.novelUrl,
novel.sourceUrl,
novel.sourceId,
novel.source,
novel.novelName,
novel.novelCover,
novel.novelSummary,
novel.author,
novel.artist,
novel.status,
novel.genre,
],
(txObj, { insertId }) => resolve(insertId),
(txObj, error) => console.log("Error ", error)
)
)
);
export const insertNovel = async novel => {
return new Promise((resolve, reject) =>
db.transaction(tx =>
tx.executeSql(
insertNovelQuery,
[
novel.novelUrl,
novel.sourceUrl,
novel.sourceId,
novel.source,
novel.novelName,
novel.novelCover || '',
novel.novelSummary,
novel.author,
novel.artist,
novel.status,
novel.genre,
],
(txObj, { insertId }) => resolve(insertId),
(txObj, error) => {
// console.log('Error ', error)
},
),
),
);
};
export const followNovel = async (followed, novelId) => {
db.transaction((tx) => {
tx.executeSql(
"UPDATE novels SET followed = ? WHERE novelId = ?",
[!followed, novelId],
(tx, res) => {},
(txObj, error) => console.log("Error ", error)
);
});
};
export const unfollowNovel = async (novelId) => {
db.transaction((tx) => {
tx.executeSql(
"UPDATE novels SET followed = 0 WHERE novelId = ?",
[novelId],
(tx, res) => {},
(txObj, error) => console.log("Error ", error)
);
});
};
const checkNovelInCacheQuery = `SELECT * FROM novels WHERE novelUrl=? LIMIT 1`;
export const checkNovelInCache = (novelUrl) => {
return new Promise((resolve, reject) =>
db.transaction((tx) => {
tx.executeSql(
checkNovelInCacheQuery,
[novelUrl],
(txObj, res) => {
if (res.rows.length !== 0) {
resolve(true);
} else {
resolve(false);
}
},
(txObj, error) => console.log("Error ", error)
);
})
db.transaction(tx => {
tx.executeSql(
'UPDATE novels SET followed = ? WHERE novelId = ?',
[!followed, novelId],
(txo, res) => {},
(txObj, error) => {
// console.log('Error ', error)
},
);
});
};
export const unfollowNovel = async novelId => {
db.transaction(tx => {
tx.executeSql(
'UPDATE novels SET followed = 0 WHERE novelId = ?',
[novelId],
(txo, res) => {},
(txObj, error) => {
// console.log('Error ', error)
},
);
});
};
const checkNovelInCacheQuery = 'SELECT * FROM novels WHERE novelUrl=? LIMIT 1';
export const checkNovelInCache = novelUrl => {
return new Promise((resolve, reject) =>
db.transaction(tx => {
tx.executeSql(
checkNovelInCacheQuery,
[novelUrl],
(txObj, res) => {
if (res.rows.length !== 0) {
resolve(true);
} else {
resolve(false);
}
},
(txObj, error) => {
// console.log('Error ', error)
},
);
}),
);
};
export const getNovel = async (sourceId, novelUrl) => {
return new Promise((resolve, reject) =>
db.transaction((tx) => {
tx.executeSql(
`SELECT * FROM novels WHERE novelUrl = ? AND sourceId = ?`,
[novelUrl, sourceId],
(txObj, { rows }) => resolve(rows.item(0)),
(txObj, error) => console.log("Error ", error)
);
})
);
return new Promise((resolve, reject) =>
db.transaction(tx => {
tx.executeSql(
'SELECT * FROM novels WHERE novelUrl = ? AND sourceId = ?',
[novelUrl, sourceId],
(txObj, { rows }) => resolve(rows.item(0)),
(txObj, error) => {
// console.log('Error ', error)
},
);
}),
);
};
export const deleteNovelCache = () => {
db.transaction((tx) => {
tx.executeSql(
"DELETE FROM novels WHERE followed = 0",
null,
(txObj, res) => showToast("Entries deleted"),
(txObj, error) => console.log("Error ", error)
);
});
};
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) =>
db.transaction((tx) =>
tx.executeSql(
restoreFromBackupQuery,
[
novel.novelUrl,
novel.sourceUrl,
novel.sourceId,
novel.source,
novel.novelName,
novel.novelCover,
novel.novelSummary,
novel.author,
novel.artist,
novel.status,
novel.genre,
novel.followed,
novel.unread,
],
async (txObj, { insertId }) => {
const chapters = await fetchChapters(
novel.sourceId,
novel.novelUrl
);
await insertChapters(insertId, chapters);
},
(txObj, error) => console.log("Error ", error)
)
)
db.transaction(tx => {
tx.executeSql(
'DELETE FROM novels WHERE followed = 0',
null,
(txObj, res) => showToast('Entries deleted'),
(txObj, error) => {
// console.log('Error ', error)
},
);
});
};
const migrateNovelQuery = `INSERT INTO novels (novelUrl, sourceUrl, sourceId, source, novelName, novelCover, novelSummary, author, artist, status, genre, followed) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;
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) => {
db.transaction(tx =>
tx.executeSql(
restoreFromBackupQuery,
[
novel.novelUrl,
novel.sourceUrl,
novel.sourceId,
novel.source,
novel.novelName,
novel.novelCover,
novel.novelSummary,
novel.author,
novel.artist,
novel.status,
novel.genre,
novel.followed,
novel.unread,
],
async (txObj, { insertId }) => {
const chapters = await fetchChapters(novel.sourceId, novel.novelUrl);
if (chapters) {
await insertChapters(insertId, chapters);
resolve();
}
},
(txObj, error) => {
resolve();
},
),
);
});
};
const migrateNovelQuery =
'INSERT INTO novels (novelUrl, sourceUrl, sourceId, source, novelName, novelCover, novelSummary, author, artist, status, genre, followed) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
export const migrateNovel = async (sourceId, novelUrl) => {
try {
const novel = await fetchNovel(sourceId, novelUrl);
try {
const novel = await fetchNovel(sourceId, novelUrl);
const options = {
taskName: "Migration",
taskTitle: `Migrating ${novel.novelName} to new source`,
taskDesc: novel.source,
taskIcon: {
name: "notification_icon",
type: "drawable",
},
color: "#00adb5",
parameters: {
delay: 1000,
},
progressBar: {
max: 1,
value: 0,
indeterminate: true,
},
};
const options = {
taskName: 'Migration',
taskTitle: `Migrating ${novel.novelName} to new source`,
taskDesc: novel.source,
taskIcon: {
name: 'notification_icon',
type: 'drawable',
},
color: '#00adb5',
parameters: {
delay: 1000,
},
progressBar: {
max: 1,
value: 0,
indeterminate: true,
},
};
const veryIntensiveTask = async () => {
await new Promise(async (resolve) => {
db.transaction((tx) =>
tx.executeSql(
migrateNovelQuery,
[
novel.novelUrl,
novel.sourceUrl,
novel.sourceId,
novel.source,
novel.novelName,
novel.novelCover,
novel.novelSummary,
novel.author,
novel.artist,
novel.status,
novel.genre,
1,
],
async (txObj, { insertId }) => {
const chapters = await fetchChapters(
novel.sourceId,
novel.novelUrl
);
await insertChapters(insertId, chapters);
resolve();
},
(txObj, error) => console.log("Error ", error)
)
);
});
};
const veryIntensiveTask = async () => {
await new Promise(async resolve => {
db.transaction(tx =>
tx.executeSql(
migrateNovelQuery,
[
novel.novelUrl,
novel.sourceUrl,
novel.sourceId,
novel.source,
novel.novelName,
novel.novelCover,
novel.novelSummary,
novel.author,
novel.artist,
novel.status,
novel.genre,
1,
],
async (txObj, { insertId }) => {
const chapters = await fetchChapters(
novel.sourceId,
novel.novelUrl,
);
await insertChapters(insertId, chapters);
resolve();
},
(txObj, error) => {
// console.log('Error ', error)
},
),
);
});
};
await BackgroundService.start(veryIntensiveTask, options);
await BackgroundService.updateNotification({
progressBar: { value: 1, indeterminate: false },
});
await BackgroundService.stop();
} catch (error) {
showToast(error.message);
}
await BackgroundService.start(veryIntensiveTask, options);
await BackgroundService.updateNotification({
progressBar: { value: 1, indeterminate: false },
});
} catch (error) {
showToast(error.message);
}
};
export const updateNovelInfo = async (info, novelId) => {
db.transaction((tx) => {
tx.executeSql(
"UPDATE novels SET novelName = ?, novelSummary = ?, author = ?, genre = ?, status = ? WHERE novelId = ?",
[
info.novelName,
info.novelSummary,
info.author,
info.genre,
info.status,
novelId,
],
(txObj, res) => {},
(txObj, error) => console.log("Error ", error)
);
db.transaction(tx => {
tx.executeSql(
'UPDATE novels SET novelName = ?, novelSummary = ?, author = ?, genre = ?, status = ? WHERE novelId = ?',
[
info.novelName,
info.novelSummary,
info.author,
info.genre,
info.status,
novelId,
],
(txObj, res) => {},
(txObj, error) => {
// console.log('Error ', error)
},
);
});
};
export const pickCustomNovelCover = async novelId => {
const image = await DocumentPicker.getDocumentAsync({ type: 'image/*' });
if (image.type === 'success' && image.uri) {
const uri = 'file://' + image.uri;
db.transaction(tx => {
tx.executeSql(
'UPDATE novels SET novelCover = ? WHERE novelId = ?',
[uri, novelId],
(txObj, res) => {},
(txObj, error) => showToast(`Error: ${error}`),
);
});
};
export const setCustomNovelCover = async (novelId) => {
const image = await DocumentPicker.getDocumentAsync({ type: "image/*" });
if (image.uri) {
db.transaction((tx) => {
tx.executeSql(
"UPDATE novels SET novelCover = ? WHERE novelId = ?",
[image.uri, novelId],
(txObj, res) => {},
(txObj, error) => console.log("Error ", error)
);
});
return image.uri;
}
return image.uri;
}
};
-38
View File
@@ -1,38 +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
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)
);
});
};
+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),
),
);

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