Compare commits

...

99 Commits

Author SHA1 Message Date
Rajarshee Chatterjee cab9a83dc0 Release v1.1.14 2023-01-29 20:53:08 +05:30
Rajarshee Chatterjee 934798809d Minor Fixes 2023-01-29 20:45:49 +05:30
Rajarshee Chatterjee 4ceacdf324 fix(source): IndoWebNovel
Closes LNReader/lnreader-sources#405
2023-01-29 18:24:13 +05:30
Rajarshee Chatterjee 650ed2ff67 Minor Fixes 2023-01-29 18:22:30 +05:30
Rajarshee Chatterjee c38fc3232e chore: Update translations (#493)
* New translations strings.json (French)
[ci skip]

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

* New translations strings.json (Portuguese, Brazilian)
[ci skip]
2023-01-29 17:11:26 +05:30
Rajarshee Chatterjee f109c5747e fix: Fix Lint 2023-01-29 17:09:07 +05:30
Rajarshee Chatterjee 79b4db0cd2 fix: Reader Settings Screen Crash (Closes #322) 2023-01-29 17:05:11 +05:30
Rajarshee Chatterjee 8858ea3d94 fix(source): Minor Fixes 2023-01-29 17:03:59 +05:30
Rajarshee Chatterjee dea534557e fix(source): Ranobes
Closes LNReader/lnreader-sources#435
2023-01-29 16:20:08 +05:30
Rajarshee Chatterjee 0b112d9465 Minor Fixes 2023-01-29 16:09:00 +05:30
Rajarshee Chatterjee ddb8cd402a refactor: Use Batch Queries 2023-01-29 15:14:00 +05:30
Rajarshee Chatterjee 172145278f feat: Bypass Cloudflare
Closes LNReader/lnreader-sources#146
Closes LNReader/lnreader-sources#172
Closes LNReader/lnreader-sources#150
Closes LNReader/lnreader-sources#319
Closes LNReader/lnreader-sources#397
Closes LNReader/lnreader-sources#404
Closes LNReader/lnreader-sources#406
Closes LNReader/lnreader-sources#407
Closes LNReader/lnreader-sources#411
2023-01-29 14:53:42 +05:30
Rider21 7acb63e0f0 fix(source): Jaomix (#520)
* fix

* Update jaomix.js
2023-01-29 10:56:54 +05:30
Patrick Loser deefc10d41 feat: New Bottom Sheet Package (#515)
* setup

* implemented new bottomsheets

* set containerHeight

* NovelBottomSheet fix

* fixed BottomInsets

* edited FilterBottomSheet + cleanup

* TrackSheet + cleanup

* removal of rn-sliding-up-panel

Closes #506
2023-01-13 05:02:45 +05:30
Patrick Loser 7880cd0187 fix: Bottom Navbar Icon Color (#513)
* fixed library button color

* changed to onSecondaryContainer
2023-01-13 04:56:15 +05:30
Patrick Loser 9a594c4dd3 feat: Skeleton Loading Screens (#510)
* CategorySkeletonLoading

* getLoadingColors

* UpdatesSkeletonLoading

* Loading for Migrate

* HistorySkeletonLoading

* HistoryScreen fix

* HistoryScreen fix

* GlobalSearchLoading name change

* SourceScreenSkeletonLoading + implementation in Library + set unread fix

* set SkeletonLoading for LoadingMoreIndicator

* ESLint fix
2023-01-11 06:52:53 +05:30
Rajarshee Chatterjee 11a32d6776 fix: Mark Chapter as Read on TTS Finish (Closes #466) 2022-12-30 10:36:11 +05:30
Rajarshee Chatterjee 49e4e87f9e feat: Add Error Boundary 2022-12-30 10:28:54 +05:30
Rajarshee Chatterjee 1d0d7be968 Minor Fixes 2022-12-30 10:20:59 +05:30
Patrick Loser 06c5efd3a6 feat: Skeleton Loading Screens (#504)
* chapter drawer themed textcolor

* started with skeleton loading

* added new package

* added way to adebt to reader styles

* MAL loading

* type fix

* SourceScreenLoading

* NovelScreenLoading

* ESLint fix

* cleanup

* resolved conflict

* GlobalSearchLoading

Co-authored-by: Patrick <patrick.los@proton.me>
2022-12-28 13:20:44 +05:30
Patrick Loser 6d67c67d5f feat: Animated Splashscreen (#499)
* chapter drawer themed textcolor

* animated splashscreen

* added lotti animation

* splashscreen centered

* upgrade to android sdk 31

* reverted sdk changes

* fixed unstyled bars

* fixed animation

Co-authored-by: Patrick <patrick.los@proton.me>
2022-12-22 16:03:49 +05:30
Patrick Loser e559824718 feat: FAB for Last Read Chapter in Library (#500)
* chapter drawer themed textcolor

* added FAB to Library screen + some insets

* Added Settings butten

Co-authored-by: Patrick <patrick.los@proton.me>
2022-12-20 21:39:30 +05:30
Patrick Loser 2aaedfcfbf feat: Adaptive Icon (#497)
Closes #475

Co-authored-by: Patrick <patrick.los@proton.me>
2022-12-18 21:26:44 +05:30
Rajarshee Chatterjee 4cbd52a369 fix: Tweak Jump to Modal UI 2022-12-10 18:55:55 +05:30
Patrick Loser 87a42d2766 fix: Chapter Drawer Themed Text Color (#492)
Co-authored-by: Patrick <patrick.los@proton.me>
2022-12-10 17:08:22 +05:30
Patrick Loser 1453c39fc3 feat: Jump to Chapter Modal Redesign (#486)
* start up

* started to change jumpToChapterModal

* finished JumpToChapterModal redesign

* delete of results and no result if no text

* edited listElement width

* reverted (automatic) changes in package-lock and build.gradle

* design changes & bugfixes

* added translations

Co-authored-by: Patrick <patrick.los@proton.me>

Closes #472
2022-12-10 16:36:46 +05:30
Rajarshee Chatterjee eccb84b4a3 chore: Update translations (#448)
* New translations strings.json (Japanese)
[ci skip]

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

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

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

* New translations strings.json (Indonesian)
[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 (Ukrainian)
[ci skip]

* New translations strings.json (Turkish)
[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 (Russian)
[ci skip]

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

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

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

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

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

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

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

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

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

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

* New translations strings.json (German)
[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 (Arabic)
[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 (Hindi)
[ci skip]

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

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

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

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

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

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

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

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

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

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

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

* New translations strings.json (French)
[ci skip]
2022-12-10 10:18:57 +05:30
Rajarshee Chatterjee 3d69f46b44 fix: Minor Fixes 2022-12-10 10:09:55 +05:30
Rajarshee Chatterjee cc94bbd152 fix: Tweak Chapter Drawer UI 2022-12-10 09:34:29 +05:30
Patrick Loser 599224a5d4 feat: Chapter Drawer (#485)
* start up

* started with the implementation of chapter_Drawer

* changed data transer via route and the navigation between chapters

* changed ReadButton and butten at the end of chapter

* changed ReadButton and butten at the end of chapter

* changed ReadButton and butten at the end of chapter

* worked on behaviour, added buttons and styled

* fixed Buttons

* highlighted current chapter

* fixed bug caused of fast scrolling

* Changed Padding/Margin Behaviour of WebViewReader for Drawer to work properly

* fixed gradients in chapter list

* changed GestureRecognizer back

* shorted given props at chapter change

* fixed and changed scroll behaviour

* added Translation

* Changed drawer chapter naming

* review

* added chapterDrawer setting

* unessecary

* swipe optional for normal ReaderScreen, impossible for WebView, openDrawerButton in the middle of footer

* changed setting name "chapterDrawer" to "useChapterDrawerSwipeNavigation"

* put useNovel Hook in chapterDrawer and reverted navigate functions

* reverted changes from original ReaderScreen line 356-359

* removed Chapter transformation

* fixed switch between chapters and mistake in switch

* unified nacigation Params + changed name and location of handleNavigateChapterParams

* fixed loading problems from other screens than Novel and added LoadingScreenV2

Co-authored-by: Patrick <patrick.los@proton.me>
Co-authored-by: Patrick Loser <69157453+AustriaGamer@users.noreply.github.com>
2022-12-10 08:39:43 +05:30
Rajarshee Chatterjee 70a22518be fix(source): NovelUpdates: Add WordPress Selector
Closes LNReader/lnreader-sources#412
2022-12-09 21:15:29 +05:30
Rajarshee Chatterjee 900a569886 fix(source): NovelRingan: Remove Duplicated Chapter Text
Closes LNReader/lnreader-sources#414
2022-12-09 10:44:19 +05:30
Rajarshee Chatterjee 6e851eed94 fix(source): ReadLightNovel Search
Closes LNReader/lnreader-sources#371
2022-12-09 10:27:17 +05:30
Rajarshee Chatterjee a2ceba9019 fix(source): ReinoWuxia
Closes LNReader/lnreader-sources#186
2022-12-09 10:11:29 +05:30
Rajarshee Chatterjee 412f216bf5 fix(source): ReadNovelFull
Closes LNReader/lnreader-sources#325
2022-12-09 09:58:09 +05:30
dependabot[bot] 1e2dddf46c chore(deps): bump qs from 6.10.1 to 6.10.3 (#488)
Bumps [qs](https://github.com/ljharb/qs) from 6.10.1 to 6.10.3.
- [Release notes](https://github.com/ljharb/qs/releases)
- [Changelog](https://github.com/ljharb/qs/blob/main/CHANGELOG.md)
- [Commits](https://github.com/ljharb/qs/compare/v6.10.1...v6.10.3)

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

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-12-08 09:10:30 +05:30
Rider21 3e526658bf fix(source): Ranobes (#489) 2022-12-07 09:22:59 +05:30
Rider21 15cdeb30ad fix(source): Update Rulate (#483)
update
2022-12-02 07:03:47 +05:30
Marcin Majewski 3cb573681f fix(delete_chapter): Excessive number of pending callbacks warn (#481)
* fix(delete_chapter): fix delete all not working

Sometimes there would be "Excessive number of pending callbacks" warn shown.
Removing chapters in batches of 10 (I tried 30 but it was still an issue) seems to work fine now.
TODO: Add some icon about deletion status and make it not lag the entire app.

* fix(delete_chapter): always-coccurent deletion

Make deleting chapters do 3 coccurent deletion at a time making it work similarly to bluebird's `Promise.map(,{coccurent:3})` and removing the `Excessive number of pending callbacks` warn gone.

* Remove unnecesarry console.log
2022-11-26 06:48:53 +05:30
Rajarshee Chatterjee 383c8f791b fix: Stats Screen Fixes 2022-11-05 23:13:02 +05:30
Rajarshee Chatterjee 125632b9ff fix: Workaround for Build Failures
https://github.com/facebook/react-native/issues/35210
2022-11-05 23:12:29 +05:30
Rider21 cb67d17747 Fix Date String (#457)
* typo correction

* fix image

* agree
2022-10-26 17:43:11 +05:30
Rajarshee Chatterjee 7ec578639e feat: Stats Screen 2022-10-24 08:08:28 +05:30
Rajarshee Chatterjee a1d6e1d6ce fix(source): WBNovel: Update Language
Closes LNReader/lnreader-sources##408
2022-10-24 07:02:04 +05:30
Rajarshee Chatterjee 1fed66792d build: Upload Artifact 2022-10-22 09:57:26 +05:30
Rajarshee Chatterjee abc815e413 Revert "chore(deps): bump react-native-reanimated from 2.2.4 to 2.10.0" (#455)
Revert "chore(deps): bump react-native-reanimated from 2.2.4 to 2.10.0 (#452)"

This reverts commit 8cb27bf95f.
2022-10-21 17:02:49 +05:30
dependabot[bot] 8cb27bf95f chore(deps): bump react-native-reanimated from 2.2.4 to 2.10.0 (#452)
Bumps [react-native-reanimated](https://github.com/software-mansion/react-native-reanimated) from 2.2.4 to 2.10.0.
- [Release notes](https://github.com/software-mansion/react-native-reanimated/releases)
- [Commits](https://github.com/software-mansion/react-native-reanimated/compare/2.2.4...2.10.0)

---
updated-dependencies:
- dependency-name: react-native-reanimated
  dependency-type: direct:production
...

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

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2022-10-21 16:44:28 +05:30
Rajarshee Chatterjee a84ee5ade4 chore: Update translations (#424)
* New translations strings.json (French)
[ci skip]

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

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

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

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

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

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

* New translations strings.json (Indonesian)
[ci skip]
2022-10-10 20:01:49 +05:30
Rajarshee Chatterjee 30ce798c2a feat(source): Add WebNovelOku (Closes LNReader/lnreader-sources#378) 2022-10-10 19:59:22 +05:30
Rajarshee Chatterjee 5ed221648c fix: Add Cloudflare Error Message for LightNovelPub 2022-09-27 21:01:11 +05:30
Rajarshee Chatterjee 93bc5f19ff fix: Update NovelTrench baseUrl (Closes LNReader/lnreader-sources#385) 2022-09-27 08:56:12 +05:30
Rider21 d527319901 feat(source): Add AuthorToday (#434)
* AuthorToday

* fix

* prettier

* test

* ok
2022-09-11 11:57:18 +05:30
Rhino 20470929e5 fix: updates tab sort as data displayed is wrong when release datetime similar (#427)
* Added following sources Zetro Translation, Nocturne, Asada Translation and Sugar Babies

* added time stamp to backup file

* fixed: updates tab sort as data displayed is wrong when release datetime similar
2022-09-10 17:19:15 +05:30
J. Fronny 69dc329fdb build: Generate versionCode (#432)
* Generate versionCode

* Fix comment
2022-09-10 17:18:27 +05:30
Rajarshee Chatterjee 78165b072e Release v1.1.13 2022-09-02 09:30:49 +05:30
Rajarshee Chatterjee d749225820 chore: Update translations (#343)
* New translations strings.json (Russian)
[ci skip]

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

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

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

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

* New translations strings.json (Turkish)
[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 (Italian)
[ci skip]

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

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

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

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

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

* New translations strings.json (German)
[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 (Arabic)
[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 (Portuguese, Brazilian)
[ci skip]

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

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

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

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

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

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

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

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

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

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

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

* New translations strings.json (Turkish)
[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 (Italian)
[ci skip]

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

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

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

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

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

* New translations strings.json (German)
[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 (Arabic)
[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 (Portuguese, Brazilian)
[ci skip]

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

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

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

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

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

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

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

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

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

* New translations strings.json (Turkish)
[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 (Italian)
[ci skip]

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

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

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

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

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

* New translations strings.json (German)
[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 (Arabic)
[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 (Portuguese, Brazilian)
[ci skip]

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

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

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

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

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

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

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

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

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

* New translations strings.json (Turkish)
[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 (Italian)
[ci skip]

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

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

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

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

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

* New translations strings.json (German)
[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 (Arabic)
[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 (Portuguese, Brazilian)
[ci skip]

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

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

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

* New translations strings.json (Russian)
[ci skip]
2022-09-02 09:30:32 +05:30
Rajarshee Chatterjee d6d487101b feat(source): Add Madara Sources
Closes LNReader/lnreader-sources#365
Closes LNReader/lnreader-sources#363
2022-08-28 11:49:05 +05:30
Rajarshee Chatterjee ba62c1ba0a Minor Fixes 2022-08-28 11:37:04 +05:30
Rajarshee Chatterjee 75f4f18448 Minor Fixes 2022-08-28 11:05:25 +05:30
Rajarshee Chatterjee f9ebec4a30 fix: Global Search Navigation (Closes #406) 2022-08-28 10:56:05 +05:30
Rajarshee Chatterjee de18e3d9d8 fix: Category Fixes 2022-08-28 10:56:05 +05:30
Rajarshee Chatterjee acaac74db4 fix: Add Chapter Name Back in Reader (Closes #410) 2022-08-28 10:56:05 +05:30
Rajarshee Chatterjee d8845661a7 fix: Long Category Names (Closes #407) 2022-08-28 10:56:05 +05:30
Rider21 b66a6130c5 feat(source): Add Ficbook (#409)
* update

* fix

* oops
2022-08-25 07:15:34 +05:30
Rhino 220f25bf72 feat: Update chapter tab sort (#402)
* Added following sources Zetro Translation, Nocturne, Asada Translation and Sugar Babies

* added time stamp to backup file

* tweaks updated chapter sort order in Updates Tab
2022-08-25 07:12:45 +05:30
Rajarshee Chatterjee 934edbb84c feat: Add Change Default Category (Closes #383) 2022-08-18 21:16:35 +05:30
Rajarshee Chatterjee 0d8a5d6172 fix: Navbar Color (Closes #386) 2022-08-18 21:13:36 +05:30
Rajarshee Chatterjee e706745f30 feat: Select Current Category in Library (Closes #396) 2022-08-18 09:43:05 +05:30
Rajarshee Chatterjee dce3652ae8 feat: Add Select All in Library (Closes #380) 2022-08-18 09:07:12 +05:30
Rajarshee Chatterjee 0ca3b8ce15 fix: Cleanup
Closes #378
Closes #393
2022-08-18 08:40:49 +05:30
Rajarshee Chatterjee c62db5cb23 chore: Update Source Icon Links 2022-08-18 07:44:55 +05:30
Rajarshee Chatterjee e33b433e84 fix: Change status bar style on background color change (Closes #150) 2022-08-17 08:18:12 +05:30
Rhino c95f376e20 feat(source): Add following sources Zetro, Nocturne, Asada and SugarBB (#389)
* Added following sources Zetro Translation, Nocturne, Asada Translation and Sugar Babies

* added time stamp to backup file
2022-08-16 23:20:00 +05:30
Rhino 9b45375638 feat: Add time stamp to backup file (#390) 2022-08-16 23:17:03 +05:30
Rajarshee Chatterjee 7fbdc6732c fix: Reset Category to default on removing from current category (Closes #379) 2022-08-16 13:27:54 +05:30
Rajarshee Chatterjee 3f9549d320 fix: Path Aliases 2022-08-16 13:03:29 +05:30
Rajarshee Chatterjee 079010b6ca chore: Fix Issue Template Label 2022-08-16 13:00:34 +05:30
Rajarshee Chatterjee 107e534565 Cleanup 2022-08-16 12:38:13 +05:30
Rajarshee Chatterjee f0665866ae feat: Custom Order in Categories (Closes #376) 2022-08-16 10:56:08 +05:30
Rajarshee Chatterjee 706b4581b1 fix: Hide "Removed from library" toast if novel is in library 2022-08-16 07:26:06 +05:30
Rajarshee Chatterjee a8d9c3abfa chore: Add Issue Templates and Moderator 2022-08-15 20:27:19 +05:30
Rajarshee Chatterjee 66798c46db Minor fixes
Closes #373
Closes #374
2022-08-15 16:51:21 +05:30
Rajarshee Chatterjee 87fa081cfd feat: Material You (#377)
* feat: Material You

* feat: Material You
2022-08-15 15:36:06 +05:30
Rajarshee Chatterjee ea26cd67e4 fix(source): Add Status in Ranobes (Closes LNReader/lnreader-sources#359) 2022-08-14 15:44:09 +05:30
Rajarshee Chatterjee 9400fcd252 feat(source): Add NeoSekai Translations and MTL-Novel
Closes LNReader/lnreader-sources#306
Closes LNReader/lnreader-sources#328
2022-08-14 15:43:22 +05:30
Rajarshee Chatterjee 30ca05df06 feat: Add novel in library on long press in search results (Closes #151) 2022-08-14 13:00:21 +05:30
Rajarshee Chatterjee eac3ee32c8 fix: Add Empty Summary placeholder and show Full Novel Title (Closes #355) 2022-08-14 11:43:49 +05:30
Rajarshee Chatterjee 0cf249eec5 fix: Use Separate States for Search Results in Browse Screen (Closes #359) 2022-08-14 11:15:56 +05:30
Rajarshee Chatterjee 27e5300f4f fix: Clear Searchbar when Screen is Unfocused (Closes #358) 2022-08-14 11:13:10 +05:30
Rajarshee Chatterjee 97e02177a4 fix: Global Search from Novel and MAL Screen (Closes #356) 2022-08-14 10:23:04 +05:30
Rajarshee Chatterjee 42b37f87fe fix(source): Remove NovelSpace and Move ArNovel to MadaraGenerator
(Closes LNReader/lnreader-sources#353)
2022-08-14 10:06:03 +05:30
Rajarshee Chatterjee 4ad1c0253b fix: Reset Novel Categories on deleting a Category (Closes #370) 2022-08-14 09:52:00 +05:30
Rajarshee Chatterjee a57ed7a7a2 feat: Add category-wise updates 2022-08-14 09:30:16 +05:30
Rajarshee Chatterjee 9fa00d9d9b fix(source): Update WuxiaWorldCo baseUrl 2022-08-14 09:28:33 +05:30
Rajarshee Chatterjee 18f63a217f fix: Category/Library bugs 2022-08-14 07:44:53 +05:30
Rajarshee Chatterjee 05401ada6b fix: Fix deleting downloaded chapters (Closes #341) 2022-08-14 07:02:25 +05:30
Rajarshee Chatterjee e256537079 feat: Add Categories (#366)
* feat: Add Categories (Closes #45)

* fix: ESLint Errors

* feat: Add Validation for Duplicate Category Name

* feat: Multiple Categories Support & Hide Default Category

* fix: Creating Tables on Clean Installation

* feat: Add Started Filter
2022-08-13 23:07:58 +05:30
Rider21 0eec38f26f feat: Source Updates (#350)
- Fix ranobes.net (Closes LNReader/lnreader-sources#70)
- Add ranobes.com source
2022-08-05 21:22:24 +05:30
Rider21 fa90d57eff refactor: Replace moment with Dayjs (#346)
* dayjs

* locales added

* fix
2022-08-02 13:17:45 +05:30
334 changed files with 13096 additions and 6047 deletions
+94
View File
@@ -0,0 +1,94 @@
# Cloned from tachiyomi
name: Issue report
description: Report an issue in LNReader
labels: [Bug]
body:
- type: textarea
id: reproduce-steps
attributes:
label: Steps to reproduce
description: Provide an example of the issue.
placeholder: |
Example:
1. First step
2. Second step
3. Issue here
validations:
required: true
- type: textarea
id: expected-behavior
attributes:
label: Expected behavior
description: Explain what you should expect to happen.
placeholder: |
Example:
"This should happen..."
validations:
required: true
- type: textarea
id: actual-behavior
attributes:
label: Actual behavior
description: Explain what actually happens.
placeholder: |
Example:
"This happened instead..."
validations:
required: true
- type: input
id: lnreader-version
attributes:
label: LNReader version
description: You can find your LNReader version in **More → About**.
placeholder: |
Example: "1.1.14"
validations:
required: true
- type: input
id: android-version
attributes:
label: Android version
description: You can find this somewhere in your Android settings.
placeholder: |
Example: "Android 11"
validations:
required: true
- type: input
id: device
attributes:
label: Device
description: List your device and model.
placeholder: |
Example: "Google Pixel 5"
validations:
required: true
- type: textarea
id: other-details
attributes:
label: Other details
placeholder: |
Additional details and attachments.
- type: checkboxes
id: acknowledgements
attributes:
label: Acknowledgements
description: Read this carefully, we will close and ignore your issue if you skimmed through this.
options:
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open or closed issue.
required: true
- label: I have written a short but informative title.
required: true
- label: If this is an issue with a source, I should be opening an issue in the [sources repository](https://github.com/lnreader/lnreader-sources/issues/new/choose).
required: true
- label: I have updated the app to version **[1.1.14](https://github.com/lnreader/lnreader/releases/latest)**.
required: true
- label: I will fill out all of the requested information in this form.
required: true
@@ -0,0 +1,40 @@
# Cloned from tachiyomi
name: Feature request
description: Suggest a feature to improve LNReader
labels: [Feature Request]
body:
- type: textarea
id: feature-description
attributes:
label: Describe your suggested feature
description: How can LNReader be improved?
placeholder: |
Example:
"It should work like this..."
validations:
required: true
- type: textarea
id: other-details
attributes:
label: Other details
placeholder: |
Additional details and attachments.
- type: checkboxes
id: acknowledgements
attributes:
label: Acknowledgements
description: Read this carefully, we will close and ignore your issue if you skimmed through this.
options:
- label: I have searched the existing issues and this is a new ticket, **NOT** a duplicate or related to another open or closed issue.
required: true
- label: I have written a short but informative title.
required: true
- label: If this is an issue with a source, I should be opening an issue in the [sources repository](https://github.com/LNReader/lnreader-sources/issues/new/choose).
required: true
- label: I have updated the app to version **[1.1.14](https://github.com/LNReader/lnreader/releases/latest)**.
required: true
- label: I will fill out all of the requested information in this form.
required: true
+11
View File
@@ -33,3 +33,14 @@ jobs:
- name: Build Android Release
run: |
cd android && ./gradlew assembleRelease --no-daemon
- name: Set Env
run: |
set -x
echo "COMMIT_COUNT=$(git rev-list --count HEAD)" >> $GITHUB_ENV
- name: Upload Artifact
uses: actions/upload-artifact@v1
with:
name: LNReader-r${{ env.COMMIT_COUNT }}
path: android/app/build/outputs/apk/release/app-release.apk
+31
View File
@@ -0,0 +1,31 @@
# Cloned from tachiyomi
name: Issue moderator
on:
issues:
types: [opened, edited, reopened]
issue_comment:
types: [created]
jobs:
moderate:
runs-on: ubuntu-latest
steps:
- name: Moderate issues
uses: tachiyomiorg/issue-moderator-action@v1
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
auto-close-rules: |
[
{
"type": "body",
"regex": ".*DELETE THIS SECTION IF YOU HAVE READ AND ACKNOWLEDGED IT.*",
"message": "The acknowledgment section was not removed."
},
{
"type": "body",
"regex": ".*\\* (LNReader version|Android version|Device): \\?.*",
"message": "Requested information in the template was not filled out."
}
]
+21 -14
View File
@@ -1,3 +1,4 @@
import 'react-native-gesture-handler';
import { enableFreeze } from 'react-native-screens';
enableFreeze(true);
@@ -9,30 +10,36 @@ 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 { createDatabase } from './src/database/db';
import { SafeAreaProvider } from 'react-native-safe-area-context';
import LottieSplashScreen from 'react-native-lottie-splash-screen';
import AppErrorBoundary from '@components/AppErrorBoundary/AppErrorBoundary';
import { GestureHandlerRootView } from 'react-native-gesture-handler';
const App = () => {
useEffect(() => {
SplashScreen.preventAutoHideAsync();
createDB();
LottieSplashScreen.hide();
createDatabase();
}, []);
return (
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<SafeAreaProvider>
<PaperProvider>
<StatusBar translucent={true} backgroundColor="transparent" />
<Main />
</PaperProvider>
</SafeAreaProvider>
</PersistGate>
</Provider>
<GestureHandlerRootView style={{ flex: 1 }}>
<AppErrorBoundary>
<Provider store={store}>
<PersistGate loading={null} persistor={persistor}>
<SafeAreaProvider>
<PaperProvider>
<StatusBar translucent={true} backgroundColor="transparent" />
<Main />
</PaperProvider>
</SafeAreaProvider>
</PersistGate>
</Provider>
</AppErrorBoundary>
</GestureHandlerRootView>
);
};
+10 -3
View File
@@ -122,6 +122,12 @@ def jscFlavor = 'org.webkit:android-jsc:+'
*/
def enableHermes = project.ext.react.get("enableHermes", false);
ext {
versionMajor = 1
versionMinor = 1
versionPatch = 14
}
android {
compileSdkVersion rootProject.ext.compileSdkVersion
@@ -134,8 +140,9 @@ android {
applicationId 'com.rajarsheechatterjee.LNReader'
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 1
versionName "1.1.12"
// Generated version code. Supports versions up to 1024.1024.2048
versionCode ((((versionMajor << 10) | versionMinor) << 11) | versionPatch)
versionName "$versionMajor.$versionMinor.$versionPatch"
}
splits {
abi {
@@ -187,7 +194,7 @@ dependencies {
//noinspection GradleDynamicVersion
implementation "com.facebook.react:react-native:+" // From node_modules
implementation "androidx.swiperefreshlayout:swiperefreshlayout:1.0.0"
implementation project(':react-native-lottie-splash-screen')
implementation 'com.github.bumptech.glide:glide:4.12.0'
annotationProcessor 'com.github.bumptech.glide:compiler:4.12.0'
+2
View File
@@ -8,6 +8,8 @@
# http://developer.android.com/guide/developing/tools/proguard.html
# Add any project specific keep options here:
-keep class com.swmansion.reanimated.** { *; }
-keep class com.facebook.react.turbomodule.** { *; }
-keep class com.facebook.hermes.unicode.** { *; }
-keep class com.facebook.jni.** { *; }
-keep public class com.dylanvann.fastimage.* {*;}
+38 -39
View File
@@ -1,26 +1,26 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.rajarsheechatterjee.LNReader">
<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.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"/>
<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.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"/>
<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>
@@ -28,24 +28,23 @@
</intent>
</queries>
<application
android:name=".MainApplication"
<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"/>
android:resource="@drawable/notification_icon" />
<meta-data
android:name="expo.modules.notifications.default_notification_color"
android:resource="@color/notification_icon_color"/>
android:resource="@color/notification_icon_color" />
<activity
android:name=".MainActivity"
android:label="@string/app_name"
@@ -54,24 +53,24 @@
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>
<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>
</application>
</manifest>
@@ -1,15 +1,14 @@
package com.rajarsheechatterjee.LNReader;
import android.content.res.Configuration;
import android.content.Intent;
import android.os.Bundle;
import android.content.res.Configuration;
import android.graphics.Color;
import android.view.KeyEvent;
import android.view.WindowManager;
import android.os.Build;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import com.facebook.react.ReactActivity;
import com.facebook.react.ReactActivityDelegate;
@@ -17,9 +16,7 @@ import com.facebook.react.ReactRootView;
import com.rajarsheechatterjee.VolumeButtonListener.VolumeButtonListener;
import com.swmansion.gesturehandler.react.RNGestureHandlerEnabledRootView;
import expo.modules.splashscreen.singletons.SplashScreen;
import expo.modules.splashscreen.SplashScreenImageResizeMode;
import org.devio.rn.splashscreen.SplashScreen;
public class MainActivity extends ReactActivity {
@@ -35,7 +32,8 @@ public class MainActivity extends ReactActivity {
@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
// 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;
@@ -45,11 +43,11 @@ public class MainActivity extends ReactActivity {
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);
w.getDecorView()
.setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
SplashScreen.show(this,R.style.SplashScreenTheme, R.id.lottie);
SplashScreen.setAnimationFinished(true);
super.onCreate(null);
SplashScreen.show(this, SplashScreenImageResizeMode.COVER, ReactRootView.class, false);
}
@Override
@@ -69,7 +67,7 @@ public class MainActivity extends ReactActivity {
return true;
case KeyEvent.KEYCODE_VOLUME_DOWN:
if (action == KeyEvent.ACTION_DOWN) {
//voldown
// voldown
VolumeButtonListener.down();
if (!VolumeButtonListener.prevent)
return super.dispatchKeyEvent(event);
@@ -21,10 +21,11 @@ import com.swmansion.reanimated.ReanimatedJSIModulePackage;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import org.devio.rn.splashscreen.SplashScreenReactPackage;
public class MainApplication extends Application implements ReactApplication {
private final ReactModuleRegistryProvider mModuleRegistryProvider = new ReactModuleRegistryProvider(
new BasePackageList().getPackageList()
);
new BasePackageList().getPackageList());
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
@Override
@@ -66,7 +67,8 @@ public class MainApplication extends Application implements ReactApplication {
}
/**
* Loads Flipper in React Native templates. Call this in the onCreate method with something like
* Loads Flipper in React Native templates. Call this in the onCreate method
* with something like
* initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
*
* @param context
@@ -77,9 +79,9 @@ public class MainApplication extends Application implements ReactApplication {
if (BuildConfig.DEBUG) {
try {
/*
We use reflection here to pick up the class that initializes Flipper,
since Flipper library is not available in release mode
*/
* We use reflection here to pick up the class that initializes Flipper,
* since Flipper library is not available in release mode
*/
Class<?> aClass = Class.forName("com.rajarsheechatterjee.LNReader.ReactNativeFlipper");
aClass
.getMethod("initializeFlipper", Context.class, ReactInstanceManager.class)
@@ -20,7 +20,6 @@ public class BasePackageList {
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.webbrowser.WebBrowserPackage()
);
Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 67 KiB

@@ -0,0 +1,25 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="center"
android:background="@color/colorPrimaryDark"
android:gravity="center"
android:orientation="vertical"
android:scaleType="centerCrop"
android:navigationBarColor="#333333"
tools:context=".MainActivity">
<com.airbnb.lottie.LottieAnimationView
android:id="@+id/lottie"
android:layout_width="200dp"
android:layout_height="200dp"
android:layout_gravity="center_horizontal"
android:orientation="horizontal"
android:paddingLeft="25dp"
app:lottie_autoPlay="true"
app:lottie_loop="false"
app:lottie_rawRes="@raw/loading" />
</LinearLayout>
@@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/iconBackground"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
<background android:drawable="@mipmap/ic_launcher_background" />
<foreground android:drawable="@mipmap/ic_launcher_foreground" />
<monochrome android:drawable="@mipmap/ic_launcher_monochrome" />
</adaptive-icon>
@@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/iconBackground"/>
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>
Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 858 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 876 B

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 464 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 876 B

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.0 KiB

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.5 KiB

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

File diff suppressed because one or more lines are too long
@@ -6,4 +6,5 @@
<color name="colorPrimaryDark">#202125</color>
<color name="navigationBarColor">#202125</color>
<color name="notification_icon_color">#00adb5</color>
<color name="ic_launcher_background">#222831</color>
</resources>
+11 -7
View File
@@ -8,16 +8,20 @@
<item name="android:textColor">#000000</item>
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="android:navigationBarColor">#202125</item>
<item name="android:statusBarColor">#202125</item>
<item name="android:navigationBarColor">@color/colorPrimaryDark</item>
<item name="android:statusBarColor">@color/colorPrimaryDark</item>
</style>
<style name="Theme.App.SplashScreen" parent="Theme.AppCompat.Light.NoActionBar">
<style name="SplashScreenTheme" parent="SplashScreen_SplashTheme">
<item name="android:statusBarColor">@color/colorPrimaryDark</item>
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
<item name="android:windowFullscreen">true</item>
<item name="android:navigationBarColor">@color/colorPrimaryDark</item>
</style>
<style name="Theme.App.SplashScreen" parent="AppTheme">
<!-- 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="icon">@drawable/no_icon</item> -->
<item name="android:windowBackground">@drawable/splashscreen</item>
<item name="android:navigationBarColor">#202125</item>
<item name="android:statusBarColor">#202125</item>
</style>
</resources>
+15
View File
@@ -6,6 +6,7 @@ buildscript {
minSdkVersion = 21
compileSdkVersion = 30
targetSdkVersion = 30
ndkVersion = "21.1.6352462"
}
repositories {
google()
@@ -21,6 +22,20 @@ buildscript {
allprojects {
repositories {
exclusiveContent {
// We get React Native's Android binaries exclusively through npm,
// from a local Maven repo inside node_modules/react-native/.
// (The use of exclusiveContent prevents looking elsewhere like Maven Central
// and potentially getting a wrong version.)
filter {
includeGroup "com.facebook.react"
}
forRepository {
maven {
url "$rootDir/../node_modules/react-native/android"
}
}
}
mavenLocal()
maven {
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
+3
View File
@@ -6,4 +6,7 @@ includeUnimodulesProjects()
apply from: file("../node_modules/@react-native-community/cli-platform-android/native_modules.gradle");
applyNativeModulesSettingsGradle(settings)
include ':react-native-lottie-splash-screen'
project(':react-native-lottie-splash-screen').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-lottie-splash-screen/android')
include ':app'
+6 -2
View File
@@ -6,17 +6,21 @@ module.exports = function (api) {
[
'module-resolver',
{
root: ['./src'],
alias: {
'@components': './src/components',
'@database': './src/database',
'@hooks': './src/hooks',
'@screens': './src/screens',
'@strings': './strings',
'@redux': './src/redux',
'@services': './src/services',
'@sources': './src/sources',
'@utils': './src/utils',
'@theme': './src/theme',
},
extensions: ['.ios.js', '.android.js', '.js', '.ts', '.tsx', '.json'],
},
],
'react-native-reanimated/plugin',
],
};
};
+922 -927
View File
File diff suppressed because it is too large Load Diff
+20 -10
View File
@@ -1,6 +1,6 @@
{
"name": "lnreader",
"version": "1.1.12",
"version": "1.1.14",
"private": true,
"scripts": {
"android": "react-native run-android",
@@ -8,24 +8,31 @@
"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}",
"buildRelease": "cd android && ./gradlew clean && ./gradlew assembleRelease",
"open": "open ./android/app/build/outputs/apk/release/",
"sources:madara": "node scripts/generateMadaraSource.cjs",
"source:generator": "node scripts/generateSource.cjs",
"strings": "node scripts/stringTypes.cjs",
"prepare": "husky install"
},
"dependencies": {
"@gorhom/bottom-sheet": "^4.4.5",
"@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-cookies/cookies": "^6.2.1",
"@react-native-picker/picker": "^2.4.1",
"@react-navigation/drawer": "^6.0.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",
"@shopify/flash-list": "^1.2.1",
"buffer": "^6.0.3",
"cheerio": "^1.0.0-rc.10",
"color": "^4.2.3",
"dayjs": "^1.11.4",
"expo": "^41.0.1",
"expo-document-picker": "^9.2.4",
"expo-file-system": "~11.0.2",
@@ -35,29 +42,32 @@
"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",
"lottie-react-native": "^5.1.4",
"prop-types": "^15.7.2",
"qs": "^6.10.1",
"qs": "^6.10.3",
"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-error-boundary": "^1.1.16",
"react-native-fast-image": "^8.5.11",
"react-native-gesture-handler": "~1.10.2",
"react-native-gesture-handler": "~2.1.3",
"react-native-lottie-splash-screen": "^1.0.1",
"react-native-markdown-display": "^7.0.0-alpha.2",
"react-native-mmkv": "^2.4.3",
"react-native-pager-view": "^5.4.9",
"react-native-paper": "^4.10.1",
"react-native-reanimated": "^2.2.4",
"react-native-paper": "^5.1.2",
"react-native-reanimated": "^2.13.0",
"react-native-render-html": "^6.3.4",
"react-native-safe-area-context": "^3.2.0",
"react-native-screens": "^3.9.0",
"react-native-shimmer-placeholder": "^2.0.9",
"react-native-swipe-gestures": "^1.0.5",
"react-native-tab-view": "^3.1.1",
"react-native-tts": "^4.1.0",
@@ -68,12 +78,12 @@
"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/color": "^3.0.3",
"@types/i18n-js": "^3.8.2",
"@types/jest": "^27.4.0",
"@types/lodash": "^4.14.179",
+14
View File
@@ -1,5 +1,7 @@
const path = require('path');
const fs = require('fs');
const prettier = require('prettier');
const prettierConfig = require('../.prettierrc');
const MadaraSources = require('../src/sources/multisrc/madara/MadaraSources.json');
const AllSources = require('../src/sources/sources.json');
@@ -7,6 +9,7 @@ const AllSources = require('../src/sources/sources.json');
const getScraperName = scraperName => {
let name = scraperName
.replace(/\s+/g, '')
.replace(/-/g, 'Dash')
.replace(/\./g, 'Dot')
.replace('1st', 'First');
@@ -26,6 +29,11 @@ MadaraSources.forEach(source => {
}", ${source.options ? JSON.stringify(source.options) : ''});\n\n`;
});
content = prettier.format(content, {
parser: 'typescript',
...prettierConfig,
});
fs.writeFile(
path.resolve(process.cwd(), 'src/sources/multisrc/madara/MadaraGenerator.ts'),
content,
@@ -52,6 +60,12 @@ MadaraSources.forEach(madaraSource => {
allSourcesContent = JSON.stringify(allSourcesContent);
allSourcesContent = prettier.format(allSourcesContent, {
parser: 'json5',
quoteProps: 'preserve',
trailingComma: 'none',
});
fs.writeFile(
path.resolve(process.cwd(), 'src/sources/sources.json'),
allSourcesContent,
-47
View File
@@ -1,47 +0,0 @@
import React from 'react';
import { Dimensions, StyleSheet, View } from 'react-native';
import { IconButton } from 'react-native-paper';
export const Actionbar = ({ active, actions, theme, style }) => {
if (active) {
return (
<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,
},
});
+88
View File
@@ -0,0 +1,88 @@
import { useTheme } from '@hooks/useTheme';
import React from 'react';
import {
Dimensions,
Pressable,
StyleProp,
StyleSheet,
View,
ViewProps,
} from 'react-native';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import color from 'color';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { overlay } from 'react-native-paper';
type Action = {
icon: string;
onPress: () => void;
};
interface ActionbarProps {
active: boolean;
actions: Action[];
viewStyle?: StyleProp<ViewProps>;
}
export const Actionbar: React.FC<ActionbarProps> = ({
active,
actions,
viewStyle,
}) => {
const theme = useTheme();
const { bottom } = useSafeAreaInsets();
if (active) {
return (
<View
style={[
styles.actionbarContainer,
{
backgroundColor: theme.isDark
? overlay(2, theme.surface)
: theme.secondaryContainer,
minHeight: 80 + bottom,
paddingBottom: bottom,
},
viewStyle,
]}
>
{actions.map(({ icon, onPress }, id) => (
<Pressable
key={id}
android_ripple={{
radius: 50,
color: color(theme.primary).alpha(0.12).string(),
borderless: true,
}}
onPress={onPress}
>
<MaterialCommunityIcons
name={icon}
color={theme.onSurface}
size={24}
/>
</Pressable>
))}
</View>
);
} else {
return null;
}
};
const styles = StyleSheet.create({
actionbarContainer: {
position: 'absolute',
width: Dimensions.get('window').width,
bottom: 0,
borderTopLeftRadius: 16,
borderTopRightRadius: 16,
elevation: 1,
flexDirection: 'row',
justifyContent: 'space-around',
alignItems: 'center',
},
});
@@ -0,0 +1,98 @@
import React from 'react';
import { StyleSheet, View, Text, StatusBar } from 'react-native';
import ErrorBoundary from 'react-native-error-boundary';
import { Button, List } from '@components';
import { useTheme } from '@hooks';
import { ButtonVariation } from '@components/Button/Button';
interface ErrorFallbackProps {
error: Error;
resetError: () => void;
}
export const ErrorFallback: React.FC<ErrorFallbackProps> = ({
error,
resetError,
}) => {
const theme = useTheme();
return (
<View style={[styles.mainCtn, { backgroundColor: theme.background }]}>
<StatusBar translucent={true} backgroundColor="transparent" />
<View style={styles.errorInfoCtn}>
<Text style={[styles.errorTitle, { color: theme.onSurface }]}>
An Unexpected Error Ocurred
</Text>
<Text style={[styles.errorDesc, { color: theme.onSurface }]}>
The application ran into an unexpected error. We suggest you
screenshot this message and then share it in our support channel on
Discord.
</Text>
<Text
style={[
styles.errorCtn,
{
backgroundColor: theme.surfaceVariant,
color: theme.onSurfaceVariant,
},
]}
numberOfLines={20}
>
{`${error.message}\n\n${error.stack}`}
</Text>
</View>
<List.Divider theme={theme} />
<Button
variation={ButtonVariation.DEFAULT}
onPress={resetError}
title={'Restart the application'}
style={styles.buttonCtn}
theme={theme}
/>
</View>
);
};
interface AppErrorBoundaryProps {
children: React.ReactNode | React.ReactElement;
}
const AppErrorBoundary: React.FC<AppErrorBoundaryProps> = ({ children }) => {
return (
<ErrorBoundary FallbackComponent={ErrorFallback}>{children}</ErrorBoundary>
);
};
export default AppErrorBoundary;
const styles = StyleSheet.create({
mainCtn: {
flex: 1,
},
errorInfoCtn: {
flex: 1,
justifyContent: 'center',
padding: 16,
},
buttonCtn: {
margin: 16,
marginBottom: 32,
},
errorTitle: {
marginBottom: 8,
textAlign: 'center',
fontSize: 20,
},
errorDesc: {
lineHeight: 20,
marginVertical: 8,
},
errorCtn: {
borderRadius: 8,
lineHeight: 20,
marginVertical: 16,
paddingVertical: 16,
paddingHorizontal: 8,
},
});
-30
View File
@@ -1,30 +0,0 @@
import React from 'react';
import PropTypes from 'prop-types';
import { Appbar as MaterialAppbar } from 'react-native-paper';
import { useTheme } from '../hooks/reduxHooks';
import { ViewPropTypes } from 'react-native';
export const Appbar = ({ title, onBackAction, style, children }) => {
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>
);
};
Appbar.propTypes = {
title: PropTypes.string,
onBackAction: PropTypes.func,
style: ViewPropTypes.style,
};
+19 -5
View File
@@ -2,24 +2,38 @@ import React from 'react';
import { StatusBar } from 'react-native';
import { Appbar as PaperAppbar } from 'react-native-paper';
import { ThemeType } from '../../theme/types';
import { MD3ThemeType } from '../../theme/types';
interface AppbarProps {
title: string;
handleGoBack: () => void;
theme: ThemeType;
theme: MD3ThemeType;
mode?: 'small' | 'medium' | 'large' | 'center-aligned';
}
const Appbar: React.FC<AppbarProps> = ({ title, handleGoBack, theme }) => (
const Appbar: React.FC<AppbarProps> = ({
title,
handleGoBack,
theme,
mode = 'large',
children,
}) => (
<PaperAppbar.Header
style={{ backgroundColor: theme.colorPrimary }}
style={{ backgroundColor: theme.surface }}
statusBarHeight={StatusBar.currentHeight}
mode={mode}
>
{handleGoBack && <PaperAppbar.BackAction onPress={handleGoBack} />}
{handleGoBack && (
<PaperAppbar.BackAction
onPress={handleGoBack}
iconColor={theme.textColorPrimary}
/>
)}
<PaperAppbar.Content
title={title}
titleStyle={{ color: theme.textColorPrimary }}
/>
{children}
</PaperAppbar.Header>
);
@@ -0,0 +1,49 @@
import React, { ReactElement, Ref, useCallback } from 'react';
import { overlay } from 'react-native-paper';
import { default as BS, BottomSheetBackdrop } from '@gorhom/bottom-sheet';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import { MD3ThemeType } from '@theme/types';
import { StyleSheet } from 'react-native';
interface BottomSheetProps {
bottomSheetRef: Ref<BS>;
snapPoints: Array<number>;
height: number;
theme: MD3ThemeType;
children: ReactElement;
}
const BottomSheet: React.FC<BottomSheetProps> = ({
bottomSheetRef,
snapPoints,
height,
theme,
children,
}) => {
const { bottom } = useSafeAreaInsets();
const renderBackdrop = useCallback(
props => <BottomSheetBackdrop {...props} disappearsOnIndex={0} />,
[],
);
return (
<BS
index={-1}
backdropComponent={renderBackdrop}
snapPoints={[0.1].concat(snapPoints)}
enablePanDownToClose={true}
ref={bottomSheetRef}
handleStyle={styles.handle}
containerHeight={height + bottom}
containerStyle={{ paddingBottom: bottom }}
backgroundStyle={{ backgroundColor: overlay(2, theme.surface) }}
>
{children}
</BS>
);
};
const styles = StyleSheet.create({
handle: {
display: 'none',
},
});
export default BottomSheet;
+36 -18
View File
@@ -1,7 +1,16 @@
import { Pressable, StyleSheet, Text, View, ViewStyle } from 'react-native';
import React from 'react';
import {
Pressable,
StyleProp,
StyleSheet,
Text,
TextStyle,
View,
ViewStyle,
} from 'react-native';
import React, { useMemo } from 'react';
import color from 'color';
import { ThemeType } from '../../theme/types';
import { MD3ThemeType } from '../../theme/types';
export enum ButtonVariation {
DEFAULT = 'default',
@@ -14,8 +23,10 @@ interface Props {
title: string;
textColor?: string;
onPress?: () => void;
theme: ThemeType;
theme: MD3ThemeType;
style?: ViewStyle;
labelStyle?: StyleProp<TextStyle>;
disabled?: boolean;
}
const Button: React.FC<Props> = ({
@@ -25,7 +36,24 @@ const Button: React.FC<Props> = ({
onPress,
theme,
style,
disabled,
labelStyle,
}) => {
const textStyles: StyleProp<TextStyle> = useMemo(
() => ({
color: disabled
? theme.textColorHint
: textColor
? textColor
: variation === ButtonVariation.OUTLINED
? theme.primary
: variation === ButtonVariation.CLEAR
? theme.primary
: theme.onSecondary,
}),
[],
);
return (
<View
style={[
@@ -36,7 +64,7 @@ const Button: React.FC<Props> = ({
borderColor: theme.textColorHint,
}
: variation !== ButtonVariation.CLEAR && {
backgroundColor: theme.colorAccent,
backgroundColor: theme.secondary,
},
style,
]}
@@ -44,20 +72,10 @@ const Button: React.FC<Props> = ({
<Pressable
style={styles.pressable}
onPress={onPress}
android_ripple={{ color: theme.rippleColor }}
android_ripple={{ color: color(theme.primary).alpha(0.12).string() }}
disabled={disabled}
>
<Text
style={{
color: textColor
? textColor
: variation === ButtonVariation.OUTLINED
? theme.colorAccent
: variation === ButtonVariation.CLEAR
? theme.colorAccent
: theme.textColorPrimary,
}}
numberOfLines={1}
>
<Text style={[textStyles, labelStyle]} numberOfLines={1}>
{title}
</Text>
</Pressable>
@@ -1,19 +1,40 @@
import React from 'react';
import { Pressable, StyleSheet, Text } from 'react-native';
import {
Pressable,
StyleProp,
StyleSheet,
Text,
TextStyle,
ViewStyle,
} from 'react-native';
import { Checkbox as PaperCheckbox } from 'react-native-paper';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import color from 'color';
export const Checkbox = ({
import { MD3ThemeType } from '../../theme/types';
interface CheckboxProps {
label: string;
status: boolean | 'indeterminate';
onPress?: () => void;
disabled?: boolean;
theme: MD3ThemeType;
viewStyle?: StyleProp<ViewStyle>;
labelStyle?: StyleProp<TextStyle>;
}
export const Checkbox: React.FC<CheckboxProps> = ({
label,
status,
onPress,
disabled = false,
theme,
disabled,
onPress,
viewStyle,
labelStyle,
}) => (
<Pressable
android_ripple={{ color: theme.rippleColor }}
style={styles.pressable}
android_ripple={{ color: color(theme.primary).alpha(0.12).string() }}
style={[styles.pressable, viewStyle]}
onPress={onPress}
disabled={disabled}
>
@@ -26,11 +47,14 @@ export const Checkbox = ({
: 'unchecked'
}
onPress={onPress}
color={theme.colorAccent}
color={theme.primary}
theme={{
colors: { disabled: theme.textColorSecondary },
}}
uncheckedColor={theme.textColorSecondary}
disabled={disabled}
/>
<Text style={{ color: theme.textColorSecondary, marginLeft: 12 }}>
<Text style={[styles.defaultLabel, { color: theme.onSurface }, labelStyle]}>
{label}
</Text>
</Pressable>
@@ -38,19 +62,19 @@ export const Checkbox = ({
export const SortItem = ({ label, status, onPress, theme }) => (
<Pressable
android_ripple={{ color: theme.rippleColor }}
android_ripple={{ color: color(theme.primary).alpha(0.12).string() }}
style={[styles.pressable, { paddingVertical: 16, paddingLeft: 64 }]}
onPress={onPress}
>
{status && (
<MaterialCommunityIcons
name={status === 'asc' ? 'arrow-up' : 'arrow-down'}
color={theme.colorAccent}
color={theme.primary}
size={21}
style={styles.icon}
/>
)}
<Text style={{ color: theme.textColorPrimary }}>{label}</Text>
<Text style={{ color: theme.onSurface }}>{label}</Text>
</Pressable>
);
@@ -66,4 +90,7 @@ const styles = StyleSheet.create({
left: 24,
alignSelf: 'center',
},
defaultLabel: {
marginLeft: 12,
},
});
+24 -6
View File
@@ -1,20 +1,38 @@
import React from 'react';
import { Pressable, StyleSheet, Text, View } from 'react-native';
import color from 'color';
import { ThemeType } from '../../theme/types';
import { MD3ThemeType } from '../../theme/types';
import { overlay } from 'react-native-paper';
interface ChipProps {
label: string;
theme: ThemeType;
theme: MD3ThemeType;
}
const Chip: React.FC<ChipProps> = ({ label, theme }) => (
<View style={[styles.chipContainer, { backgroundColor: theme.colorPrimary }]}>
<View
style={[
styles.chipContainer,
{
backgroundColor: theme.isDark
? overlay(1, theme.surface)
: theme.secondaryContainer,
},
]}
>
<Pressable
android_ripple={{ color: theme.rippleColor }}
android_ripple={{ color: color(theme.primary).alpha(0.12).string() }}
style={styles.pressable}
>
<Text style={[styles.label, { color: theme.textColorSecondary }]}>
<Text
style={[
styles.label,
{
color: theme.isDark ? theme.onSurface : theme.onSecondaryContainer,
},
]}
>
{label}
</Text>
</Pressable>
@@ -27,7 +45,7 @@ const styles = StyleSheet.create({
chipContainer: {
overflow: 'hidden',
borderRadius: 8,
height: 28,
height: 32,
marginRight: 8,
},
pressable: {
@@ -1,8 +1,8 @@
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';
import { Modal, overlay, Portal, TextInput } from 'react-native-paper';
import { MD3ThemeType } from '../../theme/types';
interface ColorPickerModalProps {
visible: boolean;
@@ -10,7 +10,7 @@ interface ColorPickerModalProps {
color: string;
onSubmit: (val: string) => void;
closeModal: () => void;
theme: ThemeType;
theme: MD3ThemeType;
showAccentColors?: boolean;
}
@@ -47,15 +47,6 @@ const ColorPickerModal: React.FC<ColorPickerModalProps> = ({
}
};
const textInputTheme = {
colors: {
primary: color,
placeholder: theme.textColorHint,
text: theme.textColorPrimary,
background: 'transparent',
},
};
const accentColors = [
'#EF5350',
'#EC407A',
@@ -86,7 +77,7 @@ const ColorPickerModal: React.FC<ColorPickerModalProps> = ({
onDismiss={onDismiss}
contentContainerStyle={[
styles.modalContainer,
{ backgroundColor: theme.colorPrimary },
{ backgroundColor: overlay(2, theme.surface) },
]}
>
<Text style={[styles.modalTitle, { color: theme.textColorPrimary }]}>
@@ -131,7 +122,7 @@ const ColorPickerModal: React.FC<ColorPickerModalProps> = ({
onChangeText={onChangeText}
onSubmitEditing={onSubmitEditing}
mode="outlined"
theme={textInputTheme}
theme={{ colors: { ...theme } }}
underlineColor={theme.textColorHint}
dense
error={Boolean(error)}
@@ -147,11 +138,11 @@ export default ColorPickerModal;
const styles = StyleSheet.create({
modalContainer: {
margin: 30,
padding: 20,
borderRadius: 8,
padding: 24,
borderRadius: 28,
},
modalTitle: {
fontSize: 18,
fontSize: 24,
marginBottom: 16,
},
errorText: {
+1 -1
View File
@@ -18,7 +18,7 @@ const ScreenContainer = ({ children, theme }) => {
);
};
const Row = ({ children, style }) => (
const Row = ({ children, style = {} }) => (
<View style={[styles.row, style]}>{children}</View>
);
+8 -5
View File
@@ -1,12 +1,13 @@
import React from 'react';
import { Pressable, View } from 'react-native';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import { ThemeType } from '../../theme/types';
import { MD3ThemeType } from '../../theme/types';
import Color from 'color';
interface ToggleButtonProps {
icon: string;
selected: boolean;
theme: ThemeType;
theme: MD3ThemeType;
color?: string;
onPress: () => void;
}
@@ -26,19 +27,21 @@ export const ToggleButton: React.FC<ToggleButtonProps> = ({
}}
>
<Pressable
android_ripple={{ color: theme.rippleColor }}
android_ripple={{ color: Color(theme.primary).alpha(0.12).string() }}
style={{
padding: 8,
alignItems: 'center',
justifyContent: 'center',
backgroundColor: selected ? theme.rippleColor : 'transparent',
backgroundColor: selected
? Color(theme.primary).alpha(0.12).string()
: 'transparent',
}}
onPress={onPress}
>
<MaterialCommunityIcons
name={icon}
color={
selected ? theme.colorAccent : color ? color : theme.textColorPrimary
selected ? theme.primary : color ? color : theme.textColorPrimary
}
size={24}
/>
@@ -1,17 +1,18 @@
import React from 'react';
import { StyleSheet } from 'react-native';
import { StyleSheet, View } from 'react-native';
import { getString } from '@strings/translations';
import { Dialog, Portal } from 'react-native-paper';
import { ThemeType } from '../../theme/types';
import { MD3ThemeType } from '../../theme/types';
import { Button } from '@components/index';
import { ButtonVariation } from '@components/Button/Button';
import { getDialogBackground } from '@theme/colors';
interface ConfirmationDialogProps {
title: string;
visible: boolean;
theme: ThemeType;
theme: MD3ThemeType;
onSubmit: () => void;
onDismiss: () => void;
}
@@ -33,26 +34,28 @@ const ConfirmationDialog: React.FC<ConfirmationDialogProps> = ({
<Dialog
visible={visible}
onDismiss={onDismiss}
style={[styles.container, { backgroundColor: theme.colorPrimary }]}
style={[
styles.container,
{ backgroundColor: getDialogBackground(theme) },
]}
>
<Dialog.Title style={[styles.title, { color: theme.textColorPrimary }]}>
{title}
</Dialog.Title>
<Dialog.Actions>
<View style={styles.buttonCtn}>
<Button
theme={theme}
onPress={handleOnSubmit}
title={getString('common.ok')}
variation={ButtonVariation.CLEAR}
/>
<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>
</View>
</Dialog>
</Portal>
);
@@ -62,13 +65,14 @@ export default ConfirmationDialog;
const styles = StyleSheet.create({
container: {
borderRadius: 8,
borderRadius: 28,
},
title: {
letterSpacing: 0,
fontSize: 16,
},
button: {
marginLeft: 4,
buttonCtn: {
flexDirection: 'row-reverse',
padding: 16,
},
});
+2 -2
View File
@@ -1,9 +1,9 @@
import { useTheme } from '@hooks/useTheme';
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 = useTheme();
return (
<View style={styles.emptyViewContainer}>
+3 -2
View File
@@ -1,12 +1,12 @@
import React from 'react';
import { StyleSheet, View, Text } from 'react-native';
import { ThemeType } from '../../theme/types';
import { MD3ThemeType } from '../../theme/types';
interface EmptyViewProps {
icon?: string;
description: string;
theme: ThemeType;
theme: MD3ThemeType;
}
const EmptyView: React.FC<EmptyViewProps> = ({ icon, description, theme }) => (
@@ -25,6 +25,7 @@ export default EmptyView;
const styles = StyleSheet.create({
container: {
flex: 1,
padding: 16,
alignItems: 'center',
justifyContent: 'center',
},
@@ -1,11 +1,11 @@
import React from 'react';
import { StyleSheet, Text, View } from 'react-native';
import { ThemeType } from '../../theme/types';
import { MD3ThemeType } from '@theme/types';
interface ErrorScreenProps {
error?: string;
theme: ThemeType;
theme: MD3ThemeType;
}
const ErrorScreen: React.FC<ErrorScreenProps> = ({ error, theme }) => {
+8 -3
View File
@@ -1,8 +1,9 @@
import React from 'react';
import { Pressable, StyleSheet, View, ViewStyle } from 'react-native';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
import Color from 'color';
import { ThemeType } from '../../theme/types';
import { MD3ThemeType } from '../../theme/types';
type Props = {
name: string;
@@ -11,7 +12,7 @@ type Props = {
disabled?: boolean;
padding?: number;
onPress?: () => void;
theme: ThemeType;
theme: MD3ThemeType;
style?: ViewStyle;
};
@@ -30,7 +31,11 @@ const IconButton: React.FC<Props> = ({
style={[styles.pressable, { padding }]}
onPress={onPress}
disabled={disabled}
android_ripple={{ color: theme.rippleColor }}
android_ripple={
onPress
? { color: Color(theme.primary).alpha(0.12).string() }
: undefined
}
>
<MaterialCommunityIcons
name={name}
+31 -21
View File
@@ -1,5 +1,6 @@
import React from 'react';
import { Pressable, StyleSheet, Text, View } from 'react-native';
import color from 'color';
import { List as PaperList, Divider as PaperDivider } from 'react-native-paper';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
@@ -9,9 +10,7 @@ const ListSection = ({ children }) => (
);
const ListSubHeader = ({ children, theme }) => (
<PaperList.Subheader
style={[styles.listSubHeader, { color: theme.colorAccent }]}
>
<PaperList.Subheader style={[styles.listSubHeader, { color: theme.primary }]}>
{children}
</PaperList.Subheader>
);
@@ -29,24 +28,26 @@ const ListItem = ({
}) => (
<PaperList.Item
title={title}
style={style}
style={[{ paddingHorizontal: 16, paddingVertical: 12 }, 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>
)}
left={
icon
? () => (
<View style={{ justifyContent: 'center' }}>
<PaperList.Icon
color={theme.primary}
icon={icon}
style={{
marginVertical: 0,
}}
/>
</View>
)
: undefined
}
right={() =>
right && (
<MaterialCommunityIcons
@@ -58,12 +59,20 @@ const ListItem = ({
)
}
onPress={onPress}
rippleColor={theme.rippleColor}
rippleColor={color(theme.primary).alpha(0.12).string()}
/>
);
const Divider = ({ theme }) => (
<PaperDivider style={{ backgroundColor: theme.dividerColor, height: 1 }} />
<PaperDivider
style={{
backgroundColor: color(theme.isDark ? '#FFFFFF' : '#000000')
.alpha(0.12)
.rgb()
.string(),
height: 1,
}}
/>
);
const InfoItem = ({ title, icon, theme }) => (
@@ -85,7 +94,7 @@ const ColorItem = ({ title, description, theme, onPress }) => (
justifyContent: 'space-between',
alignItems: 'center',
}}
android_ripple={{ color: theme.rippleColor }}
android_ripple={{ color: color(theme.primary).alpha(0.12).string() }}
onPress={onPress}
>
<View>
@@ -107,7 +116,7 @@ const ColorItem = ({ title, description, theme, onPress }) => (
);
const Icon = ({ icon, theme }) => (
<PaperList.Icon color={theme.colorAccent} icon={icon} style={{ margin: 0 }} />
<PaperList.Icon color={theme.primary} icon={icon} style={{ margin: 0 }} />
);
export const List = {
@@ -126,6 +135,7 @@ const styles = StyleSheet.create({
marginVertical: 0,
},
listSubHeader: {
fontWeight: 'bold',
paddingBottom: 5,
},
});
+32 -21
View File
@@ -1,15 +1,18 @@
import React, { ReactNode } from 'react';
import { StyleSheet, View } from 'react-native';
import { StyleSheet, Text, View } from 'react-native';
import color from 'color';
import MaterialIcon from 'react-native-vector-icons/MaterialCommunityIcons';
import { List as PaperList, Divider as PaperDivider } from 'react-native-paper';
import { ThemeType } from '../../theme/types';
import { MD3ThemeType } from '../../theme/types';
import { dividerColor } from '../../theme/colors';
interface ListItemProps {
title: string;
description?: string | null;
icon?: string;
onPress?: () => void;
theme: ThemeType;
theme: MD3ThemeType;
}
const Section = ({ children }: { children: ReactNode }) => (
@@ -21,9 +24,9 @@ const SubHeader = ({
theme,
}: {
children: ReactNode;
theme: ThemeType;
theme: MD3ThemeType;
}) => (
<PaperList.Subheader style={{ color: theme.colorAccent }}>
<PaperList.Subheader style={{ color: theme.primary }}>
{children}
</PaperList.Subheader>
);
@@ -44,7 +47,7 @@ const Item: React.FC<ListItemProps> = ({
<View style={styles.iconContainer}>
{icon && (
<PaperList.Icon
color={theme.colorAccent}
color={theme.primary}
icon={icon}
style={styles.icon}
/>
@@ -52,33 +55,35 @@ const Item: React.FC<ListItemProps> = ({
</View>
)}
onPress={onPress}
rippleColor={theme.rippleColor}
rippleColor={color(theme.primary).alpha(0.12).string()}
/>
);
const Divider = ({ theme }: { theme: ThemeType }) => (
const Divider = ({ theme }: { theme: MD3ThemeType }) => (
<PaperDivider
style={[styles.divider, { backgroundColor: theme.dividerColor }]}
style={[styles.divider, { backgroundColor: dividerColor(theme.isDark) }]}
/>
);
const InfoItem = ({
title,
icon,
icon = 'information-outline',
theme,
}: {
title: string;
icon: string;
theme: ThemeType;
theme: MD3ThemeType;
}) => (
<PaperList.Item
title={title}
titleStyle={[{ color: theme.textColorSecondary }, styles.infoItem]}
titleNumberOfLines={5}
left={() =>
icon && <PaperList.Icon color={theme.textColorSecondary} icon={icon} />
}
/>
<View style={styles.infoCtn}>
<MaterialIcon size={20} color={theme.onSurfaceVariant} name={icon} />
<Text style={[styles.infoMsg, { color: theme.onSurfaceVariant }]}>
{title}
</Text>
</View>
);
const Icon = ({ icon, theme }: { icon: string; theme: MD3ThemeType }) => (
<PaperList.Icon color={theme.primary} icon={icon} style={{ margin: 0 }} />
);
export default {
@@ -87,6 +92,7 @@ export default {
Item,
Divider,
InfoItem,
Icon,
};
const styles = StyleSheet.create({
@@ -103,7 +109,12 @@ const styles = StyleSheet.create({
icon: {
marginVertical: 0,
},
infoItem: {
fontSize: 14,
infoCtn: {
paddingVertical: 12,
paddingHorizontal: 16,
},
infoMsg: {
marginTop: 12,
fontSize: 12,
},
});
+6 -2
View File
@@ -5,6 +5,8 @@ import FastImage from 'react-native-fast-image';
import { defaultCoverUri } from '../sources/helpers/constants';
import { coverPlaceholderColor } from '../theme/colors';
import color from 'color';
const ListView = ({
item,
downloadBadge,
@@ -17,10 +19,12 @@ const ListView = ({
}) => {
return (
<Pressable
android_ripple={{ color: theme.rippleColor }}
android_ripple={{ color: color(theme.primary).alpha(0.12).string() }}
style={[
styles.listView,
isSelected && { backgroundColor: theme.rippleColor },
isSelected && {
backgroundColor: color(theme.primary).alpha(0.12).string(),
},
]}
onPress={onPress}
onLongPress={onLongPress}
@@ -1,13 +1,13 @@
import React from 'react';
import { ActivityIndicator, StyleSheet } from 'react-native';
import { ThemeType } from '../../theme/types';
import { MD3ThemeType } from '../../theme/types';
interface Props {
theme: ThemeType;
theme: MD3ThemeType;
}
const LoadingMoreIndicator: React.FC<Props> = ({ theme }) => (
<ActivityIndicator color={theme.colorAccent} style={styles.indicator} />
<ActivityIndicator color={theme.primary} style={styles.indicator} />
);
export default LoadingMoreIndicator;
@@ -1,8 +0,0 @@
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>
);
@@ -1,14 +1,10 @@
import React from 'react';
import { StyleSheet, ActivityIndicator } from 'react-native';
import { ThemeType } from '../../theme/types';
import { MD3ThemeType } from '../../theme/types';
const LoadingScreen: React.FC<{ theme: ThemeType }> = ({ theme }) => (
<ActivityIndicator
size={50}
color={theme.colorAccent}
style={styles.indicator}
/>
const LoadingScreen: React.FC<{ theme: MD3ThemeType }> = ({ theme }) => (
<ActivityIndicator size={50} color={theme.primary} style={styles.indicator} />
);
export default LoadingScreen;
+5 -4
View File
@@ -2,11 +2,12 @@ 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';
import { dividerColor, getDialogBackground } from '@theme/colors';
import { useTheme } from '@hooks/useTheme';
interface NewUpdateDialogProps {
newVersion: {
@@ -30,7 +31,7 @@ const NewUpdateDialog: React.FC<NewUpdateDialogProps> = ({ newVersion }) => {
onDismiss={() => showNewUpdateDialog(false)}
contentContainerStyle={[
styles.containerStyle,
{ backgroundColor: theme.colorPrimaryDark },
{ backgroundColor: getDialogBackground(theme) },
]}
>
<Text style={[styles.modalHeader, { color: theme.textColorPrimary }]}>
@@ -45,11 +46,11 @@ const NewUpdateDialog: React.FC<NewUpdateDialogProps> = ({ newVersion }) => {
paddingVertical: 8,
},
hr: {
backgroundColor: theme.dividerColor,
backgroundColor: dividerColor(theme.isDark),
marginVertical: 16,
},
code_inline: {
backgroundColor: theme.colorPrimaryDark,
backgroundColor: getDialogBackground(theme),
},
}}
>
+38 -26
View File
@@ -1,4 +1,4 @@
import React, { memo, useCallback } from 'react';
import React, { memo, useMemo } from 'react';
import {
StyleSheet,
View,
@@ -6,15 +6,16 @@ import {
useWindowDimensions,
Pressable,
} from 'react-native';
import color from 'color';
import { LinearGradient } from 'expo-linear-gradient';
import FastImage from 'react-native-fast-image';
import ListView from './ListView';
import { useDeviceOrientation } from '../services/utils/helpers';
import { useSettings } from '../hooks/reduxHooks';
import { coverPlaceholderColor } from '../theme/colors';
import { useLibrarySettings } from '@hooks/useSettings';
import { DisplayModes } from '@screens/library/constants/constants';
const NovelCover = ({
item,
@@ -25,43 +26,48 @@ const NovelCover = ({
onLongPress,
selectedNovels,
}) => {
const { displayMode, novelsPerRow, showDownloadBadges, showUnreadBadges } =
useSettings();
const {
displayMode = DisplayModes.Comfortable,
showDownloadBadges = true,
showUnreadBadges = true,
novelsPerRow = 3,
} = useLibrarySettings();
const window = useWindowDimensions();
const orientation = useDeviceOrientation();
const getNovelsPerRow = () =>
orientation === 'landscape' ? 6 : novelsPerRow;
const numColumns = useMemo(
() => (orientation === 'landscape' ? 6 : novelsPerRow),
[orientation, novelsPerRow],
);
const getHeight = useCallback(
() => (window.width / getNovelsPerRow()) * (4 / 3),
// eslint-disable-next-line react-hooks/exhaustive-deps
[],
const coverHeight = useMemo(
() => (window.width / numColumns) * (4 / 3),
[numColumns],
);
const selectNovel = () => onLongPress && onLongPress(item.novelId);
const uri = item.novelCover;
return displayMode !== 2 ? (
return displayMode !== DisplayModes.List ? (
<View
style={[
{
flex: 1 / getNovelsPerRow(),
flex: 1 / numColumns,
borderRadius: 6,
overflow: 'hidden',
margin: 2,
},
isSelected && {
backgroundColor: theme.colorAccent,
backgroundColor: theme.primary,
opacity: 0.8,
},
]}
>
<Pressable
android_ripple={{ color: theme.colorAccent }}
android_ripple={{ color: color(theme.primary).alpha(0.12).string() }}
style={styles.opac}
onPress={
selectedNovels && selectedNovels.length > 0 ? selectNovel : onPress
@@ -75,6 +81,7 @@ const NovelCover = ({
showUnreadBadges={showUnreadBadges}
chaptersDownloaded={item.chaptersDownloaded}
chaptersUnread={item.chaptersUnread}
theme={theme}
/>
)}
{showUnreadBadges && item.chaptersUnread && (
@@ -90,7 +97,7 @@ const NovelCover = ({
source={{ uri }}
style={[
{
height: getHeight(),
height: coverHeight,
borderRadius: 4,
backgroundColor: coverPlaceholderColor,
},
@@ -98,9 +105,11 @@ const NovelCover = ({
]}
/>
<View style={styles.compactTitleContainer}>
{displayMode === 0 && <CompactTitle novelName={item.novelName} />}
{displayMode === DisplayModes.Compact && (
<CompactTitle novelName={item.novelName} />
)}
</View>
{displayMode === 1 && (
{displayMode === DisplayModes.Comfortable && (
<ComfortableTitle novelName={item.novelName} theme={theme} />
)}
</Pressable>
@@ -112,6 +121,7 @@ const NovelCover = ({
showDownloadBadges &&
item.chaptersDownloaded && (
<DownloadBadge
theme={theme}
showUnreadBadges={showUnreadBadges}
chaptersDownloaded={item.chaptersDownloaded}
chaptersUnread={item.chaptersUnread}
@@ -186,8 +196,8 @@ const InLibraryBadge = ({ theme }) => (
style={[
styles.inLibraryBadge,
{
backgroundColor: theme.colorAccent,
color: theme.colorButtonText,
backgroundColor: theme.primary,
color: theme.onPrimary,
borderRadius: 4,
},
]}
@@ -213,8 +223,8 @@ const UnreadBadge = ({
borderRadius: 4,
},
{
backgroundColor: theme.colorAccent,
color: theme.colorButtonText,
backgroundColor: theme.primary,
color: theme.onPrimary,
},
]}
>
@@ -226,6 +236,7 @@ const DownloadBadge = ({
chaptersDownloaded,
showUnreadBadges,
chaptersUnread,
theme,
}) => (
<Text
style={[
@@ -237,6 +248,10 @@ const DownloadBadge = ({
!showUnreadBadges && {
borderRadius: 4,
},
{
backgroundColor: theme.tertiary,
color: theme.onTertiary,
},
]}
>
{chaptersDownloaded}
@@ -276,16 +291,13 @@ const styles = StyleSheet.create({
},
downloadBadge: {
backgroundColor: '#2979FF',
color: '#FFFFFF',
borderTopLeftRadius: 4,
borderBottomLeftRadius: 4,
paddingTop: 2,
paddingHorizontal: 4,
paddingHorizontal: 5,
fontSize: 12,
},
unreadBadge: {
color: '#FFFFFF',
borderTopRightRadius: 4,
borderBottomRightRadius: 4,
paddingTop: 2,
-61
View File
@@ -1,61 +0,0 @@
import React, { useCallback } from 'react';
import { StyleSheet, FlatList } from 'react-native';
import { useSettings } from '../hooks/reduxHooks';
import { useDeviceOrientation } from '../services/utils/helpers';
const NovelList = ({
data,
onScroll,
onEndReached,
renderItem,
refreshControl,
ListEmptyComponent,
ListFooterComponent,
}) => {
const { displayMode, novelsPerRow } = useSettings();
const orientation = useDeviceOrientation();
const getNovelsPerRow = () => {
if (displayMode === 2) {
return 1;
}
if (orientation === 'landscape') {
return 6;
} else {
return novelsPerRow;
}
};
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}
/>
);
};
export default NovelList;
const styles = StyleSheet.create({
flatListCont: {
flexGrow: 1,
paddingBottom: 56,
},
});
+67
View File
@@ -0,0 +1,67 @@
import { useLibrarySettings } from '@hooks/useSettings';
import { DisplayModes } from '@screens/library/constants/constants';
import React, { useCallback, useMemo } from 'react';
import {
StyleSheet,
FlatList,
FlatListProps,
ListRenderItem,
} from 'react-native';
import { SourceNovelItem } from '../sources/types';
import { LibraryNovelInfo, NovelInfo } from '../database/types';
import { useDeviceOrientation } from '../services/utils/helpers';
export type NovelListRenderItem = ListRenderItem<
LibraryNovelInfo | NovelInfo | SourceNovelItem
>;
const NovelList: React.FC<
FlatListProps<LibraryNovelInfo | NovelInfo | SourceNovelItem>
> = props => {
const { displayMode = DisplayModes.Comfortable, novelsPerRow = 3 } =
useLibrarySettings();
const orientation = useDeviceOrientation();
const isListView = displayMode === DisplayModes.List;
const numColumns = useMemo(() => {
if (isListView) {
return 1;
}
if (orientation === 'landscape') {
return 6;
} else {
return novelsPerRow;
}
}, [isListView, orientation, novelsPerRow]);
const keyExtractor = useCallback(item => item.sourceId + item.novelUrl, []);
return (
<FlatList
contentContainerStyle={[
!isListView && styles.listView,
styles.flatListCont,
]}
numColumns={numColumns}
key={numColumns}
keyExtractor={keyExtractor}
{...props}
/>
);
};
export default NovelList;
const styles = StyleSheet.create({
flatListCont: {
flexGrow: 1,
paddingBottom: 56,
},
listView: {
paddingHorizontal: 4,
},
});
-40
View File
@@ -1,40 +0,0 @@
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,
},
});
+1 -1
View File
@@ -12,7 +12,7 @@ export const RadioButton = ({ value, label, theme, labelStyle }) => (
<View style={styles.radioButtonContainer}>
<MaterialRadioButton
value={value}
color={theme.colorAccent}
color={theme.primary}
uncheckedColor={theme.textColorSecondary}
/>
<Text
@@ -1,8 +1,26 @@
import React from 'react';
import { Pressable, StyleSheet, Text } from 'react-native';
import {
Pressable,
StyleProp,
StyleSheet,
Text,
TextStyle,
ViewStyle,
} from 'react-native';
import { RadioButton as PaperRadioButton } from 'react-native-paper';
import { MD3ThemeType } from '../../theme/types';
import color from 'color';
export const RadioButton = ({
interface Props {
label: string;
status: boolean;
onPress?: () => void;
style?: StyleProp<ViewStyle>;
labelStyle?: StyleProp<TextStyle>;
theme: MD3ThemeType;
}
export const RadioButton: React.FC<Props> = ({
label,
status,
onPress,
@@ -11,19 +29,18 @@ export const RadioButton = ({
theme,
}) => (
<Pressable
android_ripple={{ color: theme.rippleColor }}
android_ripple={{ color: color(theme.primary).alpha(0.12).string() }}
style={[styles.pressable, style]}
onPress={onPress}
>
<PaperRadioButton
status={status ? 'checked' : 'unchecked'}
value={label}
onPress={onPress}
color={theme.colorAccent}
color={theme.primary}
uncheckedColor={theme.textColorSecondary}
/>
<Text
style={[labelStyle, { color: theme.textColorPrimary, marginLeft: 12 }]}
>
<Text style={[styles.label, labelStyle, { color: theme.onSurface }]}>
{label}
</Text>
</Pressable>
@@ -41,4 +58,7 @@ const styles = StyleSheet.create({
left: 24,
alignSelf: 'center',
},
label: {
marginLeft: 12,
},
});
-97
View File
@@ -1,97 +0,0 @@
import React, { useRef } from 'react';
import {
StyleSheet,
View,
TextInput,
StatusBar,
Pressable,
} from 'react-native';
import { Row } from '../Common';
import { IconButton } from '../IconButton/IconButton';
export const Searchbar = ({
theme,
placeholder,
searchText,
backAction,
onBackAction,
clearSearchbar,
onChangeText,
onSubmitEditing,
actions,
}) => {
const searchRef = useRef(null);
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({
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,
},
});
+20 -12
View File
@@ -1,9 +1,10 @@
import React, { useRef } from 'react';
import { Pressable, StyleSheet, View, TextInput } from 'react-native';
import { useSafeAreaInsets } from 'react-native-safe-area-context';
import color from 'color';
import IconButtonV2 from '../IconButtonV2/IconButtonV2';
import { ThemeType } from '../../theme/types';
import { MD3ThemeType } from '../../theme/types';
interface RightIcon {
iconName: string;
@@ -21,7 +22,7 @@ interface SearcbarProps {
handleBackAction?: () => void;
clearSearchbar: () => void;
onLeftIconPress?: () => void;
theme: ThemeType;
theme: MD3ThemeType;
}
const Searchbar: React.FC<SearcbarProps> = ({
@@ -39,24 +40,31 @@ const Searchbar: React.FC<SearcbarProps> = ({
const searchbarRef = useRef<any>(null);
const focusSearchbar = () => searchbarRef.current.focus();
const { top } = useSafeAreaInsets();
const { top, right, left } = useSafeAreaInsets();
const marginTop = top + 8;
const marginRight = right + 16;
const marginLeft = left + 16;
return (
<View
style={[
styles.searchbarContainer,
{ marginTop, backgroundColor: theme.searchBarColor },
{
marginTop,
marginRight,
marginLeft,
backgroundColor: color(theme.primary).alpha(0.08).string(),
},
]}
>
<Pressable
onPress={focusSearchbar}
android_ripple={{ color: theme.rippleColor }}
android_ripple={{ color: color(theme.primary).alpha(12).toString() }}
style={[styles.searchbar]}
>
<IconButtonV2
name={handleBackAction ? 'arrow-left' : leftIcon}
color={theme.textColorPrimary}
color={theme.onSurface}
onPress={() => {
if (handleBackAction) {
handleBackAction();
@@ -69,9 +77,9 @@ const Searchbar: React.FC<SearcbarProps> = ({
<TextInput
ref={searchbarRef}
style={[styles.textInput, { color: theme.textColorPrimary }]}
style={[styles.textInput, { color: theme.onSurface }]}
placeholder={placeholder}
placeholderTextColor={theme.textColorSecondary}
placeholderTextColor={theme.onSurface}
onChangeText={onChangeText}
onSubmitEditing={onSubmitEditing}
defaultValue={searchText}
@@ -79,7 +87,7 @@ const Searchbar: React.FC<SearcbarProps> = ({
{searchText !== '' ? (
<IconButtonV2
name="close"
color={theme.textColorPrimary}
color={theme.onSurface}
onPress={clearSearchbar}
theme={theme}
/>
@@ -88,7 +96,7 @@ const Searchbar: React.FC<SearcbarProps> = ({
<IconButtonV2
key={index}
name={icon.iconName}
color={icon.color || theme.textColorPrimary}
color={icon.color || theme.onSurface}
onPress={icon.onPress}
theme={theme}
/>
@@ -103,8 +111,8 @@ export default Searchbar;
const styles = StyleSheet.create({
searchbarContainer: {
marginHorizontal: 16,
marginBottom: 16,
minHeight: 46,
marginBottom: 12,
minHeight: 56,
borderRadius: 50,
overflow: 'hidden',
zIndex: 1,
+4 -8
View File
@@ -1,21 +1,17 @@
import React from 'react';
import { Pressable, StyleSheet, View, Text } from 'react-native';
import color from 'color';
import { Switch, List } from 'react-native-paper';
const SwitchSetting = ({ label, description, onPress, theme, icon, value }) => (
<Pressable
android_ripple={{ color: theme.rippleColor }}
android_ripple={{ color: color(theme.primary).alpha(0.12).rgb().string() }}
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 }}
/>
<List.Icon color={theme.primary} icon={icon} style={{ margin: 0 }} />
)}
<View style={[icon && { marginLeft: 16 }]}>
<Text
@@ -36,7 +32,7 @@ const SwitchSetting = ({ label, description, onPress, theme, icon, value }) => (
<Switch
value={value}
onValueChange={onPress}
color={theme.colorAccent}
color={theme.primary}
style={{ marginLeft: 8 }}
/>
</Pressable>
+5 -4
View File
@@ -1,15 +1,16 @@
import React from 'react';
import { Pressable, StyleSheet, View, Text } from 'react-native';
import color from 'color';
import { Switch } from 'react-native-paper';
import { ThemeType } from '../../theme/types';
import { MD3ThemeType } from '../../theme/types';
interface SwitchItemProps {
value: boolean;
label: string;
description?: string;
onPress: () => void;
theme: ThemeType;
theme: MD3ThemeType;
}
const SwitchItem: React.FC<SwitchItemProps> = ({
@@ -20,7 +21,7 @@ const SwitchItem: React.FC<SwitchItemProps> = ({
value,
}) => (
<Pressable
android_ripple={{ color: theme.rippleColor }}
android_ripple={{ color: color(theme.primary).alpha(0.12).string() }}
style={[styles.container]}
onPress={onPress}
>
@@ -35,7 +36,7 @@ const SwitchItem: React.FC<SwitchItemProps> = ({
<Switch
value={value}
onValueChange={onPress}
color={theme.colorAccent}
color={theme.primary}
style={styles.switch}
/>
</Pressable>
+11 -9
View File
@@ -1,5 +1,7 @@
import React from 'react';
import { View, Text, /* StyleSheet, */ Pressable } from 'react-native';
import { overlay } from 'react-native-paper';
import color from 'color';
import MaterialCommunityIcons from 'react-native-vector-icons/MaterialCommunityIcons';
export const ThemePicker = ({ theme, currentTheme, onPress }) => (
@@ -12,12 +14,12 @@ export const ThemePicker = ({ theme, currentTheme, onPress }) => (
>
<View
style={{
backgroundColor: theme.colorPrimaryDark,
backgroundColor: theme.background,
borderWidth: 3.6,
borderColor:
currentTheme.id === theme.id
? theme.colorAccent
: currentTheme.colorPrimaryDark,
? theme.primary
: currentTheme.background,
width: 95,
height: 140,
borderRadius: 16,
@@ -29,10 +31,10 @@ export const ThemePicker = ({ theme, currentTheme, onPress }) => (
{currentTheme.id === theme.id && (
<MaterialCommunityIcons
name="check"
color={theme.colorButtonText}
color={theme.onPrimary}
size={15}
style={{
backgroundColor: theme.colorAccent,
backgroundColor: theme.primary,
position: 'absolute',
top: 5,
right: 5,
@@ -45,7 +47,7 @@ export const ThemePicker = ({ theme, currentTheme, onPress }) => (
<View
style={{
height: 20,
backgroundColor: theme.colorPrimary,
backgroundColor: overlay(2, theme.surface),
elevation: 1,
justifyContent: 'center',
}}
@@ -82,7 +84,7 @@ export const ThemePicker = ({ theme, currentTheme, onPress }) => (
height: 10,
width: 16,
marginLeft: 4,
backgroundColor: theme.colorAccent,
backgroundColor: theme.primary,
borderRadius: 50,
}}
/>
@@ -114,7 +116,7 @@ export const ThemePicker = ({ theme, currentTheme, onPress }) => (
left: 0,
right: 0,
height: 24,
backgroundColor: theme.colorPrimary,
backgroundColor: color(theme.primary).alpha(0.08).string(),
justifyContent: 'center',
}}
>
@@ -141,7 +143,7 @@ export const ThemePicker = ({ theme, currentTheme, onPress }) => (
height: 12,
width: 12,
borderRadius: 50,
backgroundColor: theme.colorAccent,
backgroundColor: theme.primary,
}}
/>
<View
+2 -1
View File
@@ -4,7 +4,6 @@ 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';
@@ -12,3 +11,5 @@ 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';
export { Checkbox } from './Checkbox/Checkbox';
export { RadioButton } from './RadioButton/RadioButton';
+61 -13
View File
@@ -3,6 +3,7 @@ import {
createNovelTableQuery,
createUrlIndexQuery,
createLibraryIndexQuery,
addCategoryColumnQuery,
} from './tables/NovelTable';
import {
createChapterTableQuery,
@@ -15,36 +16,83 @@ import {
} from './tables/HistoryTable';
import { createDownloadTableQuery } from './tables/DownloadTable';
import { createUpdatesTableQuery } from './tables/UpdateTable';
import {
addCategorySortColumnQuery,
createCategoriesTableQuery,
createCategorydIndexQuery,
createDefaultCategoryQuery,
} from './tables/CategoryTable';
import {
dbTxnErrorCallback,
txnErrorCallbackWithoutToast,
} from './utils/helpers';
import { noop } from 'lodash';
const dbName = 'lnreader.db';
const db = SQLite.openDatabase(dbName);
export const createDB = () => {
const createTables = () => {
db.transaction(tx => {
tx.executeSql(createNovelTableQuery);
tx.executeSql(createCategoriesTableQuery, [], () => {
tx.executeSql(
addCategorySortColumnQuery,
undefined,
noop,
txnErrorCallbackWithoutToast,
);
tx.executeSql(
createDefaultCategoryQuery,
undefined,
noop,
txnErrorCallbackWithoutToast,
);
});
tx.executeSql(createNovelTableQuery, [], () => {
tx.executeSql(
addCategoryColumnQuery,
undefined,
noop,
txnErrorCallbackWithoutToast,
);
});
tx.executeSql(createChapterTableQuery);
tx.executeSql(createHistoryTableQuery);
tx.executeSql(createDownloadTableQuery);
tx.executeSql(createUpdatesTableQuery);
});
};
/**
* Indexes
*/
const createIndexes = () => {
db.transaction(tx => {
tx.executeSql(createUrlIndexQuery);
tx.executeSql(createLibraryIndexQuery);
tx.executeSql(createNovelIdIndexQuery);
tx.executeSql(createUnreadChaptersIndexQuery);
tx.executeSql(createChapterIdIndexQuery);
tx.executeSql(createCategorydIndexQuery);
});
};
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');
});
export const createDatabase = async () => {
createTables();
createIndexes();
};
/**
* For Testing
*/
export const deleteDatabase = () => {
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');
tx.executeSql('DROP TABLE categories');
},
dbTxnErrorCallback,
noop,
);
};
+97
View File
@@ -0,0 +1,97 @@
import * as SQLite from 'expo-sqlite';
import { noop } from 'lodash';
import { Category } from '../types';
import { txnErrorCallback } from '../utils/helpers';
const db = SQLite.openDatabase('lnreader.db');
const getCategoriesQuery = `
SELECT * FROM categories ORDER BY CASE WHEN id > 1 THEN 1 ELSE 0 END, IFNULL(sort, 9999)
`;
export const getCategoriesFromDb = (): Promise<Category[]> => {
return new Promise(resolve =>
db.transaction(tx => {
tx.executeSql(
getCategoriesQuery,
[],
(txObj, { rows }) => resolve((rows as any)._array),
txnErrorCallback,
);
}),
);
};
const createCategoryQuery = `
INSERT INTO categories (name)
VALUES
(?)
`;
export const createCategory = (categoryName: string): void =>
db.transaction(tx =>
tx.executeSql(createCategoryQuery, [categoryName], noop, txnErrorCallback),
);
const deleteCategoryQuery = `
DELETE FROM categories
WHERE
id = ?
`;
export const deleteCategoryById = (categoryId: number): void =>
db.transaction(tx =>
tx.executeSql(deleteCategoryQuery, [categoryId], noop, txnErrorCallback),
);
const updateCategoryQuery = `
UPDATE categories SET name = ? WHERE id = ?
`;
export const updateCategory = (
categoryId: number,
categoryName: string,
): void =>
db.transaction(tx =>
tx.executeSql(
updateCategoryQuery,
[categoryName, categoryId],
noop,
txnErrorCallback,
),
);
const isCategoryNameDuplicateQuery = `
SELECT COUNT(*) as isDuplicate FROM categories WHERE name = ?
`;
export const isCategoryNameDuplicate = (
categoryName: string,
): Promise<boolean> => {
return new Promise(resolve =>
db.transaction(tx => {
tx.executeSql(
isCategoryNameDuplicateQuery,
[categoryName],
(txObj, { rows: { _array } }) =>
resolve(Boolean(_array[0]?.isDuplicate)),
txnErrorCallback,
);
}),
);
};
const updateCategoryOrderQuery = `
UPDATE categories SET sort = ? WHERE id = ?
`;
export const updateCategoryOrderInDb = (categories: Category[]): void =>
db.transaction(tx => {
categories.map(category => {
tx.executeSql(
updateCategoryOrderQuery,
[category.sort, category.id],
noop,
txnErrorCallback,
);
});
});
+118 -75
View File
@@ -5,47 +5,37 @@ import { ChapterItem } from '../types';
import * as cheerio from 'cheerio';
import RNFetchBlob from 'rn-fetch-blob';
import { txnErrorCallback } from '@database/utils/helpers';
const db = SQLite.openDatabase('lnreader.db');
const insertChaptersQuery =
'INSERT INTO chapters (chapterUrl, chapterName, releaseDate, novelId) values (?, ?, ?, ?)';
const insertChaptersQuery = `
INSERT INTO chapters (
chapterUrl, chapterName, releaseDate,
novelId
)
values
(?, ?, ?, ?)
`;
export const insertChapters = async (
novelId: number,
chapters: ChapterItem[],
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 ')
},
);
if (!chapters?.length) {
return;
}
db.transaction(tx => {
chapters.map(chapter =>
tx.executeSql(insertChaptersQuery, [
chapter.chapterUrl,
chapter.chapterName,
chapter.releaseDate,
novelId,
]),
);
});
};
const getChaptersQuery = (sort = 'ORDER BY chapterId ASC', filter = '') =>
@@ -61,10 +51,7 @@ export const getChapters = (novelId: number, sort: string, filter: string) => {
const _array = (rows as any)._array;
resolve(_array);
},
(_txObj, _error) => {
// console.log('Error ', error)
return false;
},
txnErrorCallback,
);
}),
);
@@ -260,7 +247,10 @@ const createImageFolder = async (
await RNFetchBlob.fs.mkdir(p);
}
};
await mkdirIfNot(path);
await RNFetchBlob.fs.createFile(path, '.nomedia', 'utf8');
if (data) {
const { sourceId, novelId, chapterId } = data;
await mkdirIfNot(`${path}/${sourceId}/`);
@@ -317,30 +307,67 @@ export const downloadChapter = async (
chapterUrl: string,
chapterId: number,
) => {
const source = sourceManager(sourceId);
try {
const source = sourceManager(sourceId);
const chapter = await source.parseChapter(novelUrl, chapterUrl);
const chapter = await source.parseChapter(novelUrl, chapterUrl);
const imagedChapterText =
chapter.chapterText &&
(await downloadImages(chapter.chapterText, sourceId, novelId, chapterId));
if (chapter.chapterText?.length) {
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;
},
db.transaction(tx => {
tx.executeSql(
'UPDATE chapters SET downloaded = 1 WHERE chapterId = ?',
[chapterId],
);
tx.executeSql(
downloadChapterQuery,
[chapterId, chapter.chapterName, imagedChapterText],
(_txObj, _res) => {
return true;
},
txnErrorCallback,
);
});
} else {
throw new Error("Either chapter is empty or the app couldn't scrape it");
}
} catch (error) {
throw error;
}
};
const deleteDownloadedImages = async (
sourceId: number,
novelId: number,
chapterId: number,
) => {
try {
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]}`);
}
}
}
}
} catch (error) {}
};
export const deleteChapter = async (
@@ -352,23 +379,7 @@ export const deleteChapter = async (
'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]}`);
}
}
}
}
await deleteDownloadedImages(sourceId, novelId, chapterId);
db.transaction(tx => {
tx.executeSql(
@@ -394,6 +405,38 @@ export const deleteChapter = async (
});
};
export const deleteChapters = async (
sourceId: number,
chapters?: ChapterItem[],
) => {
if (!chapters?.length) {
return;
}
const chapterIdsString = chapters
?.map(chapter => chapter.chapterId)
.toString();
const updateIsDownloadedQuery = `UPDATE chapters SET downloaded = 0 WHERE chapterId IN (${chapterIdsString})`;
const deleteChapterQuery = `DELETE FROM downloads WHERE downloadChapterId IN (${chapterIdsString})`;
await Promise.all(
chapters?.map(chapter =>
deleteDownloadedImages(sourceId, chapter.novelId, chapter.chapterId),
),
);
db.transaction(tx => {
tx.executeSql(
updateIsDownloadedQuery,
undefined,
undefined,
txnErrorCallback,
);
tx.executeSql(deleteChapterQuery, undefined, undefined, txnErrorCallback);
});
};
const getLastReadChapterQuery = `
SELECT chapters.*
FROM history
+5 -4
View File
@@ -1,5 +1,6 @@
/* eslint-disable @typescript-eslint/no-unused-vars */
import { txnErrorCallback } from '@database/utils/helpers';
import * as SQLite from 'expo-sqlite';
import { showToast } from '../../hooks/showToast';
import { sourceManager } from '../../sources/sourceManager';
@@ -36,7 +37,7 @@ export const fetchAndInsertChapterInDb = async (
downloadChapterQuery,
[chapterId, chapter.chapterName, chapter.chapterText],
(txObj, res) => {},
(_txObj, error) => showToast(error),
txnErrorCallback,
);
});
};
@@ -55,7 +56,7 @@ export const deleteChapterFromDb = (chapterId: number) => {
deleteChapterFromDbQuery,
[chapterId],
(txObj, res) => {},
(_txObj, error) => showToast(error.message),
txnErrorCallback,
);
});
};
@@ -122,13 +123,13 @@ export const deleteReadChaptersFromDb = (chapterId: number) => {
updateChaptersDeletedQuery,
[],
(txObj, res) => {},
(txObj, error) => showToast(error.message),
txnErrorCallback,
);
tx.executeSql(
deleteReadChaptersFromDbQuery,
[],
(txObj, res) => showToast('Deleted read chapters.'),
(txObj, error) => showToast(error.message),
txnErrorCallback,
);
});
};
-95
View File
@@ -1,95 +0,0 @@
import * as SQLite from 'expo-sqlite';
const db = SQLite.openDatabase('lnreader.db');
const getLibraryQuery = (sort, filter) => `
SELECT novels.*, C.chaptersUnread, D.chaptersDownloaded, H.lastReadAt
FROM novels
LEFT JOIN (
SELECT chapters.novelId, COUNT(*) AS chaptersUnread
FROM chapters
WHERE chapters.read = 0
GROUP BY chapters.novelId
) AS C
ON novels.novelId = C.novelId
LEFT JOIN (
SELECT chapters.novelId, COUNT(*) AS chaptersDownloaded
FROM chapters
WHERE chapters.downloaded = 1
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 ${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(JSON.stringify(_array, null, 2));
resolve(_array);
},
(txObj, error) => {
// console.log('Error ', error);
},
);
}),
);
};
const searchLibraryQuery = (searchText, sort, filter) =>
`
SELECT novels.*, C.chaptersUnread, D.chaptersDownloaded, H.lastReadAt
FROM novels
LEFT JOIN (
SELECT chapters.novelId, COUNT(*) AS chaptersUnread
FROM chapters
WHERE chapters.read = 0
GROUP BY chapters.novelId
) AS C
ON novels.novelId = C.novelId
LEFT JOIN (
SELECT chapters.novelId, COUNT(*) AS chaptersDownloaded
FROM chapters
WHERE chapters.downloaded = 1
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 : ''} `;
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);
},
);
});
});
};
+96
View File
@@ -0,0 +1,96 @@
import { LibraryFilter } from '@screens/library/constants/constants';
import * as SQLite from 'expo-sqlite';
import { LibraryNovelInfo, NovelInfo } from '../types';
import { txnErrorCallback } from '../utils/helpers';
const db = SQLite.openDatabase('lnreader.db');
const getLibraryQuery = `
SELECT novels.*, C.chaptersUnread, D.chaptersDownloaded, H.lastReadAt
FROM novels
LEFT JOIN (
SELECT chapters.novelId, COUNT(*) AS chaptersUnread
FROM chapters
WHERE chapters.read = 0
GROUP BY chapters.novelId
) AS C
ON novels.novelId = C.novelId
LEFT JOIN (
SELECT chapters.novelId, COUNT(*) AS chaptersDownloaded
FROM chapters
WHERE chapters.downloaded = 1
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
`;
export const getLibrary = ({
filter,
searchText,
sortOrder,
downloadedOnlyMode,
}: {
sortOrder?: string;
filter?: string;
searchText?: string;
downloadedOnlyMode?: boolean;
}): Promise<LibraryNovelInfo[]> => {
let query = getLibraryQuery;
if (filter) {
query += ` AND ${filter}`;
}
if (downloadedOnlyMode) {
query += ' ' + LibraryFilter.DownloadedOnly;
}
if (searchText) {
query += ` AND novelName LIKE '%${searchText}%'`;
}
if (sortOrder) {
query += ` ORDER BY ${sortOrder}`;
}
return new Promise(resolve =>
db.transaction(tx => {
tx.executeSql(
query,
undefined,
(txObj, { rows: { _array } }) => resolve(_array),
txnErrorCallback,
);
}),
);
};
export const getLibraryNovelsFromDb = (
onlyOngoingNovels?: boolean,
): Promise<NovelInfo[]> => {
let getLibraryNovelsQuery = 'SELECT * FROM novels WHERE followed = 1';
if (onlyOngoingNovels) {
getLibraryNovelsQuery += " AND status NOT LIKE 'Completed'";
}
return new Promise(resolve =>
db.transaction(tx => {
tx.executeSql(
getLibraryNovelsQuery,
undefined,
(txObj, { rows: { _array } }) => resolve(_array),
txnErrorCallback,
);
}),
);
};
+6 -5
View File
@@ -8,9 +8,11 @@ import { fetchChapters, fetchNovel } from '../../services/Source/source';
import { insertChapters } from './ChapterQueries';
import { showToast } from '../../hooks/showToast';
import { txnErrorCallback } from '../utils/helpers';
import { noop } from 'lodash';
const insertNovelQuery =
'INSERT INTO novels (novelUrl, sourceUrl, sourceId, source, novelName, novelCover, novelSummary, author, artist, status, genre) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
'INSERT INTO novels (novelUrl, sourceUrl, sourceId, source, novelName, novelCover, novelSummary, author, artist, status, genre, categoryIds) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)';
export const insertNovel = async novel => {
return new Promise((resolve, reject) =>
@@ -29,11 +31,10 @@ export const insertNovel = async novel => {
novel.artist,
novel.status,
novel.genre,
JSON.stringify(novel.categoryIds),
],
(txObj, { insertId }) => resolve(insertId),
(txObj, error) => {
// console.log('Error ', error)
},
txnErrorCallback,
),
),
);
@@ -142,7 +143,7 @@ export const restoreLibrary = async novel => {
async (txObj, { insertId }) => {
const chapters = await fetchChapters(novel.sourceId, novel.novelUrl);
if (chapters) {
if (chapters.length) {
await insertChapters(insertId, chapters);
resolve();
+205
View File
@@ -0,0 +1,205 @@
import { noop, xor } from 'lodash';
import * as SQLite from 'expo-sqlite';
const db = SQLite.openDatabase('lnreader.db');
import { txnErrorCallback } from '../utils/helpers';
import { LibraryNovelInfo } from '../types';
import { fetchNovel } from '../../services/Source/source';
import { showToast } from '@hooks/showToast';
import { getString } from '@strings/translations';
import { checkNovelInCache } from './NovelQueries';
export const getCategoryNovelsFromDb = async (
categoryId: number,
onlyOngoingNovels?: boolean,
): Promise<LibraryNovelInfo[]> => {
let query = `
SELECT
*
FROM
novels
WHERE (
categoryIds LIKE '[${categoryId}]'
OR
categoryIds LIKE '[${categoryId},%'
OR
categoryIds LIKE '%,${categoryId}]'
OR
categoryIds LIKE '%,${categoryId},%'
)
AND
followed = 1
`;
if (onlyOngoingNovels) {
query += ' AND status NOT LIKE "Completed"';
}
return new Promise(resolve => {
db.transaction(tx => {
tx.executeSql(
query,
undefined,
(txObj, { rows }) => resolve((rows as any)._array),
txnErrorCallback,
);
});
});
};
export const resetCategoryIdsToDefault = async (deletedCategoryId: number) => {
const categoryNovels = await getCategoryNovelsFromDb(deletedCategoryId);
db.transaction(tx => {
categoryNovels.forEach(novel => {
let categoryIds = xor(JSON.parse(novel.categoryIds), [deletedCategoryId]);
categoryIds = categoryIds.length ? categoryIds : [1];
tx.executeSql('UPDATE novels SET categoryIds = ? WHERE novelId = ?', [
JSON.stringify(categoryIds),
novel.novelId,
]);
});
});
};
export const insertNovelInLibrary = async (
sourceId: number,
novelUrl: string,
inLibrary: boolean,
defaultCategoryId: number,
) => {
if (inLibrary) {
showToast(getString('browseScreen.removeFromLibrary'));
db.transaction(tx => {
tx.executeSql(
`
UPDATE novels SET
followed = 0
WHERE
sourceId = ?
AND
novelUrl = ?
`,
[sourceId, novelUrl],
noop,
txnErrorCallback,
);
});
} else {
showToast(getString('browseScreen.addedToLibrary'));
const novelSavedInDb = await checkNovelInCache(novelUrl);
if (novelSavedInDb) {
db.transaction(tx => {
tx.executeSql(
`
UPDATE novels SET
followed = 1
WHERE
sourceId = ?
AND
novelUrl = ?
`,
[sourceId, novelUrl],
noop,
txnErrorCallback,
);
});
return;
}
const novel = await fetchNovel(sourceId, novelUrl);
db.transaction(tx => {
tx.executeSql(
`
INSERT INTO novels
(
novelUrl,
sourceUrl,
sourceId,
source,
novelName,
novelCover,
novelSummary,
author,
artist,
status,
genre,
followed,
categoryIds
)
VALUES
(?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?)`,
[
novel.novelUrl,
novel.sourceUrl,
novel.sourceId,
novel.source,
novel.novelName,
novel.novelCover,
novel.novelSummary,
novel.author,
novel.artist,
novel.status,
novel.genre,
JSON.stringify([defaultCategoryId]),
],
(txObj, { insertId }) => {
novel.chapters?.map(chapter => {
tx.executeSql(
`
INSERT INTO chapters
(chapterUrl, chapterName, releaseDate, novelId)
VALUES
(?, ?, ?, ?)`,
[
chapter.chapterUrl,
chapter.chapterName,
chapter.releaseDate,
insertId,
],
);
});
},
txnErrorCallback,
);
});
}
};
export const updateNovelCategoryById = async (
novelId: number,
categoryIds: number[],
) => {
db.transaction(tx => {
tx.executeSql(
'UPDATE novels SET categoryIds = ? WHERE novelId = ?',
[JSON.stringify(categoryIds.length ? categoryIds : [1]), novelId],
noop,
txnErrorCallback,
);
});
};
export const updateNovelCategoryByIds = async (
novelIds: number[],
categoryIds: number[],
) => {
db.transaction(tx => {
novelIds.map(novelId =>
tx.executeSql(
'UPDATE novels SET categoryIds = ? WHERE novelId = ?',
[JSON.stringify(categoryIds.length ? categoryIds : [1]), novelId],
noop,
txnErrorCallback,
),
);
});
};
+178
View File
@@ -0,0 +1,178 @@
import * as SQLite from 'expo-sqlite';
import { countBy } from 'lodash';
import { LibraryStats } from '../types';
import { txnErrorCallback } from '../utils/helpers';
const db = SQLite.openDatabase('lnreader.db');
const getLibraryStatsQuery = `
SELECT COUNT(*) as novelsCount, COUNT(DISTINCT sourceId) as sourcesCount
FROM novels
WHERE novels.followed = 1
`;
const getChaptersReadCountQuery = `
SELECT COUNT(*) as chaptersRead
FROM chapters
JOIN novels
ON chapters.novelId = novels.novelId
WHERE chapters.read = 1 AND novels.followed = 1
`;
const getChaptersTotalCountQuery = `
SELECT COUNT(*) as chaptersCount
FROM chapters
JOIN novels
ON chapters.novelId = novels.novelId
WHERE novels.followed = 1
`;
const getChaptersUnreadCountQuery = `
SELECT COUNT(*) as chaptersUnread
FROM chapters
JOIN novels
ON chapters.novelId = novels.novelId
WHERE chapters.read = 0 AND novels.followed = 1
`;
const getChaptersDownloadedCountQuery = `
SELECT COUNT(*) as chaptersDownloaded
FROM chapters
JOIN novels
ON chapters.novelId = novels.novelId
WHERE chapters.downloaded = 1 AND novels.followed = 1
`;
const getNovelGenresQuery = `
SELECT genre
FROM novels
WHERE novels.followed = 1
`;
const getNovelStatusQuery = `
SELECT status
FROM novels
WHERE novels.followed = 1
`;
export const getLibraryStatsFromDb = async (): Promise<LibraryStats> => {
return new Promise(resolve => {
db.transaction(tx => {
tx.executeSql(
getLibraryStatsQuery,
undefined,
(txObj, { rows: { _array } }) => {
resolve(_array[0]);
},
txnErrorCallback,
);
});
});
};
export const getChaptersTotalCountFromDb = async (): Promise<LibraryStats> => {
return new Promise(resolve => {
db.transaction(tx => {
tx.executeSql(
getChaptersTotalCountQuery,
undefined,
(txObj, { rows: { _array } }) => {
resolve(_array[0]);
},
txnErrorCallback,
);
});
});
};
export const getChaptersReadCountFromDb = async (): Promise<LibraryStats> => {
return new Promise(resolve => {
db.transaction(tx => {
tx.executeSql(
getChaptersReadCountQuery,
undefined,
(txObj, { rows: { _array } }) => {
resolve(_array[0]);
},
txnErrorCallback,
);
});
});
};
export const getChaptersUnreadCountFromDb = async (): Promise<LibraryStats> => {
return new Promise(resolve => {
db.transaction(tx => {
tx.executeSql(
getChaptersUnreadCountQuery,
undefined,
(txObj, { rows: { _array } }) => {
resolve(_array[0]);
},
txnErrorCallback,
);
});
});
};
export const getChaptersDownloadedCountFromDb =
async (): Promise<LibraryStats> => {
return new Promise(resolve => {
db.transaction(tx => {
tx.executeSql(
getChaptersDownloadedCountQuery,
undefined,
(txObj, { rows: { _array } }) => {
resolve(_array[0]);
},
txnErrorCallback,
);
});
});
};
export const getNovelGenresFromDb = async (): Promise<LibraryStats> => {
return new Promise(resolve => {
db.transaction(tx => {
tx.executeSql(
getNovelGenresQuery,
undefined,
(txObj, { rows: { _array } }) => {
let genres: string[] = [];
_array.forEach((item: { genre: string }) => {
const novelGenres = item.genre?.split(/\s*,\s*/);
if (novelGenres?.length) {
genres.push(...novelGenres);
}
});
resolve({ genres: countBy(genres) });
},
txnErrorCallback,
);
});
});
};
export const getNovelStatusFromDb = async (): Promise<LibraryStats> => {
return new Promise(resolve => {
db.transaction(tx => {
tx.executeSql(
getNovelStatusQuery,
undefined,
(txObj, { rows: { _array } }) => {
let status: string[] = [];
_array.forEach((item: { status: string }) => {
status.push(item.status);
});
resolve({ status: countBy(status) });
},
txnErrorCallback,
);
});
});
};
+1 -1
View File
@@ -35,7 +35,7 @@ const getUpdatesFromDbQuery = `
WHERE
date(updates.updateTime) > date('now','-3 months')
ORDER BY
updates.updateTime DESC
updates.updateTime DESC, chapters.chapterName DESC, chapters.releaseDate DESC
`;
export const getUpdatesFromDb = async (): Promise<Update[]> => {
+17
View File
@@ -0,0 +1,17 @@
export const createCategoriesTableQuery = `
CREATE TABLE IF NOT EXISTS categories(
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
lastUpdatedAt DATETIME DEFAULT CURRENT_TIMESTAMP,
sort INTEGER DEFAULT NULL
)
`;
export const createDefaultCategoryQuery =
'INSERT INTO categories (id, name) VALUES (1, "Default");';
export const createCategorydIndexQuery =
'CREATE INDEX IF NOT EXISTS categoryIdIndex ON categories(id)';
export const addCategorySortColumnQuery =
'ALTER TABLE categories ADD COLUMN sort INTEGER DEFAULT NULL';
+5 -1
View File
@@ -13,7 +13,8 @@ export const createNovelTableQuery = `
status TEXT,
genre TEXT,
followed INTEGER DEFAULT 0,
unread INTEGER DEFAULT 1
unread INTEGER DEFAULT 1,
categoryIds TEXT DEFAULT "[1]"
)`;
export const createUrlIndexQuery =
@@ -21,3 +22,6 @@ export const createUrlIndexQuery =
export const createLibraryIndexQuery =
'CREATE INDEX IF NOT EXISTS novelFollowedIndex ON novels(followed)';
export const addCategoryColumnQuery =
'ALTER TABLE novels ADD COLUMN categoryIds TEXT DEFAULT "[1]"';
+20
View File
@@ -11,11 +11,13 @@ export interface NovelInfo {
author?: string;
status?: string;
followed: number;
categoryIds: string;
}
export interface LibraryNovelInfo extends NovelInfo {
chaptersUnread: number;
chaptersDownloaded: number;
categoryIds: string;
}
export interface ChapterItem {
@@ -66,3 +68,21 @@ export interface Update {
bookmark: number;
releaseDate: string;
}
export interface Category {
id: number;
name: string;
lastUpdatedAt: Date;
sort: number | null;
}
export interface LibraryStats {
novelsCount?: number;
chaptersCount?: number;
chaptersRead?: number;
chaptersUnread?: number;
chaptersDownloaded?: number;
sourcesCount?: number;
genres?: Record<string, number>;
status?: Record<string, number>;
}

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